From e024da277b38854e0f0878879c0175f257ac2b93 Mon Sep 17 00:00:00 2001 From: David Pierron Date: Thu, 22 Oct 2020 18:39:54 +0200 Subject: [PATCH 1/8] first working version of the skynetiads plugin --- gen/sam/genericsam_group_generator.py | 21 + gen/sam/group_generator.py | 11 +- gen/sam/sam_group_generator.py | 5 +- gen/sam/sam_hawk.py | 6 +- gen/sam/sam_hq7.py | 4 +- gen/sam/sam_patriot.py | 8 +- gen/sam/sam_rapier.py | 4 +- gen/sam/sam_roland.py | 6 +- gen/sam/sam_sa10.py | 10 +- gen/sam/sam_sa11.py | 6 +- gen/sam/sam_sa2.py | 4 +- gen/sam/sam_sa3.py | 4 +- gen/sam/sam_sa6.py | 4 +- resources/plugins/plugins.json | 3 +- resources/plugins/skynetiads/mist_4_3_74.lua | 6825 +++++++++++++++++ resources/plugins/skynetiads/plugin.json | 63 + .../skynetiads/skynet-iads-compiled.lua | 2963 +++++++ .../plugins/skynetiads/skynetiads-config.lua | 130 + 18 files changed, 10042 insertions(+), 35 deletions(-) create mode 100644 gen/sam/genericsam_group_generator.py create mode 100644 resources/plugins/skynetiads/mist_4_3_74.lua create mode 100644 resources/plugins/skynetiads/plugin.json create mode 100644 resources/plugins/skynetiads/skynet-iads-compiled.lua create mode 100644 resources/plugins/skynetiads/skynetiads-config.lua diff --git a/gen/sam/genericsam_group_generator.py b/gen/sam/genericsam_group_generator.py new file mode 100644 index 00000000..e8a1bcb2 --- /dev/null +++ b/gen/sam/genericsam_group_generator.py @@ -0,0 +1,21 @@ +import random + +from dcs.vehicles import AirDefence +from game import db +from gen.sam.group_generator import GroupGenerator + + +class GenericSamGroupGenerator(GroupGenerator): + """ + This is the base for all SAM group generators + """ + + def getGroupNamePrefix(self, faction): + if not faction: + return "" + + # prefix the SAM site for use with the Skynet IADS plugin + prefix = "BLUE SAM " + if db.FACTIONS[faction]["side"] == "red": + prefix = "RED SAM " + return prefix diff --git a/gen/sam/group_generator.py b/gen/sam/group_generator.py index e6620211..74e1d681 100644 --- a/gen/sam/group_generator.py +++ b/gen/sam/group_generator.py @@ -8,16 +8,23 @@ from dcs.unit import Vehicle class GroupGenerator(): - def __init__(self, game, ground_object): + def __init__(self, game, ground_object, faction = None): self.game = game self.go = ground_object self.position = ground_object.position self.heading = random.randint(0, 359) - self.vg = unitgroup.VehicleGroup(self.game.next_group_id(), self.go.group_identifier) + groupNamePrefix = self.getGroupNamePrefix(faction) + self.vg = unitgroup.VehicleGroup(self.game.next_group_id(), groupNamePrefix + self.go.group_identifier) wp = self.vg.add_waypoint(self.position, PointAction.OffRoad, 0) wp.ETA_locked = True + def getGroupNamePrefix(self, faction): + if not faction: + return "" + + return "" + def generate(self): raise NotImplementedError diff --git a/gen/sam/sam_group_generator.py b/gen/sam/sam_group_generator.py index add0c9fa..968e6866 100644 --- a/gen/sam/sam_group_generator.py +++ b/gen/sam/sam_group_generator.py @@ -117,7 +117,6 @@ def get_faction_possible_sams_generator(faction: str) -> List[UnitType]: """ return [SAM_MAP[u] for u in get_faction_possible_sams_units(faction)] - def generate_anti_air_group(game, parent_cp, ground_object, faction:str): """ This generate a SAM group @@ -129,7 +128,7 @@ def generate_anti_air_group(game, parent_cp, ground_object, faction:str): possible_sams = get_faction_possible_sams_units(faction) if len(possible_sams) > 0: sam = random.choice(possible_sams) - generator = SAM_MAP[sam](game, ground_object) + generator = SAM_MAP[sam](game, ground_object, faction) generator.generate() return generator.get_generated_group() return None @@ -149,5 +148,3 @@ def generate_shorad_group(game, parent_cp, ground_object, faction:str): - - diff --git a/gen/sam/sam_hawk.py b/gen/sam/sam_hawk.py index 89c11bc0..da8c700a 100644 --- a/gen/sam/sam_hawk.py +++ b/gen/sam/sam_hawk.py @@ -2,10 +2,10 @@ import random from dcs.vehicles import AirDefence -from gen.sam.group_generator import GroupGenerator +from gen.sam.genericsam_group_generator import GenericSamGroupGenerator -class HawkGenerator(GroupGenerator): +class HawkGenerator(GenericSamGroupGenerator): """ This generate an HAWK group """ @@ -14,8 +14,8 @@ class HawkGenerator(GroupGenerator): price = 115 def generate(self): - self.add_unit(AirDefence.SAM_Hawk_PCP, "PCP", self.position.x, self.position.y, self.heading) self.add_unit(AirDefence.SAM_Hawk_SR_AN_MPQ_50, "SR", self.position.x + 20, self.position.y, self.heading) + self.add_unit(AirDefence.SAM_Hawk_PCP, "PCP", self.position.x, self.position.y, self.heading) self.add_unit(AirDefence.SAM_Hawk_TR_AN_MPQ_46, "TR", self.position.x + 40, self.position.y, self.heading) # Triple A for close range defense diff --git a/gen/sam/sam_hq7.py b/gen/sam/sam_hq7.py index f8a531ea..adba14b5 100644 --- a/gen/sam/sam_hq7.py +++ b/gen/sam/sam_hq7.py @@ -2,10 +2,10 @@ import random from dcs.vehicles import AirDefence -from gen.sam.group_generator import GroupGenerator +from gen.sam.genericsam_group_generator import GenericSamGroupGenerator -class HQ7Generator(GroupGenerator): +class HQ7Generator(GenericSamGroupGenerator): """ This generate an HQ7 group """ diff --git a/gen/sam/sam_patriot.py b/gen/sam/sam_patriot.py index b55dbaea..782655e2 100644 --- a/gen/sam/sam_patriot.py +++ b/gen/sam/sam_patriot.py @@ -2,10 +2,10 @@ import random from dcs.vehicles import AirDefence -from gen.sam.group_generator import GroupGenerator +from gen.sam.genericsam_group_generator import GenericSamGroupGenerator -class PatriotGenerator(GroupGenerator): +class PatriotGenerator(GenericSamGroupGenerator): """ This generate a Patriot group """ @@ -15,11 +15,11 @@ class PatriotGenerator(GroupGenerator): def generate(self): # Command Post + self.add_unit(AirDefence.SAM_Patriot_STR_AN_MPQ_53, "ICC", self.position.x + 30, self.position.y + 30, self.heading) self.add_unit(AirDefence.SAM_Patriot_AMG_AN_MRC_137, "MRC", self.position.x, self.position.y, self.heading) self.add_unit(AirDefence.SAM_Patriot_ECS_AN_MSQ_104, "MSQ", self.position.x + 30, self.position.y, self.heading) self.add_unit(AirDefence.SAM_Patriot_ICC, "ICC", self.position.x + 60, self.position.y, self.heading) self.add_unit(AirDefence.SAM_Patriot_EPP_III, "EPP", self.position.x, self.position.y + 30, self.heading) - self.add_unit(AirDefence.SAM_Patriot_STR_AN_MPQ_53, "ICC", self.position.x + 30, self.position.y + 30, self.heading) num_launchers = random.randint(3, 4) positions = self.get_circular_position(num_launchers, launcher_distance=120, coverage=360) @@ -30,4 +30,4 @@ class PatriotGenerator(GroupGenerator): num_launchers = random.randint(3, 4) positions = self.get_circular_position(num_launchers, launcher_distance=300, coverage=360) for i, position in enumerate(positions): - self.add_unit(AirDefence.AAA_Vulcan_M163, "SPAAA#" + str(i), position[0], position[1], position[2]) + self.add_unit(AirDefence.AAA_Vulcan_M163, "SPAAA#" + str(i), position[0], position[1], position[2]) \ No newline at end of file diff --git a/gen/sam/sam_rapier.py b/gen/sam/sam_rapier.py index 99b7b205..981a098e 100644 --- a/gen/sam/sam_rapier.py +++ b/gen/sam/sam_rapier.py @@ -2,10 +2,10 @@ import random from dcs.vehicles import AirDefence -from gen.sam.group_generator import GroupGenerator +from gen.sam.genericsam_group_generator import GenericSamGroupGenerator -class RapierGenerator(GroupGenerator): +class RapierGenerator(GenericSamGroupGenerator): """ This generate a Rapier Group """ diff --git a/gen/sam/sam_roland.py b/gen/sam/sam_roland.py index 9e31d5fe..1f970517 100644 --- a/gen/sam/sam_roland.py +++ b/gen/sam/sam_roland.py @@ -1,9 +1,9 @@ from dcs.vehicles import AirDefence, Unarmed -from gen.sam.group_generator import GroupGenerator +from gen.sam.genericsam_group_generator import GenericSamGroupGenerator -class RolandGenerator(GroupGenerator): +class RolandGenerator(GenericSamGroupGenerator): """ This generate a Roland group """ @@ -12,7 +12,7 @@ class RolandGenerator(GroupGenerator): price = 40 def generate(self): - self.add_unit(AirDefence.SAM_Roland_ADS, "ADS", self.position.x, self.position.y, self.heading) self.add_unit(AirDefence.SAM_Roland_EWR, "EWR", self.position.x + 40, self.position.y, self.heading) + self.add_unit(AirDefence.SAM_Roland_ADS, "ADS", self.position.x, self.position.y, self.heading) self.add_unit(Unarmed.Transport_M818, "TRUCK", self.position.x + 80, self.position.y, self.heading) diff --git a/gen/sam/sam_sa10.py b/gen/sam/sam_sa10.py index ae2102f0..6ea5809f 100644 --- a/gen/sam/sam_sa10.py +++ b/gen/sam/sam_sa10.py @@ -2,10 +2,10 @@ import random from dcs.vehicles import AirDefence -from gen.sam.group_generator import GroupGenerator +from gen.sam.genericsam_group_generator import GenericSamGroupGenerator -class SA10Generator(GroupGenerator): +class SA10Generator(GenericSamGroupGenerator): """ This generate a SA-10 group """ @@ -14,15 +14,15 @@ class SA10Generator(GroupGenerator): price = 450 def generate(self): - # Command Post - self.add_unit(AirDefence.SAM_SA_10_S_300PS_CP_54K6, "CP", self.position.x, self.position.y, self.heading) - # Search Radar self.add_unit(AirDefence.SAM_SA_10_S_300PS_SR_5N66M, "SR1", self.position.x, self.position.y + 40, self.heading) # Search radar for missiles (optionnal) self.add_unit(AirDefence.SAM_SA_10_S_300PS_SR_64H6E, "SR2", self.position.x - 40, self.position.y, self.heading) + # Command Post + self.add_unit(AirDefence.SAM_SA_10_S_300PS_CP_54K6, "CP", self.position.x, self.position.y, self.heading) + # 2 Tracking radars self.add_unit(AirDefence.SAM_SA_10_S_300PS_TR_30N6, "TR1", self.position.x - 40, self.position.y - 40, self.heading) diff --git a/gen/sam/sam_sa11.py b/gen/sam/sam_sa11.py index 3af6c242..e7634b92 100644 --- a/gen/sam/sam_sa11.py +++ b/gen/sam/sam_sa11.py @@ -2,10 +2,10 @@ import random from dcs.vehicles import AirDefence -from gen.sam.group_generator import GroupGenerator +from gen.sam.genericsam_group_generator import GenericSamGroupGenerator -class SA11Generator(GroupGenerator): +class SA11Generator(GenericSamGroupGenerator): """ This generate a SA-11 group """ @@ -14,8 +14,8 @@ class SA11Generator(GroupGenerator): price = 180 def generate(self): - self.add_unit(AirDefence.SAM_SA_11_Buk_CC_9S470M1, "CC", self.position.x, self.position.y, self.heading) self.add_unit(AirDefence.SAM_SA_11_Buk_SR_9S18M1, "SR", self.position.x+20, self.position.y, self.heading) + self.add_unit(AirDefence.SAM_SA_11_Buk_CC_9S470M1, "CC", self.position.x, self.position.y, self.heading) num_launchers = random.randint(2, 4) positions = self.get_circular_position(num_launchers, launcher_distance=140, coverage=180) diff --git a/gen/sam/sam_sa2.py b/gen/sam/sam_sa2.py index c108c1e8..ff77265f 100644 --- a/gen/sam/sam_sa2.py +++ b/gen/sam/sam_sa2.py @@ -2,10 +2,10 @@ import random from dcs.vehicles import AirDefence -from gen.sam.group_generator import GroupGenerator +from gen.sam.genericsam_group_generator import GenericSamGroupGenerator -class SA2Generator(GroupGenerator): +class SA2Generator(GenericSamGroupGenerator): """ This generate a SA-2 group """ diff --git a/gen/sam/sam_sa3.py b/gen/sam/sam_sa3.py index 455bab19..e57f184c 100644 --- a/gen/sam/sam_sa3.py +++ b/gen/sam/sam_sa3.py @@ -2,10 +2,10 @@ import random from dcs.vehicles import AirDefence -from gen.sam.group_generator import GroupGenerator +from gen.sam.genericsam_group_generator import GenericSamGroupGenerator -class SA3Generator(GroupGenerator): +class SA3Generator(GenericSamGroupGenerator): """ This generate a SA-3 group """ diff --git a/gen/sam/sam_sa6.py b/gen/sam/sam_sa6.py index 7ec2afca..1028ed76 100644 --- a/gen/sam/sam_sa6.py +++ b/gen/sam/sam_sa6.py @@ -2,10 +2,10 @@ import random from dcs.vehicles import AirDefence -from gen.sam.group_generator import GroupGenerator +from gen.sam.genericsam_group_generator import GenericSamGroupGenerator -class SA6Generator(GroupGenerator): +class SA6Generator(GenericSamGroupGenerator): """ This generate a SA-6 group """ diff --git a/resources/plugins/plugins.json b/resources/plugins/plugins.json index 21b44606..e313029b 100644 --- a/resources/plugins/plugins.json +++ b/resources/plugins/plugins.json @@ -1,5 +1,6 @@ -[ +[ "veaf", + "skynetiads", "jtacautolase", "base" ] diff --git a/resources/plugins/skynetiads/mist_4_3_74.lua b/resources/plugins/skynetiads/mist_4_3_74.lua new file mode 100644 index 00000000..4aa9db47 --- /dev/null +++ b/resources/plugins/skynetiads/mist_4_3_74.lua @@ -0,0 +1,6825 @@ +--[[-- +MIST Mission Scripting Tools. +## Description: +MIssion Scripting Tools (MIST) is a collection of Lua functions +and databases that is intended to be a supplement to the standard +Lua functions included in the simulator scripting engine. + +MIST functions and databases provide ready-made solutions to many common +scripting tasks and challenges, enabling easier scripting and saving +mission scripters time. The table mist.flagFuncs contains a set of +Lua functions (that are similar to Slmod functions) that do not +require detailed Lua knowledge to use. + +However, the majority of MIST does require knowledge of the Lua language, +and, if you are going to utilize these components of MIST, it is necessary +that you read the Simulator Scripting Engine guide on the official ED wiki. + +## Links: + +ED Forum Thread: + +##Github: + +Development + +Official Releases + +@script MIST +@author Speed +@author Grimes +@author lukrop +]] +mist = {} + +-- don't change these +mist.majorVersion = 4 +mist.minorVersion = 3 +mist.build = 74 + +-- forward declaration of log shorthand +local log + +do -- the main scope + local coroutines = {} + + local tempSpawnedUnits = {} -- birth events added here + local tempSpawnedGroups = {} + local tempSpawnGroupsCounter = 0 + + local mistAddedObjects = {} -- mist.dynAdd unit data added here + local mistAddedGroups = {} -- mist.dynAdd groupdata added here + local writeGroups = {} + local lastUpdateTime = 0 + + local updateAliveUnitsCounter = 0 + local updateTenthSecond = 0 + + local mistGpId = 7000 + local mistUnitId = 7000 + local mistDynAddIndex = {[' air '] = 0, [' hel '] = 0, [' gnd '] = 0, [' bld '] = 0, [' static '] = 0, [' shp '] = 0} + + local scheduledTasks = {} + local taskId = 0 + local idNum = 0 + + mist.nextGroupId = 1 + mist.nextUnitId = 1 + + local dbLog + + local function initDBs() -- mist.DBs scope + mist.DBs = {} + + mist.DBs.missionData = {} + if env.mission then + + mist.DBs.missionData.startTime = env.mission.start_time + mist.DBs.missionData.theatre = env.mission.theatre + mist.DBs.missionData.version = env.mission.version + mist.DBs.missionData.files = {} + if type(env.mission.resourceCounter) == 'table' then + for fIndex, fData in pairs (env.mission.resourceCounter) do + mist.DBs.missionData.files[#mist.DBs.missionData.files + 1] = mist.utils.deepCopy(fIndex) + end + end + -- if we add more coalition specific data then bullsye should be categorized by coaliton. For now its just the bullseye table + mist.DBs.missionData.bullseye = {red = {}, blue = {}} + mist.DBs.missionData.bullseye.red.x = env.mission.coalition.red.bullseye.x --should it be point.x? + mist.DBs.missionData.bullseye.red.y = env.mission.coalition.red.bullseye.y + mist.DBs.missionData.bullseye.blue.x = env.mission.coalition.blue.bullseye.x + mist.DBs.missionData.bullseye.blue.y = env.mission.coalition.blue.bullseye.y + end + + mist.DBs.zonesByName = {} + mist.DBs.zonesByNum = {} + + + if env.mission.triggers and env.mission.triggers.zones then + for zone_ind, zone_data in pairs(env.mission.triggers.zones) do + if type(zone_data) == 'table' then + local zone = mist.utils.deepCopy(zone_data) + zone.point = {} -- point is used by SSE + zone.point.x = zone_data.x + zone.point.y = 0 + zone.point.z = zone_data.y + + mist.DBs.zonesByName[zone_data.name] = zone + mist.DBs.zonesByNum[#mist.DBs.zonesByNum + 1] = mist.utils.deepCopy(zone) --[[deepcopy so that the zone in zones_by_name and the zone in + zones_by_num se are different objects.. don't want them linked.]] + end + end + end + + mist.DBs.navPoints = {} + mist.DBs.units = {} + --Build mist.db.units and mist.DBs.navPoints + for coa_name, coa_data in pairs(env.mission.coalition) do + + if (coa_name == 'red' or coa_name == 'blue') and type(coa_data) == 'table' then + mist.DBs.units[coa_name] = {} + + -- build nav points DB + mist.DBs.navPoints[coa_name] = {} + if coa_data.nav_points then --navpoints + --mist.debug.writeData (mist.utils.serialize,{'NavPoints',coa_data.nav_points}, 'NavPoints.txt') + for nav_ind, nav_data in pairs(coa_data.nav_points) do + + if type(nav_data) == 'table' then + mist.DBs.navPoints[coa_name][nav_ind] = mist.utils.deepCopy(nav_data) + + mist.DBs.navPoints[coa_name][nav_ind].name = nav_data.callsignStr -- name is a little bit more self-explanatory. + mist.DBs.navPoints[coa_name][nav_ind].point = {} -- point is used by SSE, support it. + mist.DBs.navPoints[coa_name][nav_ind].point.x = nav_data.x + mist.DBs.navPoints[coa_name][nav_ind].point.y = 0 + mist.DBs.navPoints[coa_name][nav_ind].point.z = nav_data.y + end + end + end + if coa_data.country then --there is a country table + for cntry_id, cntry_data in pairs(coa_data.country) do + + local countryName = string.lower(cntry_data.name) + mist.DBs.units[coa_name][countryName] = {} + mist.DBs.units[coa_name][countryName].countryId = cntry_data.id + + if type(cntry_data) == 'table' then --just making sure + + for obj_type_name, obj_type_data in pairs(cntry_data) do + + if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" or obj_type_name == "static" then --should be an unncessary check + + local category = obj_type_name + + if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then --there's a group! + + mist.DBs.units[coa_name][countryName][category] = {} + + for group_num, group_data in pairs(obj_type_data.group) do + + if group_data and group_data.units and type(group_data.units) == 'table' then --making sure again- this is a valid group + + mist.DBs.units[coa_name][countryName][category][group_num] = {} + local groupName = group_data.name + if env.mission.version > 7 then + groupName = env.getValueDictByKey(groupName) + end + mist.DBs.units[coa_name][countryName][category][group_num].groupName = groupName + mist.DBs.units[coa_name][countryName][category][group_num].groupId = group_data.groupId + mist.DBs.units[coa_name][countryName][category][group_num].category = category + mist.DBs.units[coa_name][countryName][category][group_num].coalition = coa_name + mist.DBs.units[coa_name][countryName][category][group_num].country = countryName + mist.DBs.units[coa_name][countryName][category][group_num].countryId = cntry_data.id + mist.DBs.units[coa_name][countryName][category][group_num].startTime = group_data.start_time + mist.DBs.units[coa_name][countryName][category][group_num].task = group_data.task + mist.DBs.units[coa_name][countryName][category][group_num].hidden = group_data.hidden + + mist.DBs.units[coa_name][countryName][category][group_num].units = {} + + mist.DBs.units[coa_name][countryName][category][group_num].radioSet = group_data.radioSet + mist.DBs.units[coa_name][countryName][category][group_num].uncontrolled = group_data.uncontrolled + mist.DBs.units[coa_name][countryName][category][group_num].frequency = group_data.frequency + mist.DBs.units[coa_name][countryName][category][group_num].modulation = group_data.modulation + + for unit_num, unit_data in pairs(group_data.units) do + local units_tbl = mist.DBs.units[coa_name][countryName][category][group_num].units --pointer to the units table for this group + + units_tbl[unit_num] = {} + if env.mission.version > 7 then + units_tbl[unit_num].unitName = env.getValueDictByKey(unit_data.name) + else + units_tbl[unit_num].unitName = unit_data.name + end + units_tbl[unit_num].type = unit_data.type + units_tbl[unit_num].skill = unit_data.skill --will be nil for statics + units_tbl[unit_num].unitId = unit_data.unitId + units_tbl[unit_num].category = category + units_tbl[unit_num].coalition = coa_name + units_tbl[unit_num].country = countryName + units_tbl[unit_num].countryId = cntry_data.id + units_tbl[unit_num].heading = unit_data.heading + units_tbl[unit_num].playerCanDrive = unit_data.playerCanDrive + units_tbl[unit_num].alt = unit_data.alt + units_tbl[unit_num].alt_type = unit_data.alt_type + units_tbl[unit_num].speed = unit_data.speed + units_tbl[unit_num].livery_id = unit_data.livery_id + if unit_data.point then --ME currently does not work like this, but it might one day + units_tbl[unit_num].point = unit_data.point + else + units_tbl[unit_num].point = {} + units_tbl[unit_num].point.x = unit_data.x + units_tbl[unit_num].point.y = unit_data.y + end + units_tbl[unit_num].x = unit_data.x + units_tbl[unit_num].y = unit_data.y + + units_tbl[unit_num].callsign = unit_data.callsign + units_tbl[unit_num].onboard_num = unit_data.onboard_num + units_tbl[unit_num].hardpoint_racks = unit_data.hardpoint_racks + units_tbl[unit_num].psi = unit_data.psi + + + units_tbl[unit_num].groupName = groupName + units_tbl[unit_num].groupId = group_data.groupId + + if unit_data.AddPropAircraft then + units_tbl[unit_num].AddPropAircraft = unit_data.AddPropAircraft + end + + if category == 'static' then + units_tbl[unit_num].categoryStatic = unit_data.category + units_tbl[unit_num].shape_name = unit_data.shape_name + if unit_data.mass then + units_tbl[unit_num].mass = unit_data.mass + end + + if unit_data.canCargo then + units_tbl[unit_num].canCargo = unit_data.canCargo + end + end + + end --for unit_num, unit_data in pairs(group_data.units) do + end --if group_data and group_data.units then + end --for group_num, group_data in pairs(obj_type_data.group) do + end --if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then + end --if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" or obj_type_name == "static" then + end --for obj_type_name, obj_type_data in pairs(cntry_data) do + end --if type(cntry_data) == 'table' then + end --for cntry_id, cntry_data in pairs(coa_data.country) do + end --if coa_data.country then --there is a country table + end --if coa_name == 'red' or coa_name == 'blue' and type(coa_data) == 'table' then + end --for coa_name, coa_data in pairs(mission.coalition) do + + mist.DBs.unitsByName = {} + mist.DBs.unitsById = {} + mist.DBs.unitsByCat = {} + + mist.DBs.unitsByCat.helicopter = {} -- adding default categories + mist.DBs.unitsByCat.plane = {} + mist.DBs.unitsByCat.ship = {} + mist.DBs.unitsByCat.static = {} + mist.DBs.unitsByCat.vehicle = {} + + mist.DBs.unitsByNum = {} + + mist.DBs.groupsByName = {} + mist.DBs.groupsById = {} + mist.DBs.humansByName = {} + mist.DBs.humansById = {} + + mist.DBs.dynGroupsAdded = {} -- will be filled by mist.dbUpdate from dynamically spawned groups + mist.DBs.activeHumans = {} + + mist.DBs.aliveUnits = {} -- will be filled in by the "updateAliveUnits" coroutine in mist.main. + + mist.DBs.removedAliveUnits = {} -- will be filled in by the "updateAliveUnits" coroutine in mist.main. + + mist.DBs.const = {} + + -- not accessible by SSE, must use static list :-/ + mist.DBs.const.callsigns = { + ['NATO'] = { + ['rules'] = { + ['groupLimit'] = 9, + }, + ['AWACS'] = { + ['Overlord'] = 1, + ['Magic'] = 2, + ['Wizard'] = 3, + ['Focus'] = 4, + ['Darkstar'] = 5, + }, + ['TANKER'] = { + ['Texaco'] = 1, + ['Arco'] = 2, + ['Shell'] = 3, + }, + ['JTAC'] = { + ['Axeman'] = 1, + ['Darknight'] = 2, + ['Warrior'] = 3, + ['Pointer'] = 4, + ['Eyeball'] = 5, + ['Moonbeam'] = 6, + ['Whiplash'] = 7, + ['Finger'] = 8, + ['Pinpoint'] = 9, + ['Ferret'] = 10, + ['Shaba'] = 11, + ['Playboy'] = 12, + ['Hammer'] = 13, + ['Jaguar'] = 14, + ['Deathstar'] = 15, + ['Anvil'] = 16, + ['Firefly'] = 17, + ['Mantis'] = 18, + ['Badger'] = 19, + }, + ['aircraft'] = { + ['Enfield'] = 1, + ['Springfield'] = 2, + ['Uzi'] = 3, + ['Colt'] = 4, + ['Dodge'] = 5, + ['Ford'] = 6, + ['Chevy'] = 7, + ['Pontiac'] = 8, + }, + + ['unique'] = { + ['A10'] = { + ['Hawg'] = 9, + ['Boar'] = 10, + ['Pig'] = 11, + ['Tusk'] = 12, + ['rules'] = { + ['canUseAircraft'] = true, + ['appliesTo'] = { + 'A-10C', + 'A-10A', + }, + }, + }, + }, + }, + } + mist.DBs.const.shapeNames = { + ["Landmine"] = "landmine", + ["FARP CP Blindage"] = "kp_ug", + ["Subsidiary structure C"] = "saray-c", + ["Barracks 2"] = "kazarma2", + ["Small house 2C"] = "dom2c", + ["Military staff"] = "aviashtab", + ["Tech hangar A"] = "ceh_ang_a", + ["Oil derrick"] = "neftevyshka", + ["Tech combine"] = "kombinat", + ["Garage B"] = "garage_b", + ["Airshow_Crowd"] = "Crowd1", + ["Hangar A"] = "angar_a", + ["Repair workshop"] = "tech", + ["Subsidiary structure D"] = "saray-d", + ["FARP Ammo Dump Coating"] = "SetkaKP", + ["Small house 1C area"] = "dom2c-all", + ["Tank 2"] = "airbase_tbilisi_tank_01", + ["Boiler-house A"] = "kotelnaya_a", + ["Workshop A"] = "tec_a", + ["Small werehouse 1"] = "s1", + ["Garage small B"] = "garagh-small-b", + ["Small werehouse 4"] = "s4", + ["Shop"] = "magazin", + ["Subsidiary structure B"] = "saray-b", + ["FARP Fuel Depot"] = "GSM Rus", + ["Coach cargo"] = "wagon-gruz", + ["Electric power box"] = "tr_budka", + ["Tank 3"] = "airbase_tbilisi_tank_02", + ["Red_Flag"] = "H-flag_R", + ["Container red 3"] = "konteiner_red3", + ["Garage A"] = "garage_a", + ["Hangar B"] = "angar_b", + ["Black_Tyre"] = "H-tyre_B", + ["Cafe"] = "stolovaya", + ["Restaurant 1"] = "restoran1", + ["Subsidiary structure A"] = "saray-a", + ["Container white"] = "konteiner_white", + ["Warehouse"] = "sklad", + ["Tank"] = "bak", + ["Railway crossing B"] = "pereezd_small", + ["Subsidiary structure F"] = "saray-f", + ["Farm A"] = "ferma_a", + ["Small werehouse 3"] = "s3", + ["Water tower A"] = "wodokachka_a", + ["Railway station"] = "r_vok_sd", + ["Coach a tank blue"] = "wagon-cisterna_blue", + ["Supermarket A"] = "uniwersam_a", + ["Coach a platform"] = "wagon-platforma", + ["Garage small A"] = "garagh-small-a", + ["TV tower"] = "tele_bash", + ["Comms tower M"] = "tele_bash_m", + ["Small house 1A"] = "domik1a", + ["Farm B"] = "ferma_b", + ["GeneratorF"] = "GeneratorF", + ["Cargo1"] = "ab-212_cargo", + ["Container red 2"] = "konteiner_red2", + ["Subsidiary structure E"] = "saray-e", + ["Coach a passenger"] = "wagon-pass", + ["Black_Tyre_WF"] = "H-tyre_B_WF", + ["Electric locomotive"] = "elektrowoz", + ["Shelter"] = "ukrytie", + ["Coach a tank yellow"] = "wagon-cisterna_yellow", + ["Railway crossing A"] = "pereezd_big", + [".Ammunition depot"] = "SkladC", + ["Small werehouse 2"] = "s2", + ["Windsock"] = "H-Windsock_RW", + ["Shelter B"] = "ukrytie_b", + ["Fuel tank"] = "toplivo-bak", + ["Locomotive"] = "teplowoz", + [".Command Center"] = "ComCenter", + ["Pump station"] = "nasos", + ["Black_Tyre_RF"] = "H-tyre_B_RF", + ["Coach cargo open"] = "wagon-gruz-otkr", + ["Subsidiary structure 3"] = "hozdomik3", + ["FARP Tent"] = "PalatkaB", + ["White_Tyre"] = "H-tyre_W", + ["Subsidiary structure G"] = "saray-g", + ["Container red 1"] = "konteiner_red1", + ["Small house 1B area"] = "domik1b-all", + ["Subsidiary structure 1"] = "hozdomik1", + ["Container brown"] = "konteiner_brown", + ["Small house 1B"] = "domik1b", + ["Subsidiary structure 2"] = "hozdomik2", + ["Chemical tank A"] = "him_bak_a", + ["WC"] = "WC", + ["Small house 1A area"] = "domik1a-all", + ["White_Flag"] = "H-Flag_W", + ["Airshow_Cone"] = "Comp_cone", + } + + + -- create mist.DBs.oldAliveUnits + -- do + -- local intermediate_alive_units = {} -- between 0 and 0.5 secs old + -- local function make_old_alive_units() -- called every 0.5 secs, makes the old_alive_units DB which is just a copy of alive_units that is 0.5 to 1 sec old + -- if intermediate_alive_units then + -- mist.DBs.oldAliveUnits = mist.utils.deepCopy(intermediate_alive_units) + -- end + -- intermediate_alive_units = mist.utils.deepCopy(mist.DBs.aliveUnits) + -- timer.scheduleFunction(make_old_alive_units, nil, timer.getTime() + 0.5) + -- end + + -- make_old_alive_units() + -- end + + --Build DBs + for coa_name, coa_data in pairs(mist.DBs.units) do + for cntry_name, cntry_data in pairs(coa_data) do + for category_name, category_data in pairs(cntry_data) do + if type(category_data) == 'table' then + for group_ind, group_data in pairs(category_data) do + if type(group_data) == 'table' and group_data.units and type(group_data.units) == 'table' and #group_data.units > 0 then -- OCD paradigm programming + mist.DBs.groupsByName[group_data.groupName] = mist.utils.deepCopy(group_data) + mist.DBs.groupsById[group_data.groupId] = mist.utils.deepCopy(group_data) + for unit_ind, unit_data in pairs(group_data.units) do + mist.DBs.unitsByName[unit_data.unitName] = mist.utils.deepCopy(unit_data) + mist.DBs.unitsById[unit_data.unitId] = mist.utils.deepCopy(unit_data) + + mist.DBs.unitsByCat[unit_data.category] = mist.DBs.unitsByCat[unit_data.category] or {} -- future-proofing against new categories... + table.insert(mist.DBs.unitsByCat[unit_data.category], mist.utils.deepCopy(unit_data)) + dbLog:info('inserting $1', unit_data.unitName) + table.insert(mist.DBs.unitsByNum, mist.utils.deepCopy(unit_data)) + + if unit_data.skill and (unit_data.skill == "Client" or unit_data.skill == "Player") then + mist.DBs.humansByName[unit_data.unitName] = mist.utils.deepCopy(unit_data) + mist.DBs.humansById[unit_data.unitId] = mist.utils.deepCopy(unit_data) + --if Unit.getByName(unit_data.unitName) then + -- mist.DBs.activeHumans[unit_data.unitName] = mist.utils.deepCopy(unit_data) + -- mist.DBs.activeHumans[unit_data.unitName].playerName = Unit.getByName(unit_data.unitName):getPlayerName() + --end + end + end + end + end + end + end + end + end + + --DynDBs + mist.DBs.MEunits = mist.utils.deepCopy(mist.DBs.units) + mist.DBs.MEunitsByName = mist.utils.deepCopy(mist.DBs.unitsByName) + mist.DBs.MEunitsById = mist.utils.deepCopy(mist.DBs.unitsById) + mist.DBs.MEunitsByCat = mist.utils.deepCopy(mist.DBs.unitsByCat) + mist.DBs.MEunitsByNum = mist.utils.deepCopy(mist.DBs.unitsByNum) + mist.DBs.MEgroupsByName = mist.utils.deepCopy(mist.DBs.groupsByName) + mist.DBs.MEgroupsById = mist.utils.deepCopy(mist.DBs.groupsById) + + mist.DBs.deadObjects = {} + + do + local mt = {} + + function mt.__newindex(t, key, val) + local original_key = key --only for duplicate runtime IDs. + local key_ind = 1 + while mist.DBs.deadObjects[key] do + dbLog:warn('duplicate runtime id of previously dead object key: $1', key) + key = tostring(original_key) .. ' #' .. tostring(key_ind) + key_ind = key_ind + 1 + end + + if mist.DBs.aliveUnits and mist.DBs.aliveUnits[val.object.id_] then + --dbLog:info('object found in alive_units') + val.objectData = mist.utils.deepCopy(mist.DBs.aliveUnits[val.object.id_]) + local pos = Object.getPosition(val.object) + if pos then + val.objectPos = pos.p + end + val.objectType = mist.DBs.aliveUnits[val.object.id_].category + + elseif mist.DBs.removedAliveUnits and mist.DBs.removedAliveUnits[val.object.id_] then -- it didn't exist in alive_units, check old_alive_units + --dbLog:info('object found in old_alive_units') + val.objectData = mist.utils.deepCopy(mist.DBs.removedAliveUnits[val.object.id_]) + local pos = Object.getPosition(val.object) + if pos then + val.objectPos = pos.p + end + val.objectType = mist.DBs.removedAliveUnits[val.object.id_].category + + else --attempt to determine if static object... + --dbLog:info('object not found in alive units or old alive units') + local pos = Object.getPosition(val.object) + if pos then + local static_found = false + for ind, static in pairs(mist.DBs.unitsByCat.static) do + if ((pos.p.x - static.point.x)^2 + (pos.p.z - static.point.y)^2)^0.5 < 0.1 then --really, it should be zero... + dbLog:info('correlated dead static object to position') + val.objectData = static + val.objectPos = pos.p + val.objectType = 'static' + static_found = true + break + end + end + if not static_found then + val.objectPos = pos.p + val.objectType = 'building' + end + else + val.objectType = 'unknown' + end + end + rawset(t, key, val) + end + + setmetatable(mist.DBs.deadObjects, mt) + end + + do -- mist unitID funcs + for id, idData in pairs(mist.DBs.unitsById) do + if idData.unitId > mist.nextUnitId then + mist.nextUnitId = mist.utils.deepCopy(idData.unitId) + end + if idData.groupId > mist.nextGroupId then + mist.nextGroupId = mist.utils.deepCopy(idData.groupId) + end + end + end + + + end + + local function updateAliveUnits() -- coroutine function + local lalive_units = mist.DBs.aliveUnits -- local references for faster execution + local lunits = mist.DBs.unitsByNum + local ldeepcopy = mist.utils.deepCopy + local lUnit = Unit + local lremovedAliveUnits = mist.DBs.removedAliveUnits + local updatedUnits = {} + + if #lunits > 0 then + local units_per_run = math.ceil(#lunits/20) + if units_per_run < 5 then + units_per_run = 5 + end + + for i = 1, #lunits do + if lunits[i].category ~= 'static' then -- can't get statics with Unit.getByName :( + local unit = lUnit.getByName(lunits[i].unitName) + if unit then + --dbLog:info("unit named $1 alive!", lunits[i].unitName) -- spammy + local pos = unit:getPosition() + local newtbl = ldeepcopy(lunits[i]) + if pos then + newtbl.pos = pos.p + end + newtbl.unit = unit + --newtbl.rt_id = unit.id_ + lalive_units[unit.id_] = newtbl + updatedUnits[unit.id_] = true + end + end + if i%units_per_run == 0 then + coroutine.yield() + end + end + -- All units updated, remove any "alive" units that were not updated- they are dead! + for unit_id, unit in pairs(lalive_units) do + if not updatedUnits[unit_id] then + lremovedAliveUnits[unit_id] = unit + lalive_units[unit_id] = nil + end + end + end + end + + local function dbUpdate(event, objType) + dbLog:info('dbUpdate') + local newTable = {} + newTable.startTime = 0 + if type(event) == 'string' then -- if name of an object. + local newObject + if Group.getByName(event) then + newObject = Group.getByName(event) + elseif StaticObject.getByName(event) then + newObject = StaticObject.getByName(event) + -- log:info('its static') + else + log:warn('$1 is not a Unit or Static Object. This should not be possible', event) + return false + end + + newTable.name = newObject:getName() + newTable.groupId = tonumber(newObject:getID()) + newTable.groupName = newObject:getName() + local unitOneRef + if objType == 'static' then + unitOneRef = newObject + newTable.countryId = tonumber(newObject:getCountry()) + newTable.coalitionId = tonumber(newObject:getCoalition()) + newTable.category = 'static' + else + unitOneRef = newObject:getUnits() + newTable.countryId = tonumber(unitOneRef[1]:getCountry()) + newTable.coalitionId = tonumber(unitOneRef[1]:getCoalition()) + newTable.category = tonumber(newObject:getCategory()) + end + for countryData, countryId in pairs(country.id) do + if newTable.country and string.upper(countryData) == string.upper(newTable.country) or countryId == newTable.countryId then + newTable.countryId = countryId + newTable.country = string.lower(countryData) + for coaData, coaId in pairs(coalition.side) do + if coaId == coalition.getCountryCoalition(countryId) then + newTable.coalition = string.lower(coaData) + end + end + end + end + for catData, catId in pairs(Unit.Category) do + if objType == 'group' and Group.getByName(newTable.groupName):isExist() then + if catId == Group.getByName(newTable.groupName):getCategory() then + newTable.category = string.lower(catData) + end + elseif objType == 'static' and StaticObject.getByName(newTable.groupName):isExist() then + if catId == StaticObject.getByName(newTable.groupName):getCategory() then + newTable.category = string.lower(catData) + end + + end + end + local gfound = false + for index, data in pairs(mistAddedGroups) do + if mist.stringMatch(data.name, newTable.groupName) == true then + gfound = true + newTable.task = data.task + newTable.modulation = data.modulation + newTable.uncontrolled = data.uncontrolled + newTable.radioSet = data.radioSet + newTable.hidden = data.hidden + newTable.startTime = data.start_time + mistAddedGroups[index] = nil + end + end + + if gfound == false then + newTable.uncontrolled = false + newTable.hidden = false + end + + newTable.units = {} + if objType == 'group' then + for unitId, unitData in pairs(unitOneRef) do + newTable.units[unitId] = {} + newTable.units[unitId].unitName = unitData:getName() + + newTable.units[unitId].x = mist.utils.round(unitData:getPosition().p.x) + newTable.units[unitId].y = mist.utils.round(unitData:getPosition().p.z) + newTable.units[unitId].point = {} + newTable.units[unitId].point.x = newTable.units[unitId].x + newTable.units[unitId].point.y = newTable.units[unitId].y + newTable.units[unitId].alt = mist.utils.round(unitData:getPosition().p.y) + newTable.units[unitId].speed = mist.vec.mag(unitData:getVelocity()) + + newTable.units[unitId].heading = mist.getHeading(unitData, true) + + newTable.units[unitId].type = unitData:getTypeName() + newTable.units[unitId].unitId = tonumber(unitData:getID()) + + + newTable.units[unitId].groupName = newTable.groupName + newTable.units[unitId].groupId = newTable.groupId + newTable.units[unitId].countryId = newTable.countryId + newTable.units[unitId].coalitionId = newTable.coalitionId + newTable.units[unitId].coalition = newTable.coalition + newTable.units[unitId].country = newTable.country + local found = false + for index, data in pairs(mistAddedObjects) do + if mist.stringMatch(data.name, newTable.units[unitId].unitName) == true then + found = true + newTable.units[unitId].livery_id = data.livery_id + newTable.units[unitId].skill = data.skill + newTable.units[unitId].alt_type = data.alt_type + newTable.units[unitId].callsign = data.callsign + newTable.units[unitId].psi = data.psi + mistAddedObjects[index] = nil + end + if found == false then + newTable.units[unitId].skill = "High" + newTable.units[unitId].alt_type = "BARO" + end + end + + end + else -- its a static + newTable.category = 'static' + newTable.units[1] = {} + newTable.units[1].unitName = newObject:getName() + newTable.units[1].category = 'static' + newTable.units[1].x = mist.utils.round(newObject:getPosition().p.x) + newTable.units[1].y = mist.utils.round(newObject:getPosition().p.z) + newTable.units[1].point = {} + newTable.units[1].point.x = newTable.units[1].x + newTable.units[1].point.y = newTable.units[1].y + newTable.units[1].alt = mist.utils.round(newObject:getPosition().p.y) + newTable.units[1].heading = mist.getHeading(newObject, true) + newTable.units[1].type = newObject:getTypeName() + newTable.units[1].unitId = tonumber(newObject:getID()) + newTable.units[1].groupName = newTable.name + newTable.units[1].groupId = newTable.groupId + newTable.units[1].countryId = newTable.countryId + newTable.units[1].country = newTable.country + newTable.units[1].coalitionId = newTable.coalitionId + newTable.units[1].coalition = newTable.coalition + if newObject:getCategory() == 6 and newObject:getCargoDisplayName() then + local mass = newObject:getCargoDisplayName() + mass = string.gsub(mass, ' ', '') + mass = string.gsub(mass, 'kg', '') + newTable.units[1].mass = tonumber(mass) + newTable.units[1].categoryStatic = 'Cargos' + newTable.units[1].canCargo = true + newTable.units[1].shape_name = 'ab-212_cargo' + end + + ----- search mist added objects for extra data if applicable + for index, data in pairs(mistAddedObjects) do + if mist.stringMatch(data.name, newTable.units[1].unitName) == true then + newTable.units[1].shape_name = data.shape_name -- for statics + newTable.units[1].livery_id = data.livery_id + newTable.units[1].airdromeId = data.airdromeId + newTable.units[1].mass = data.mass + newTable.units[1].canCargo = data.canCargo + newTable.units[1].categoryStatic = data.categoryStatic + newTable.units[1].type = 'cargo1' + mistAddedObjects[index] = nil + end + end + end + end + --mist.debug.writeData(mist.utils.serialize,{'msg', newTable}, timer.getAbsTime() ..'Group.lua') + newTable.timeAdded = timer.getAbsTime() -- only on the dynGroupsAdded table. For other reference, see start time + --mist.debug.dumpDBs() + --end + dbLog:info('endDbUpdate') + return newTable + end + + --[[DB update code... FRACK. I need to refactor some of it. + + The problem is that the DBs need to account better for shared object names. Needs to write over some data and outright remove other. + + If groupName is used then entire group needs to be rewritten + what to do with old groups units DB entries?. Names cant be assumed to be the same. + + + -- new spawn event check. + -- event handler filters everything into groups: tempSpawnedGroups + -- this function then checks DBs to see if data has changed + ]] + local function checkSpawnedEventsNew() + if tempSpawnGroupsCounter > 0 then + --[[local updatesPerRun = math.ceil(#tempSpawnedGroupsCounter/20) + if updatesPerRun < 5 then + updatesPerRun = 5 + end]] + + dbLog:info('iterate') + for name, gType in pairs(tempSpawnedGroups) do + dbLog:info(name) + local updated = false + + if mist.DBs.groupsByName[name] then + -- first check group level properties, groupId, countryId, coalition + dbLog:info('Found in DBs, check if updated') + local dbTable = mist.DBs.groupsByName[name] + dbLog:info(dbTable) + if gType ~= 'static' then + dbLog:info('Not static') + local _g = Group.getByName(name) + local _u = _g:getUnit(1) + if dbTable.groupId ~= tonumber(_g:getID()) or _u:getCountry() ~= dbTable.countryId or _u:getCoalition() ~= dbTable.coaltionId then + dbLog:info('Group Data mismatch') + updated = true + else + dbLog:info('No Mismatch') + end + + end + end + dbLog:info('Updated: $1', updated) + if updated == false and gType ~= 'static' then -- time to check units + dbLog:info('No Group Mismatch, Check Units') + for index, uObject in pairs(Group.getByName(name):getUnits()) do + dbLog:info(index) + if mist.DBs.unitsByName[uObject:getName()] then + dbLog:info('UnitByName table exists') + local uTable = mist.DBs.unitsByName[uObject:getName()] + if tonumber(uObject:getID()) ~= uTable.unitId or uObject:getTypeName() ~= uTable.type then + dbLog:info('Unit Data mismatch') + updated = true + break + end + end + end + end + + if updated == true or not mist.DBs.groupsByName[name] then + dbLog:info('Get Table') + writeGroups[#writeGroups+1] = {data = dbUpdate(name, gType), isUpdated = updated} + + end + -- Work done, so remove + tempSpawnedGroups[name] = nil + tempSpawnGroupsCounter = tempSpawnGroupsCounter - 1 + end + end + end + + local function updateDBTables() + local i = #writeGroups + + local savesPerRun = math.ceil(i/10) + if savesPerRun < 5 then + savesPerRun = 5 + end + if i > 0 then + dbLog:info('updateDBTables') + local ldeepCopy = mist.utils.deepCopy + for x = 1, i do + dbLog:info(writeGroups[x]) + local newTable = writeGroups[x].data + local updated = writeGroups[x].isUpdated + local mistCategory + if type(newTable.category) == 'string' then + mistCategory = string.lower(newTable.category) + end + + if string.upper(newTable.category) == 'GROUND_UNIT' then + mistCategory = 'vehicle' + newTable.category = mistCategory + elseif string.upper(newTable.category) == 'AIRPLANE' then + mistCategory = 'plane' + newTable.category = mistCategory + elseif string.upper(newTable.category) == 'HELICOPTER' then + mistCategory = 'helicopter' + newTable.category = mistCategory + elseif string.upper(newTable.category) == 'SHIP' then + mistCategory = 'ship' + newTable.category = mistCategory + end + dbLog:info('Update unitsBy') + for newId, newUnitData in pairs(newTable.units) do + dbLog:info(newId) + newUnitData.category = mistCategory + if newUnitData.unitId then + dbLog:info('byId') + mist.DBs.unitsById[tonumber(newUnitData.unitId)] = ldeepCopy(newUnitData) + end + dbLog:info(updated) + if mist.DBs.unitsByName[newUnitData.unitName] and updated == true then--if unit existed before and something was updated, write over the entry for a given unit name just in case. + dbLog:info('Updating Unit Tables') + for i = 1, #mist.DBs.unitsByCat[mistCategory] do + if mist.DBs.unitsByCat[mistCategory][i].unitName == newUnitData.unitName then + dbLog:info('Entry Found, Rewriting for unitsByCat') + mist.DBs.unitsByCat[mistCategory][i] = ldeepCopy(newUnitData) + break + end + end + for i = 1, #mist.DBs.unitsByNum do + if mist.DBs.unitsByNum[i].unitName == newUnitData.unitName then + dbLog:info('Entry Found, Rewriting for unitsByNum') + mist.DBs.unitsByNum[i] = ldeepCopy(newUnitData) + break + end + end + + else + dbLog:info('Unitname not in use, add as normal') + mist.DBs.unitsByCat[mistCategory][#mist.DBs.unitsByCat[mistCategory] + 1] = ldeepCopy(newUnitData) + mist.DBs.unitsByNum[#mist.DBs.unitsByNum + 1] = ldeepCopy(newUnitData) + end + mist.DBs.unitsByName[newUnitData.unitName] = ldeepCopy(newUnitData) + + + end + -- this is a really annoying DB to populate. Gotta create new tables in case its missing + dbLog:info('write mist.DBs.units') + if not mist.DBs.units[newTable.coalition] then + mist.DBs.units[newTable.coalition] = {} + end + + if not mist.DBs.units[newTable.coalition][newTable.country] then + mist.DBs.units[newTable.coalition][(newTable.country)] = {} + mist.DBs.units[newTable.coalition][(newTable.country)].countryId = newTable.countryId + end + if not mist.DBs.units[newTable.coalition][newTable.country][mistCategory] then + mist.DBs.units[newTable.coalition][(newTable.country)][mistCategory] = {} + end + + if updated == true then + dbLog:info('Updating DBsUnits') + for i = 1, #mist.DBs.units[newTable.coalition][(newTable.country)][mistCategory] do + if mist.DBs.units[newTable.coalition][(newTable.country)][mistCategory][i].groupName == newTable.groupName then + dbLog:info('Entry Found, Rewriting') + mist.DBs.units[newTable.coalition][(newTable.country)][mistCategory][i] = ldeepCopy(newTable) + break + end + end + else + mist.DBs.units[newTable.coalition][(newTable.country)][mistCategory][#mist.DBs.units[newTable.coalition][(newTable.country)][mistCategory] + 1] = ldeepCopy(newTable) + end + + + if newTable.groupId then + mist.DBs.groupsById[newTable.groupId] = ldeepCopy(newTable) + end + + mist.DBs.groupsByName[newTable.name] = ldeepCopy(newTable) + mist.DBs.dynGroupsAdded[#mist.DBs.dynGroupsAdded + 1] = ldeepCopy(newTable) + + writeGroups[x] = nil + if x%savesPerRun == 0 then + coroutine.yield() + end + end + if timer.getTime() > lastUpdateTime then + lastUpdateTime = timer.getTime() + end + dbLog:info('endUpdateTables') + end + end + + local function groupSpawned(event) + -- dont need to add units spawned in at the start of the mission if mist is loaded in init line + if event.id == world.event.S_EVENT_BIRTH and timer.getTime0() < timer.getAbsTime() then + dbLog:info('unitSpawnEvent') + + --table.insert(tempSpawnedUnits,(event.initiator)) + ------- + -- New functionality below. + ------- + if Object.getCategory(event.initiator) == 1 and not Unit.getPlayerName(event.initiator) then -- simple player check, will need to later check to see if unit was spawned with a player in a flight + dbLog:info('Object is a Unit') + dbLog:info(Unit.getGroup(event.initiator):getName()) + if not tempSpawnedGroups[Unit.getGroup(event.initiator):getName()] then + dbLog:info('added') + tempSpawnedGroups[Unit.getGroup(event.initiator):getName()] = 'group' + tempSpawnGroupsCounter = tempSpawnGroupsCounter + 1 + end + elseif Object.getCategory(event.initiator) == 3 or Object.getCategory(event.initiator) == 6 then + dbLog:info('Object is Static') + tempSpawnedGroups[StaticObject.getName(event.initiator)] = 'static' + tempSpawnGroupsCounter = tempSpawnGroupsCounter + 1 + end + + + end + end + + local function doScheduledFunctions() + local i = 1 + while i <= #scheduledTasks do + if not scheduledTasks[i].rep then -- not a repeated process + if scheduledTasks[i].t <= timer.getTime() then + local task = scheduledTasks[i] -- local reference + table.remove(scheduledTasks, i) + local err, errmsg = pcall(task.f, unpack(task.vars, 1, table.maxn(task.vars))) + if not err then + log:error('Error in scheduled function: $1', errmsg) + end + --task.f(unpack(task.vars, 1, table.maxn(task.vars))) -- do the task, do not increment i + else + i = i + 1 + end + else + if scheduledTasks[i].st and scheduledTasks[i].st <= timer.getTime() then --if a stoptime was specified, and the stop time exceeded + table.remove(scheduledTasks, i) -- stop time exceeded, do not execute, do not increment i + elseif scheduledTasks[i].t <= timer.getTime() then + local task = scheduledTasks[i] -- local reference + task.t = timer.getTime() + task.rep --schedule next run + local err, errmsg = pcall(task.f, unpack(task.vars, 1, table.maxn(task.vars))) + if not err then + log:error('Error in scheduled function: $1' .. errmsg) + end + --scheduledTasks[i].f(unpack(scheduledTasks[i].vars, 1, table.maxn(scheduledTasks[i].vars))) -- do the task + i = i + 1 + else + i = i + 1 + end + end + end + end + + -- Event handler to start creating the dead_objects table + local function addDeadObject(event) + if event.id == world.event.S_EVENT_DEAD or event.id == world.event.S_EVENT_CRASH then + if event.initiator and event.initiator.id_ and event.initiator.id_ > 0 then + + local id = event.initiator.id_ -- initial ID, could change if there is a duplicate id_ already dead. + local val = {object = event.initiator} -- the new entry in mist.DBs.deadObjects. + + local original_id = id --only for duplicate runtime IDs. + local id_ind = 1 + while mist.DBs.deadObjects[id] do + --log:info('duplicate runtime id of previously dead object id: $1', id) + id = tostring(original_id) .. ' #' .. tostring(id_ind) + id_ind = id_ind + 1 + end + + if mist.DBs.aliveUnits and mist.DBs.aliveUnits[val.object.id_] then + --log:info('object found in alive_units') + val.objectData = mist.utils.deepCopy(mist.DBs.aliveUnits[val.object.id_]) + local pos = Object.getPosition(val.object) + if pos then + val.objectPos = pos.p + end + val.objectType = mist.DBs.aliveUnits[val.object.id_].category + --[[if mist.DBs.activeHumans[Unit.getName(val.object)] then + --trigger.action.outText('remove via death: ' .. Unit.getName(val.object),20) + mist.DBs.activeHumans[Unit.getName(val.object)] = nil + end]] + elseif mist.DBs.removedAliveUnits and mist.DBs.removedAliveUnits[val.object.id_] then -- it didn't exist in alive_units, check old_alive_units + --log:info('object found in old_alive_units') + val.objectData = mist.utils.deepCopy(mist.DBs.removedAliveUnits[val.object.id_]) + local pos = Object.getPosition(val.object) + if pos then + val.objectPos = pos.p + end + val.objectType = mist.DBs.removedAliveUnits[val.object.id_].category + + else --attempt to determine if static object... + --log:info('object not found in alive units or old alive units') + local pos = Object.getPosition(val.object) + if pos then + local static_found = false + for ind, static in pairs(mist.DBs.unitsByCat.static) do + if ((pos.p.x - static.point.x)^2 + (pos.p.z - static.point.y)^2)^0.5 < 0.1 then --really, it should be zero... + --log:info('correlated dead static object to position') + val.objectData = static + val.objectPos = pos.p + val.objectType = 'static' + static_found = true + break + end + end + if not static_found then + val.objectPos = pos.p + val.objectType = 'building' + end + else + val.objectType = 'unknown' + end + end + mist.DBs.deadObjects[id] = val + end + end + end + + --[[ + local function addClientsToActive(event) + if event.id == world.event.S_EVENT_PLAYER_ENTER_UNIT or event.id == world.event.S_EVENT_BIRTH then + log:info(event) + if Unit.getPlayerName(event.initiator) then + log:info(Unit.getPlayerName(event.initiator)) + local newU = mist.utils.deepCopy(mist.DBs.unitsByName[Unit.getName(event.initiator)]) + newU.playerName = Unit.getPlayerName(event.initiator) + mist.DBs.activeHumans[Unit.getName(event.initiator)] = newU + --trigger.action.outText('added: ' .. Unit.getName(event.initiator), 20) + end + elseif event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT and event.initiator then + if mist.DBs.activeHumans[Unit.getName(event.initiator)] then + mist.DBs.activeHumans[Unit.getName(event.initiator)] = nil + -- trigger.action.outText('removed via control: ' .. Unit.getName(event.initiator), 20) + end + end + end + + mist.addEventHandler(addClientsToActive) + ]] + + --- init function. + -- creates logger, adds default event handler + -- and calls main the first time. + -- @function mist.init + function mist.init() + -- create logger + mist.log = mist.Logger:new("MIST") + dbLog = mist.Logger:new('MISTDB', 'warning') + + log = mist.log -- log shorthand + -- set warning log level, showing only + -- warnings and errors + log:setLevel("warning") + + log:info("initializing databases") + initDBs() + + -- add event handler for group spawns + mist.addEventHandler(groupSpawned) + mist.addEventHandler(addDeadObject) + + -- call main the first time therafter it reschedules itself. + mist.main() + --log:msg('MIST version $1.$2.$3 loaded', mist.majorVersion, mist.minorVersion, mist.build) + return + end + + --- The main function. + -- Run 100 times per second. + -- You shouldn't call this function. + function mist.main() + timer.scheduleFunction(mist.main, {}, timer.getTime() + 0.01) --reschedule first in case of Lua error + + updateTenthSecond = updateTenthSecond + 1 + if updateTenthSecond == 10 then + updateTenthSecond = 0 + + checkSpawnedEventsNew() + + if not coroutines.updateDBTables then + coroutines.updateDBTables = coroutine.create(updateDBTables) + end + + coroutine.resume(coroutines.updateDBTables) + + if coroutine.status(coroutines.updateDBTables) == 'dead' then + coroutines.updateDBTables = nil + end + end + + --updating alive units + updateAliveUnitsCounter = updateAliveUnitsCounter + 1 + if updateAliveUnitsCounter == 5 then + updateAliveUnitsCounter = 0 + + if not coroutines.updateAliveUnits then + coroutines.updateAliveUnits = coroutine.create(updateAliveUnits) + end + + coroutine.resume(coroutines.updateAliveUnits) + + if coroutine.status(coroutines.updateAliveUnits) == 'dead' then + coroutines.updateAliveUnits = nil + end + end + + doScheduledFunctions() + end -- end of mist.main + + --- Returns next unit id. + -- @treturn number next unit id. + function mist.getNextUnitId() + mist.nextUnitId = mist.nextUnitId + 1 + if mist.nextUnitId > 6900 then + mist.nextUnitId = 14000 + end + return mist.nextUnitId + end + + --- Returns next group id. + -- @treturn number next group id. + function mist.getNextGroupId() + mist.nextGroupId = mist.nextGroupId + 1 + if mist.nextGroupId > 6900 then + mist.nextGroupId = 14000 + end + return mist.nextGroupId + end + + --- Returns timestamp of last database update. + -- @treturn timestamp of last database update + function mist.getLastDBUpdateTime() + return lastUpdateTime + end + + --- Spawns a static object to the game world. + -- @todo write good docs + -- @tparam table staticObj table containing data needed for the object creation + function mist.dynAddStatic(newObj) + + if newObj.units and newObj.units[1] then -- if its mist format + for entry, val in pairs(newObj.units[1]) do + if newObj[entry] and newObj[entry] ~= val or not newObj[entry] then + newObj[entry] = val + end + end + end + --log:info(newObj) + + local cntry = newObj.country + if newObj.countryId then + cntry = newObj.countryId + end + + local newCountry = '' + + for countryId, countryName in pairs(country.name) do + if type(cntry) == 'string' then + cntry = cntry:gsub("%s+", "_") + if tostring(countryName) == string.upper(cntry) then + newCountry = countryName + end + elseif type(cntry) == 'number' then + if countryId == cntry then + newCountry = countryName + end + end + end + + if newCountry == '' then + log:error("Country not found: $1", cntry) + return false + end + + if newObj.clone or not newObj.groupId then + mistGpId = mistGpId + 1 + newObj.groupId = mistGpId + end + + if newObj.clone or not newObj.unitId then + mistUnitId = mistUnitId + 1 + newObj.unitId = mistUnitId + end + + if newObj.clone or not newObj.name then + mistDynAddIndex[' static '] = mistDynAddIndex[' static '] + 1 + newObj.name = (newCountry .. ' static ' .. mistDynAddIndex[' static ']) + end + + if not newObj.dead then + newObj.dead = false + end + + if not newObj.heading then + newObj.heading = math.random(360) + end + + if newObj.categoryStatic then + newObj.category = newObj.categoryStatic + end + if newObj.mass then + newObj.category = 'Cargos' + end + + if newObj.shapeName then + newObj.shape_name = newObj.shapeName + end + + if not newObj.shape_name then + log:info('shape_name not present') + if mist.DBs.const.shapeNames[newObj.type] then + newObj.shape_name = mist.DBs.const.shapeNames[newObj.type] + end + end + + mistAddedObjects[#mistAddedObjects + 1] = mist.utils.deepCopy(newObj) + if newObj.x and newObj.y and newObj.type and type(newObj.x) == 'number' and type(newObj.y) == 'number' and type(newObj.type) == 'string' then + --log:info('addStaticObject') + coalition.addStaticObject(country.id[newCountry], newObj) + + return newObj + end + log:error("Failed to add static object due to missing or incorrect value. X: $1, Y: $2, Type: $3", newObj.x, newObj.y, newObj.type) + return false + end + + --- Spawns a dynamic group into the game world. + -- Same as coalition.add function in SSE. checks the passed data to see if its valid. + -- Will generate groupId, groupName, unitId, and unitName if needed + -- @tparam table newGroup table containting values needed for spawning a group. + function mist.dynAdd(newGroup) + + --mist.debug.writeData(mist.utils.serialize,{'msg', newGroup}, 'newGroupOrig.lua') + local cntry = newGroup.country + if newGroup.countryId then + cntry = newGroup.countryId + end + + local groupType = newGroup.category + local newCountry = '' + -- validate data + for countryId, countryName in pairs(country.name) do + if type(cntry) == 'string' then + cntry = cntry:gsub("%s+", "_") + if tostring(countryName) == string.upper(cntry) then + newCountry = countryName + end + elseif type(cntry) == 'number' then + if countryId == cntry then + newCountry = countryName + end + end + end + + if newCountry == '' then + log:error("Country not found: $1", cntry) + return false + end + + local newCat = '' + for catName, catId in pairs(Unit.Category) do + if type(groupType) == 'string' then + if tostring(catName) == string.upper(groupType) then + newCat = catName + end + elseif type(groupType) == 'number' then + if catId == groupType then + newCat = catName + end + end + + if catName == 'GROUND_UNIT' and (string.upper(groupType) == 'VEHICLE' or string.upper(groupType) == 'GROUND') then + newCat = 'GROUND_UNIT' + elseif catName == 'AIRPLANE' and string.upper(groupType) == 'PLANE' then + newCat = 'AIRPLANE' + end + end + local typeName + if newCat == 'GROUND_UNIT' then + typeName = ' gnd ' + elseif newCat == 'AIRPLANE' then + typeName = ' air ' + elseif newCat == 'HELICOPTER' then + typeName = ' hel ' + elseif newCat == 'SHIP' then + typeName = ' shp ' + elseif newCat == 'BUILDING' then + typeName = ' bld ' + end + if newGroup.clone or not newGroup.groupId then + mistDynAddIndex[typeName] = mistDynAddIndex[typeName] + 1 + mistGpId = mistGpId + 1 + newGroup.groupId = mistGpId + end + if newGroup.groupName or newGroup.name then + if newGroup.groupName then + newGroup.name = newGroup.groupName + elseif newGroup.name then + newGroup.name = newGroup.name + end + end + + if newGroup.clone and mist.DBs.groupsByName[newGroup.name] or not newGroup.name then + newGroup.name = tostring(newCountry .. tostring(typeName) .. mistDynAddIndex[typeName]) + end + + if not newGroup.hidden then + newGroup.hidden = false + end + + if not newGroup.visible then + newGroup.visible = false + end + + if (newGroup.start_time and type(newGroup.start_time) ~= 'number') or not newGroup.start_time then + if newGroup.startTime then + newGroup.start_time = mist.utils.round(newGroup.startTime) + else + newGroup.start_time = 0 + end + end + + + for unitIndex, unitData in pairs(newGroup.units) do + local originalName = newGroup.units[unitIndex].unitName or newGroup.units[unitIndex].name + if newGroup.clone or not unitData.unitId then + mistUnitId = mistUnitId + 1 + newGroup.units[unitIndex].unitId = mistUnitId + end + if newGroup.units[unitIndex].unitName or newGroup.units[unitIndex].name then + if newGroup.units[unitIndex].unitName then + newGroup.units[unitIndex].name = newGroup.units[unitIndex].unitName + elseif newGroup.units[unitIndex].name then + newGroup.units[unitIndex].name = newGroup.units[unitIndex].name + end + end + if newGroup.clone or not unitData.name then + newGroup.units[unitIndex].name = tostring(newGroup.name .. ' unit' .. unitIndex) + end + + if not unitData.skill then + newGroup.units[unitIndex].skill = 'Random' + end + + if not unitData.alt then + if newCat == 'AIRPLANE' then + newGroup.units[unitIndex].alt = 2000 + newGroup.units[unitIndex].alt_type = 'RADIO' + newGroup.units[unitIndex].speed = 150 + elseif newCat == 'HELICOPTER' then + newGroup.units[unitIndex].alt = 500 + newGroup.units[unitIndex].alt_type = 'RADIO' + newGroup.units[unitIndex].speed = 60 + else + --[[log:info('check height') + newGroup.units[unitIndex].alt = land.getHeight({x = newGroup.units[unitIndex].x, y = newGroup.units[unitIndex].y}) + newGroup.units[unitIndex].alt_type = 'BARO']] + end + + + end + + if newCat == 'AIRPLANE' or newCat == 'HELICOPTER' then + if newGroup.units[unitIndex].alt_type and newGroup.units[unitIndex].alt_type ~= 'BARO' or not newGroup.units[unitIndex].alt_type then + newGroup.units[unitIndex].alt_type = 'RADIO' + end + if not unitData.speed then + if newCat == 'AIRPLANE' then + newGroup.units[unitIndex].speed = 150 + elseif newCat == 'HELICOPTER' then + newGroup.units[unitIndex].speed = 60 + end + end + if not unitData.payload then + newGroup.units[unitIndex].payload = mist.getPayload(originalName) + end + end + mistAddedObjects[#mistAddedObjects + 1] = mist.utils.deepCopy(newGroup.units[unitIndex]) + end + mistAddedGroups[#mistAddedGroups + 1] = mist.utils.deepCopy(newGroup) + if newGroup.route and not newGroup.route.points then + if not newGroup.route.points and newGroup.route[1] then + local copyRoute = newGroup.route + newGroup.route = {} + newGroup.route.points = copyRoute + end + end + newGroup.country = newCountry + + + --mist.debug.writeData(mist.utils.serialize,{'msg', newGroup}, 'newGroup.lua') + + -- sanitize table + newGroup.groupName = nil + newGroup.clone = nil + newGroup.category = nil + newGroup.country = nil + + newGroup.tasks = {} + + for unitIndex, unitData in pairs(newGroup.units) do + newGroup.units[unitIndex].unitName = nil + end + + coalition.addGroup(country.id[newCountry], Unit.Category[newCat], newGroup) + + return newGroup + + end + + --- Schedules a function. + -- Modified Slmod task scheduler, superior to timer.scheduleFunction + -- @tparam function f function to schedule + -- @tparam table vars array containing all parameters passed to the function + -- @tparam number t time in seconds from mission start to schedule the function to. + -- @tparam[opt] number rep time between repetitions of the function + -- @tparam[opt] number st time in seconds from mission start at which the function + -- should stop to be rescheduled. + -- @treturn number scheduled function id. + function mist.scheduleFunction(f, vars, t, rep, st) + --verify correct types + assert(type(f) == 'function', 'variable 1, expected function, got ' .. type(f)) + assert(type(vars) == 'table' or vars == nil, 'variable 2, expected table or nil, got ' .. type(f)) + assert(type(t) == 'number', 'variable 3, expected number, got ' .. type(t)) + assert(type(rep) == 'number' or rep == nil, 'variable 4, expected number or nil, got ' .. type(rep)) + assert(type(st) == 'number' or st == nil, 'variable 5, expected number or nil, got ' .. type(st)) + if not vars then + vars = {} + end + taskId = taskId + 1 + table.insert(scheduledTasks, {f = f, vars = vars, t = t, rep = rep, st = st, id = taskId}) + return taskId + end + + --- Removes a scheduled function. + -- @tparam number id function id + -- @treturn boolean true if function was successfully removed, false otherwise. + function mist.removeFunction(id) + local i = 1 + local removedFunction = false + while i <= #scheduledTasks do + if scheduledTasks[i].id == id then + table.remove(scheduledTasks, i) + removedFunction = true + else + i = i + 1 + end + end + return removedFunction + end + + --- Registers an event handler. + -- @tparam function f function handling event + -- @treturn number id of the event handler + function mist.addEventHandler(f) --id is optional! + local handler = {} + idNum = idNum + 1 + handler.id = idNum + handler.f = f + function handler:onEvent(event) + self.f(event) + end + world.addEventHandler(handler) + return handler.id + end + + --- Removes event handler with given id. + -- @tparam number id event handler id + -- @treturn boolean true on success, false otherwise + function mist.removeEventHandler(id) + for key, handler in pairs(world.eventHandlers) do + if handler.id and handler.id == id then + world.eventHandlers[key] = nil + return true + end + end + return false + end +end + +-- Begin common funcs +do + --- Returns MGRS coordinates as string. + -- @tparam string MGRS MGRS coordinates + -- @tparam number acc the accuracy of each easting/northing. + -- Can be: 0, 1, 2, 3, 4, or 5. + function mist.tostringMGRS(MGRS, acc) + if acc == 0 then + return MGRS.UTMZone .. ' ' .. MGRS.MGRSDigraph + else + return MGRS.UTMZone .. ' ' .. MGRS.MGRSDigraph .. ' ' .. string.format('%0' .. acc .. 'd', mist.utils.round(MGRS.Easting/(10^(5-acc)), 0)) + .. ' ' .. string.format('%0' .. acc .. 'd', mist.utils.round(MGRS.Northing/(10^(5-acc)), 0)) + end + end + + --[[acc: + in DM: decimal point of minutes. + In DMS: decimal point of seconds. + position after the decimal of the least significant digit: + So: + 42.32 - acc of 2. + ]] + function mist.tostringLL(lat, lon, acc, DMS) + + local latHemi, lonHemi + if lat > 0 then + latHemi = 'N' + else + latHemi = 'S' + end + + if lon > 0 then + lonHemi = 'E' + else + lonHemi = 'W' + end + + lat = math.abs(lat) + lon = math.abs(lon) + + local latDeg = math.floor(lat) + local latMin = (lat - latDeg)*60 + + local lonDeg = math.floor(lon) + local lonMin = (lon - lonDeg)*60 + + if DMS then -- degrees, minutes, and seconds. + local oldLatMin = latMin + latMin = math.floor(latMin) + local latSec = mist.utils.round((oldLatMin - latMin)*60, acc) + + local oldLonMin = lonMin + lonMin = math.floor(lonMin) + local lonSec = mist.utils.round((oldLonMin - lonMin)*60, acc) + + if latSec == 60 then + latSec = 0 + latMin = latMin + 1 + end + + if lonSec == 60 then + lonSec = 0 + lonMin = lonMin + 1 + end + + local secFrmtStr -- create the formatting string for the seconds place + if acc <= 0 then -- no decimal place. + secFrmtStr = '%02d' + else + local width = 3 + acc -- 01.310 - that's a width of 6, for example. + secFrmtStr = '%0' .. width .. '.' .. acc .. 'f' + end + + return string.format('%02d', latDeg) .. ' ' .. string.format('%02d', latMin) .. '\' ' .. string.format(secFrmtStr, latSec) .. '"' .. latHemi .. ' ' + .. string.format('%02d', lonDeg) .. ' ' .. string.format('%02d', lonMin) .. '\' ' .. string.format(secFrmtStr, lonSec) .. '"' .. lonHemi + + else -- degrees, decimal minutes. + latMin = mist.utils.round(latMin, acc) + lonMin = mist.utils.round(lonMin, acc) + + if latMin == 60 then + latMin = 0 + latDeg = latDeg + 1 + end + + if lonMin == 60 then + lonMin = 0 + lonDeg = lonDeg + 1 + end + + local minFrmtStr -- create the formatting string for the minutes place + if acc <= 0 then -- no decimal place. + minFrmtStr = '%02d' + else + local width = 3 + acc -- 01.310 - that's a width of 6, for example. + minFrmtStr = '%0' .. width .. '.' .. acc .. 'f' + end + + return string.format('%02d', latDeg) .. ' ' .. string.format(minFrmtStr, latMin) .. '\'' .. latHemi .. ' ' + .. string.format('%02d', lonDeg) .. ' ' .. string.format(minFrmtStr, lonMin) .. '\'' .. lonHemi + + end + end + + --[[ required: az - radian + required: dist - meters + optional: alt - meters (set to false or nil if you don't want to use it). + optional: metric - set true to get dist and alt in km and m. + precision will always be nearest degree and NM or km.]] + function mist.tostringBR(az, dist, alt, metric) + az = mist.utils.round(mist.utils.toDegree(az), 0) + + if metric then + dist = mist.utils.round(dist/1000, 0) + else + dist = mist.utils.round(mist.utils.metersToNM(dist), 0) + end + + local s = string.format('%03d', az) .. ' for ' .. dist + + if alt then + if metric then + s = s .. ' at ' .. mist.utils.round(alt, 0) + else + s = s .. ' at ' .. mist.utils.round(mist.utils.metersToFeet(alt), 0) + end + end + return s + end + + function mist.getNorthCorrection(gPoint) --gets the correction needed for true north + local point = mist.utils.deepCopy(gPoint) + if not point.z then --Vec2; convert to Vec3 + point.z = point.y + point.y = 0 + end + local lat, lon = coord.LOtoLL(point) + local north_posit = coord.LLtoLO(lat + 1, lon) + return math.atan2(north_posit.z - point.z, north_posit.x - point.x) + end + + --- Returns skill of the given unit. + -- @tparam string unitName unit name + -- @return skill of the unit + function mist.getUnitSkill(unitName) + if mist.DBs.unitsByName[unitName] then + if Unit.getByName(unitName) then + local lunit = Unit.getByName(unitName) + local data = mist.DBs.unitsByName[unitName] + if data.unitName == unitName and data.type == lunit:getTypeName() and data.unitId == tonumber(lunit:getID()) and data.skill then + return data.skill + end + end + end + log:error("Unit not found in DB: $1", unitName) + return false + end + + --- Returns an array containing a group's units positions. + -- e.g. + -- { + -- [1] = {x = 299435.224, y = -1146632.6773}, + -- [2] = {x = 663324.6563, y = 322424.1112} + -- } + -- @tparam number|string groupIdent group id or name + -- @treturn table array containing positions of each group member + function mist.getGroupPoints(groupIdent) + -- search by groupId and allow groupId and groupName as inputs + local gpId = groupIdent + if type(groupIdent) == 'string' and not tonumber(groupIdent) then + if mist.DBs.MEgroupsByName[groupIdent] then + gpId = mist.DBs.MEgroupsByName[groupIdent].groupId + else + log:error("Group not found in mist.DBs.MEgroupsByName: $1", groupIdent) + end + end + + for coa_name, coa_data in pairs(env.mission.coalition) do + if (coa_name == 'red' or coa_name == 'blue') and type(coa_data) == 'table' then + if coa_data.country then --there is a country table + for cntry_id, cntry_data in pairs(coa_data.country) do + for obj_type_name, obj_type_data in pairs(cntry_data) do + if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" then -- only these types have points + if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then --there's a group! + for group_num, group_data in pairs(obj_type_data.group) do + if group_data and group_data.groupId == gpId then -- this is the group we are looking for + if group_data.route and group_data.route.points and #group_data.route.points > 0 then + local points = {} + for point_num, point in pairs(group_data.route.points) do + if not point.point then + points[point_num] = { x = point.x, y = point.y } + else + points[point_num] = point.point --it's possible that the ME could move to the point = Vec2 notation. + end + end + return points + end + return + end --if group_data and group_data.name and group_data.name == 'groupname' + end --for group_num, group_data in pairs(obj_type_data.group) do + end --if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then + end --if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" or obj_type_name == "static" then + end --for obj_type_name, obj_type_data in pairs(cntry_data) do + end --for cntry_id, cntry_data in pairs(coa_data.country) do + end --if coa_data.country then --there is a country table + end --if coa_name == 'red' or coa_name == 'blue' and type(coa_data) == 'table' then + end --for coa_name, coa_data in pairs(mission.coalition) do + end + + --- getUnitAttitude(unit) return values. + -- Yaw, AoA, ClimbAngle - relative to earth reference + -- DOES NOT TAKE INTO ACCOUNT WIND. + -- @table attitude + -- @tfield number Heading in radians, range of 0 to 2*pi, + -- relative to true north. + -- @tfield number Pitch in radians, range of -pi/2 to pi/2 + -- @tfield number Roll in radians, range of 0 to 2*pi, + -- right roll is positive direction. + -- @tfield number Yaw in radians, range of -pi to pi, + -- right yaw is positive direction. + -- @tfield number AoA in radians, range of -pi to pi, + -- rotation of aircraft to the right in comparison to + -- flight direction being positive. + -- @tfield number ClimbAngle in radians, range of -pi/2 to pi/2 + + --- Returns the attitude of a given unit. + -- Will work on any unit, even if not an aircraft. + -- @tparam Unit unit unit whose attitude is returned. + -- @treturn table @{attitude} + function mist.getAttitude(unit) + local unitpos = unit:getPosition() + if unitpos then + + local Heading = math.atan2(unitpos.x.z, unitpos.x.x) + + Heading = Heading + mist.getNorthCorrection(unitpos.p) + + if Heading < 0 then + Heading = Heading + 2*math.pi -- put heading in range of 0 to 2*pi + end + ---- heading complete.---- + + local Pitch = math.asin(unitpos.x.y) + ---- pitch complete.---- + + -- now get roll: + --maybe not the best way to do it, but it works. + + --first, make a vector that is perpendicular to y and unitpos.x with cross product + local cp = mist.vec.cp(unitpos.x, {x = 0, y = 1, z = 0}) + + --now, get dot product of of this cross product with unitpos.z + local dp = mist.vec.dp(cp, unitpos.z) + + --now get the magnitude of the roll (magnitude of the angle between two vectors is acos(vec1.vec2/|vec1||vec2|) + local Roll = math.acos(dp/(mist.vec.mag(cp)*mist.vec.mag(unitpos.z))) + + --now, have to get sign of roll. + -- by convention, making right roll positive + -- to get sign of roll, use the y component of unitpos.z. For right roll, y component is negative. + + if unitpos.z.y > 0 then -- left roll, flip the sign of the roll + Roll = -Roll + end + ---- roll complete. ---- + + --now, work on yaw, AoA, climb, and abs velocity + local Yaw + local AoA + local ClimbAngle + + -- get unit velocity + local unitvel = unit:getVelocity() + if mist.vec.mag(unitvel) ~= 0 then --must have non-zero velocity! + local AxialVel = {} --unit velocity transformed into aircraft axes directions + + --transform velocity components in direction of aircraft axes. + AxialVel.x = mist.vec.dp(unitpos.x, unitvel) + AxialVel.y = mist.vec.dp(unitpos.y, unitvel) + AxialVel.z = mist.vec.dp(unitpos.z, unitvel) + + --Yaw is the angle between unitpos.x and the x and z velocities + --define right yaw as positive + Yaw = math.acos(mist.vec.dp({x = 1, y = 0, z = 0}, {x = AxialVel.x, y = 0, z = AxialVel.z})/mist.vec.mag({x = AxialVel.x, y = 0, z = AxialVel.z})) + + --now set correct direction: + if AxialVel.z > 0 then + Yaw = -Yaw + end + + -- AoA is angle between unitpos.x and the x and y velocities + AoA = math.acos(mist.vec.dp({x = 1, y = 0, z = 0}, {x = AxialVel.x, y = AxialVel.y, z = 0})/mist.vec.mag({x = AxialVel.x, y = AxialVel.y, z = 0})) + + --now set correct direction: + if AxialVel.y > 0 then + AoA = -AoA + end + + ClimbAngle = math.asin(unitvel.y/mist.vec.mag(unitvel)) + end + return { Heading = Heading, Pitch = Pitch, Roll = Roll, Yaw = Yaw, AoA = AoA, ClimbAngle = ClimbAngle} + else + log:error("Couldn't get unit's position") + end + end + + --- Returns heading of given unit. + -- @tparam Unit unit unit whose heading is returned. + -- @param rawHeading + -- @treturn number heading of the unit, in range + -- of 0 to 2*pi. + function mist.getHeading(unit, rawHeading) + local unitpos = unit:getPosition() + if unitpos then + local Heading = math.atan2(unitpos.x.z, unitpos.x.x) + if not rawHeading then + Heading = Heading + mist.getNorthCorrection(unitpos.p) + end + if Heading < 0 then + Heading = Heading + 2*math.pi -- put heading in range of 0 to 2*pi + end + return Heading + end + end + + --- Returns given unit's pitch + -- @tparam Unit unit unit whose pitch is returned. + -- @treturn number pitch of given unit + function mist.getPitch(unit) + local unitpos = unit:getPosition() + if unitpos then + return math.asin(unitpos.x.y) + end + end + + --- Returns given unit's roll. + -- @tparam Unit unit unit whose roll is returned. + -- @treturn number roll of given unit + function mist.getRoll(unit) + local unitpos = unit:getPosition() + if unitpos then + -- now get roll: + --maybe not the best way to do it, but it works. + + --first, make a vector that is perpendicular to y and unitpos.x with cross product + local cp = mist.vec.cp(unitpos.x, {x = 0, y = 1, z = 0}) + + --now, get dot product of of this cross product with unitpos.z + local dp = mist.vec.dp(cp, unitpos.z) + + --now get the magnitude of the roll (magnitude of the angle between two vectors is acos(vec1.vec2/|vec1||vec2|) + local Roll = math.acos(dp/(mist.vec.mag(cp)*mist.vec.mag(unitpos.z))) + + --now, have to get sign of roll. + -- by convention, making right roll positive + -- to get sign of roll, use the y component of unitpos.z. For right roll, y component is negative. + + if unitpos.z.y > 0 then -- left roll, flip the sign of the roll + Roll = -Roll + end + return Roll + end + end + + --- Returns given unit's yaw. + -- @tparam Unit unit unit whose yaw is returned. + -- @treturn number yaw of given unit. + function mist.getYaw(unit) + local unitpos = unit:getPosition() + if unitpos then + -- get unit velocity + local unitvel = unit:getVelocity() + if mist.vec.mag(unitvel) ~= 0 then --must have non-zero velocity! + local AxialVel = {} --unit velocity transformed into aircraft axes directions + + --transform velocity components in direction of aircraft axes. + AxialVel.x = mist.vec.dp(unitpos.x, unitvel) + AxialVel.y = mist.vec.dp(unitpos.y, unitvel) + AxialVel.z = mist.vec.dp(unitpos.z, unitvel) + + --Yaw is the angle between unitpos.x and the x and z velocities + --define right yaw as positive + local Yaw = math.acos(mist.vec.dp({x = 1, y = 0, z = 0}, {x = AxialVel.x, y = 0, z = AxialVel.z})/mist.vec.mag({x = AxialVel.x, y = 0, z = AxialVel.z})) + + --now set correct direction: + if AxialVel.z > 0 then + Yaw = -Yaw + end + return Yaw + end + end + end + + --- Returns given unit's angle of attack. + -- @tparam Unit unit unit to get AoA from. + -- @treturn number angle of attack of the given unit. + function mist.getAoA(unit) + local unitpos = unit:getPosition() + if unitpos then + local unitvel = unit:getVelocity() + if mist.vec.mag(unitvel) ~= 0 then --must have non-zero velocity! + local AxialVel = {} --unit velocity transformed into aircraft axes directions + + --transform velocity components in direction of aircraft axes. + AxialVel.x = mist.vec.dp(unitpos.x, unitvel) + AxialVel.y = mist.vec.dp(unitpos.y, unitvel) + AxialVel.z = mist.vec.dp(unitpos.z, unitvel) + + -- AoA is angle between unitpos.x and the x and y velocities + local AoA = math.acos(mist.vec.dp({x = 1, y = 0, z = 0}, {x = AxialVel.x, y = AxialVel.y, z = 0})/mist.vec.mag({x = AxialVel.x, y = AxialVel.y, z = 0})) + + --now set correct direction: + if AxialVel.y > 0 then + AoA = -AoA + end + return AoA + end + end + end + + --- Returns given unit's climb angle. + -- @tparam Unit unit unit to get climb angle from. + -- @treturn number climb angle of given unit. + function mist.getClimbAngle(unit) + local unitpos = unit:getPosition() + if unitpos then + local unitvel = unit:getVelocity() + if mist.vec.mag(unitvel) ~= 0 then --must have non-zero velocity! + return math.asin(unitvel.y/mist.vec.mag(unitvel)) + end + end + end + + --[[-- + Unit name table. + Many Mist functions require tables of unit names, which are known + in Mist as UnitNameTables. These follow a special set of shortcuts + borrowed from Slmod. These shortcuts alleviate the problem of entering + huge lists of unit names by hand, and in many cases, they remove the + need to even know the names of the units in the first place! + + These are the unit table "short-cut" commands: + + Prefixes: + "[-u]" - subtract this unit if its in the table + "[g]" - add this group to the table + "[-g]" - subtract this group from the table + "[c]" - add this country's units + "[-c]" - subtract this country's units if any are in the table + + Stand-alone identifiers + "[all]" - add all units + "[-all]" - subtract all units (not very useful by itself) + "[blue]" - add all blue units + "[-blue]" - subtract all blue units + "[red]" - add all red coalition units + "[-red]" - subtract all red units + + Compound Identifiers: + "[c][helicopter]" - add all of this country's helicopters + "[-c][helicopter]" - subtract all of this country's helicopters + "[c][plane]" - add all of this country's planes + "[-c][plane]" - subtract all of this country's planes + "[c][ship]" - add all of this country's ships + "[-c][ship]" - subtract all of this country's ships + "[c][vehicle]" - add all of this country's vehicles + "[-c][vehicle]" - subtract all of this country's vehicles + + "[all][helicopter]" - add all helicopters + "[-all][helicopter]" - subtract all helicopters + "[all][plane]" - add all planes + "[-all][plane]" - subtract all planes + "[all][ship]" - add all ships + "[-all][ship]" - subtract all ships + "[all][vehicle]" - add all vehicles + "[-all][vehicle]" - subtract all vehicles + + "[blue][helicopter]" - add all blue coalition helicopters + "[-blue][helicopter]" - subtract all blue coalition helicopters + "[blue][plane]" - add all blue coalition planes + "[-blue][plane]" - subtract all blue coalition planes + "[blue][ship]" - add all blue coalition ships + "[-blue][ship]" - subtract all blue coalition ships + "[blue][vehicle]" - add all blue coalition vehicles + "[-blue][vehicle]" - subtract all blue coalition vehicles + + "[red][helicopter]" - add all red coalition helicopters + "[-red][helicopter]" - subtract all red coalition helicopters + "[red][plane]" - add all red coalition planes + "[-red][plane]" - subtract all red coalition planes + "[red][ship]" - add all red coalition ships + "[-red][ship]" - subtract all red coalition ships + "[red][vehicle]" - add all red coalition vehicles + "[-red][vehicle]" - subtract all red coalition vehicles + + Country names to be used in [c] and [-c] short-cuts: + Turkey + Norway + The Netherlands + Spain + 11 + UK + Denmark + USA + Georgia + Germany + Belgium + Canada + France + Israel + Ukraine + Russia + South Ossetia + Abkhazia + Italy + Australia + Austria + Belarus + Bulgaria + Czech Republic + China + Croatia + Finland + Greece + Hungary + India + Iran + Iraq + Japan + Kazakhstan + North Korea + Pakistan + Poland + Romania + Saudi Arabia + Serbia, Slovakia + South Korea + Sweden + Switzerland + Syria + USAF Aggressors + + Do NOT use a '[u]' notation for single units. Single units are referenced + the same way as before: Simply input their names as strings. + + These unit tables are evaluated in order, and you cannot subtract a unit + from a table before it is added. For example: + + {'[blue]', '[-c]Georgia'} + + will evaluate to all of blue coalition except those units owned by the + country named "Georgia"; however: + + {'[-c]Georgia', '[blue]'} + + will evaluate to all of the units in blue coalition, because the addition + of all units owned by blue coalition occurred AFTER the subtraction of all + units owned by Georgia (which actually subtracted nothing at all, since + there were no units in the table when the subtraction occurred). + + More examples: + + {'[blue][plane]', '[-c]Georgia', '[-g]Hawg 1'} + + Evaluates to all blue planes, except those blue units owned by the country + named "Georgia" and the units in the group named "Hawg1". + + + {'[g]arty1', '[g]arty2', '[-u]arty1_AD', '[-u]arty2_AD', 'Shark 11' } + + Evaluates to the unit named "Shark 11", plus all the units in groups named + "arty1" and "arty2" except those that are named "arty1\_AD" and "arty2\_AD". + + @table UnitNameTable + ]] + + --- Returns a table containing unit names. + -- @tparam table tbl sequential strings + -- @treturn table @{UnitNameTable} + function mist.makeUnitTable(tbl) + --Assumption: will be passed a table of strings, sequential + log:info(tbl) + local units_by_name = {} + + local l_munits = mist.DBs.units --local reference for faster execution + for i = 1, #tbl do + local unit = tbl[i] + if unit:sub(1,4) == '[-u]' then --subtract a unit + if units_by_name[unit:sub(5)] then -- 5 to end + units_by_name[unit:sub(5)] = nil --remove + end + elseif unit:sub(1,3) == '[g]' then -- add a group + for coa, coa_tbl in pairs(l_munits) do + for country, country_table in pairs(coa_tbl) do + for unit_type, unit_type_tbl in pairs(country_table) do + if type(unit_type_tbl) == 'table' then + for group_ind, group_tbl in pairs(unit_type_tbl) do + if type(group_tbl) == 'table' and group_tbl.groupName == unit:sub(4) then + -- index 4 to end + for unit_ind, unit in pairs(group_tbl.units) do + units_by_name[unit.unitName] = true --add + end + end + end + end + end + end + end + elseif unit:sub(1,4) == '[-g]' then -- subtract a group + for coa, coa_tbl in pairs(l_munits) do + for country, country_table in pairs(coa_tbl) do + for unit_type, unit_type_tbl in pairs(country_table) do + if type(unit_type_tbl) == 'table' then + for group_ind, group_tbl in pairs(unit_type_tbl) do + if type(group_tbl) == 'table' and group_tbl.groupName == unit:sub(5) then + -- index 5 to end + for unit_ind, unit in pairs(group_tbl.units) do + if units_by_name[unit.unitName] then + units_by_name[unit.unitName] = nil --remove + end + end + end + end + end + end + end + end + elseif unit:sub(1,3) == '[c]' then -- add a country + local category = '' + local country_start = 4 + if unit:sub(4,15) == '[helicopter]' then + category = 'helicopter' + country_start = 16 + elseif unit:sub(4,10) == '[plane]' then + category = 'plane' + country_start = 11 + elseif unit:sub(4,9) == '[ship]' then + category = 'ship' + country_start = 10 + elseif unit:sub(4,12) == '[vehicle]' then + category = 'vehicle' + country_start = 13 + end + for coa, coa_tbl in pairs(l_munits) do + for country, country_table in pairs(coa_tbl) do + if country == string.lower(unit:sub(country_start)) then -- match + for unit_type, unit_type_tbl in pairs(country_table) do + if type(unit_type_tbl) == 'table' and (category == '' or unit_type == category) then + for group_ind, group_tbl in pairs(unit_type_tbl) do + if type(group_tbl) == 'table' then + for unit_ind, unit in pairs(group_tbl.units) do + units_by_name[unit.unitName] = true --add + end + end + end + end + end + end + end + end + elseif unit:sub(1,4) == '[-c]' then -- subtract a country + local category = '' + local country_start = 5 + if unit:sub(5,16) == '[helicopter]' then + category = 'helicopter' + country_start = 17 + elseif unit:sub(5,11) == '[plane]' then + category = 'plane' + country_start = 12 + elseif unit:sub(5,10) == '[ship]' then + category = 'ship' + country_start = 11 + elseif unit:sub(5,13) == '[vehicle]' then + category = 'vehicle' + country_start = 14 + end + for coa, coa_tbl in pairs(l_munits) do + for country, country_table in pairs(coa_tbl) do + if country == string.lower(unit:sub(country_start)) then -- match + for unit_type, unit_type_tbl in pairs(country_table) do + if type(unit_type_tbl) == 'table' and (category == '' or unit_type == category) then + for group_ind, group_tbl in pairs(unit_type_tbl) do + if type(group_tbl) == 'table' then + for unit_ind, unit in pairs(group_tbl.units) do + if units_by_name[unit.unitName] then + units_by_name[unit.unitName] = nil --remove + end + end + end + end + end + end + end + end + end + elseif unit:sub(1,6) == '[blue]' then -- add blue coalition + local category = '' + if unit:sub(7) == '[helicopter]' then + category = 'helicopter' + elseif unit:sub(7) == '[plane]' then + category = 'plane' + elseif unit:sub(7) == '[ship]' then + category = 'ship' + elseif unit:sub(7) == '[vehicle]' then + category = 'vehicle' + end + for coa, coa_tbl in pairs(l_munits) do + if coa == 'blue' then + for country, country_table in pairs(coa_tbl) do + for unit_type, unit_type_tbl in pairs(country_table) do + if type(unit_type_tbl) == 'table' and (category == '' or unit_type == category) then + for group_ind, group_tbl in pairs(unit_type_tbl) do + if type(group_tbl) == 'table' then + for unit_ind, unit in pairs(group_tbl.units) do + units_by_name[unit.unitName] = true --add + end + end + end + end + end + end + end + end + elseif unit:sub(1,7) == '[-blue]' then -- subtract blue coalition + local category = '' + if unit:sub(8) == '[helicopter]' then + category = 'helicopter' + elseif unit:sub(8) == '[plane]' then + category = 'plane' + elseif unit:sub(8) == '[ship]' then + category = 'ship' + elseif unit:sub(8) == '[vehicle]' then + category = 'vehicle' + end + for coa, coa_tbl in pairs(l_munits) do + if coa == 'blue' then + for country, country_table in pairs(coa_tbl) do + for unit_type, unit_type_tbl in pairs(country_table) do + if type(unit_type_tbl) == 'table' and (category == '' or unit_type == category) then + for group_ind, group_tbl in pairs(unit_type_tbl) do + if type(group_tbl) == 'table' then + for unit_ind, unit in pairs(group_tbl.units) do + if units_by_name[unit.unitName] then + units_by_name[unit.unitName] = nil --remove + end + end + end + end + end + end + end + end + end + elseif unit:sub(1,5) == '[red]' then -- add red coalition + local category = '' + if unit:sub(6) == '[helicopter]' then + category = 'helicopter' + elseif unit:sub(6) == '[plane]' then + category = 'plane' + elseif unit:sub(6) == '[ship]' then + category = 'ship' + elseif unit:sub(6) == '[vehicle]' then + category = 'vehicle' + end + for coa, coa_tbl in pairs(l_munits) do + if coa == 'red' then + for country, country_table in pairs(coa_tbl) do + for unit_type, unit_type_tbl in pairs(country_table) do + if type(unit_type_tbl) == 'table' and (category == '' or unit_type == category) then + for group_ind, group_tbl in pairs(unit_type_tbl) do + if type(group_tbl) == 'table' then + for unit_ind, unit in pairs(group_tbl.units) do + units_by_name[unit.unitName] = true --add + end + end + end + end + end + end + end + end + elseif unit:sub(1,6) == '[-red]' then -- subtract red coalition + local category = '' + if unit:sub(7) == '[helicopter]' then + category = 'helicopter' + elseif unit:sub(7) == '[plane]' then + category = 'plane' + elseif unit:sub(7) == '[ship]' then + category = 'ship' + elseif unit:sub(7) == '[vehicle]' then + category = 'vehicle' + end + for coa, coa_tbl in pairs(l_munits) do + if coa == 'red' then + for country, country_table in pairs(coa_tbl) do + for unit_type, unit_type_tbl in pairs(country_table) do + if type(unit_type_tbl) == 'table' and (category == '' or unit_type == category) then + for group_ind, group_tbl in pairs(unit_type_tbl) do + if type(group_tbl) == 'table' then + for unit_ind, unit in pairs(group_tbl.units) do + if units_by_name[unit.unitName] then + units_by_name[unit.unitName] = nil --remove + end + end + end + end + end + end + end + end + end + elseif unit:sub(1,5) == '[all]' then -- add all of a certain category (or all categories) + local category = '' + if unit:sub(6) == '[helicopter]' then + category = 'helicopter' + elseif unit:sub(6) == '[plane]' then + category = 'plane' + elseif unit:sub(6) == '[ship]' then + category = 'ship' + elseif unit:sub(6) == '[vehicle]' then + category = 'vehicle' + end + for coa, coa_tbl in pairs(l_munits) do + for country, country_table in pairs(coa_tbl) do + for unit_type, unit_type_tbl in pairs(country_table) do + if type(unit_type_tbl) == 'table' and (category == '' or unit_type == category) then + for group_ind, group_tbl in pairs(unit_type_tbl) do + if type(group_tbl) == 'table' then + for unit_ind, unit in pairs(group_tbl.units) do + units_by_name[unit.unitName] = true --add + end + end + end + end + end + end + end + elseif unit:sub(1,6) == '[-all]' then -- subtract all of a certain category (or all categories) + local category = '' + if unit:sub(7) == '[helicopter]' then + category = 'helicopter' + elseif unit:sub(7) == '[plane]' then + category = 'plane' + elseif unit:sub(7) == '[ship]' then + category = 'ship' + elseif unit:sub(7) == '[vehicle]' then + category = 'vehicle' + end + for coa, coa_tbl in pairs(l_munits) do + for country, country_table in pairs(coa_tbl) do + for unit_type, unit_type_tbl in pairs(country_table) do + if type(unit_type_tbl) == 'table' and (category == '' or unit_type == category) then + for group_ind, group_tbl in pairs(unit_type_tbl) do + if type(group_tbl) == 'table' then + for unit_ind, unit in pairs(group_tbl.units) do + if units_by_name[unit.unitName] then + units_by_name[unit.unitName] = nil --remove + end + end + end + end + end + end + end + end + else -- just a regular unit + units_by_name[unit] = true --add + end + end + + local units_tbl = {} -- indexed sequentially + for unit_name, val in pairs(units_by_name) do + if val then + units_tbl[#units_tbl + 1] = unit_name -- add all the units to the table + end + end + + + units_tbl.processed = timer.getTime() --add the processed flag + return units_tbl +end + +function mist.getDeadMapObjsInZones(zone_names) + -- zone_names: table of zone names + -- returns: table of dead map objects (indexed numerically) + local map_objs = {} + local zones = {} + for i = 1, #zone_names do + if mist.DBs.zonesByName[zone_names[i]] then + zones[#zones + 1] = mist.DBs.zonesByName[zone_names[i]] + end + end + for obj_id, obj in pairs(mist.DBs.deadObjects) do + if obj.objectType and obj.objectType == 'building' then --dead map object + for i = 1, #zones do + if ((zones[i].point.x - obj.objectPos.x)^2 + (zones[i].point.z - obj.objectPos.z)^2)^0.5 <= zones[i].radius then + map_objs[#map_objs + 1] = mist.utils.deepCopy(obj) + end + end + end + end + return map_objs +end + +function mist.getDeadMapObjsInPolygonZone(zone) + -- zone_names: table of zone names + -- returns: table of dead map objects (indexed numerically) + local map_objs = {} + for obj_id, obj in pairs(mist.DBs.deadObjects) do + if obj.objectType and obj.objectType == 'building' then --dead map object + if mist.pointInPolygon(obj.objectPos, zone) then + map_objs[#map_objs + 1] = mist.utils.deepCopy(obj) + end + end + end + return map_objs +end + +function mist.pointInPolygon(point, poly, maxalt) --raycasting point in polygon. Code from http://softsurfer.com/Archive/algorithm_0103/algorithm_0103.htm + --[[local type_tbl = { + point = {'table'}, + poly = {'table'}, + maxalt = {'number', 'nil'}, + } + + local err, errmsg = mist.utils.typeCheck('mist.pointInPolygon', type_tbl, {point, poly, maxalt}) + assert(err, errmsg) + ]] + point = mist.utils.makeVec3(point) + local px = point.x + local pz = point.z + local cn = 0 + local newpoly = mist.utils.deepCopy(poly) + + if not maxalt or (point.y <= maxalt) then + local polysize = #newpoly + newpoly[#newpoly + 1] = newpoly[1] + + newpoly[1] = mist.utils.makeVec3(newpoly[1]) + + for k = 1, polysize do + newpoly[k+1] = mist.utils.makeVec3(newpoly[k+1]) + if ((newpoly[k].z <= pz) and (newpoly[k+1].z > pz)) or ((newpoly[k].z > pz) and (newpoly[k+1].z <= pz)) then + local vt = (pz - newpoly[k].z) / (newpoly[k+1].z - newpoly[k].z) + if (px < newpoly[k].x + vt*(newpoly[k+1].x - newpoly[k].x)) then + cn = cn + 1 + end + end + end + + return cn%2 == 1 + else + return false + end +end + +function mist.getUnitsInPolygon(unit_names, polyZone, max_alt) + local units = {} + + for i = 1, #unit_names do + units[#units + 1] = Unit.getByName(unitNames[i]) + end + + local inZoneUnits = {} + for i =1, #units do + if units[i]:isActive() and mist.pointInPolygon(units[i]:getPosition().p, polyZone, max_alt) then + inZoneUnits[inZoneUnits + 1] = units[i] + end + end + + return inZoneUnits +end + +function mist.getUnitsInZones(unit_names, zone_names, zone_type) + + zone_type = zone_type or 'cylinder' + if zone_type == 'c' or zone_type == 'cylindrical' or zone_type == 'C' then + zone_type = 'cylinder' + end + if zone_type == 's' or zone_type == 'spherical' or zone_type == 'S' then + zone_type = 'sphere' + end + + assert(zone_type == 'cylinder' or zone_type == 'sphere', 'invalid zone_type: ' .. tostring(zone_type)) + + local units = {} + local zones = {} + + for k = 1, #unit_names do + local unit = Unit.getByName(unit_names[k]) + if unit then + units[#units + 1] = unit + end + end + + + for k = 1, #zone_names do + local zone = trigger.misc.getZone(zone_names[k]) + if zone then + zones[#zones + 1] = {radius = zone.radius, x = zone.point.x, y = zone.point.y, z = zone.point.z} + end + end + + local in_zone_units = {} + + for units_ind = 1, #units do + for zones_ind = 1, #zones do + if zone_type == 'sphere' then --add land height value for sphere zone type + local alt = land.getHeight({x = zones[zones_ind].x, y = zones[zones_ind].z}) + if alt then + zones[zones_ind].y = alt + end + end + local unit_pos = units[units_ind]:getPosition().p + if unit_pos and units[units_ind]:isActive() == true then + if zone_type == 'cylinder' and (((unit_pos.x - zones[zones_ind].x)^2 + (unit_pos.z - zones[zones_ind].z)^2)^0.5 <= zones[zones_ind].radius) then + in_zone_units[#in_zone_units + 1] = units[units_ind] + break + elseif zone_type == 'sphere' and (((unit_pos.x - zones[zones_ind].x)^2 + (unit_pos.y - zones[zones_ind].y)^2 + (unit_pos.z - zones[zones_ind].z)^2)^0.5 <= zones[zones_ind].radius) then + in_zone_units[#in_zone_units + 1] = units[units_ind] + break + end + end + end + end + return in_zone_units +end + +function mist.getUnitsInMovingZones(unit_names, zone_unit_names, radius, zone_type) + + zone_type = zone_type or 'cylinder' + if zone_type == 'c' or zone_type == 'cylindrical' or zone_type == 'C' then + zone_type = 'cylinder' + end + if zone_type == 's' or zone_type == 'spherical' or zone_type == 'S' then + zone_type = 'sphere' + end + + assert(zone_type == 'cylinder' or zone_type == 'sphere', 'invalid zone_type: ' .. tostring(zone_type)) + + local units = {} + local zone_units = {} + + for k = 1, #unit_names do + local unit = Unit.getByName(unit_names[k]) + if unit then + units[#units + 1] = unit + end + end + + for k = 1, #zone_unit_names do + local unit = Unit.getByName(zone_unit_names[k]) + if unit then + zone_units[#zone_units + 1] = unit + end + end + + local in_zone_units = {} + + for units_ind = 1, #units do + for zone_units_ind = 1, #zone_units do + local unit_pos = units[units_ind]:getPosition().p + local zone_unit_pos = zone_units[zone_units_ind]:getPosition().p + if unit_pos and zone_unit_pos and units[units_ind]:isActive() == true then + if zone_type == 'cylinder' and (((unit_pos.x - zone_unit_pos.x)^2 + (unit_pos.z - zone_unit_pos.z)^2)^0.5 <= radius) then + in_zone_units[#in_zone_units + 1] = units[units_ind] + break + elseif zone_type == 'sphere' and (((unit_pos.x - zone_unit_pos.x)^2 + (unit_pos.y - zone_unit_pos.y)^2 + (unit_pos.z - zone_unit_pos.z)^2)^0.5 <= radius) then + in_zone_units[#in_zone_units + 1] = units[units_ind] + break + end + end + end + end + return in_zone_units +end + +function mist.getUnitsLOS(unitset1, altoffset1, unitset2, altoffset2, radius) + log:info("$1, $2, $3, $4, $5", unitset1, altoffset1, unitset2, altoffset2, radius) + radius = radius or math.huge + local unit_info1 = {} + local unit_info2 = {} + + -- get the positions all in one step, saves execution time. + for unitset1_ind = 1, #unitset1 do + local unit1 = Unit.getByName(unitset1[unitset1_ind]) + if unit1 and unit1:isActive() == true then + unit_info1[#unit_info1 + 1] = {} + unit_info1[#unit_info1].unit = unit1 + unit_info1[#unit_info1].pos = unit1:getPosition().p + end + end + + for unitset2_ind = 1, #unitset2 do + local unit2 = Unit.getByName(unitset2[unitset2_ind]) + if unit2 and unit2:isActive() == true then + unit_info2[#unit_info2 + 1] = {} + unit_info2[#unit_info2].unit = unit2 + unit_info2[#unit_info2].pos = unit2:getPosition().p + end + end + + local LOS_data = {} + -- now compute los + for unit1_ind = 1, #unit_info1 do + local unit_added = false + for unit2_ind = 1, #unit_info2 do + if radius == math.huge or (mist.vec.mag(mist.vec.sub(unit_info1[unit1_ind].pos, unit_info2[unit2_ind].pos)) < radius) then -- inside radius + local point1 = { x = unit_info1[unit1_ind].pos.x, y = unit_info1[unit1_ind].pos.y + altoffset1, z = unit_info1[unit1_ind].pos.z} + local point2 = { x = unit_info2[unit2_ind].pos.x, y = unit_info2[unit2_ind].pos.y + altoffset2, z = unit_info2[unit2_ind].pos.z} + if land.isVisible(point1, point2) then + if unit_added == false then + unit_added = true + LOS_data[#LOS_data + 1] = {} + LOS_data[#LOS_data].unit = unit_info1[unit1_ind].unit + LOS_data[#LOS_data].vis = {} + LOS_data[#LOS_data].vis[#LOS_data[#LOS_data].vis + 1] = unit_info2[unit2_ind].unit + else + LOS_data[#LOS_data].vis[#LOS_data[#LOS_data].vis + 1] = unit_info2[unit2_ind].unit + end + end + end + end + end + + return LOS_data +end + +function mist.getAvgPoint(points) + local avgX, avgY, avgZ, totNum = 0, 0, 0, 0 + for i = 1, #points do + local nPoint = mist.utils.makeVec3(points[i]) + if nPoint.z then + avgX = avgX + nPoint.x + avgY = avgY + nPoint.y + avgZ = avgZ + nPoint.z + totNum = totNum + 1 + end + end + if totNum ~= 0 then + return {x = avgX/totNum, y = avgY/totNum, z = avgZ/totNum} + end +end + +--Gets the average position of a group of units (by name) +function mist.getAvgPos(unitNames) + local avgX, avgY, avgZ, totNum = 0, 0, 0, 0 + for i = 1, #unitNames do + local unit + if Unit.getByName(unitNames[i]) then + unit = Unit.getByName(unitNames[i]) + elseif StaticObject.getByName(unitNames[i]) then + unit = StaticObject.getByName(unitNames[i]) + end + if unit then + local pos = unit:getPosition().p + if pos then -- you never know O.o + avgX = avgX + pos.x + avgY = avgY + pos.y + avgZ = avgZ + pos.z + totNum = totNum + 1 + end + end + end + if totNum ~= 0 then + return {x = avgX/totNum, y = avgY/totNum, z = avgZ/totNum} + end +end + +function mist.getAvgGroupPos(groupName) + if type(groupName) == 'string' and Group.getByName(groupName) and Group.getByName(groupName):isExist() == true then + groupName = Group.getByName(groupName) + end + local units = {} + for i = 1, #groupName:getSize() do + table.insert(units, groupName.getUnit(i):getName()) + end + + return mist.getAvgPos(units) + +end + +--[[ vars for mist.getMGRSString: +vars.units - table of unit names (NOT unitNameTable- maybe this should change). +vars.acc - integer between 0 and 5, inclusive +]] +function mist.getMGRSString(vars) + local units = vars.units + local acc = vars.acc or 5 + local avgPos = mist.getAvgPos(units) + if avgPos then + return mist.tostringMGRS(coord.LLtoMGRS(coord.LOtoLL(avgPos)), acc) + end +end + +--[[ vars for mist.getLLString +vars.units - table of unit names (NOT unitNameTable- maybe this should change). +vars.acc - integer, number of numbers after decimal place +vars.DMS - if true, output in degrees, minutes, seconds. Otherwise, output in degrees, minutes. +]] +function mist.getLLString(vars) + local units = vars.units + local acc = vars.acc or 3 + local DMS = vars.DMS + local avgPos = mist.getAvgPos(units) + if avgPos then + local lat, lon = coord.LOtoLL(avgPos) + return mist.tostringLL(lat, lon, acc, DMS) + end +end + +--[[ +vars.units- table of unit names (NOT unitNameTable- maybe this should change). +vars.ref - vec3 ref point, maybe overload for vec2 as well? +vars.alt - boolean, if used, includes altitude in string +vars.metric - boolean, gives distance in km instead of NM. +]] +function mist.getBRString(vars) + local units = vars.units + local ref = mist.utils.makeVec3(vars.ref, 0) -- turn it into Vec3 if it is not already. + local alt = vars.alt + local metric = vars.metric + local avgPos = mist.getAvgPos(units) + if avgPos then + local vec = {x = avgPos.x - ref.x, y = avgPos.y - ref.y, z = avgPos.z - ref.z} + local dir = mist.utils.getDir(vec, ref) + local dist = mist.utils.get2DDist(avgPos, ref) + if alt then + alt = avgPos.y + end + return mist.tostringBR(dir, dist, alt, metric) + end +end + +-- Returns the Vec3 coordinates of the average position of the concentration of units most in the heading direction. +--[[ vars for mist.getLeadingPos: +vars.units - table of unit names +vars.heading - direction +vars.radius - number +vars.headingDegrees - boolean, switches heading to degrees +]] +function mist.getLeadingPos(vars) + local units = vars.units + local heading = vars.heading + local radius = vars.radius + if vars.headingDegrees then + heading = mist.utils.toRadian(vars.headingDegrees) + end + + local unitPosTbl = {} + for i = 1, #units do + local unit = Unit.getByName(units[i]) + if unit and unit:isExist() then + unitPosTbl[#unitPosTbl + 1] = unit:getPosition().p + end + end + if #unitPosTbl > 0 then -- one more more units found. + -- first, find the unit most in the heading direction + local maxPos = -math.huge + + local maxPosInd -- maxPos - the furthest in direction defined by heading; maxPosInd = + for i = 1, #unitPosTbl do + local rotatedVec2 = mist.vec.rotateVec2(mist.utils.makeVec2(unitPosTbl[i]), heading) + if (not maxPos) or maxPos < rotatedVec2.x then + maxPos = rotatedVec2.x + maxPosInd = i + end + end + + --now, get all the units around this unit... + local avgPos + if radius then + local maxUnitPos = unitPosTbl[maxPosInd] + local avgx, avgy, avgz, totNum = 0, 0, 0, 0 + for i = 1, #unitPosTbl do + if mist.utils.get2DDist(maxUnitPos, unitPosTbl[i]) <= radius then + avgx = avgx + unitPosTbl[i].x + avgy = avgy + unitPosTbl[i].y + avgz = avgz + unitPosTbl[i].z + totNum = totNum + 1 + end + end + avgPos = { x = avgx/totNum, y = avgy/totNum, z = avgz/totNum} + else + avgPos = unitPosTbl[maxPosInd] + end + + return avgPos + end +end + +--[[ vars for mist.getLeadingMGRSString: +vars.units - table of unit names +vars.heading - direction +vars.radius - number +vars.headingDegrees - boolean, switches heading to degrees +vars.acc - number, 0 to 5. +]] +function mist.getLeadingMGRSString(vars) + local pos = mist.getLeadingPos(vars) + if pos then + local acc = vars.acc or 5 + return mist.tostringMGRS(coord.LLtoMGRS(coord.LOtoLL(pos)), acc) + end +end + +--[[ vars for mist.getLeadingLLString: +vars.units - table of unit names +vars.heading - direction, number +vars.radius - number +vars.headingDegrees - boolean, switches heading to degrees +vars.acc - number of digits after decimal point (can be negative) +vars.DMS - boolean, true if you want DMS. +]] +function mist.getLeadingLLString(vars) + local pos = mist.getLeadingPos(vars) + if pos then + local acc = vars.acc or 3 + local DMS = vars.DMS + local lat, lon = coord.LOtoLL(pos) + return mist.tostringLL(lat, lon, acc, DMS) + end +end + +--[[ vars for mist.getLeadingBRString: +vars.units - table of unit names +vars.heading - direction, number +vars.radius - number +vars.headingDegrees - boolean, switches heading to degrees +vars.metric - boolean, if true, use km instead of NM. +vars.alt - boolean, if true, include altitude. +vars.ref - vec3/vec2 reference point. +]] +function mist.getLeadingBRString(vars) + local pos = mist.getLeadingPos(vars) + if pos then + local ref = vars.ref + local alt = vars.alt + local metric = vars.metric + + local vec = {x = pos.x - ref.x, y = pos.y - ref.y, z = pos.z - ref.z} + local dir = mist.utils.getDir(vec, ref) + local dist = mist.utils.get2DDist(pos, ref) + if alt then + alt = pos.y + end + return mist.tostringBR(dir, dist, alt, metric) + end +end + +end + +--- Group functions. +-- @section groups +do -- group functions scope + + --- Check table used for group creation. + -- @tparam table groupData table to check. + -- @treturn boolean true if a group can be spawned using + -- this table, false otherwise. + function mist.groupTableCheck(groupData) + -- return false if country, category + -- or units are missing + if not groupData.country or + not groupData.category or + not groupData.units then + return false + end + -- return false if unitData misses + -- x, y or type + for unitId, unitData in pairs(groupData.units) do + if not unitData.x or + not unitData.y or + not unitData.type then + return false + end + end + -- everything we need is here return true + return true + end + + --- Returns group data table of give group. + function mist.getCurrentGroupData(gpName) + local dbData = mist.getGroupData(gpName) + + if Group.getByName(gpName) and Group.getByName(gpName):isExist() == true then + local newGroup = Group.getByName(gpName) + local newData = {} + newData.name = gpName + newData.groupId = tonumber(newGroup:getID()) + newData.category = newGroup:getCategory() + newData.groupName = gpName + newData.hidden = dbData.hidden + + if newData.category == 2 then + newData.category = 'vehicle' + elseif newData.category == 3 then + newData.category = 'ship' + end + + newData.units = {} + local newUnits = newGroup:getUnits() + for unitNum, unitData in pairs(newGroup:getUnits()) do + newData.units[unitNum] = {} + local uName = unitData:getName() + + if mist.DBs.unitsByName[uName] and unitData:getTypeName() == mist.DBs.unitsByName[uName].type and mist.DBs.unitsByName[uName].unitId == tonumber(unitData:getID()) then -- If old data matches most of new data + newData.units[unitNum] = mist.utils.deepCopy(mist.DBs.unitsByName[uName]) + else + newData.units[unitNum].unitId = tonumber(unitData:getID()) + newData.units[unitNum].type = unitData:getTypeName() + newData.units[unitNum].skill = mist.getUnitSkill(uName) + newData.country = string.lower(country.name[unitData:getCountry()]) + newData.units[unitNum].callsign = unitData:getCallsign() + newData.units[unitNum].unitName = uName + end + + newData.units[unitNum].x = unitData:getPosition().p.x + newData.units[unitNum].y = unitData:getPosition().p.z + newData.units[unitNum].point = {x = newData.units[unitNum].x, y = newData.units[unitNum].y} + newData.units[unitNum].heading = mist.getHeading(unitData, true) -- added to DBs + newData.units[unitNum].alt = unitData:getPosition().p.y + newData.units[unitNum].speed = mist.vec.mag(unitData:getVelocity()) + + end + + return newData + elseif StaticObject.getByName(gpName) and StaticObject.getByName(gpName):isExist() == true then + local staticObj = StaticObject.getByName(gpName) + dbData.units[1].x = staticObj:getPosition().p.x + dbData.units[1].y = staticObj:getPosition().p.z + dbData.units[1].alt = staticObj:getPosition().p.y + dbData.units[1].heading = mist.getHeading(staticObj, true) + + return dbData + end + + end + + function mist.getGroupData(gpName) + local found = false + local newData = {} + if mist.DBs.groupsByName[gpName] then + newData = mist.utils.deepCopy(mist.DBs.groupsByName[gpName]) + found = true + end + + if found == false then + for groupName, groupData in pairs(mist.DBs.groupsByName) do + if mist.stringMatch(groupName, gpName) == true then + newData = mist.utils.deepCopy(groupData) + newData.groupName = groupName + found = true + break + end + end + end + + local payloads + if newData.category == 'plane' or newData.category == 'helicopter' then + payloads = mist.getGroupPayload(newData.groupName) + end + if found == true then + --newData.hidden = false -- maybe add this to DBs + + for unitNum, unitData in pairs(newData.units) do + newData.units[unitNum] = {} + + newData.units[unitNum].unitId = unitData.unitId + --newData.units[unitNum].point = unitData.point + newData.units[unitNum].x = unitData.point.x + newData.units[unitNum].y = unitData.point.y + newData.units[unitNum].alt = unitData.alt + newData.units[unitNum].alt_type = unitData.alt_type + newData.units[unitNum].speed = unitData.speed + newData.units[unitNum].type = unitData.type + newData.units[unitNum].skill = unitData.skill + newData.units[unitNum].unitName = unitData.unitName + newData.units[unitNum].heading = unitData.heading -- added to DBs + newData.units[unitNum].playerCanDrive = unitData.playerCanDrive -- added to DBs + + + if newData.category == 'plane' or newData.category == 'helicopter' then + newData.units[unitNum].payload = payloads[unitNum] + newData.units[unitNum].livery_id = unitData.livery_id + newData.units[unitNum].onboard_num = unitData.onboard_num + newData.units[unitNum].callsign = unitData.callsign + newData.units[unitNum].AddPropAircraft = unitData.AddPropAircraft + end + if newData.category == 'static' then + newData.units[unitNum].categoryStatic = unitData.categoryStatic + newData.units[unitNum].mass = unitData.mass + newData.units[unitNum].canCargo = unitData.canCargo + newData.units[unitNum].shape_name = unitData.shape_name + end + end + --log:info(newData) + return newData + else + log:error('$1 not found in MIST database', gpName) + return + end + end + + function mist.getPayload(unitIdent) + -- refactor to search by groupId and allow groupId and groupName as inputs + local unitId = unitIdent + if type(unitIdent) == 'string' and not tonumber(unitIdent) then + if mist.DBs.MEunitsByName[unitIdent] then + unitId = mist.DBs.MEunitsByName[unitIdent].unitId + else + log:error("Unit not found in mist.DBs.MEunitsByName: $1", unitIdent) + end + end + local gpId = mist.DBs.MEunitsById[unitId].groupId + + if gpId and unitId then + for coa_name, coa_data in pairs(env.mission.coalition) do + if (coa_name == 'red' or coa_name == 'blue') and type(coa_data) == 'table' then + if coa_data.country then --there is a country table + for cntry_id, cntry_data in pairs(coa_data.country) do + for obj_type_name, obj_type_data in pairs(cntry_data) do + if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" then -- only these types have points + if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then --there's a group! + for group_num, group_data in pairs(obj_type_data.group) do + if group_data and group_data.groupId == gpId then + for unitIndex, unitData in pairs(group_data.units) do --group index + if unitData.unitId == unitId then + return unitData.payload + end + end + end + end + end + end + end + end + end + end + end + else + log:error('Need string or number. Got: $1', type(unitIdent)) + return false + end + log:warn("Couldn't find payload for unit: $1", unitIdent) + return + end + + function mist.getGroupPayload(groupIdent) + local gpId = groupIdent + if type(groupIdent) == 'string' and not tonumber(groupIdent) then + if mist.DBs.MEgroupsByName[groupIdent] then + gpId = mist.DBs.MEgroupsByName[groupIdent].groupId + else + log:error('$1 not found in mist.DBs.MEgroupsByName', groupIdent) + end + end + + if gpId then + for coa_name, coa_data in pairs(env.mission.coalition) do + if (coa_name == 'red' or coa_name == 'blue') and type(coa_data) == 'table' then + if coa_data.country then --there is a country table + for cntry_id, cntry_data in pairs(coa_data.country) do + for obj_type_name, obj_type_data in pairs(cntry_data) do + if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" then -- only these types have points + if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then --there's a group! + for group_num, group_data in pairs(obj_type_data.group) do + if group_data and group_data.groupId == gpId then + local payloads = {} + for unitIndex, unitData in pairs(group_data.units) do --group index + payloads[unitIndex] = unitData.payload + end + return payloads + end + end + end + end + end + end + end + end + end + else + log:error('Need string or number. Got: $1', type(groupIdent)) + return false + end + log:warn("Couldn't find payload for group: $1", groupIdent) + return + + end + + function mist.teleportToPoint(vars) -- main teleport function that all of teleport/respawn functions call + local point = vars.point + + local gpName + if vars.gpName then + gpName = vars.gpName + elseif vars.groupName then + gpName = vars.groupName + else + log:error('Missing field groupName or gpName in variable table') + end + + local action = vars.action + + local disperse = vars.disperse or false + local maxDisp = vars.maxDisp + if not vars.maxDisp then + maxDisp = 200 + else + maxDisp = vars.maxDisp + end + local radius = vars.radius or 0 + local innerRadius = vars.innerRadius + + local route = vars.route + local dbData = false + + local newGroupData + if gpName and not vars.groupData then + if string.lower(action) == 'teleport' or string.lower(action) == 'tele' then + newGroupData = mist.getCurrentGroupData(gpName) + elseif string.lower(action) == 'respawn' then + newGroupData = mist.getGroupData(gpName) + dbData = true + elseif string.lower(action) == 'clone' then + newGroupData = mist.getGroupData(gpName) + newGroupData.clone = 'order66' + dbData = true + else + action = 'tele' + newGroupData = mist.getCurrentGroupData(gpName) + end + else + action = 'tele' + newGroupData = vars.groupData + end + + --log:info('get Randomized Point') + local diff = {x = 0, y = 0} + local newCoord, origCoord + if point then + local valid = false + + local validTerrain + if string.lower(newGroupData.category) == 'ship' then + validTerrain = {'SHALLOW_WATER' , 'WATER'} + elseif string.lower(newGroupData.category) == 'vehicle' then + validTerrain = {'LAND', 'ROAD'} + else + validTerrain = {'LAND', 'ROAD', 'SHALLOW_WATER', 'WATER', 'RUNWAY'} + end + + for i = 1, 100 do + newCoord = mist.getRandPointInCircle(point, radius, innerRadius) + if mist.isTerrainValid(newCoord, validTerrain) then + origCoord = mist.utils.deepCopy(newCoord) + diff = {x = (newCoord.x - newGroupData.units[1].x), y = (newCoord.y - newGroupData.units[1].y)} + valid = true + break + end + end + if valid == false then + log:error('Point supplied in variable table is not a valid coordinate. Valid coords: $1', validTerrain) + return false + end + end + if not newGroupData.country and mist.DBs.groupsByName[newGroupData.groupName].country then + newGroupData.country = mist.DBs.groupsByName[newGroupData.groupName].country + end + if not newGroupData.category and mist.DBs.groupsByName[newGroupData.groupName].category then + newGroupData.category = mist.DBs.groupsByName[newGroupData.groupName].category + end + + for unitNum, unitData in pairs(newGroupData.units) do + if disperse then + if maxDisp and type(maxDisp) == 'number' and unitNum ~= 1 then + newCoord = mist.getRandPointInCircle(origCoord, maxDisp) + --else + --newCoord = mist.getRandPointInCircle(zone.point, zone.radius) + end + + newGroupData.units[unitNum].x = newCoord.x + newGroupData.units[unitNum].y = newCoord.y + else + newGroupData.units[unitNum].x = unitData.x + diff.x + newGroupData.units[unitNum].y = unitData.y + diff.y + end + if point then + if (newGroupData.category == 'plane' or newGroupData.category == 'helicopter') then + if point.z and point.y > 0 and point.y > land.getHeight({newGroupData.units[unitNum].x, newGroupData.units[unitNum].y}) + 10 then + newGroupData.units[unitNum].alt = point.y + else + if newGroupData.category == 'plane' then + newGroupData.units[unitNum].alt = land.getHeight({newGroupData.units[unitNum].x, newGroupData.units[unitNum].y}) + math.random(300, 9000) + else + newGroupData.units[unitNum].alt = land.getHeight({newGroupData.units[unitNum].x, newGroupData.units[unitNum].y}) + math.random(200, 3000) + end + end + end + end + end + + if newGroupData.start_time then + newGroupData.startTime = newGroupData.start_time + end + + if newGroupData.startTime and newGroupData.startTime ~= 0 and dbData == true then + local timeDif = timer.getAbsTime() - timer.getTime0() + if timeDif > newGroupData.startTime then + newGroupData.startTime = 0 + else + newGroupData.startTime = newGroupData.startTime - timeDif + end + + end + + if route then + newGroupData.route = route + end + --mist.debug.writeData(mist.utils.serialize,{'teleportToPoint', newGroupData}, 'newGroupData.lua') + if string.lower(newGroupData.category) == 'static' then + --log:info(newGroupData) + return mist.dynAddStatic(newGroupData) + end + return mist.dynAdd(newGroupData) + + end + + function mist.respawnInZone(gpName, zone, disperse, maxDisp) + + if type(gpName) == 'table' and gpName:getName() then + gpName = gpName:getName() + elseif type(gpName) == 'table' and gpName[1]:getName() then + gpName = math.random(#gpName) + else + gpName = tostring(gpName) + end + + if type(zone) == 'string' then + zone = trigger.misc.getZone(zone) + elseif type(zone) == 'table' and not zone.radius then + zone = trigger.misc.getZone(zone[math.random(1, #zone)]) + end + local vars = {} + vars.gpName = gpName + vars.action = 'respawn' + vars.point = zone.point + vars.radius = zone.radius + vars.disperse = disperse + vars.maxDisp = maxDisp + return mist.teleportToPoint(vars) + end + + function mist.cloneInZone(gpName, zone, disperse, maxDisp) + --log:info('cloneInZone') + if type(gpName) == 'table' then + gpName = gpName:getName() + else + gpName = tostring(gpName) + end + + if type(zone) == 'string' then + zone = trigger.misc.getZone(zone) + elseif type(zone) == 'table' and not zone.radius then + zone = trigger.misc.getZone(zone[math.random(1, #zone)]) + end + local vars = {} + vars.gpName = gpName + vars.action = 'clone' + vars.point = zone.point + vars.radius = zone.radius + vars.disperse = disperse + vars.maxDisp = maxDisp + --log:info('do teleport') + return mist.teleportToPoint(vars) + end + + function mist.teleportInZone(gpName, zone, disperse, maxDisp) -- groupName, zoneName or table of Zone Names, keepForm is a boolean + if type(gpName) == 'table' and gpName:getName() then + gpName = gpName:getName() + else + gpName = tostring(gpName) + end + + if type(zone) == 'string' then + zone = trigger.misc.getZone(zone) + elseif type(zone) == 'table' and not zone.radius then + zone = trigger.misc.getZone(zone[math.random(1, #zone)]) + end + + local vars = {} + vars.gpName = gpName + vars.action = 'tele' + vars.point = zone.point + vars.radius = zone.radius + vars.disperse = disperse + vars.maxDisp = maxDisp + return mist.teleportToPoint(vars) + end + + function mist.respawnGroup(gpName, task) + local vars = {} + vars.gpName = gpName + vars.action = 'respawn' + if task and type(task) ~= 'number' then + vars.route = mist.getGroupRoute(gpName, 'task') + end + local newGroup = mist.teleportToPoint(vars) + if task and type(task) == 'number' then + local newRoute = mist.getGroupRoute(gpName, 'task') + mist.scheduleFunction(mist.goRoute, {newGroup, newRoute}, timer.getTime() + task) + end + return newGroup + end + + function mist.cloneGroup(gpName, task) + local vars = {} + vars.gpName = gpName + vars.action = 'clone' + if task and type(task) ~= 'number' then + vars.route = mist.getGroupRoute(gpName, 'task') + end + local newGroup = mist.teleportToPoint(vars) + if task and type(task) == 'number' then + local newRoute = mist.getGroupRoute(gpName, 'task') + mist.scheduleFunction(mist.goRoute, {newGroup, newRoute}, timer.getTime() + task) + end + return newGroup + end + + function mist.teleportGroup(gpName, task) + local vars = {} + vars.gpName = gpName + vars.action = 'teleport' + if task and type(task) ~= 'number' then + vars.route = mist.getGroupRoute(gpName, 'task') + end + local newGroup = mist.teleportToPoint(vars) + if task and type(task) == 'number' then + local newRoute = mist.getGroupRoute(gpName, 'task') + mist.scheduleFunction(mist.goRoute, {newGroup, newRoute}, timer.getTime() + task) + end + return newGroup + end + + function mist.spawnRandomizedGroup(groupName, vars) -- need to debug + if Group.getByName(groupName) and Group.getByName(groupName):isExist() == true then + local gpData = mist.getGroupData(groupName) + gpData.units = mist.randomizeGroupOrder(gpData.units, vars) + gpData.route = mist.getGroupRoute(groupName, 'task') + + mist.dynAdd(gpData) + end + + return true + end + + function mist.randomizeNumTable(vars) + local newTable = {} + + local excludeIndex = {} + local randomTable = {} + + if vars and vars.exclude and type(vars.exclude) == 'table' then + for index, data in pairs(vars.exclude) do + excludeIndex[data] = true + end + end + + local low, hi, size + + if vars.size then + size = vars.size + end + + if vars and vars.lowerLimit and type(vars.lowerLimit) == 'number' then + low = mist.utils.round(vars.lowerLimit) + else + low = 1 + end + + if vars and vars.upperLimit and type(vars.upperLimit) == 'number' then + hi = mist.utils.round(vars.upperLimit) + else + hi = size + end + + local choices = {} + -- add to exclude list and create list of what to randomize + for i = 1, size do + if not (i >= low and i <= hi) then + + excludeIndex[i] = true + end + if not excludeIndex[i] then + table.insert(choices, i) + else + newTable[i] = i + end + end + + for ind, num in pairs(choices) do + local found = false + local x = 0 + while found == false do + x = mist.random(size) -- get random number from list + local addNew = true + for index, _ in pairs(excludeIndex) do + if index == x then + addNew = false + break + end + end + if addNew == true then + excludeIndex[x] = true + found = true + end + excludeIndex[x] = true + + end + newTable[num] = x + end + --[[ + for i = 1, #newTable do + log:info(newTable[i]) + end + ]] + return newTable + end + + function mist.randomizeGroupOrder(passedUnits, vars) + -- figure out what to exclude, and send data to other func + local units = passedUnits + + if passedUnits.units then + units = passUnits.units + end + + local exclude = {} + local excludeNum = {} + if vars and vars.excludeType and type(vars.excludeType) == 'table' then + exclude = vars.excludeType + end + + if vars and vars.excludeNum and type(vars.excludeNum) == 'table' then + excludeNum = vars.excludeNum + end + + local low, hi + + if vars and vars.lowerLimit and type(vars.lowerLimit) == 'number' then + low = mist.utils.round(vars.lowerLimit) + else + low = 1 + end + + if vars and vars.upperLimit and type(vars.upperLimit) == 'number' then + hi = mist.utils.round(vars.upperLimit) + else + hi = #units + end + + + local excludeNum = {} + for unitIndex, unitData in pairs(units) do + if unitIndex >= low and unitIndex <= hi then -- if within range + local found = false + if #exclude > 0 then + for excludeType, index in pairs(exclude) do -- check if excluded + if mist.stringMatch(excludeType, unitData.type) then -- if excluded + excludeNum[unitIndex] = unitIndex + found = true + end + end + end + else -- unitIndex is either to low, or to high: added to exclude list + excludeNum[unitIndex] = unitId + end + end + + local newGroup = {} + local newOrder = mist.randomizeNumTable({exclude = excludeNum, size = #units}) + + for unitIndex, unitData in pairs(units) do + for i = 1, #newOrder do + if newOrder[i] == unitIndex then + newGroup[i] = mist.utils.deepCopy(units[i]) -- gets all of the unit data + newGroup[i].type = mist.utils.deepCopy(unitData.type) + newGroup[i].skill = mist.utils.deepCopy(unitData.skill) + newGroup[i].unitName = mist.utils.deepCopy(unitData.unitName) + newGroup[i].unitIndex = mist.utils.deepCopy(unitData.unitIndex) -- replaces the units data with a new type + end + end + end + return newGroup + end + + function mist.random(firstNum, secondNum) -- no support for decimals + local lowNum, highNum + if not secondNum then + highNum = firstNum + lowNum = 1 + else + lowNum = firstNum + highNum = secondNum + end + local total = 1 + if math.abs(highNum - lowNum + 1) < 50 then -- if total values is less than 50 + total = math.modf(50/math.abs(highNum - lowNum + 1)) -- make x copies required to be above 50 + end + local choices = {} + for i = 1, total do -- iterate required number of times + for x = lowNum, highNum do -- iterate between the range + choices[#choices +1] = x -- add each entry to a table + end + end + local rtnVal = math.random(#choices) -- will now do a math.random of at least 50 choices + for i = 1, 10 do + rtnVal = math.random(#choices) -- iterate a few times for giggles + end + return choices[rtnVal] + end + + function mist.stringMatch(s1, s2, bool) + local exclude = {'%-', '%(', '%)', '%_', '%[', '%]', '%.', '%#', '% ', '%{', '%}', '%$', '%%', '%?', '%+', '%^'} + if type(s1) == 'string' and type(s2) == 'string' then + for i , str in pairs(exclude) do + s1 = string.gsub(s1, str, '') + s2 = string.gsub(s2, str, '') + end + if not bool then + s1 = string.lower(s1) + s2 = string.lower(s2) + end + log:info('Comparing: $1 and $2', s1, s2) + if s1 == s2 then + return true + else + return false + end + else + log:error('Either the first or second variable were not a string') + return false + end + end + + mist.matchString = mist.stringMatch -- both commands work because order out type of I + + --[[ scope: +{ + units = {...}, -- unit names. + coa = {...}, -- coa names + countries = {...}, -- country names + CA = {...}, -- looks just like coa. + unitTypes = { red = {}, blue = {}, all = {}, Russia = {},} +} + + +scope examples: + +{ units = { 'Hawg11', 'Hawg12' }, CA = {'blue'} } + +{ countries = {'Georgia'}, unitTypes = {blue = {'A-10C', 'A-10A'}}} + +{ coa = {'all'}} + +{unitTypes = { blue = {'A-10C'}}} +]] +end + +--- Utility functions. +-- E.g. conversions between units etc. +-- @section mist.utils +do -- mist.util scope + mist.utils = {} + + --- Converts angle in radians to degrees. + -- @param angle angle in radians + -- @return angle in degrees + function mist.utils.toDegree(angle) + return angle*180/math.pi + end + + --- Converts angle in degrees to radians. + -- @param angle angle in degrees + -- @return angle in degrees + function mist.utils.toRadian(angle) + return angle*math.pi/180 + end + + --- Converts meters to nautical miles. + -- @param meters distance in meters + -- @return distance in nautical miles + function mist.utils.metersToNM(meters) + return meters/1852 + end + + --- Converts meters to feet. + -- @param meters distance in meters + -- @return distance in feet + function mist.utils.metersToFeet(meters) + return meters/0.3048 + end + + --- Converts nautical miles to meters. + -- @param nm distance in nautical miles + -- @return distance in meters + function mist.utils.NMToMeters(nm) + return nm*1852 + end + + --- Converts feet to meters. + -- @param feet distance in feet + -- @return distance in meters + function mist.utils.feetToMeters(feet) + return feet*0.3048 + end + + --- Converts meters per second to knots. + -- @param mps speed in m/s + -- @return speed in knots + function mist.utils.mpsToKnots(mps) + return mps*3600/1852 + end + + --- Converts meters per second to kilometers per hour. + -- @param mps speed in m/s + -- @return speed in km/h + function mist.utils.mpsToKmph(mps) + return mps*3.6 + end + + --- Converts knots to meters per second. + -- @param knots speed in knots + -- @return speed in m/s + function mist.utils.knotsToMps(knots) + return knots*1852/3600 + end + + --- Converts kilometers per hour to meters per second. + -- @param kmph speed in km/h + -- @return speed in m/s + function mist.utils.kmphToMps(kmph) + return kmph/3.6 + end + + --- Converts a Vec3 to a Vec2. + -- @tparam Vec3 vec the 3D vector + -- @return vector converted to Vec2 + function mist.utils.makeVec2(vec) + if vec.z then + return {x = vec.x, y = vec.z} + else + return {x = vec.x, y = vec.y} -- it was actually already vec2. + end + end + + --- Converts a Vec2 to a Vec3. + -- @tparam Vec2 vec the 2D vector + -- @param y optional new y axis (altitude) value. If omitted it's 0. + function mist.utils.makeVec3(vec, y) + if not vec.z then + if vec.alt and not y then + y = vec.alt + elseif not y then + y = 0 + end + return {x = vec.x, y = y, z = vec.y} + else + return {x = vec.x, y = vec.y, z = vec.z} -- it was already Vec3, actually. + end + end + + --- Converts a Vec2 to a Vec3 using ground level as altitude. + -- The ground level at the specific point is used as altitude (y-axis) + -- for the new vector. Optionally a offset can be specified. + -- @tparam Vec2 vec the 2D vector + -- @param[opt] offset offset to be applied to the ground level + -- @return new 3D vector + function mist.utils.makeVec3GL(vec, offset) + local adj = offset or 0 + + if not vec.z then + return {x = vec.x, y = (land.getHeight(vec) + adj), z = vec.y} + else + return {x = vec.x, y = (land.getHeight({x = vec.x, y = vec.z}) + adj), z = vec.z} + end + end + + --- Returns the center of a zone as Vec3. + -- @tparam string|table zone trigger zone name or table + -- @treturn Vec3 center of the zone + function mist.utils.zoneToVec3(zone) + local new = {} + if type(zone) == 'table' then + if zone.point then + new.x = zone.point.x + new.y = zone.point.y + new.z = zone.point.z + elseif zone.x and zone.y and zone.z then + return zone + end + return new + elseif type(zone) == 'string' then + zone = trigger.misc.getZone(zone) + if zone then + new.x = zone.point.x + new.y = zone.point.y + new.z = zone.point.z + return new + end + end + end + + --- Returns heading-error corrected direction. + -- True-north corrected direction from point along vector vec. + -- @tparam Vec3 vec + -- @tparam Vec2 point + -- @return heading-error corrected direction from point. + function mist.utils.getDir(vec, point) + local dir = math.atan2(vec.z, vec.x) + if point then + dir = dir + mist.getNorthCorrection(point) + end + if dir < 0 then + dir = dir + 2 * math.pi -- put dir in range of 0 to 2*pi + end + return dir + end + + --- Returns distance in meters between two points. + -- @tparam Vec2|Vec3 point1 first point + -- @tparam Vec2|Vec3 point2 second point + -- @treturn number distance between given points. + function mist.utils.get2DDist(point1, point2) + point1 = mist.utils.makeVec3(point1) + point2 = mist.utils.makeVec3(point2) + return mist.vec.mag({x = point1.x - point2.x, y = 0, z = point1.z - point2.z}) + end + + --- Returns distance in meters between two points in 3D space. + -- @tparam Vec3 point1 first point + -- @tparam Vec3 point2 second point + -- @treturn number distancen between given points in 3D space. + function mist.utils.get3DDist(point1, point2) + return mist.vec.mag({x = point1.x - point2.x, y = point1.y - point2.y, z = point1.z - point2.z}) + end + + --- Creates a waypoint from a vector. + -- @tparam Vec2|Vec3 vec position of the new waypoint + -- @treturn Waypoint a new waypoint to be used inside paths. + function mist.utils.vecToWP(vec) + local newWP = {} + newWP.x = vec.x + newWP.y = vec.y + if vec.z then + newWP.alt = vec.y + newWP.y = vec.z + else + newWP.alt = land.getHeight({x = vec.x, y = vec.y}) + end + return newWP + end + + --- Creates a waypoint from a unit. + -- This function also considers the units speed. + -- The alt_type of this waypoint is set to "BARO". + -- @tparam Unit pUnit Unit whose position and speed will be used. + -- @treturn Waypoint new waypoint. + function mist.utils.unitToWP(pUnit) + local unit = mist.utils.deepCopy(pUnit) + if type(unit) == 'string' then + if Unit.getByName(unit) then + unit = Unit.getByName(unit) + end + end + if unit:isExist() == true then + local new = mist.utils.vecToWP(unit:getPosition().p) + new.speed = mist.vec.mag(unit:getVelocity()) + new.alt_type = "BARO" + + return new + end + log:error("$1 not found or doesn't exist", pUnit) + return false + end + + --- Creates a deep copy of a object. + -- Usually this object is a table. + -- See also: from http://lua-users.org/wiki/CopyTable + -- @param object object to copy + -- @return copy of object + function mist.utils.deepCopy(object) + local lookup_table = {} + local function _copy(object) + if type(object) ~= "table" then + return object + elseif lookup_table[object] then + return lookup_table[object] + end + local new_table = {} + lookup_table[object] = new_table + for index, value in pairs(object) do + new_table[_copy(index)] = _copy(value) + end + return setmetatable(new_table, getmetatable(object)) + end + return _copy(object) + end + + --- Simple rounding function. + -- From http://lua-users.org/wiki/SimpleRound + -- use negative idp for rounding ahead of decimal place, positive for rounding after decimal place + -- @tparam number num number to round + -- @param idp + function mist.utils.round(num, idp) + local mult = 10^(idp or 0) + return math.floor(num * mult + 0.5) / mult + end + + --- Rounds all numbers inside a table. + -- @tparam table tbl table in which to round numbers + -- @param idp + function mist.utils.roundTbl(tbl, idp) + for id, val in pairs(tbl) do + if type(val) == 'number' then + tbl[id] = mist.utils.round(val, idp) + end + end + return tbl + end + + --- Executes the given string. + -- borrowed from Slmod + -- @tparam string s string containing LUA code. + -- @treturn boolean true if successfully executed, false otherwise + function mist.utils.dostring(s) + local f, err = loadstring(s) + if f then + return true, f() + else + return false, err + end + end + + --- Checks a table's types. + -- This function checks a tables types against a specifically forged type table. + -- @param fname + -- @tparam table type_tbl + -- @tparam table var_tbl + -- @usage -- specifically forged type table + -- type_tbl = { + -- {'table', 'number'}, + -- 'string', + -- 'number', + -- 'number', + -- {'string','nil'}, + -- {'number', 'nil'} + -- } + -- -- my_tbl index 1 must be a table or a number; + -- -- index 2, a string; index 3, a number; + -- -- index 4, a number; index 5, either a string or nil; + -- -- and index 6, either a number or nil. + -- mist.utils.typeCheck(type_tbl, my_tb) + -- @return true if table passes the check, false otherwise. + function mist.utils.typeCheck(fname, type_tbl, var_tbl) + -- log:info('type check') + for type_key, type_val in pairs(type_tbl) do + -- log:info('type_key: $1 type_val: $2', type_key, type_val) + + --type_key can be a table of accepted keys- so try to find one that is not nil + local type_key_str = '' + local act_key = type_key -- actual key within var_tbl - necessary to use for multiple possible key variables. Initialize to type_key + if type(type_key) == 'table' then + + for i = 1, #type_key do + if i ~= 1 then + type_key_str = type_key_str .. '/' + end + type_key_str = type_key_str .. tostring(type_key[i]) + if var_tbl[type_key[i]] ~= nil then + act_key = type_key[i] -- found a non-nil entry, make act_key now this val. + end + end + else + type_key_str = tostring(type_key) + end + + local err_msg = 'Error in function ' .. fname .. ', parameter "' .. type_key_str .. '", expected: ' + local passed_check = false + + if type(type_tbl[type_key]) == 'table' then + -- log:info('err_msg, before: $1', err_msg) + for j = 1, #type_tbl[type_key] do + + if j == 1 then + err_msg = err_msg .. type_tbl[type_key][j] + else + err_msg = err_msg .. ' or ' .. type_tbl[type_key][j] + end + + if type(var_tbl[act_key]) == type_tbl[type_key][j] then + passed_check = true + end + end + -- log:info('err_msg, after: $1', err_msg) + else + -- log:info('err_msg, before: $1', err_msg) + err_msg = err_msg .. type_tbl[type_key] + -- log:info('err_msg, after: $1', err_msg) + if type(var_tbl[act_key]) == type_tbl[type_key] then + passed_check = true + end + + end + + if not passed_check then + err_msg = err_msg .. ', got ' .. type(var_tbl[act_key]) + return false, err_msg + end + end + return true + end + + --- Serializes the give variable to a string. + -- borrowed from slmod + -- @param var variable to serialize + -- @treturn string variable serialized to string + function mist.utils.basicSerialize(var) + if var == nil then + return "\"\"" + else + if ((type(var) == 'number') or + (type(var) == 'boolean') or + (type(var) == 'function') or + (type(var) == 'table') or + (type(var) == 'userdata') ) then + return tostring(var) + elseif type(var) == 'string' then + var = string.format('%q', var) + return var + end + end +end + +--- Serialize value +-- borrowed from slmod (serialize_slmod) +-- @param name +-- @param value value to serialize +-- @param level +function mist.utils.serialize(name, value, level) + --Based on ED's serialize_simple2 + local function basicSerialize(o) + if type(o) == "number" then + return tostring(o) + elseif type(o) == "boolean" then + return tostring(o) + else -- assume it is a string + return mist.utils.basicSerialize(o) + end + end + + local function serializeToTbl(name, value, level) + local var_str_tbl = {} + if level == nil then level = "" end + if level ~= "" then level = level.." " end + + table.insert(var_str_tbl, level .. name .. " = ") + + if type(value) == "number" or type(value) == "string" or type(value) == "boolean" then + table.insert(var_str_tbl, basicSerialize(value) .. ",\n") + elseif type(value) == "table" then + table.insert(var_str_tbl, "\n"..level.."{\n") + + for k,v in pairs(value) do -- serialize its fields + local key + if type(k) == "number" then + key = string.format("[%s]", k) + else + key = string.format("[%q]", k) + end + + table.insert(var_str_tbl, mist.utils.serialize(key, v, level.." ")) + + end + if level == "" then + table.insert(var_str_tbl, level.."} -- end of "..name.."\n") + + else + table.insert(var_str_tbl, level.."}, -- end of "..name.."\n") + + end + else + log:error('Cannot serialize a $1', type(value)) + end + return var_str_tbl + end + + local t_str = serializeToTbl(name, value, level) + + return table.concat(t_str) +end + +--- Serialize value supporting cycles. +-- borrowed from slmod (serialize_wcycles) +-- @param name +-- @param value value to serialize +-- @param saved +function mist.utils.serializeWithCycles(name, value, saved) + --mostly straight out of Programming in Lua + local function basicSerialize(o) + if type(o) == "number" then + return tostring(o) + elseif type(o) == "boolean" then + return tostring(o) + else -- assume it is a string + return mist.utils.basicSerialize(o) + end + end + + local t_str = {} + saved = saved or {} -- initial value + if ((type(value) == 'string') or (type(value) == 'number') or (type(value) == 'table') or (type(value) == 'boolean')) then + table.insert(t_str, name .. " = ") + if type(value) == "number" or type(value) == "string" or type(value) == "boolean" then + table.insert(t_str, basicSerialize(value) .. "\n") + else + + if saved[value] then -- value already saved? + table.insert(t_str, saved[value] .. "\n") + else + saved[value] = name -- save name for next time + table.insert(t_str, "{}\n") + for k,v in pairs(value) do -- save its fields + local fieldname = string.format("%s[%s]", name, basicSerialize(k)) + table.insert(t_str, mist.utils.serializeWithCycles(fieldname, v, saved)) + end + end + end + return table.concat(t_str) + else + return "" + end +end + +--- Serialize a table to a single line string. +-- serialization of a table all on a single line, no comments, made to replace old get_table_string function +-- borrowed from slmod +-- @tparam table tbl table to serialize. +-- @treturn string string containing serialized table +function mist.utils.oneLineSerialize(tbl) + if type(tbl) == 'table' then --function only works for tables! + + local tbl_str = {} + + tbl_str[#tbl_str + 1] = '{ ' + + for ind,val in pairs(tbl) do -- serialize its fields + if type(ind) == "number" then + tbl_str[#tbl_str + 1] = '[' + tbl_str[#tbl_str + 1] = tostring(ind) + tbl_str[#tbl_str + 1] = '] = ' + else --must be a string + tbl_str[#tbl_str + 1] = '[' + tbl_str[#tbl_str + 1] = mist.utils.basicSerialize(ind) + tbl_str[#tbl_str + 1] = '] = ' + end + + if ((type(val) == 'number') or (type(val) == 'boolean')) then + tbl_str[#tbl_str + 1] = tostring(val) + tbl_str[#tbl_str + 1] = ', ' + elseif type(val) == 'string' then + tbl_str[#tbl_str + 1] = mist.utils.basicSerialize(val) + tbl_str[#tbl_str + 1] = ', ' + elseif type(val) == 'nil' then -- won't ever happen, right? + tbl_str[#tbl_str + 1] = 'nil, ' + elseif type(val) == 'table' then + tbl_str[#tbl_str + 1] = mist.utils.oneLineSerialize(val) + tbl_str[#tbl_str + 1] = ', ' --I think this is right, I just added it + else + log:war('Unable to serialize value type $1 at index $2', mist.utils.basicSerialize(type(val)), tostring(ind)) + end + + end + tbl_str[#tbl_str + 1] = '}' + return table.concat(tbl_str) + end +end + +--- Returns table in a easy readable string representation. +-- this function is not meant for serialization because it uses +-- newlines for better readability. +-- @param tbl table to show +-- @param loc +-- @param indent +-- @param tableshow_tbls +-- @return human readable string representation of given table +function mist.utils.tableShow(tbl, loc, indent, tableshow_tbls) --based on serialize_slmod, this is a _G serialization + tableshow_tbls = tableshow_tbls or {} --create table of tables + loc = loc or "" + indent = indent or "" + if type(tbl) == 'table' then --function only works for tables! + tableshow_tbls[tbl] = loc + + local tbl_str = {} + + tbl_str[#tbl_str + 1] = indent .. '{\n' + + for ind,val in pairs(tbl) do -- serialize its fields + if type(ind) == "number" then + tbl_str[#tbl_str + 1] = indent + tbl_str[#tbl_str + 1] = loc .. '[' + tbl_str[#tbl_str + 1] = tostring(ind) + tbl_str[#tbl_str + 1] = '] = ' + else + tbl_str[#tbl_str + 1] = indent + tbl_str[#tbl_str + 1] = loc .. '[' + tbl_str[#tbl_str + 1] = mist.utils.basicSerialize(ind) + tbl_str[#tbl_str + 1] = '] = ' + end + + if ((type(val) == 'number') or (type(val) == 'boolean')) then + tbl_str[#tbl_str + 1] = tostring(val) + tbl_str[#tbl_str + 1] = ',\n' + elseif type(val) == 'string' then + tbl_str[#tbl_str + 1] = mist.utils.basicSerialize(val) + tbl_str[#tbl_str + 1] = ',\n' + elseif type(val) == 'nil' then -- won't ever happen, right? + tbl_str[#tbl_str + 1] = 'nil,\n' + elseif type(val) == 'table' then + if tableshow_tbls[val] then + tbl_str[#tbl_str + 1] = tostring(val) .. ' already defined: ' .. tableshow_tbls[val] .. ',\n' + else + tableshow_tbls[val] = loc .. '[' .. mist.utils.basicSerialize(ind) .. ']' + tbl_str[#tbl_str + 1] = tostring(val) .. ' ' + tbl_str[#tbl_str + 1] = mist.utils.tableShow(val, loc .. '[' .. mist.utils.basicSerialize(ind).. ']', indent .. ' ', tableshow_tbls) + tbl_str[#tbl_str + 1] = ',\n' + end + elseif type(val) == 'function' then + if debug and debug.getinfo then + local fcnname = tostring(val) + local info = debug.getinfo(val, "S") + if info.what == "C" then + tbl_str[#tbl_str + 1] = string.format('%q', fcnname .. ', C function') .. ',\n' + else + if (string.sub(info.source, 1, 2) == [[./]]) then + tbl_str[#tbl_str + 1] = string.format('%q', fcnname .. ', defined in (' .. info.linedefined .. '-' .. info.lastlinedefined .. ')' .. info.source) ..',\n' + else + tbl_str[#tbl_str + 1] = string.format('%q', fcnname .. ', defined in (' .. info.linedefined .. '-' .. info.lastlinedefined .. ')') ..',\n' + end + end + + else + tbl_str[#tbl_str + 1] = 'a function,\n' + end + else + tbl_str[#tbl_str + 1] = 'unable to serialize value type ' .. mist.utils.basicSerialize(type(val)) .. ' at index ' .. tostring(ind) + end + end + + tbl_str[#tbl_str + 1] = indent .. '}' + return table.concat(tbl_str) + end +end +end + +--- Debug functions +-- @section mist.debug +do -- mist.debug scope + mist.debug = {} + + --- Dumps the global table _G. + -- This dumps the global table _G to a file in + -- the DCS\Logs directory. + -- This function requires you to disable script sanitization + -- in $DCS_ROOT\Scripts\MissionScripting.lua to access lfs and io + -- libraries. + -- @param fname + function mist.debug.dump_G(fname) + if lfs and io then + local fdir = lfs.writedir() .. [[Logs\]] .. fname + local f = io.open(fdir, 'w') + f:write(mist.utils.tableShow(_G)) + f:close() + log:info('Wrote debug data to $1', fdir) + --trigger.action.outText(errmsg, 10) + else + log:alert('insufficient libraries to run mist.debug.dump_G, you must disable the sanitization of the io and lfs libraries in ./Scripts/MissionScripting.lua') + --trigger.action.outText(errmsg, 10) + end + end + + --- Write debug data to file. + -- This function requires you to disable script sanitization + -- in $DCS_ROOT\Scripts\MissionScripting.lua to access lfs and io + -- libraries. + -- @param fcn + -- @param fcnVars + -- @param fname + function mist.debug.writeData(fcn, fcnVars, fname) + if lfs and io then + local fdir = lfs.writedir() .. [[Logs\]] .. fname + local f = io.open(fdir, 'w') + f:write(fcn(unpack(fcnVars, 1, table.maxn(fcnVars)))) + f:close() + log:info('Wrote debug data to $1', fdir) + local errmsg = 'mist.debug.writeData wrote data to ' .. fdir + trigger.action.outText(errmsg, 10) + else + local errmsg = 'Error: insufficient libraries to run mist.debug.writeData, you must disable the sanitization of the io and lfs libraries in ./Scripts/MissionScripting.lua' + log:alert(errmsg) + trigger.action.outText(errmsg, 10) + end + end + + --- Write mist databases to file. + -- This function requires you to disable script sanitization + -- in $DCS_ROOT\Scripts\MissionScripting.lua to access lfs and io + -- libraries. + function mist.debug.dumpDBs() + for DBname, DB in pairs(mist.DBs) do + if type(DB) == 'table' and type(DBname) == 'string' then + mist.debug.writeData(mist.utils.serialize, {DBname, DB}, 'mist_DBs_' .. DBname .. '.lua') + end + end + end +end + +--- 3D Vector functions +-- @section mist.vec +do -- mist.vec scope + mist.vec = {} + + --- Vector addition. + -- @tparam Vec3 vec1 first vector + -- @tparam Vec3 vec2 second vector + -- @treturn Vec3 new vector, sum of vec1 and vec2. + function mist.vec.add(vec1, vec2) + return {x = vec1.x + vec2.x, y = vec1.y + vec2.y, z = vec1.z + vec2.z} + end + + --- Vector substraction. + -- @tparam Vec3 vec1 first vector + -- @tparam Vec3 vec2 second vector + -- @treturn Vec3 new vector, vec2 substracted from vec1. + function mist.vec.sub(vec1, vec2) + return {x = vec1.x - vec2.x, y = vec1.y - vec2.y, z = vec1.z - vec2.z} + end + + --- Vector scalar multiplication. + -- @tparam Vec3 vec vector to multiply + -- @tparam number mult scalar multiplicator + -- @treturn Vec3 new vector multiplied with the given scalar + function mist.vec.scalarMult(vec, mult) + return {x = vec.x*mult, y = vec.y*mult, z = vec.z*mult} + end + + mist.vec.scalar_mult = mist.vec.scalarMult + + --- Vector dot product. + -- @tparam Vec3 vec1 first vector + -- @tparam Vec3 vec2 second vector + -- @treturn number dot product of given vectors + function mist.vec.dp (vec1, vec2) + return vec1.x*vec2.x + vec1.y*vec2.y + vec1.z*vec2.z + end + + --- Vector cross product. + -- @tparam Vec3 vec1 first vector + -- @tparam Vec3 vec2 second vector + -- @treturn Vec3 new vector, cross product of vec1 and vec2. + function mist.vec.cp(vec1, vec2) + return { x = vec1.y*vec2.z - vec1.z*vec2.y, y = vec1.z*vec2.x - vec1.x*vec2.z, z = vec1.x*vec2.y - vec1.y*vec2.x} + end + + --- Vector magnitude + -- @tparam Vec3 vec vector + -- @treturn number magnitude of vector vec + function mist.vec.mag(vec) + return (vec.x^2 + vec.y^2 + vec.z^2)^0.5 + end + + --- Unit vector + -- @tparam Vec3 vec + -- @treturn Vec3 unit vector of vec + function mist.vec.getUnitVec(vec) + local mag = mist.vec.mag(vec) + return { x = vec.x/mag, y = vec.y/mag, z = vec.z/mag } + end + + --- Rotate vector. + -- @tparam Vec2 vec2 to rotoate + -- @tparam number theta + -- @return Vec2 rotated vector. + function mist.vec.rotateVec2(vec2, theta) + return { x = vec2.x*math.cos(theta) - vec2.y*math.sin(theta), y = vec2.x*math.sin(theta) + vec2.y*math.cos(theta)} + end +end + +--- Flag functions. +-- The mist "Flag functions" are functions that are similar to Slmod functions +-- that detect a game condition and set a flag when that game condition is met. +-- +-- They are intended to be used by persons with little or no experience in Lua +-- programming, but with a good knowledge of the DCS mission editor. +-- @section mist.flagFunc +do -- mist.flagFunc scope + mist.flagFunc = {} + + --- Sets a flag if map objects are destroyed inside a zone. + -- Once this function is run, it will start a continuously evaluated process + -- that will set a flag true if map objects (such as bridges, buildings in + -- town, etc.) die (or have died) in a mission editor zone (or set of zones). + -- This will only happen once; once the flag is set true, the process ends. + -- @usage + -- -- Example vars table + -- vars = { + -- zones = { "zone1", "zone2" }, -- can also be a single string + -- flag = 3, -- number of the flag + -- stopflag = 4, -- optional number of the stop flag + -- req_num = 10, -- optional minimum amount of map objects needed to die + -- } + -- mist.flagFuncs.mapobjs_dead_zones(vars) + -- @tparam table vars table containing parameters. + function mist.flagFunc.mapobjs_dead_zones(vars) + --[[vars needs to be: +zones = table or string, +flag = number, +stopflag = number or nil, +req_num = number or nil + +AND used by function, +initial_number + +]] + -- type_tbl + local type_tbl = { + [{'zones', 'zone'}] = {'table', 'string'}, + flag = {'number', 'string'}, + [{'stopflag', 'stopFlag'}] = {'number', 'string', 'nil'}, + [{'req_num', 'reqnum'}] = {'number', 'nil'}, + } + + local err, errmsg = mist.utils.typeCheck('mist.flagFunc.mapobjs_dead_zones', type_tbl, vars) + assert(err, errmsg) + local zones = vars.zones or vars.zone + local flag = vars.flag + local stopflag = vars.stopflag or vars.stopFlag or -1 + local req_num = vars.req_num or vars.reqnum or 1 + local initial_number = vars.initial_number + + if type(zones) == 'string' then + zones = {zones} + end + + if not initial_number then + initial_number = #mist.getDeadMapObjsInZones(zones) + end + + if stopflag == -1 or (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then + if (#mist.getDeadMapObjsInZones(zones) - initial_number) >= req_num and trigger.misc.getUserFlag(flag) == 0 then + trigger.action.setUserFlag(flag, true) + return + else + mist.scheduleFunction(mist.flagFunc.mapobjs_dead_zones, {{zones = zones, flag = flag, stopflag = stopflag, req_num = req_num, initial_number = initial_number}}, timer.getTime() + 1) + end + end + end + + --- Sets a flag if map objects are destroyed inside a polygon. + -- Once this function is run, it will start a continuously evaluated process + -- that will set a flag true if map objects (such as bridges, buildings in + -- town, etc.) die (or have died) in a polygon. + -- This will only happen once; once the flag is set true, the process ends. + -- @usage + -- -- Example vars table + -- vars = { + -- zone = { + -- [1] = mist.DBs.unitsByName['NE corner'].point, + -- [2] = mist.DBs.unitsByName['SE corner'].point, + -- [3] = mist.DBs.unitsByName['SW corner'].point, + -- [4] = mist.DBs.unitsByName['NW corner'].point + -- } + -- flag = 3, -- number of the flag + -- stopflag = 4, -- optional number of the stop flag + -- req_num = 10, -- optional minimum amount of map objects needed to die + -- } + -- mist.flagFuncs.mapobjs_dead_zones(vars) + -- @tparam table vars table containing parameters. + function mist.flagFunc.mapobjs_dead_polygon(vars) + --[[vars needs to be: +zone = table, +flag = number, +stopflag = number or nil, +req_num = number or nil + +AND used by function, +initial_number + +]] + -- type_tbl + local type_tbl = { + [{'zone', 'polyzone'}] = 'table', + flag = {'number', 'string'}, + [{'stopflag', 'stopFlag'}] = {'number', 'string', 'nil'}, + [{'req_num', 'reqnum'}] = {'number', 'nil'}, + } + + local err, errmsg = mist.utils.typeCheck('mist.flagFunc.mapobjs_dead_polygon', type_tbl, vars) + assert(err, errmsg) + local zone = vars.zone or vars.polyzone + local flag = vars.flag + local stopflag = vars.stopflag or vars.stopFlag or -1 + local req_num = vars.req_num or vars.reqnum or 1 + local initial_number = vars.initial_number + + if not initial_number then + initial_number = #mist.getDeadMapObjsInPolygonZone(zone) + end + + if stopflag == -1 or (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then + if (#mist.getDeadMapObjsInPolygonZone(zone) - initial_number) >= req_num and trigger.misc.getUserFlag(flag) == 0 then + trigger.action.setUserFlag(flag, true) + return + else + mist.scheduleFunction(mist.flagFunc.mapobjs_dead_polygon, {{zone = zone, flag = flag, stopflag = stopflag, req_num = req_num, initial_number = initial_number}}, timer.getTime() + 1) + end + end + end + + --- Sets a flag if unit(s) is/are inside a polygon. + -- @tparam table vars @{unitsInPolygonVars} + -- @usage -- set flag 11 to true as soon as any blue vehicles + -- -- are inside the polygon shape created off of the waypoints + -- -- of the group forest1 + -- mist.flagFunc.units_in_polygon { + -- units = {'[blue][vehicle]'}, + -- zone = mist.getGroupPoints('forest1'), + -- flag = 11 + -- } + function mist.flagFunc.units_in_polygon(vars) + --[[vars needs to be: +units = table, +zone = table, +flag = number, +stopflag = number or nil, +maxalt = number or nil, +interval = number or nil, +req_num = number or nil +toggle = boolean or nil +unitTableDef = table or nil +]] + -- type_tbl + local type_tbl = { + [{'units', 'unit'}] = 'table', + [{'zone', 'polyzone'}] = 'table', + flag = {'number', 'string'}, + [{'stopflag', 'stopFlag'}] = {'number', 'string', 'nil'}, + [{'maxalt', 'alt'}] = {'number', 'nil'}, + interval = {'number', 'nil'}, + [{'req_num', 'reqnum'}] = {'number', 'nil'}, + toggle = {'boolean', 'nil'}, + unitTableDef = {'table', 'nil'}, + } + + local err, errmsg = mist.utils.typeCheck('mist.flagFunc.units_in_polygon', type_tbl, vars) + assert(err, errmsg) + local units = vars.units or vars.unit + local zone = vars.zone or vars.polyzone + local flag = vars.flag + local stopflag = vars.stopflag or vars.stopFlag or -1 + local interval = vars.interval or 1 + local maxalt = vars.maxalt or vars.alt + local req_num = vars.req_num or vars.reqnum or 1 + local toggle = vars.toggle or nil + local unitTableDef = vars.unitTableDef + + if not units.processed then + unitTableDef = mist.utils.deepCopy(units) + end + + if (units.processed and units.processed < mist.getLastDBUpdateTime()) or not units.processed then -- run unit table short cuts + if unitTableDef then + units = mist.makeUnitTable(unitTableDef) + end + end + + if stopflag == -1 or (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == 0) then + local num_in_zone = 0 + for i = 1, #units do + local unit = Unit.getByName(units[i]) + if unit then + local pos = unit:getPosition().p + if mist.pointInPolygon(pos, zone, maxalt) then + num_in_zone = num_in_zone + 1 + if num_in_zone >= req_num and trigger.misc.getUserFlag(flag) == 0 then + trigger.action.setUserFlag(flag, true) + break + end + end + end + end + if toggle and (num_in_zone < req_num) and trigger.misc.getUserFlag(flag) > 0 then + trigger.action.setUserFlag(flag, false) + end + -- do another check in case stopflag was set true by this function + if (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == 0) then + mist.scheduleFunction(mist.flagFunc.units_in_polygon, {{units = units, zone = zone, flag = flag, stopflag = stopflag, interval = interval, req_num = req_num, maxalt = maxalt, toggle = toggle, unitTableDef = unitTableDef}}, timer.getTime() + interval) + end + end + + end + + --- Sets a flag if unit(s) is/are inside a trigger zone. + -- @todo document + function mist.flagFunc.units_in_zones(vars) + --[[vars needs to be: + units = table, + zones = table, + flag = number, + stopflag = number or nil, + zone_type = string or nil, + req_num = number or nil, + interval = number or nil + toggle = boolean or nil + ]] + -- type_tbl + local type_tbl = { + units = 'table', + zones = 'table', + flag = {'number', 'string'}, + [{'stopflag', 'stopFlag'}] = {'number', 'string', 'nil'}, + [{'zone_type', 'zonetype'}] = {'string', 'nil'}, + [{'req_num', 'reqnum'}] = {'number', 'nil'}, + interval = {'number', 'nil'}, + toggle = {'boolean', 'nil'}, + unitTableDef = {'table', 'nil'}, + } + + local err, errmsg = mist.utils.typeCheck('mist.flagFunc.units_in_zones', type_tbl, vars) + assert(err, errmsg) + local units = vars.units + local zones = vars.zones + local flag = vars.flag + local stopflag = vars.stopflag or vars.stopFlag or -1 + local zone_type = vars.zone_type or vars.zonetype or 'cylinder' + local req_num = vars.req_num or vars.reqnum or 1 + local interval = vars.interval or 1 + local toggle = vars.toggle or nil + local unitTableDef = vars.unitTableDef + + if not units.processed then + unitTableDef = mist.utils.deepCopy(units) + end + + if (units.processed and units.processed < mist.getLastDBUpdateTime()) or not units.processed then -- run unit table short cuts + if unitTableDef then + units = mist.makeUnitTable(unitTableDef) + end + end + + if stopflag == -1 or (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then + + local in_zone_units = mist.getUnitsInZones(units, zones, zone_type) + + if #in_zone_units >= req_num and trigger.misc.getUserFlag(flag) == 0 then + trigger.action.setUserFlag(flag, true) + elseif #in_zone_units < req_num and toggle then + trigger.action.setUserFlag(flag, false) + end + -- do another check in case stopflag was set true by this function + if (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then + mist.scheduleFunction(mist.flagFunc.units_in_zones, {{units = units, zones = zones, flag = flag, stopflag = stopflag, zone_type = zone_type, req_num = req_num, interval = interval, toggle = toggle, unitTableDef = unitTableDef}}, timer.getTime() + interval) + end + end + + end + + --- Sets a flag if unit(s) is/are inside a moving zone. + -- @todo document + function mist.flagFunc.units_in_moving_zones(vars) + --[[vars needs to be: + units = table, + zone_units = table, + radius = number, + flag = number, + stopflag = number or nil, + zone_type = string or nil, + req_num = number or nil, + interval = number or nil + toggle = boolean or nil + ]] + -- type_tbl + local type_tbl = { + units = 'table', + [{'zone_units', 'zoneunits'}] = 'table', + radius = 'number', + flag = {'number', 'string'}, + [{'stopflag', 'stopFlag'}] = {'number', 'string', 'nil'}, + [{'zone_type', 'zonetype'}] = {'string', 'nil'}, + [{'req_num', 'reqnum'}] = {'number', 'nil'}, + interval = {'number', 'nil'}, + toggle = {'boolean', 'nil'}, + unitTableDef = {'table', 'nil'}, + zUnitTableDef = {'table', 'nil'}, + } + + local err, errmsg = mist.utils.typeCheck('mist.flagFunc.units_in_moving_zones', type_tbl, vars) + assert(err, errmsg) + local units = vars.units + local zone_units = vars.zone_units or vars.zoneunits + local radius = vars.radius + local flag = vars.flag + local stopflag = vars.stopflag or vars.stopFlag or -1 + local zone_type = vars.zone_type or vars.zonetype or 'cylinder' + local req_num = vars.req_num or vars.reqnum or 1 + local interval = vars.interval or 1 + local toggle = vars.toggle or nil + local unitTableDef = vars.unitTableDef + local zUnitTableDef = vars.zUnitTableDef + + if not units.processed then + unitTableDef = mist.utils.deepCopy(units) + end + + if not zone_units.processed then + zUnitTableDef = mist.utils.deepCopy(zone_units) + end + + if (units.processed and units.processed < mist.getLastDBUpdateTime()) or not units.processed then -- run unit table short cuts + if unitTableDef then + units = mist.makeUnitTable(unitTableDef) + end + end + + if (zone_units.processed and zone_units.processed < mist.getLastDBUpdateTime()) or not zone_units.processed then -- run unit table short cuts + if zUnitTableDef then + zone_units = mist.makeUnitTable(zUnitTableDef) + end + + end + + if stopflag == -1 or (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then + + local in_zone_units = mist.getUnitsInMovingZones(units, zone_units, radius, zone_type) + + if #in_zone_units >= req_num and trigger.misc.getUserFlag(flag) == 0 then + trigger.action.setUserFlag(flag, true) + elseif #in_zone_units < req_num and toggle then + trigger.action.setUserFlag(flag, false) + end + -- do another check in case stopflag was set true by this function + if (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then + mist.scheduleFunction(mist.flagFunc.units_in_moving_zones, {{units = units, zone_units = zone_units, radius = radius, flag = flag, stopflag = stopflag, zone_type = zone_type, req_num = req_num, interval = interval, toggle = toggle, unitTableDef = unitTableDef, zUnitTableDef = zUnitTableDef}}, timer.getTime() + interval) + end + end + + end + + --- Sets a flag if units have line of sight to each other. + -- @todo document + function mist.flagFunc.units_LOS(vars) + --[[vars needs to be: +unitset1 = table, +altoffset1 = number, +unitset2 = table, +altoffset2 = number, +flag = number, +stopflag = number or nil, +radius = number or nil, +interval = number or nil, +req_num = number or nil +toggle = boolean or nil +]] + -- type_tbl + local type_tbl = { + [{'unitset1', 'units1'}] = 'table', + [{'altoffset1', 'alt1'}] = 'number', + [{'unitset2', 'units2'}] = 'table', + [{'altoffset2', 'alt2'}] = 'number', + flag = {'number', 'string'}, + [{'stopflag', 'stopFlag'}] = {'number', 'string', 'nil'}, + [{'req_num', 'reqnum'}] = {'number', 'nil'}, + interval = {'number', 'nil'}, + radius = {'number', 'nil'}, + toggle = {'boolean', 'nil'}, + unitTableDef1 = {'table', 'nil'}, + unitTableDef2 = {'table', 'nil'}, + } + + local err, errmsg = mist.utils.typeCheck('mist.flagFunc.units_LOS', type_tbl, vars) + assert(err, errmsg) + local unitset1 = vars.unitset1 or vars.units1 + local altoffset1 = vars.altoffset1 or vars.alt1 + local unitset2 = vars.unitset2 or vars.units2 + local altoffset2 = vars.altoffset2 or vars.alt2 + local flag = vars.flag + local stopflag = vars.stopflag or vars.stopFlag or -1 + local interval = vars.interval or 1 + local radius = vars.radius or math.huge + local req_num = vars.req_num or vars.reqnum or 1 + local toggle = vars.toggle or nil + local unitTableDef1 = vars.unitTableDef1 + local unitTableDef2 = vars.unitTableDef2 + + if not unitset1.processed then + unitTableDef1 = mist.utils.deepCopy(unitset1) + end + + if not unitset2.processed then + unitTableDef2 = mist.utils.deepCopy(unitset2) + end + + if (unitset1.processed and unitset1.processed < mist.getLastDBUpdateTime()) or not unitset1.processed then -- run unit table short cuts + if unitTableDef1 then + unitset1 = mist.makeUnitTable(unitTableDef1) + end + end + + if (unitset2.processed and unitset2.processed < mist.getLastDBUpdateTime()) or not unitset2.processed then -- run unit table short cuts + if unitTableDef2 then + unitset2 = mist.makeUnitTable(unitTableDef2) + end + end + + + if stopflag == -1 or (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then + + local unitLOSdata = mist.getUnitsLOS(unitset1, altoffset1, unitset2, altoffset2, radius) + + if #unitLOSdata >= req_num and trigger.misc.getUserFlag(flag) == 0 then + trigger.action.setUserFlag(flag, true) + elseif #unitLOSdata < req_num and toggle then + trigger.action.setUserFlag(flag, false) + end + -- do another check in case stopflag was set true by this function + if (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then + mist.scheduleFunction(mist.flagFunc.units_LOS, {{unitset1 = unitset1, altoffset1 = altoffset1, unitset2 = unitset2, altoffset2 = altoffset2, flag = flag, stopflag = stopflag, radius = radius, req_num = req_num, interval = interval, toggle = toggle, unitTableDef1 = unitTableDef1, unitTableDef2 = unitTableDef2}}, timer.getTime() + interval) + end + end + end + + --- Sets a flag if group is alive. + -- @todo document + function mist.flagFunc.group_alive(vars) + --[[vars +groupName +flag +toggle +interval +stopFlag + +]] + local type_tbl = { + [{'group', 'groupname', 'gp', 'groupName'}] = 'string', + flag = {'number', 'string'}, + [{'stopflag', 'stopFlag'}] = {'number', 'string', 'nil'}, + interval = {'number', 'nil'}, + toggle = {'boolean', 'nil'}, + } + + local err, errmsg = mist.utils.typeCheck('mist.flagFunc.group_alive', type_tbl, vars) + assert(err, errmsg) + + local groupName = vars.groupName or vars.group or vars.gp or vars.Groupname + local flag = vars.flag + local stopflag = vars.stopflag or vars.stopFlag or -1 + local interval = vars.interval or 1 + local toggle = vars.toggle or nil + + + if stopflag == -1 or (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then + if Group.getByName(groupName) and Group.getByName(groupName):isExist() == true and #Group.getByName(groupName):getUnits() > 0 then + if trigger.misc.getUserFlag(flag) == 0 then + trigger.action.setUserFlag(flag, true) + end + else + if toggle then + trigger.action.setUserFlag(flag, false) + end + end + end + + if (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then + mist.scheduleFunction(mist.flagFunc.group_alive, {{groupName = groupName, flag = flag, stopflag = stopflag, interval = interval, toggle = toggle}}, timer.getTime() + interval) + end + + end + + --- Sets a flag if group is dead. + -- @todo document + function mist.flagFunc.group_dead(vars) + local type_tbl = { + [{'group', 'groupname', 'gp', 'groupName'}] = 'string', + flag = {'number', 'string'}, + [{'stopflag', 'stopFlag'}] = {'number', 'string', 'nil'}, + interval = {'number', 'nil'}, + toggle = {'boolean', 'nil'}, + } + + local err, errmsg = mist.utils.typeCheck('mist.flagFunc.group_dead', type_tbl, vars) + assert(err, errmsg) + + local groupName = vars.groupName or vars.group or vars.gp or vars.Groupname + local flag = vars.flag + local stopflag = vars.stopflag or vars.stopFlag or -1 + local interval = vars.interval or 1 + local toggle = vars.toggle or nil + + + if stopflag == -1 or (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then + if (Group.getByName(groupName) and Group.getByName(groupName):isExist() == false) or (Group.getByName(groupName) and #Group.getByName(groupName):getUnits() < 1) or not Group.getByName(groupName) then + if trigger.misc.getUserFlag(flag) == 0 then + trigger.action.setUserFlag(flag, true) + end + else + if toggle then + trigger.action.setUserFlag(flag, false) + end + end + end + + if (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then + mist.scheduleFunction(mist.flagFunc.group_dead, {{groupName = groupName, flag = flag, stopflag = stopflag, interval = interval, toggle = toggle}}, timer.getTime() + interval) + end + end + + --- Sets a flag if less than given percent of group is alive. + -- @todo document + function mist.flagFunc.group_alive_less_than(vars) + local type_tbl = { + [{'group', 'groupname', 'gp', 'groupName'}] = 'string', + percent = 'number', + flag = {'number', 'string'}, + [{'stopflag', 'stopFlag'}] = {'number', 'string', 'nil'}, + interval = {'number', 'nil'}, + toggle = {'boolean', 'nil'}, + } + + local err, errmsg = mist.utils.typeCheck('mist.flagFunc.group_alive_less_than', type_tbl, vars) + assert(err, errmsg) + + local groupName = vars.groupName or vars.group or vars.gp or vars.Groupname + local flag = vars.flag + local percent = vars.percent + local stopflag = vars.stopflag or vars.stopFlag or -1 + local interval = vars.interval or 1 + local toggle = vars.toggle or nil + + + if stopflag == -1 or (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then + if Group.getByName(groupName) and Group.getByName(groupName):isExist() == true then + if Group.getByName(groupName):getSize()/Group.getByName(groupName):getInitialSize() < percent/100 then + if trigger.misc.getUserFlag(flag) == 0 then + trigger.action.setUserFlag(flag, true) + end + else + if toggle then + trigger.action.setUserFlag(flag, false) + end + end + else + if trigger.misc.getUserFlag(flag) == 0 then + trigger.action.setUserFlag(flag, true) + end + end + end + + if (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then + mist.scheduleFunction(mist.flagFunc.group_alive_less_than, {{groupName = groupName, flag = flag, stopflag = stopflag, interval = interval, toggle = toggle, percent = percent}}, timer.getTime() + interval) + end + end + + --- Sets a flag if more than given percent of group is alive. + -- @todo document + function mist.flagFunc.group_alive_more_than(vars) + local type_tbl = { + [{'group', 'groupname', 'gp', 'groupName'}] = 'string', + percent = 'number', + flag = {'number', 'string'}, + [{'stopflag', 'stopFlag'}] = {'number', 'string', 'nil'}, + interval = {'number', 'nil'}, + toggle = {'boolean', 'nil'}, + } + + local err, errmsg = mist.utils.typeCheck('mist.flagFunc.group_alive_more_than', type_tbl, vars) + assert(err, errmsg) + + local groupName = vars.groupName or vars.group or vars.gp or vars.Groupname + local flag = vars.flag + local percent = vars.percent + local stopflag = vars.stopflag or vars.stopFlag or -1 + local interval = vars.interval or 1 + local toggle = vars.toggle or nil + + + if stopflag == -1 or (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then + if Group.getByName(groupName) and Group.getByName(groupName):isExist() == true then + if Group.getByName(groupName):getSize()/Group.getByName(groupName):getInitialSize() > percent/100 then + if trigger.misc.getUserFlag(flag) == 0 then + trigger.action.setUserFlag(flag, true) + end + else + if toggle and trigger.misc.getUserFlag(flag) == 1 then + trigger.action.setUserFlag(flag, false) + end + end + else --- just in case + if toggle and trigger.misc.getUserFlag(flag) == 1 then + trigger.action.setUserFlag(flag, false) + end + end + end + + if (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then + mist.scheduleFunction(mist.flagFunc.group_alive_more_than, {{groupName = groupName, flag = flag, stopflag = stopflag, interval = interval, toggle = toggle, percent = percent}}, timer.getTime() + interval) + end + end + + mist.flagFunc.mapobjsDeadPolygon = mist.flagFunc.mapobjs_dead_polygon + mist.flagFunc.mapobjsDeadZones = mist.flagFunc.Mapobjs_dead_zones + mist.flagFunc.unitsInZones = mist.flagFunc.units_in_zones + mist.flagFunc.unitsInMovingZones = mist.flagFunc.units_in_moving_zones + mist.flagFunc.unitsInPolygon = mist.flagFunc.units_in_polygon + mist.flagFunc.unitsLOS = mist.flagFunc.units_LOS + mist.flagFunc.groupAlive = mist.flagFunc.group_alive + mist.flagFunc.groupDead = mist.flagFunc.group_dead + mist.flagFunc.groupAliveMoreThan = mist.flagFunc.group_alive_more_than + mist.flagFunc.groupAliveLessThan = mist.flagFunc.group_alive_less_than + +end + +--- Message functions. +-- Messaging system +-- @section mist.msg +do -- mist.msg scope + local messageList = {} + -- this defines the max refresh rate of the message box it honestly only needs to + -- go faster than this for precision timing stuff (which could be its own function) + local messageDisplayRate = 0.1 + local messageID = 0 + local displayActive = false + local displayFuncId = 0 + + local caSlots = false + local caMSGtoGroup = false + + if env.mission.groundControl then -- just to be sure? + for index, value in pairs(env.mission.groundControl) do + if type(value) == 'table' then + for roleName, roleVal in pairs(value) do + for rIndex, rVal in pairs(roleVal) do + if rIndex == 'red' or rIndex == 'blue' then + if env.mission.groundControl[index][roleName][rIndex] > 0 then + caSlots = true + break + end + end + end + end + elseif type(value) == 'boolean' and value == true then + caSlots = true + break + end + end + end + + local function mistdisplayV5() + --[[thoughts to improve upon + event handler based activeClients table. + display messages only when there is an update + possibly co-routine it. + ]] + end + + local function mistdisplayV4() + local activeClients = {} + + for clientId, clientData in pairs(mist.DBs.humansById) do + if Unit.getByName(clientData.unitName) and Unit.getByName(clientData.unitName):isExist() == true then + activeClients[clientData.groupId] = clientData.groupName + end + end + + --[[if caSlots == true and caMSGtoGroup == true then + + end]] + + + if #messageList > 0 then + if displayActive == false then + displayActive = true + end + --mist.debug.writeData(mist.utils.serialize,{'msg', messageList}, 'messageList.lua') + local msgTableText = {} + local msgTableSound = {} + + for messageId, messageData in pairs(messageList) do + if messageData.displayedFor > messageData.displayTime then + messageData:remove() -- now using the remove/destroy function. + else + if messageData.displayedFor then + messageData.displayedFor = messageData.displayedFor + messageDisplayRate + end + local nextSound = 1000 + local soundIndex = 0 + + if messageData.multSound and #messageData.multSound > 0 then + for index, sData in pairs(messageData.multSound) do + if sData.time <= messageData.displayedFor and sData.played == false and sData.time < nextSound then -- find index of the next sound to be played + nextSound = sData.time + soundIndex = index + end + end + if soundIndex ~= 0 then + messageData.multSound[soundIndex].played = true + end + end + + for recIndex, recData in pairs(messageData.msgFor) do -- iterate recipiants + if recData == 'RED' or recData == 'BLUE' or activeClients[recData] then -- rec exists + if messageData.text then -- text + if not msgTableText[recData] then -- create table entry for text + msgTableText[recData] = {} + msgTableText[recData].text = {} + if recData == 'RED' or recData == 'BLUE' then + msgTableText[recData].text[1] = '-------Combined Arms Message-------- \n' + end + msgTableText[recData].text[#msgTableText[recData].text + 1] = messageData.text + msgTableText[recData].displayTime = messageData.displayTime - messageData.displayedFor + else -- add to table entry and adjust display time if needed + if recData == 'RED' or recData == 'BLUE' then + msgTableText[recData].text[#msgTableText[recData].text + 1] = '\n ---------------- Combined Arms Message: \n' + else + msgTableText[recData].text[#msgTableText[recData].text + 1] = '\n ---------------- \n' + end + msgTableText[recData].text[#msgTableText[recData].text + 1] = messageData.text + if msgTableText[recData].displayTime < messageData.displayTime - messageData.displayedFor then + msgTableText[recData].displayTime = messageData.displayTime - messageData.displayedFor + else + msgTableText[recData].displayTime = 1 + end + end + end + if soundIndex ~= 0 then + msgTableSound[recData] = messageData.multSound[soundIndex].file + end + end + end + + + end + end + ------- new display + + if caSlots == true and caMSGtoGroup == false then + if msgTableText.RED then + trigger.action.outTextForCoalition(coalition.side.RED, table.concat(msgTableText.RED.text), msgTableText.RED.displayTime, true) + + end + if msgTableText.BLUE then + trigger.action.outTextForCoalition(coalition.side.BLUE, table.concat(msgTableText.BLUE.text), msgTableText.BLUE.displayTime, true) + end + end + + for index, msgData in pairs(msgTableText) do + if type(index) == 'number' then -- its a groupNumber + trigger.action.outTextForGroup(index, table.concat(msgData.text), msgData.displayTime, true) + end + end + --- new audio + if msgTableSound.RED then + trigger.action.outSoundForCoalition(coalition.side.RED, msgTableSound.RED) + end + if msgTableSound.BLUE then + trigger.action.outSoundForCoalition(coalition.side.BLUE, msgTableSound.BLUE) + end + + + for index, file in pairs(msgTableSound) do + if type(index) == 'number' then -- its a groupNumber + trigger.action.outSoundForGroup(index, file) + end + end + else + mist.removeFunction(displayFuncId) + displayActive = false + end + + end + + local typeBase = { + ['Mi-8MT'] = {'Mi-8MTV2', 'Mi-8MTV', 'Mi-8'}, + ['MiG-21Bis'] = {'Mig-21'}, + ['MiG-15bis'] = {'Mig-15'}, + ['FW-190D9'] = {'FW-190'}, + ['Bf-109K-4'] = {'Bf-109'}, + } + + --[[function mist.setCAGroupMSG(val) + if type(val) == 'boolean' then + caMSGtoGroup = val + return true + end + return false +end]] + + mist.message = { + + add = function(vars) + local function msgSpamFilter(recList, spamBlockOn) + for id, name in pairs(recList) do + if name == spamBlockOn then + -- log:info('already on recList') + return recList + end + end + --log:info('add to recList') + table.insert(recList, spamBlockOn) + return recList + end + + --[[ + local vars = {} + vars.text = 'Hello World' + vars.displayTime = 20 + vars.msgFor = {coa = {'red'}, countries = {'Ukraine', 'Georgia'}, unitTypes = {'A-10C'}} + mist.message.add(vars) + + Displays the message for all red coalition players. Players belonging to Ukraine and Georgia, and all A-10Cs on the map + + ]] + + + local new = {} + new.text = vars.text -- The actual message + new.displayTime = vars.displayTime -- How long will the message appear for + new.displayedFor = 0 -- how long the message has been displayed so far + new.name = vars.name -- ID to overwrite the older message (if it exists) Basically it replaces a message that is displayed with new text. + new.addedAt = timer.getTime() + new.update = true + + if vars.multSound and vars.multSound[1] then + new.multSound = vars.multSound + else + new.multSound = {} + end + + if vars.sound or vars.fileName then -- converts old sound file system into new multSound format + local sound = vars.sound + if vars.fileName then + sound = vars.fileName + end + new.multSound[#new.multSound+1] = {time = 0.1, file = sound} + end + + if #new.multSound > 0 then + for i, data in pairs(new.multSound) do + data.played = false + end + end + + local newMsgFor = {} -- list of all groups message displays for + for forIndex, forData in pairs(vars.msgFor) do + for list, listData in pairs(forData) do + for clientId, clientData in pairs(mist.DBs.humansById) do + forIndex = string.lower(forIndex) + if type(listData) == 'string' then + listData = string.lower(listData) + end + if (forIndex == 'coa' and (listData == string.lower(clientData.coalition) or listData == 'all')) or (forIndex == 'countries' and string.lower(clientData.country) == listData) or (forIndex == 'units' and string.lower(clientData.unitName) == listData) then -- + newMsgFor = msgSpamFilter(newMsgFor, clientData.groupId) -- so units dont get the same message twice if complex rules are given + --table.insert(newMsgFor, clientId) + elseif forIndex == 'unittypes' then + for typeId, typeData in pairs(listData) do + local found = false + for clientDataEntry, clientDataVal in pairs(clientData) do + if type(clientDataVal) == 'string' then + if mist.matchString(list, clientDataVal) == true or list == 'all' then + local sString = typeData + for rName, pTbl in pairs(typeBase) do -- just a quick check to see if the user may have meant something and got the specific type of the unit wrong + for pIndex, pName in pairs(pTbl) do + if mist.stringMatch(sString, pName) then + sString = rName + end + end + end + if sString == clientData.type then + found = true + newMsgFor = msgSpamFilter(newMsgFor, clientData.groupId) -- sends info oto other function to see if client is already recieving the current message. + --table.insert(newMsgFor, clientId) + end + end + end + if found == true then -- shouldn't this be elsewhere too? + break + end + end + end + + end + end + for coaData, coaId in pairs(coalition.side) do + if string.lower(forIndex) == 'coa' or string.lower(forIndex) == 'ca' then + if listData == string.lower(coaData) or listData == 'all' then + newMsgFor = msgSpamFilter(newMsgFor, coaData) + end + end + end + end + end + + if #newMsgFor > 0 then + new.msgFor = newMsgFor -- I swear its not confusing + + else + return false + end + + + if vars.name and type(vars.name) == 'string' then + for i = 1, #messageList do + if messageList[i].name then + if messageList[i].name == vars.name then + --log:info('updateMessage') + messageList[i].displayedFor = 0 + messageList[i].addedAt = timer.getTime() + messageList[i].sound = new.sound + messageList[i].text = new.text + messageList[i].msgFor = new.msgFor + messageList[i].multSound = new.multSound + messageList[i].update = true + return messageList[i].messageID + end + end + end + end + + messageID = messageID + 1 + new.messageID = messageID + + --mist.debug.writeData(mist.utils.serialize,{'msg', new}, 'newMsg.lua') + + + messageList[#messageList + 1] = new + + local mt = { __index = mist.message} + setmetatable(new, mt) + + if displayActive == false then + displayActive = true + displayFuncId = mist.scheduleFunction(mistdisplayV4, {}, timer.getTime() + messageDisplayRate, messageDisplayRate) + end + + return messageID + + end, + + remove = function(self) -- Now a self variable; the former functionality taken up by mist.message.removeById. + for i, msgData in pairs(messageList) do + if messageList[i] == self then + table.remove(messageList, i) + return true --removal successful + end + end + return false -- removal not successful this script fails at life! + end, + + removeById = function(id) -- This function is NOT passed a self variable; it is the remove by id function. + for i, msgData in pairs(messageList) do + if messageList[i].messageID == id then + table.remove(messageList, i) + return true --removal successful + end + end + return false -- removal not successful this script fails at life! + end, + } + + --[[ vars for mist.msgMGRS +vars.units - table of unit names (NOT unitNameTable- maybe this should change). +vars.acc - integer between 0 and 5, inclusive +vars.text - text in the message +vars.displayTime - self explanatory +vars.msgFor - scope +]] + function mist.msgMGRS(vars) + local units = vars.units + local acc = vars.acc + local text = vars.text + local displayTime = vars.displayTime + local msgFor = vars.msgFor + + local s = mist.getMGRSString{units = units, acc = acc} + local newText + if text then + if string.find(text, '%%s') then -- look for %s + newText = string.format(text, s) -- insert the coordinates into the message + else + -- just append to the end. + newText = text .. s + end + else + newText = s + end + mist.message.add{ + text = newText, + displayTime = displayTime, + msgFor = msgFor + } + end + + --[[ vars for mist.msgLL +vars.units - table of unit names (NOT unitNameTable- maybe this should change) (Yes). +vars.acc - integer, number of numbers after decimal place +vars.DMS - if true, output in degrees, minutes, seconds. Otherwise, output in degrees, minutes. +vars.text - text in the message +vars.displayTime - self explanatory +vars.msgFor - scope +]] + function mist.msgLL(vars) + local units = vars.units -- technically, I don't really need to do this, but it helps readability. + local acc = vars.acc + local DMS = vars.DMS + local text = vars.text + local displayTime = vars.displayTime + local msgFor = vars.msgFor + + local s = mist.getLLString{units = units, acc = acc, DMS = DMS} + local newText + if text then + if string.find(text, '%%s') then -- look for %s + newText = string.format(text, s) -- insert the coordinates into the message + else + -- just append to the end. + newText = text .. s + end + else + newText = s + end + + mist.message.add{ + text = newText, + displayTime = displayTime, + msgFor = msgFor + } + + end + + --[[ +vars.units- table of unit names (NOT unitNameTable- maybe this should change). +vars.ref - vec3 ref point, maybe overload for vec2 as well? +vars.alt - boolean, if used, includes altitude in string +vars.metric - boolean, gives distance in km instead of NM. +vars.text - text of the message +vars.displayTime +vars.msgFor - scope +]] + function mist.msgBR(vars) + local units = vars.units -- technically, I don't really need to do this, but it helps readability. + local ref = vars.ref -- vec2/vec3 will be handled in mist.getBRString + local alt = vars.alt + local metric = vars.metric + local text = vars.text + local displayTime = vars.displayTime + local msgFor = vars.msgFor + + local s = mist.getBRString{units = units, ref = ref, alt = alt, metric = metric} + local newText + if text then + if string.find(text, '%%s') then -- look for %s + newText = string.format(text, s) -- insert the coordinates into the message + else + -- just append to the end. + newText = text .. s + end + else + newText = s + end + + mist.message.add{ + text = newText, + displayTime = displayTime, + msgFor = msgFor + } + + end + + -- basically, just sub-types of mist.msgBR... saves folks the work of getting the ref point. + --[[ +vars.units- table of unit names (NOT unitNameTable- maybe this should change). +vars.ref - string red, blue +vars.alt - boolean, if used, includes altitude in string +vars.metric - boolean, gives distance in km instead of NM. +vars.text - text of the message +vars.displayTime +vars.msgFor - scope +]] + function mist.msgBullseye(vars) + if string.lower(vars.ref) == 'red' then + vars.ref = mist.DBs.missionData.bullseye.red + mist.msgBR(vars) + elseif string.lower(vars.ref) == 'blue' then + vars.ref = mist.DBs.missionData.bullseye.blue + mist.msgBR(vars) + end + end + + --[[ +vars.units- table of unit names (NOT unitNameTable- maybe this should change). +vars.ref - unit name of reference point +vars.alt - boolean, if used, includes altitude in string +vars.metric - boolean, gives distance in km instead of NM. +vars.text - text of the message +vars.displayTime +vars.msgFor - scope +]] + function mist.msgBRA(vars) + if Unit.getByName(vars.ref) and Unit.getByName(vars.ref):isExist() == true then + vars.ref = Unit.getByName(vars.ref):getPosition().p + if not vars.alt then + vars.alt = true + end + mist.msgBR(vars) + end + end + + --[[ vars for mist.msgLeadingMGRS: +vars.units - table of unit names +vars.heading - direction +vars.radius - number +vars.headingDegrees - boolean, switches heading to degrees (optional) +vars.acc - number, 0 to 5. +vars.text - text of the message +vars.displayTime +vars.msgFor - scope +]] + function mist.msgLeadingMGRS(vars) + local units = vars.units -- technically, I don't really need to do this, but it helps readability. + local heading = vars.heading + local radius = vars.radius + local headingDegrees = vars.headingDegrees + local acc = vars.acc + local text = vars.text + local displayTime = vars.displayTime + local msgFor = vars.msgFor + + local s = mist.getLeadingMGRSString{units = units, heading = heading, radius = radius, headingDegrees = headingDegrees, acc = acc} + local newText + if text then + if string.find(text, '%%s') then -- look for %s + newText = string.format(text, s) -- insert the coordinates into the message + else + -- just append to the end. + newText = text .. s + end + else + newText = s + end + + mist.message.add{ + text = newText, + displayTime = displayTime, + msgFor = msgFor + } + + + end + + --[[ vars for mist.msgLeadingLL: +vars.units - table of unit names +vars.heading - direction, number +vars.radius - number +vars.headingDegrees - boolean, switches heading to degrees (optional) +vars.acc - number of digits after decimal point (can be negative) +vars.DMS - boolean, true if you want DMS. (optional) +vars.text - text of the message +vars.displayTime +vars.msgFor - scope +]] + function mist.msgLeadingLL(vars) + local units = vars.units -- technically, I don't really need to do this, but it helps readability. + local heading = vars.heading + local radius = vars.radius + local headingDegrees = vars.headingDegrees + local acc = vars.acc + local DMS = vars.DMS + local text = vars.text + local displayTime = vars.displayTime + local msgFor = vars.msgFor + + local s = mist.getLeadingLLString{units = units, heading = heading, radius = radius, headingDegrees = headingDegrees, acc = acc, DMS = DMS} + local newText + + if text then + if string.find(text, '%%s') then -- look for %s + newText = string.format(text, s) -- insert the coordinates into the message + else + -- just append to the end. + newText = text .. s + end + else + newText = s + end + + mist.message.add{ + text = newText, + displayTime = displayTime, + msgFor = msgFor + } + + end + + --[[ +vars.units - table of unit names +vars.heading - direction, number +vars.radius - number +vars.headingDegrees - boolean, switches heading to degrees (optional) +vars.metric - boolean, if true, use km instead of NM. (optional) +vars.alt - boolean, if true, include altitude. (optional) +vars.ref - vec3/vec2 reference point. +vars.text - text of the message +vars.displayTime +vars.msgFor - scope +]] + function mist.msgLeadingBR(vars) + local units = vars.units -- technically, I don't really need to do this, but it helps readability. + local heading = vars.heading + local radius = vars.radius + local headingDegrees = vars.headingDegrees + local metric = vars.metric + local alt = vars.alt + local ref = vars.ref -- vec2/vec3 will be handled in mist.getBRString + local text = vars.text + local displayTime = vars.displayTime + local msgFor = vars.msgFor + + local s = mist.getLeadingBRString{units = units, heading = heading, radius = radius, headingDegrees = headingDegrees, metric = metric, alt = alt, ref = ref} + local newText + + if text then + if string.find(text, '%%s') then -- look for %s + newText = string.format(text, s) -- insert the coordinates into the message + else + -- just append to the end. + newText = text .. s + end + else + newText = s + end + + mist.message.add{ + text = newText, + displayTime = displayTime, + msgFor = msgFor + } + end +end + +--- Demo functions. +-- @section mist.demos +do -- mist.demos scope + mist.demos = {} + + function mist.demos.printFlightData(unit) + if unit:isExist() then + local function printData(unit, prevVel, prevE, prevTime) + local angles = mist.getAttitude(unit) + if angles then + local Heading = angles.Heading + local Pitch = angles.Pitch + local Roll = angles.Roll + local Yaw = angles.Yaw + local AoA = angles.AoA + local ClimbAngle = angles.ClimbAngle + + if not Heading then + Heading = 'NA' + else + Heading = string.format('%12.2f', mist.utils.toDegree(Heading)) + end + + if not Pitch then + Pitch = 'NA' + else + Pitch = string.format('%12.2f', mist.utils.toDegree(Pitch)) + end + + if not Roll then + Roll = 'NA' + else + Roll = string.format('%12.2f', mist.utils.toDegree(Roll)) + end + + local AoAplusYaw = 'NA' + if AoA and Yaw then + AoAplusYaw = string.format('%12.2f', mist.utils.toDegree((AoA^2 + Yaw^2)^0.5)) + end + + if not Yaw then + Yaw = 'NA' + else + Yaw = string.format('%12.2f', mist.utils.toDegree(Yaw)) + end + + if not AoA then + AoA = 'NA' + else + AoA = string.format('%12.2f', mist.utils.toDegree(AoA)) + end + + if not ClimbAngle then + ClimbAngle = 'NA' + else + ClimbAngle = string.format('%12.2f', mist.utils.toDegree(ClimbAngle)) + end + local unitPos = unit:getPosition() + local unitVel = unit:getVelocity() + local curTime = timer.getTime() + local absVel = string.format('%12.2f', mist.vec.mag(unitVel)) + + + local unitAcc = 'NA' + local Gs = 'NA' + local axialGs = 'NA' + local transGs = 'NA' + if prevVel and prevTime then + local xAcc = (unitVel.x - prevVel.x)/(curTime - prevTime) + local yAcc = (unitVel.y - prevVel.y)/(curTime - prevTime) + local zAcc = (unitVel.z - prevVel.z)/(curTime - prevTime) + + unitAcc = string.format('%12.2f', mist.vec.mag({x = xAcc, y = yAcc, z = zAcc})) + Gs = string.format('%12.2f', mist.vec.mag({x = xAcc, y = yAcc + 9.81, z = zAcc})/9.81) + axialGs = string.format('%12.2f', mist.vec.dp({x = xAcc, y = yAcc + 9.81, z = zAcc}, unitPos.x)/9.81) + transGs = string.format('%12.2f', mist.vec.mag(mist.vec.cp({x = xAcc, y = yAcc + 9.81, z = zAcc}, unitPos.x))/9.81) + end + + local E = 0.5*mist.vec.mag(unitVel)^2 + 9.81*unitPos.p.y + + local energy = string.format('%12.2e', E) + + local dEdt = 'NA' + if prevE and prevTime then + dEdt = string.format('%12.2e', (E - prevE)/(curTime - prevTime)) + end + + trigger.action.outText(string.format('%-25s', 'Heading: ') .. Heading .. ' degrees\n' .. string.format('%-25s', 'Roll: ') .. Roll .. ' degrees\n' .. string.format('%-25s', 'Pitch: ') .. Pitch + .. ' degrees\n' .. string.format('%-25s', 'Yaw: ') .. Yaw .. ' degrees\n' .. string.format('%-25s', 'AoA: ') .. AoA .. ' degrees\n' .. string.format('%-25s', 'AoA plus Yaw: ') .. AoAplusYaw .. ' degrees\n' .. string.format('%-25s', 'Climb Angle: ') .. + ClimbAngle .. ' degrees\n' .. string.format('%-25s', 'Absolute Velocity: ') .. absVel .. ' m/s\n' .. string.format('%-25s', 'Absolute Acceleration: ') .. unitAcc ..' m/s^2\n' + .. string.format('%-25s', 'Axial G loading: ') .. axialGs .. ' g\n' .. string.format('%-25s', 'Transverse G loading: ') .. transGs .. ' g\n' .. string.format('%-25s', 'Absolute G loading: ') .. Gs .. ' g\n' .. string.format('%-25s', 'Energy: ') .. energy .. ' J/kg\n' .. string.format('%-25s', 'dE/dt: ') .. dEdt ..' J/(kg*s)', 1) + return unitVel, E, curTime + end + end + + local function frameFinder(unit, prevVel, prevE, prevTime) + if unit:isExist() then + local currVel = unit:getVelocity() + if prevVel and (prevVel.x ~= currVel.x or prevVel.y ~= currVel.y or prevVel.z ~= currVel.z) or (prevTime and (timer.getTime() - prevTime) > 0.25) then + prevVel, prevE, prevTime = printData(unit, prevVel, prevE, prevTime) + end + mist.scheduleFunction(frameFinder, {unit, prevVel, prevE, prevTime}, timer.getTime() + 0.005) -- it can't go this fast, limited to the 100 times a sec check right now. + end + end + + + local curVel = unit:getVelocity() + local curTime = timer.getTime() + local curE = 0.5*mist.vec.mag(curVel)^2 + 9.81*unit:getPosition().p.y + frameFinder(unit, curVel, curE, curTime) + + end + + end + +end + +--- Time conversion functions. +-- @section mist.time +do -- mist.time scope + mist.time = {} + -- returns a string for specified military time + -- theTime is optional + -- if present current time in mil time is returned + -- if number or table the time is converted into mil tim + function mist.time.convertToSec(timeTable) + + timeInSec = 0 + if timeTable and type(timeTable) == 'number' then + timeInSec = timeTable + elseif timeTable and type(timeTable) == 'table' and (timeTable.d or timeTable.h or timeTable.m or timeTable.s) then + if timeTable.d and type(timeTable.d) == 'number' then + timeInSec = timeInSec + (timeTable.d*86400) + end + if timeTable.h and type(timeTable.h) == 'number' then + timeInSec = timeInSec + (timeTable.h*3600) + end + if timeTable.m and type(timeTable.m) == 'number' then + timeInSec = timeInSec + (timeTable.m*60) + end + if timeTable.s and type(timeTable.s) == 'number' then + timeInSec = timeInSec + timeTable.s + end + + end + return timeInSec + end + + function mist.time.getDHMS(timeInSec) + if timeInSec and type(timeInSec) == 'number' then + local tbl = {d = 0, h = 0, m = 0, s = 0} + if timeInSec > 86400 then + while timeInSec > 86400 do + tbl.d = tbl.d + 1 + timeInSec = timeInSec - 86400 + end + end + if timeInSec > 3600 then + while timeInSec > 3600 do + tbl.h = tbl.h + 1 + timeInSec = timeInSec - 3600 + end + end + if timeInSec > 60 then + while timeInSec > 60 do + tbl.m = tbl.m + 1 + timeInSec = timeInSec - 60 + end + end + tbl.s = timeInSec + return tbl + else + log:error("Didn't recieve number") + return + end + end + + function mist.getMilString(theTime) + local timeInSec = 0 + if theTime then + timeInSec = mist.time.convertToSec(theTime) + else + timeInSec = mist.utils.round(timer.getAbsTime(), 0) + end + + local DHMS = mist.time.getDHMS(timeInSec) + + return tostring(string.format('%02d', DHMS.h) .. string.format('%02d',DHMS.m)) + end + + function mist.getClockString(theTime, hour) + local timeInSec = 0 + if theTime then + timeInSec = mist.time.convertToSec(theTime) + else + timeInSec = mist.utils.round(timer.getAbsTime(), 0) + end + local DHMS = mist.time.getDHMS(timeInSec) + if hour then + if DHMS.h > 12 then + DHMS.h = DHMS.h - 12 + return tostring(string.format('%02d', DHMS.h) .. ':' .. string.format('%02d',DHMS.m) .. ':' .. string.format('%02d',DHMS.s) .. ' PM') + else + return tostring(string.format('%02d', DHMS.h) .. ':' .. string.format('%02d',DHMS.m) .. ':' .. string.format('%02d',DHMS.s) .. ' AM') + end + else + return tostring(string.format('%02d', DHMS.h) .. ':' .. string.format('%02d',DHMS.m) .. ':' .. string.format('%02d',DHMS.s)) + end + end + + -- returns the date in string format + -- both variables optional + -- first val returns with the month as a string + -- 2nd val defins if it should be written the American way or the wrong way. + function mist.time.getDate(convert) + local cal = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} -- + local date = {} + + if not env.mission.date then -- Not likely to happen. Resaving mission auto updates this to remove it. + date.d = 0 + date.m = 6 + date.y = 2011 + else + date.d = env.mission.date.Day + date.m = env.mission.date.Month + date.y = env.mission.date.Year + end + local start = 86400 + local timeInSec = mist.utils.round(timer.getAbsTime()) + if convert and type(convert) == 'number' then + timeInSec = convert + end + if timeInSec > 86400 then + while start < timeInSec do + if date.d >= cal[date.m] then + if date.m == 2 and date.d == 28 then -- HOLY COW we can edit years now. Gotta re-add this! + if date.y % 4 == 0 and date.y % 100 == 0 and date.y % 400 ~= 0 or date.y % 4 > 0 then + date.m = date.m + 1 + date.d = 0 + end + --date.d = 29 + else + date.m = date.m + 1 + date.d = 0 + end + end + if date.m == 13 then + date.m = 1 + date.y = date.y + 1 + end + date.d = date.d + 1 + start = start + 86400 + + end + end + return date + end + + function mist.time.relativeToStart(time) + if type(time) == 'number' then + return time - timer.getTime0() + end + end + + function mist.getDateString(rtnType, murica, oTime) -- returns date based on time + local word = {'January', 'Feburary', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' } -- 'etc + local curTime = 0 + if oTime then + curTime = oTime + else + curTime = mist.utils.round(timer.getAbsTime()) + end + local tbl = mist.time.getDate(curTime) + + if rtnType then + if murica then + return tostring(word[tbl.m] .. ' ' .. tbl.d .. ' ' .. tbl.y) + else + return tostring(tbl.d .. ' ' .. word[tbl.m] .. ' ' .. tbl.y) + end + else + if murica then + return tostring(tbl.m .. '.' .. tbl.d .. '.' .. tbl.y) + else + return tostring(tbl.d .. '.' .. tbl.m .. '.' .. tbl.y) + end + end + end + --WIP + function mist.time.milToGame(milString, rtnType) --converts a military time. By default returns the abosolute time that event would occur. With optional value it returns how many seconds from time of call till that time. + local curTime = mist.utils.round(timer.getAbsTime()) + local milTimeInSec = 0 + + if milString and type(milString) == 'string' and string.len(milString) >= 4 then + local hr = tonumber(string.sub(milString, 1, 2)) + local mi = tonumber(string.sub(milString, 3)) + milTimeInSec = milTimeInSec + (mi*60) + (hr*3600) + elseif milString and type(milString) == 'table' and (milString.d or milString.h or milString.m or milString.s) then + milTimeInSec = mist.time.convertToSec(milString) + end + + local startTime = timer.getTime0() + local daysOffset = 0 + if startTime > 86400 then + daysOffset = mist.utils.round(startTime/86400) + if daysOffset > 0 then + milTimeInSec = milTimeInSec *daysOffset + end + end + + if curTime > milTimeInSec then + milTimeInSec = milTimeInSec + 86400 + end + if rtnType then + milTimeInSec = milTimeInSec - startTime + end + return milTimeInSec + end + + +end + +--- Group task functions. +-- @section tasks +do -- group tasks scope + mist.ground = {} + mist.fixedWing = {} + mist.heli = {} + mist.air = {} + mist.air.fixedWing = {} + mist.air.heli = {} + + --- Tasks group to follow a route. + -- This sets the mission task for the given group. + -- Any wrapped actions inside the path (like enroute + -- tasks) will be executed. + -- @tparam Group group group to task. + -- @tparam table path containing + -- points defining a route. + function mist.goRoute(group, path) + local misTask = { + id = 'Mission', + params = { + route = { + points = mist.utils.deepCopy(path), + }, + }, + } + if type(group) == 'string' then + group = Group.getByName(group) + end + if group then + local groupCon = group:getController() + if groupCon then + groupCon:setTask(misTask) + return true + end + end + return false + end + + -- same as getGroupPoints but returns speed and formation type along with vec2 of point} + function mist.getGroupRoute(groupIdent, task) + -- refactor to search by groupId and allow groupId and groupName as inputs + local gpId = groupIdent + if mist.DBs.MEgroupsByName[groupIdent] then + gpId = mist.DBs.MEgroupsByName[groupIdent].groupId + else + log:error('$1 not found in mist.DBs.MEgroupsByName', groupIdent) + end + + for coa_name, coa_data in pairs(env.mission.coalition) do + if (coa_name == 'red' or coa_name == 'blue') and type(coa_data) == 'table' then + if coa_data.country then --there is a country table + for cntry_id, cntry_data in pairs(coa_data.country) do + for obj_type_name, obj_type_data in pairs(cntry_data) do + if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" then -- only these types have points + if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then --there's a group! + for group_num, group_data in pairs(obj_type_data.group) do + if group_data and group_data.groupId == gpId then -- this is the group we are looking for + if group_data.route and group_data.route.points and #group_data.route.points > 0 then + local points = {} + + for point_num, point in pairs(group_data.route.points) do + local routeData = {} + if not point.point then + routeData.x = point.x + routeData.y = point.y + else + routeData.point = point.point --it's possible that the ME could move to the point = Vec2 notation. + end + routeData.form = point.action + routeData.speed = point.speed + routeData.alt = point.alt + routeData.alt_type = point.alt_type + routeData.airdromeId = point.airdromeId + routeData.helipadId = point.helipadId + routeData.type = point.type + routeData.action = point.action + if task then + routeData.task = point.task + end + points[point_num] = routeData + end + + return points + end + log:error('Group route not defined in mission editor for groupId: $1', gpId) + return + end --if group_data and group_data.name and group_data.name == 'groupname' + end --for group_num, group_data in pairs(obj_type_data.group) do + end --if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then + end --if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" or obj_type_name == "static" then + end --for obj_type_name, obj_type_data in pairs(cntry_data) do + end --for cntry_id, cntry_data in pairs(coa_data.country) do + end --if coa_data.country then --there is a country table + end --if coa_name == 'red' or coa_name == 'blue' and type(coa_data) == 'table' then + end --for coa_name, coa_data in pairs(mission.coalition) do + end + + -- function mist.ground.buildPath() end -- ???? + + function mist.ground.patrolRoute(vars) + log:info('patrol') + local tempRoute = {} + local useRoute = {} + local gpData = vars.gpData + if type(gpData) == 'string' then + gpData = Group.getByName(gpData) + end + + local useGroupRoute + if not vars.useGroupRoute then + useGroupRoute = vars.gpData + else + useGroupRoute = vars.useGroupRoute + end + local routeProvided = false + if not vars.route then + if useGroupRoute then + tempRoute = mist.getGroupRoute(useGroupRoute) + end + else + useRoute = vars.route + local posStart = mist.getLeadPos(gpData) + useRoute[1] = mist.ground.buildWP(posStart, useRoute[1].action, useRoute[1].speed) + routeProvided = true + end + + + local overRideSpeed = vars.speed or 'default' + local pType = vars.pType + local offRoadForm = vars.offRoadForm or 'default' + local onRoadForm = vars.onRoadForm or 'default' + + if routeProvided == false and #tempRoute > 0 then + local posStart = mist.getLeadPos(gpData) + + + useRoute[#useRoute + 1] = mist.ground.buildWP(posStart, offRoadForm, overRideSpeed) + for i = 1, #tempRoute do + local tempForm = tempRoute[i].action + local tempSpeed = tempRoute[i].speed + + if offRoadForm == 'default' then + tempForm = tempRoute[i].action + end + if onRoadForm == 'default' then + onRoadForm = 'On Road' + end + if (string.lower(tempRoute[i].action) == 'on road' or string.lower(tempRoute[i].action) == 'onroad' or string.lower(tempRoute[i].action) == 'on_road') then + tempForm = onRoadForm + else + tempForm = offRoadForm + end + + if type(overRideSpeed) == 'number' then + tempSpeed = overRideSpeed + end + + + useRoute[#useRoute + 1] = mist.ground.buildWP(tempRoute[i], tempForm, tempSpeed) + end + + if pType and string.lower(pType) == 'doubleback' then + local curRoute = mist.utils.deepCopy(useRoute) + for i = #curRoute, 2, -1 do + useRoute[#useRoute + 1] = mist.ground.buildWP(curRoute[i], curRoute[i].action, curRoute[i].speed) + end + end + + useRoute[1].action = useRoute[#useRoute].action -- make it so the first WP matches the last WP + end + + local cTask3 = {} + local newPatrol = {} + newPatrol.route = useRoute + newPatrol.gpData = gpData:getName() + cTask3[#cTask3 + 1] = 'mist.ground.patrolRoute(' + cTask3[#cTask3 + 1] = mist.utils.oneLineSerialize(newPatrol) + cTask3[#cTask3 + 1] = ')' + cTask3 = table.concat(cTask3) + local tempTask = { + id = 'WrappedAction', + params = { + action = { + id = 'Script', + params = { + command = cTask3, + + }, + }, + }, + } + + useRoute[#useRoute].task = tempTask + log:info(useRoute) + mist.goRoute(gpData, useRoute) + + return + end + + function mist.ground.patrol(gpData, pType, form, speed) + local vars = {} + + if type(gpData) == 'table' and gpData:getName() then + gpData = gpData:getName() + end + + vars.useGroupRoute = gpData + vars.gpData = gpData + vars.pType = pType + vars.offRoadForm = form + vars.speed = speed + + mist.ground.patrolRoute(vars) + + return + end + + -- No longer accepts path + function mist.ground.buildWP(point, overRideForm, overRideSpeed) + + local wp = {} + wp.x = point.x + + if point.z then + wp.y = point.z + else + wp.y = point.y + end + local form, speed + + if point.speed and not overRideSpeed then + wp.speed = point.speed + elseif type(overRideSpeed) == 'number' then + wp.speed = overRideSpeed + else + wp.speed = mist.utils.kmphToMps(20) + end + + if point.form and not overRideForm then + form = point.form + else + form = overRideForm + end + + if not form then + wp.action = 'Cone' + else + form = string.lower(form) + if form == 'off_road' or form == 'off road' then + wp.action = 'Off Road' + elseif form == 'on_road' or form == 'on road' then + wp.action = 'On Road' + elseif form == 'rank' or form == 'line_abrest' or form == 'line abrest' or form == 'lineabrest'then + wp.action = 'Rank' + elseif form == 'cone' then + wp.action = 'Cone' + elseif form == 'diamond' then + wp.action = 'Diamond' + elseif form == 'vee' then + wp.action = 'Vee' + elseif form == 'echelon_left' or form == 'echelon left' or form == 'echelonl' then + wp.action = 'EchelonL' + elseif form == 'echelon_right' or form == 'echelon right' or form == 'echelonr' then + wp.action = 'EchelonR' + else + wp.action = 'Cone' -- if nothing matched + end + end + + wp.type = 'Turning Point' + + return wp + + end + + function mist.fixedWing.buildWP(point, WPtype, speed, alt, altType) + + local wp = {} + wp.x = point.x + + if point.z then + wp.y = point.z + else + wp.y = point.y + end + + if alt and type(alt) == 'number' then + wp.alt = alt + else + wp.alt = 2000 + end + + if altType then + altType = string.lower(altType) + if altType == 'radio' or altType == 'agl' then + wp.alt_type = 'RADIO' + elseif altType == 'baro' or altType == 'asl' then + wp.alt_type = 'BARO' + end + else + wp.alt_type = 'RADIO' + end + + if point.speed then + speed = point.speed + end + + if point.type then + WPtype = point.type + end + + if not speed then + wp.speed = mist.utils.kmphToMps(500) + else + wp.speed = speed + end + + if not WPtype then + wp.action = 'Turning Point' + else + WPtype = string.lower(WPtype) + if WPtype == 'flyover' or WPtype == 'fly over' or WPtype == 'fly_over' then + wp.action = 'Fly Over Point' + elseif WPtype == 'turningpoint' or WPtype == 'turning point' or WPtype == 'turning_point' then + wp.action = 'Turning Point' + else + wp.action = 'Turning Point' + end + end + + wp.type = 'Turning Point' + return wp + end + + function mist.heli.buildWP(point, WPtype, speed, alt, altType) + + local wp = {} + wp.x = point.x + + if point.z then + wp.y = point.z + else + wp.y = point.y + end + + if alt and type(alt) == 'number' then + wp.alt = alt + else + wp.alt = 500 + end + + if altType then + altType = string.lower(altType) + if altType == 'radio' or altType == 'agl' then + wp.alt_type = 'RADIO' + elseif altType == 'baro' or altType == 'asl' then + wp.alt_type = 'BARO' + end + else + wp.alt_type = 'RADIO' + end + + if point.speed then + speed = point.speed + end + + if point.type then + WPtype = point.type + end + + if not speed then + wp.speed = mist.utils.kmphToMps(200) + else + wp.speed = speed + end + + if not WPtype then + wp.action = 'Turning Point' + else + WPtype = string.lower(WPtype) + if WPtype == 'flyover' or WPtype == 'fly over' or WPtype == 'fly_over' then + wp.action = 'Fly Over Point' + elseif WPtype == 'turningpoint' or WPtype == 'turning point' or WPtype == 'turning_point' then + wp.action = 'Turning Point' + else + wp.action = 'Turning Point' + end + end + + wp.type = 'Turning Point' + return wp + end + + -- need to return a Vec3 or Vec2? + function mist.getRandPointInCircle(point, radius, innerRadius) + local theta = 2*math.pi*math.random() + local rad = math.random() + math.random() + if rad > 1 then + rad = 2 - rad + end + + local radMult + if innerRadius and innerRadius <= radius then + radMult = (radius - innerRadius)*rad + innerRadius + else + radMult = radius*rad + end + + if not point.z then --might as well work with vec2/3 + point.z = point.y + end + + local rndCoord + if radius > 0 then + rndCoord = {x = math.cos(theta)*radMult + point.x, y = math.sin(theta)*radMult + point.z} + else + rndCoord = {x = point.x, y = point.z} + end + return rndCoord + end + + function mist.getRandomPointInZone(zoneName, innerRadius) + if type(zoneName) == 'string' and type(trigger.misc.getZone(zoneName)) == 'table' then + return mist.getRandPointInCircle(trigger.misc.getZone(zoneName).point, trigger.misc.getZone(zoneName).radius, innerRadius) + end + return false + end + + function mist.groupToRandomPoint(vars) + local group = vars.group --Required + local point = vars.point --required + local radius = vars.radius or 0 + local innerRadius = vars.innerRadius + local form = vars.form or 'Cone' + local heading = vars.heading or math.random()*2*math.pi + local headingDegrees = vars.headingDegrees + local speed = vars.speed or mist.utils.kmphToMps(20) + + + local useRoads + if not vars.disableRoads then + useRoads = true + else + useRoads = false + end + + local path = {} + + if headingDegrees then + heading = headingDegrees*math.pi/180 + end + + if heading >= 2*math.pi then + heading = heading - 2*math.pi + end + + local rndCoord = mist.getRandPointInCircle(point, radius, innerRadius) + + local offset = {} + local posStart = mist.getLeadPos(group) + + offset.x = mist.utils.round(math.sin(heading - (math.pi/2)) * 50 + rndCoord.x, 3) + offset.z = mist.utils.round(math.cos(heading + (math.pi/2)) * 50 + rndCoord.y, 3) + path[#path + 1] = mist.ground.buildWP(posStart, form, speed) + + + if useRoads == true and ((point.x - posStart.x)^2 + (point.z - posStart.z)^2)^0.5 > radius * 1.3 then + path[#path + 1] = mist.ground.buildWP({x = posStart.x + 11, z = posStart.z + 11}, 'off_road', speed) + path[#path + 1] = mist.ground.buildWP(posStart, 'on_road', speed) + path[#path + 1] = mist.ground.buildWP(offset, 'on_road', speed) + else + path[#path + 1] = mist.ground.buildWP({x = posStart.x + 25, z = posStart.z + 25}, form, speed) + end + + path[#path + 1] = mist.ground.buildWP(offset, form, speed) + path[#path + 1] = mist.ground.buildWP(rndCoord, form, speed) + + mist.goRoute(group, path) + + return + end + + function mist.groupRandomDistSelf(gpData, dist, form, heading, speed) + local pos = mist.getLeadPos(gpData) + local fakeZone = {} + fakeZone.radius = dist or math.random(300, 1000) + fakeZone.point = {x = pos.x, y = pos.y, z = pos.z} + mist.groupToRandomZone(gpData, fakeZone, form, heading, speed) + + return + end + + function mist.groupToRandomZone(gpData, zone, form, heading, speed) + if type(gpData) == 'string' then + gpData = Group.getByName(gpData) + end + + if type(zone) == 'string' then + zone = trigger.misc.getZone(zone) + elseif type(zone) == 'table' and not zone.radius then + zone = trigger.misc.getZone(zone[math.random(1, #zone)]) + end + + if speed then + speed = mist.utils.kmphToMps(speed) + end + + local vars = {} + vars.group = gpData + vars.radius = zone.radius + vars.form = form + vars.headingDegrees = heading + vars.speed = speed + vars.point = mist.utils.zoneToVec3(zone) + + mist.groupToRandomPoint(vars) + + return + end + + function mist.isTerrainValid(coord, terrainTypes) -- vec2/3 and enum or table of acceptable terrain types + if coord.z then + coord.y = coord.z + end + local typeConverted = {} + + if type(terrainTypes) == 'string' then -- if its a string it does this check + for constId, constData in pairs(land.SurfaceType) do + if string.lower(constId) == string.lower(terrainTypes) or string.lower(constData) == string.lower(terrainTypes) then + table.insert(typeConverted, constId) + end + end + elseif type(terrainTypes) == 'table' then -- if its a table it does this check + for typeId, typeData in pairs(terrainTypes) do + for constId, constData in pairs(land.SurfaceType) do + if string.lower(constId) == string.lower(typeData) or string.lower(constData) == string.lower(typeId) then + table.insert(typeConverted, constId) + end + end + end + end + for validIndex, validData in pairs(typeConverted) do + if land.getSurfaceType(coord) == land.SurfaceType[validData] then + return true + end + end + return false + end + + function mist.terrainHeightDiff(coord, searchSize) + local samples = {} + local searchRadius = 5 + if searchSize then + searchRadius = searchSize + end + if type(coord) == 'string' then + coord = mist.utils.zoneToVec3(coord) + end + + coord = mist.utils.makeVec2(coord) + + samples[#samples + 1] = land.getHeight(coord) + for i = 0, 360, 30 do + samples[#samples + 1] = land.getHeight({x = (coord.x + (math.sin(math.rad(i))*searchRadius)), y = (coord.y + (math.cos(math.rad(i))*searchRadius))}) + if searchRadius >= 20 then -- if search radius is sorta large, take a sample halfway between center and outer edge + samples[#samples + 1] = land.getHeight({x = (coord.x + (math.sin(math.rad(i))*(searchRadius/2))), y = (coord.y + (math.cos(math.rad(i))*(searchRadius/2)))}) + end + end + local tMax, tMin = 0, 1000000 + for index, height in pairs(samples) do + if height > tMax then + tMax = height + end + if height < tMin then + tMin = height + end + end + return mist.utils.round(tMax - tMin, 2) + end + + function mist.groupToPoint(gpData, point, form, heading, speed, useRoads) + if type(point) == 'string' then + point = trigger.misc.getZone(point) + end + if speed then + speed = mist.utils.kmphToMps(speed) + end + + local vars = {} + vars.group = gpData + vars.form = form + vars.headingDegrees = heading + vars.speed = speed + vars.disableRoads = useRoads + vars.point = mist.utils.zoneToVec3(point) + mist.groupToRandomPoint(vars) + + return + end + + function mist.getLeadPos(group) + if type(group) == 'string' then -- group name + group = Group.getByName(group) + end + + local units = group:getUnits() + + local leader = units[1] + if not Unit.isExist(leader) then -- SHOULD be good, but if there is a bug, this code future-proofs it then. + local lowestInd = math.huge + for ind, unit in pairs(units) do + if Unit.isExist(unit) and ind < lowestInd then + lowestInd = ind + return unit:getPosition().p + end + end + end + if leader and Unit.isExist(leader) then -- maybe a little too paranoid now... + return leader:getPosition().p + end + end + +end + +--- Database tables. +-- @section mist.DBs + +--- Mission data +-- @table mist.DBs.missionData +-- @field startTime mission start time +-- @field theatre mission theatre/map e.g. Caucasus +-- @field version mission version +-- @field files mission resources + +--- Tables used as parameters. +-- @section varTables + +--- mist.flagFunc.units_in_polygon parameter table. +-- @table unitsInPolygonVars +-- @tfield table unit name table @{UnitNameTable}. +-- @tfield table zone table defining a polygon. +-- @tfield number|string flag flag to set to true. +-- @tfield[opt] number|string stopflag if set to true the function +-- will stop evaluating. +-- @tfield[opt] number maxalt maximum altitude (MSL) for the +-- polygon. +-- @tfield[opt] number req_num minimum number of units that have +-- to be in the polygon. +-- @tfield[opt] number interval sets the interval for +-- checking if units are inside of the polygon in seconds. Default: 1. +-- @tfield[opt] boolean toggle switch the flag to false if required +-- conditions are not met. Default: false. +-- @tfield[opt] table unitTableDef + +--- Logger class. +-- @type mist.Logger +do -- mist.Logger scope + mist.Logger = {} + + --- parses text and substitutes keywords with values from given array. + -- @param text string containing keywords to substitute with values + -- or a variable. + -- @param ... variables to use for substitution in string. + -- @treturn string new string with keywords substituted or + -- value of variable as string. + local function formatText(text, ...) + if type(text) ~= 'string' then + if type(text) == 'table' then + text = mist.utils.oneLineSerialize(text) + else + text = tostring(text) + end + else + for index,value in ipairs(arg) do + -- TODO: check for getmetatabel(value).__tostring + if type(value) == 'table' then + value = mist.utils.oneLineSerialize(value) + else + value = tostring(value) + end + text = text:gsub('$' .. index, value) + end + end + local fName = nil + local cLine = nil + if debug then + local dInfo = debug.getinfo(3) + fName = dInfo.name + cLine = dInfo.currentline + -- local fsrc = dinfo.short_src + --local fLine = dInfo.linedefined + end + if fName and cLine then + return fName .. '|' .. cLine .. ': ' .. text + elseif cLine then + return cLine .. ': ' .. text + else + return ' ' .. text + end + end + + local function splitText(text) + local tbl = {} + while text:len() > 4000 do + local sub = text:sub(1, 4000) + text = text:sub(4001) + table.insert(tbl, sub) + end + table.insert(tbl, text) + return tbl + end + + --- Creates a new logger. + -- Each logger has it's own tag and log level. + -- @tparam string tag tag which appears at the start of + -- every log line produced by this logger. + -- @tparam[opt] number|string level the log level defines which messages + -- will be logged and which will be omitted. Log level 3 beeing the most verbose + -- and 0 disabling all output. This can also be a string. Allowed strings are: + -- "none" (0), "error" (1), "warning" (2) and "info" (3). + -- @usage myLogger = mist.Logger:new("MyScript") + -- @usage myLogger = mist.Logger:new("MyScript", 2) + -- @usage myLogger = mist.Logger:new("MyScript", "info") + -- @treturn mist.Logger + function mist.Logger:new(tag, level) + local l = {} + l.tag = tag + setmetatable(l, self) + self.__index = self + self:setLevel(level) + return l + end + + --- Sets the level of verbosity for this logger. + -- @tparam[opt] number|string level the log level defines which messages + -- will be logged and which will be omitted. Log level 3 beeing the most verbose + -- and 0 disabling all output. This can also be a string. Allowed strings are: + -- "none" (0), "error" (1), "warning" (2) and "info" (3). + -- @usage myLogger:setLevel("info") + -- @usage -- log everything + --myLogger:setLevel(3) + function mist.Logger:setLevel(level) + if not level then + self.level = 2 + else + if type(level) == 'string' then + if level == 'none' or level == 'off' then + self.level = 0 + elseif level == 'error' then + self.level = 1 + elseif level == 'warning' or level == 'warn' then + self.level = 2 + elseif level == 'info' then + self.level = 3 + end + elseif type(level) == 'number' then + self.level = level + else + self.level = 2 + end + end + end + + --- Logs error and shows alert window. + -- This logs an error to the dcs.log and shows a popup window, + -- pausing the simulation. This works always even if logging is + -- disabled by setting a log level of "none" or 0. + -- @tparam string text the text with keywords to substitute. + -- @param ... variables to be used for substitution. + -- @usage myLogger:alert("Shit just hit the fan! WEEEE!!!11") + function mist.Logger:alert(text, ...) + text = formatText(text, unpack(arg)) + if text:len() > 4000 then + local texts = splitText(text) + for i = 1, #texts do + if i == 1 then + env.error(self.tag .. '|' .. texts[i], true) + else + env.error(texts[i]) + end + end + else + env.error(self.tag .. '|' .. text, true) + end + end + + --- Logs a message, disregarding the log level. + -- @tparam string text the text with keywords to substitute. + -- @param ... variables to be used for substitution. + -- @usage myLogger:msg("Always logged!") + function mist.Logger:msg(text, ...) + text = formatText(text, unpack(arg)) + if text:len() > 4000 then + local texts = splitText(text) + for i = 1, #texts do + if i == 1 then + env.info(self.tag .. '|' .. texts[i]) + else + env.info(texts[i]) + end + end + else + env.info(self.tag .. '|' .. text) + end + end + + --- Logs an error. + -- logs a message prefixed with this loggers tag to dcs.log as + -- long as at least the "error" log level (1) is set. + -- @tparam string text the text with keywords to substitute. + -- @param ... variables to be used for substitution. + -- @usage myLogger:error("Just an error!") + -- @usage myLogger:error("Foo is $1 instead of $2", foo, "bar") + function mist.Logger:error(text, ...) + if self.level >= 1 then + text = formatText(text, unpack(arg)) + if text:len() > 4000 then + local texts = splitText(text) + for i = 1, #texts do + if i == 1 then + env.error(self.tag .. '|' .. texts[i]) + else + env.error(texts[i]) + end + end + else + env.error(self.tag .. '|' .. text) + end + end + end + + --- Logs a warning. + -- logs a message prefixed with this loggers tag to dcs.log as + -- long as at least the "warning" log level (2) is set. + -- @tparam string text the text with keywords to substitute. + -- @param ... variables to be used for substitution. + -- @usage myLogger:warn("Mother warned you! Those $1 from the interwebs are $2", {"geeks", 1337}) + function mist.Logger:warn(text, ...) + if self.level >= 2 then + text = formatText(text, unpack(arg)) + if text:len() > 4000 then + local texts = splitText(text) + for i = 1, #texts do + if i == 1 then + env.warning(self.tag .. '|' .. texts[i]) + else + env.warning(texts[i]) + end + end + else + env.warning(self.tag .. '|' .. text) + end + end + end + + --- Logs a info. + -- logs a message prefixed with this loggers tag to dcs.log as + -- long as the highest log level (3) "info" is set. + -- @tparam string text the text with keywords to substitute. + -- @param ... variables to be used for substitution. + -- @see warn + function mist.Logger:info(text, ...) + if self.level >= 3 then + text = formatText(text, unpack(arg)) + if text:len() > 4000 then + local texts = splitText(text) + for i = 1, #texts do + if i == 1 then + env.info(self.tag .. '|' .. texts[i]) + else + env.info(texts[i]) + end + end + else + env.info(self.tag .. '|' .. text) + end + end + end + +end + +-- initialize mist +mist.init() +env.info(('Mist version ' .. mist.majorVersion .. '.' .. mist.minorVersion .. '.' .. mist.build .. ' loaded.')) + +-- vim: noet:ts=2:sw=2 diff --git a/resources/plugins/skynetiads/plugin.json b/resources/plugins/skynetiads/plugin.json new file mode 100644 index 00000000..bcfe83c5 --- /dev/null +++ b/resources/plugins/skynetiads/plugin.json @@ -0,0 +1,63 @@ +{ + "mnemonic": "skynetiads", + "nameInUI": "Skynet IADS", + "defaultValue": false, + "specificOptions": [ + { + "nameInUI": "create IADS for RED coalition", + "mnemonic": "createRedIADS", + "defaultValue": true + }, + { + "nameInUI": "create IADS for BLUE coalition", + "mnemonic": "createBlueIADS", + "defaultValue": true + }, + { + "nameInUI": "Long-range SAM act as EWR for RED coalition", + "mnemonic": "actAsEwrRED", + "defaultValue": true + }, + { + "nameInUI": "Long-range SAM act as EWR for BLUE coalition", + "mnemonic": "actAsEwrBLUE", + "defaultValue": true + }, + { + "nameInUI": "Include RED IADS in radio menu", + "mnemonic": "includeRedInRadio", + "defaultValue": true + }, + { + "nameInUI": "Include BLUE IADS in radio menu", + "mnemonic": "includeBlueInRadio", + "defaultValue": true + }, + { + "nameInUI": "Generate debug information for RED IADS", + "mnemonic": "debugRED", + "defaultValue": false + }, + { + "nameInUI": "Generate debug information for BLUE IADS", + "mnemonic": "debugBLUE", + "defaultValue": false + } + ], + "scriptsWorkOrders": [ + { + "file": "mist_4_3_74.lua", + "mnemonic": "mist" + }, + { + "file": "skynet-iads-compiled.lua", + "mnemonic": "skynetiads-script" + } + ], + "configurationWorkOrders": [ + { + "file": "skynetiads-config.lua", + "mnemonic": "skynetiads-config" + } + ] +} \ No newline at end of file diff --git a/resources/plugins/skynetiads/skynet-iads-compiled.lua b/resources/plugins/skynetiads/skynet-iads-compiled.lua new file mode 100644 index 00000000..1187e93a --- /dev/null +++ b/resources/plugins/skynetiads/skynet-iads-compiled.lua @@ -0,0 +1,2963 @@ +env.info("--- SKYNET VERSION: 1.1.3 | BUILD TIME: 30.09.2020 1816Z ---") +do +--this file contains the required units per sam type +samTypesDB = { + ['S-300'] = { + ['type'] = 'complex', + ['searchRadar'] = { + ['S-300PS 40B6MD sr'] = { + }, + ['S-300PS 64H6E sr'] = { + }, + }, + ['trackingRadar'] = { + ['S-300PS 40B6M tr'] = { + }, + }, + ['launchers'] = { + ['S-300PS 5P85D ln'] = { + }, + ['S-300PS 5P85C ln'] = { + }, + }, + ['misc'] = { + ['S-300PS 54K6 cp'] = { + ['required'] = true, + }, + }, + ['name'] = { + ['NATO'] = 'SA-10 Grumble', + }, + ['harm_detection_chance'] = 90 + }, + ['Buk'] = { + ['type'] = 'complex', + ['searchRadar'] = { + ['SA-11 Buk SR 9S18M1'] = { + }, + }, + ['launchers'] = { + ['SA-11 Buk LN 9A310M1'] = { + }, + }, + ['misc'] = { + ['SA-11 Buk CC 9S470M1'] = { + ['required'] = true, + }, + }, + ['name'] = { + ['NATO'] = 'SA-11 Gadfly', + }, + ['harm_detection_chance'] = 70 + }, + ['s-125'] = { + ['type'] = 'complex', + ['searchRadar'] = { + ['p-19 s-125 sr'] = { + }, + }, + ['trackingRadar'] = { + ['snr s-125 tr'] = { + }, + }, + ['launchers'] = { + ['5p73 s-125 ln'] = { + }, + }, + ['name'] = { + ['NATO'] = 'SA-3 Goa', + }, + ['harm_detection_chance'] = 40 + }, + ['s-75'] = { + ['type'] = 'complex', + ['searchRadar'] = { + ['p-19 s-125 sr'] = { + }, + }, + ['trackingRadar'] = { + ['SNR_75V'] = { + }, + }, + ['launchers'] = { + ['S_75M_Volhov'] = { + }, + }, + ['name'] = { + ['NATO'] = 'SA-2 Guideline', + }, + ['harm_detection_chance'] = 30 + }, + ['Kub'] = { + ['type'] = 'complex', + ['searchRadar'] = { + ['Kub 1S91 str'] = { + }, + }, + ['launchers'] = { + ['Kub 2P25 ln'] = { + }, + }, + ['name'] = { + ['NATO'] = 'SA-6 Gainful', + }, + ['harm_detection_chance'] = 40 + }, + ['Patriot'] = { + ['type'] = 'complex', + ['searchRadar'] = { + ['Patriot str'] = { + }, + }, + + ['launchers'] = { + ['Patriot ln'] = { + }, + }, + ['misc'] = { + ['Patriot cp'] = { + ['required'] = false, + }, + ['Patriot EPP'] = { + ['required'] = false, + }, + ['Patriot ECS'] = { + ['required'] = true, + }, + ['Patriot AMG'] = { + ['required'] = false, + }, + }, + + + ['name'] = { + ['NATO'] = 'Patriot', + }, + ['harm_detection_chance'] = 90 + }, + ['Hawk'] = { + ['type'] = 'complex', + ['searchRadar'] = { + ['Hawk sr'] = { + }, + }, + ['trackingRadar'] = { + ['Hawk tr'] = { + }, + }, + ['launchers'] = { + ['Hawk ln'] = { + }, + }, + + ['name'] = { + ['NATO'] = 'Hawk', + }, + ['harm_detection_chance'] = 40 + + }, + ['Roland ADS'] = { + ['type'] = 'single', + ['searchRadar'] = { + ['Roland ADS'] = { + }, + }, + ['launchers'] = { + ['Roland ADS'] = { + }, + }, + + ['name'] = { + ['NATO'] = 'Roland ADS', + }, + ['harm_detection_chance'] = 60 + }, + ['2S6 Tunguska'] = { + ['type'] = 'single', + ['searchRadar'] = { + ['2S6 Tunguska'] = { + }, + }, + ['launchers'] = { + ['2S6 Tunguska'] = { + }, + }, + ['name'] = { + ['NATO'] = 'SA-19 Grison', + }, + }, + ['Osa'] = { + ['type'] = 'single', + ['searchRadar'] = { + ['Osa 9A33 ln'] = { + }, + }, + ['launchers'] = { + ['Osa 9A33 ln'] = { + + }, + }, + ['name'] = { + ['NATO'] = 'SA-8 Gecko', + }, + ['harm_detection_chance'] = 20 + }, + ['Strela-10M3'] = { + ['type'] = 'single', + ['searchRadar'] = { + ['Strela-10M3'] = { + ['trackingRadar'] = true, + }, + }, + ['launchers'] = { + ['Strela-10M3'] = { + }, + }, + ['name'] = { + ['NATO'] = 'SA-13 Gopher', + }, + }, + ['Strela-1 9P31'] = { + ['type'] = 'single', + ['searchRadar'] = { + ['Strela-1 9P31'] = { + }, + }, + ['launchers'] = { + ['Strela-1 9P31'] = { + }, + }, + ['name'] = { + ['NATO'] = 'SA-9 Gaskin', + }, + ['harm_detection_chance'] = 20 + }, + ['Tor'] = { + ['type'] = 'single', + ['searchRadar'] = { + ['Tor 9A331'] = { + }, + }, + ['launchers'] = { + ['Tor 9A331'] = { + }, + }, + ['name'] = { + ['NATO'] = 'SA-15 Gauntlet', + }, + }, + ['Gepard'] = { + ['type'] = 'single', + ['searchRadar'] = { + ['Gepard'] = { + }, + }, + ['launchers'] = { + ['Gepard'] = { + }, + }, + ['name'] = { + ['NATO'] = 'Gepard', + }, + ['harm_detection_chance'] = 10 + }, + ['Rapier'] = { + ['searchRadar'] = { + ['rapier_fsa_blindfire_radar'] = { + }, + }, + ['launchers'] = { + ['rapier_fsa_launcher'] = { + ['trackingRadar'] = true, + }, + }, + ['misc'] = { + ['rapier_fsa_optical_tracker_unit'] = { + ['required'] = true, + }, + }, + ['name'] = { + ['NATO'] = 'Rapier', + }, + ['harm_detection_chance'] = 10 + }, + ['ZSU-23-4 Shilka'] = { + ['type'] = 'single', + ['searchRadar'] = { + ['ZSU-23-4 Shilka'] = { + }, + }, + ['launchers'] = { + ['ZSU-23-4 Shilka'] = { + }, + }, + ['name'] = { + ['NATO'] = 'Zues', + }, + ['harm_detection_chance'] = 10 + }, + ['HQ-7'] = { + ['searchRadar'] = { + ['HQ-7_STR_SP'] = { + }, + }, + ['launchers'] = { + ['HQ-7_LN_SP'] = { + }, + }, + ['name'] = { + ['NATO'] = 'CSA-4', + }, + ['harm_detection_chance'] = 30 + }, +--- Start of EW radars: + ['1L13 EWR'] = { + ['type'] = 'ewr', + ['searchRadar'] = { + ['1L13 EWR'] = { + }, + }, + ['name'] = { + ['NATO'] = '1L13 EWR', + }, + ['harm_detection_chance'] = 60 + }, + ['55G6 EWR'] = { + ['type'] = 'ewr', + ['searchRadar'] = { + ['55G6 EWR'] = { + }, + }, + ['name'] = { + ['NATO'] = '55G6 EWR', + }, + ['harm_detection_chance'] = 60 + }, + ['Dog Ear'] = { + ['type'] = 'ewr', + ['searchRadar'] = { + ['Dog Ear radar'] = { + }, + }, + ['name'] = { + ['NATO'] = 'Dog Ear', + }, + ['harm_detection_chance'] = 20 + }, + ['Roland Radar'] = { + ['type'] = 'ewr', + ['searchRadar'] = { + ['Roland Radar'] = { + }, + }, + ['name'] = { + ['NATO'] = 'Roland EWR', + }, + ['harm_detection_chance'] = 60 + }, + ['p-19 s-125 sr'] = { + ['searchRadar'] = { + ['p-19 s-125 sr'] = { + }, + }, + ['name'] = { + ['NATO'] = 'Flat Face', + }, + ['harm_detection_chance'] = 40 + }, + ['Patriot str'] = { + ['searchRadar'] = { + ['Patriot str'] = { + }, + }, + ['name'] = { + ['NATO'] = 'Patriot str', + }, + ['harm_detection_chance'] = 80 + }, + ['EW S-300'] = { + ['searchRadar'] = { + ['S-300PS 40B6MD sr'] = { + }, + ['S-300PS 64H6E sr'] = { + }, + }, + ['name'] = { + ['NATO'] = 'Big Bird', + }, + ['harm_detection_chance'] = 90 + }, + ['SA-11 Buk SR 9S18M1'] = { + ['searchRadar'] = { + ['SA-11 Buk SR 9S18M1'] = { + }, + }, + ['name'] = { + ['NATO'] = 'Snow Drift', + }, + ['harm_detection_chance'] = 70 + }, + ['Kub 1S91 str'] = { + ['searchRadar'] = { + ['Kub 1S91 str'] = { + }, + }, + ['name'] = { + ['NATO'] = 'Straight Flush', + }, + ['harm_detection_chance'] = 40 + }, + ['Hawk str'] = { + ['searchRadar'] = { + ['Hawk sr'] = { + }, + }, + ['name'] = { + ['NATO'] = 'Hawk str', + }, + ['harm_detection_chance'] = 40 + }, +} +end +do + +SkynetIADS = {} +SkynetIADS.__index = SkynetIADS + +SkynetIADS.database = samTypesDB + +function SkynetIADS:create(name) + local iads = {} + setmetatable(iads, SkynetIADS) + iads.radioMenu = nil + iads.earlyWarningRadars = {} + iads.samSites = {} + iads.commandCenters = {} + iads.ewRadarScanMistTaskID = nil + iads.samSetupMistTaskID = nil + iads.coalition = nil + iads.contacts = {} + iads.maxTargetAge = 32 + iads.name = name + if iads.name == nil then + iads.name = "" + end + iads.contactUpdateInterval = 5 + iads.samSetupTime = 60 + iads.destroyedUnitResponsibleForUpdateAutonomousStateOfSAMSite = nil + iads.debugOutput = {} + iads.debugOutput.IADSStatus = false + iads.debugOutput.samWentDark = false + iads.debugOutput.contacts = false + iads.debugOutput.radarWentLive = false + iads.debugOutput.ewRadarNoConnection = false + iads.debugOutput.samNoConnection = false + iads.debugOutput.jammerProbability = false + iads.debugOutput.addedEWRadar = false + iads.debugOutput.hasNoPower = false + iads.debugOutput.addedSAMSite = false + iads.debugOutput.warnings = true + iads.debugOutput.harmDefence = false + iads.debugOutput.samSiteStatusEnvOutput = false + iads.debugOutput.earlyWarningRadarStatusEnvOutput = false + return iads +end + +function SkynetIADS:setUpdateInterval(interval) + self.contactUpdateInterval = interval +end + +function SkynetIADS:setCoalition(item) + if item then + local coalitionID = item:getCoalition() + if self.coalitionID == nil then + self.coalitionID = coalitionID + end + if self.coalitionID ~= coalitionID then + self:printOutput("element: "..item:getName().." has a different coalition than the IADS", true) + end + end +end + +function SkynetIADS:addJammer(jammer) + table.insert(self.jammers, jammer) +end + +function SkynetIADS:getCoalition() + return self.coalitionID +end + +function SkynetIADS:getDestroyedEarlyWarningRadars() + local destroyedSites = {} + for i = 1, #self.earlyWarningRadars do + local ewSite = self.earlyWarningRadars[i] + if ewSite:isDestroyed() then + table.insert(destroyedSites, ewSite) + end + end + return destroyedSites +end + +function SkynetIADS:getUsableAbstractRadarElemtentsOfTable(abstractRadarTable) + local usable = {} + for i = 1, #abstractRadarTable do + local abstractRadarElement = abstractRadarTable[i] + if abstractRadarElement:hasActiveConnectionNode() and abstractRadarElement:hasWorkingPowerSource() and abstractRadarElement:isDestroyed() == false then + table.insert(usable, abstractRadarElement) + end + end + return usable +end + +function SkynetIADS:getUsableEarlyWarningRadars() + return self:getUsableAbstractRadarElemtentsOfTable(self.earlyWarningRadars) +end + +function SkynetIADS:createTableDelegator(units) + local sites = SkynetIADSTableDelegator:create() + for i = 1, #units do + local site = units[i] + table.insert(sites, site) + end + return sites +end + +function SkynetIADS:addEarlyWarningRadarsByPrefix(prefix) + self:deactivateEarlyWarningRadars() + self.earlyWarningRadars = {} + for unitName, unit in pairs(mist.DBs.unitsByName) do + local pos = self:findSubString(unitName, prefix) + --somehow the MIST unit db contains StaticObject, we check to see we only add Units + local unit = Unit.getByName(unitName) + if pos and pos == 1 and unit then + self:addEarlyWarningRadar(unitName) + end + end + return self:createTableDelegator(self.earlyWarningRadars) +end + +function SkynetIADS:addEarlyWarningRadar(earlyWarningRadarUnitName) + local earlyWarningRadarUnit = Unit.getByName(earlyWarningRadarUnitName) + if earlyWarningRadarUnit == nil then + self:printOutput("you have added an EW Radar that does not exist, check name of Unit in Setup and Mission editor: "..earlyWarningRadarUnitName, true) + return + end + self:setCoalition(earlyWarningRadarUnit) + local ewRadar = nil + local category = earlyWarningRadarUnit:getDesc().category + if category == Unit.Category.AIRPLANE or category == Unit.Category.SHIP then + ewRadar = SkynetIADSAWACSRadar:create(earlyWarningRadarUnit, self) + else + ewRadar = SkynetIADSEWRadar:create(earlyWarningRadarUnit, self) + end + ewRadar:setupElements() + ewRadar:setCachedTargetsMaxAge(self:getCachedTargetsMaxAge()) + -- for performance improvement, if iads is not scanning no update coverage update needs to be done, will be executed once when iads activates + if self.ewRadarScanMistTaskID ~= nil then + self:updateIADSCoverage() + end + ewRadar:goLive() + table.insert(self.earlyWarningRadars, ewRadar) + if self:getDebugSettings().addedEWRadar then + self:printOutput(ewRadar:getDescription().." added to IADS") + end + return ewRadar +end + +function SkynetIADS:getCachedTargetsMaxAge() + return self.contactUpdateInterval +end + +function SkynetIADS:getEarlyWarningRadars() + return self:createTableDelegator(self.earlyWarningRadars) +end + +function SkynetIADS:getEarlyWarningRadarByUnitName(unitName) + for i = 1, #self.earlyWarningRadars do + local ewRadar = self.earlyWarningRadars[i] + if ewRadar:getDCSName() == unitName then + return ewRadar + end + end +end + +function SkynetIADS:findSubString(haystack, needle) + return string.find(haystack, needle, 1, true) +end + +function SkynetIADS:addSAMSitesByPrefix(prefix) + self:deativateSAMSites() + self.samSites = {} + for groupName, groupData in pairs(mist.DBs.groupsByName) do + local pos = self:findSubString(groupName, prefix) + if pos and pos == 1 then + --mist returns groups, units and, StaticObjects + local dcsObject = Group.getByName(groupName) + if dcsObject then + self:addSAMSite(groupName) + end + end + end + return self:createTableDelegator(self.samSites) +end + +function SkynetIADS:getSAMSitesByPrefix(prefix) + local returnSams = {} + for i = 1, #self.samSites do + local samSite = self.samSites[i] + local groupName = samSite:getDCSName() + local pos = self:findSubString(groupName, prefix) + if pos and pos == 1 then + table.insert(returnSams, samSite) + end + end + return self:createTableDelegator(returnSams) +end + +function SkynetIADS:addSAMSite(samSiteName) + local samSiteDCS = Group.getByName(samSiteName) + if samSiteDCS == nil then + self:printOutput("you have added an SAM Site that does not exist, check name of Group in Setup and Mission editor: "..tostring(samSiteName), true) + return + end + self:setCoalition(samSiteDCS) + local samSite = SkynetIADSSamSite:create(samSiteDCS, self) + samSite:setupElements() + -- for performance improvement, if iads is not scanning no update coverage update needs to be done, will be executed once when iads activates + if self.ewRadarScanMistTaskID ~= nil then + self:updateIADSCoverage() + end + samSite:setCachedTargetsMaxAge(self:getCachedTargetsMaxAge()) + samSite:goLive() + if samSite:getNatoName() == "UNKNOWN" then + self:printOutput("you have added an SAM Site that Skynet IADS can not handle: "..samSite:getDCSName(), true) + samSite:cleanUp() + else + samSite:goDark() + table.insert(self.samSites, samSite) + if self:getDebugSettings().addedSAMSite then + self:printOutput(samSite:getDescription().." added to IADS") + end + return samSite + end +end + +function SkynetIADS:getUsableSAMSites() + return self:getUsableAbstractRadarElemtentsOfTable(self.samSites) +end + +function SkynetIADS:getDestroyedSAMSites() + local destroyedSites = {} + for i = 1, #self.samSites do + local samSite = self.samSites[i] + if samSite:isDestroyed() then + table.insert(destroyedSites, samSite) + end + end + return destroyedSites +end + +function SkynetIADS:getSAMSites() + return self:createTableDelegator(self.samSites) +end + +function SkynetIADS:getActiveSAMSites() + local activeSAMSites = {} + for i = 1, #self.samSites do + if self.samSites[i]:isActive() then + table.insert(activeSAMSites, self.samSites[i]) + end + end + return activeSAMSites +end + +function SkynetIADS:getSAMSiteByGroupName(groupName) + for i = 1, #self.samSites do + local samSite = self.samSites[i] + if samSite:getDCSName() == groupName then + return samSite + end + end +end + +function SkynetIADS:getSAMSitesByNatoName(natoName) + local selectedSAMSites = SkynetIADSTableDelegator:create() + for i = 1, #self.samSites do + local samSite = self.samSites[i] + if samSite:getNatoName() == natoName then + table.insert(selectedSAMSites, samSite) + end + end + return selectedSAMSites +end + +function SkynetIADS:addCommandCenter(commandCenter) + self:setCoalition(commandCenter) + local comCenter = SkynetIADSCommandCenter:create(commandCenter, self) + table.insert(self.commandCenters, comCenter) + return comCenter +end + +function SkynetIADS:isCommandCenterUsable() + local hasWorkingCommandCenter = (#self.commandCenters == 0) + for i = 1, #self.commandCenters do + local comCenter = self.commandCenters[i] + if comCenter:isDestroyed() == false and comCenter:hasWorkingPowerSource() then + hasWorkingCommandCenter = true + break + else + hasWorkingCommandCenter = false + end + end + return hasWorkingCommandCenter +end + +function SkynetIADS:getCommandCenters() + return self.commandCenters +end + +function SkynetIADS:setSAMSitesToAutonomousMode() + for i= 1, #self.samSites do + samSite = self.samSites[i] + samSite:goAutonomous() + end +end + +function SkynetIADS.evaluateContacts(self) + if self:isCommandCenterUsable() == false then + if self:getDebugSettings().noWorkingCommmandCenter then + self:printOutput("No Working Command Center") + end + self:setSAMSitesToAutonomousMode() + return + end + + local ewRadars = self:getUsableEarlyWarningRadars() + local samSites = self:getUsableSAMSites() + + -- rewrote this part of the code to keep loops to a minimum + + --will add SAM Sites acting as EW Rardars to the ewRadars array: + for i = 1, #samSites do + local samSite = samSites[i] + --We inform SAM sites that a target update is about to happen. If they have no targets in range after the cycle they go dark + samSite:targetCycleUpdateStart() + if samSite:getActAsEW() then + table.insert(ewRadars, samSite) + end + --if the sam site is not in ew mode and active we grab the detected targets right here + if samSite:isActive() and samSite:getActAsEW() == false then + local contacts = samSite:getDetectedTargets() + for j = 1, #contacts do + local contact = contacts[j] + self:mergeContact(contact) + end + end + end + + local samSitesToTrigger = {} + + for i = 1, #ewRadars do + local ewRadar = ewRadars[i] + --call go live in case ewRadar had to shut down (HARM attack) + ewRadar:goLive() + -- if an awacs has traveled more than a predeterminded distance we update the autonomous state of the sams + if getmetatable(ewRadar) == SkynetIADSAWACSRadar and ewRadar:isUpdateOfAutonomousStateOfSAMSitesRequired() then + self:updateAutonomousStatesOfSAMSites() + end + local ewContacts = ewRadar:getDetectedTargets() + if #ewContacts > 0 then + local samSitesUnderCoverage = ewRadar:getSAMSitesInCoveredArea() + for j = 1, #samSitesUnderCoverage do + local samSiteUnterCoverage = samSitesUnderCoverage[j] + -- only if a SAM site is not active we add it to the hash of SAM sites to be iterated later on + if samSiteUnterCoverage:isActive() == false then + --we add them to a hash to make sure each SAM site is in the collection only once, reducing the number of loops we conduct later on + samSitesToTrigger[samSiteUnterCoverage:getDCSName()] = samSiteUnterCoverage + end + end + for j = 1, #ewContacts do + local contact = ewContacts[j] + self:mergeContact(contact) + end + end + end + + self:cleanAgedTargets() + + for samName, samToTrigger in pairs(samSitesToTrigger) do + for j = 1, #self.contacts do + local contact = self.contacts[j] + -- the DCS Radar only returns enemy aircraft, if that should change a coalition check will be required + -- currently every type of object in the air is handed of to the SAM site, including missiles + local description = contact:getDesc() + local category = description.category + if category and category ~= Unit.Category.GROUND_UNIT and category ~= Unit.Category.SHIP and category ~= Unit.Category.STRUCTURE then + samToTrigger:informOfContact(contact) + end + end + end + + for i = 1, #samSites do + local samSite = samSites[i] + samSite:targetCycleUpdateEnd() + end + + self:printSystemStatus() +end + +function SkynetIADS:cleanAgedTargets() + local contactsToKeep = {} + for i = 1, #self.contacts do + local contact = self.contacts[i] + if contact:getAge() < self.maxTargetAge then + table.insert(contactsToKeep, contact) + end + end + self.contacts = contactsToKeep +end + +function SkynetIADS:buildSAMSitesInCoveredArea() + local samSites = self:getUsableSAMSites() + for i = 1, #samSites do + local samSite = samSites[i] + samSite:updateSAMSitesInCoveredArea() + end + + local ewRadars = self:getUsableEarlyWarningRadars() + for i = 1, #ewRadars do + local ewRadar = ewRadars[i] + ewRadar:updateSAMSitesInCoveredArea() + end +end + +function SkynetIADS:updateIADSCoverage() + self:buildSAMSitesInCoveredArea() + self:enforceRebuildAutonomousStateOfSAMSites() + --update moose connector with radar group names Skynet is able to use + self:getMooseConnector():update() +end + +function SkynetIADS:updateAutonomousStatesOfSAMSites(deadUnit) + --deat unit is to prevent multiple calls via the event handling of SkynetIADSAbstractElement when a units power source or connection node is destroyed + if deadUnit == nil or self.destroyedUnitResponsibleForUpdateAutonomousStateOfSAMSite ~= deadUnit then + self:updateIADSCoverage() + self.destroyedUnitResponsibleForUpdateAutonomousStateOfSAMSite = deadUnit + end +end + +function SkynetIADS:enforceRebuildAutonomousStateOfSAMSites() + local ewRadars = self:getUsableEarlyWarningRadars() + local samSites = self:getUsableSAMSites() + + for i = 1, #samSites do + local samSite = samSites[i] + if samSite:getActAsEW() then + table.insert(ewRadars, samSite) + end + end + + for i = 1, #samSites do + local samSite = samSites[i] + local inRange = false + for j = 1, #ewRadars do + if samSite:isInRadarDetectionRangeOf(ewRadars[j]) then + inRange = true + end + end + if inRange == false then + samSite:goAutonomous() + else + samSite:resetAutonomousState() + end + end +end + +function SkynetIADS:mergeContact(contact) + local existingContact = false + for i = 1, #self.contacts do + local iadsContact = self.contacts[i] + if iadsContact:getName() == contact:getName() then + iadsContact:refresh() + existingContact = true + end + end + if existingContact == false then + table.insert(self.contacts, contact) + end +end + +function SkynetIADS:getContacts() + return self.contacts +end + +function SkynetIADS:printOutput(output, typeWarning) + if typeWarning == true and self.debugOutput.warnings or typeWarning == nil then + if typeWarning == true then + output = "WARNING: "..output + end + trigger.action.outText(output, 4) + end +end + +function SkynetIADS:getDebugSettings() + return self.debugOutput +end + +-- will start going through the Early Warning Radars and SAM sites to check what targets they have detected +function SkynetIADS.activate(self) + mist.removeFunction(self.ewRadarScanMistTaskID) + mist.removeFunction(self.samSetupMistTaskID) + self.ewRadarScanMistTaskID = mist.scheduleFunction(SkynetIADS.evaluateContacts, {self}, 1, self.contactUpdateInterval) + self:updateIADSCoverage() +end + +function SkynetIADS:setupSAMSitesAndThenActivate(setupTime) + if setupTime then + self.samSetupTime = setupTime + end + local samSites = self:getSAMSites() + for i = 1, #samSites do + local sam = samSites[i] + sam:goLive() + --stop harm scan, because this function will shut down point defences + sam:stopScanningForHARMs() + --point defences will go dark after sam:goLive() call on the SAM they are protecting, so we load them and call a separate goLive call here, some SAMs will therefore receive 2 goLive calls + -- this should not have a negative impact on performance + local pointDefences = sam:getPointDefences() + for j = 1, #pointDefences do + pointDefence = pointDefences[j] + pointDefence:goLive() + end + end + self.samSetupMistTaskID = mist.scheduleFunction(SkynetIADS.postSetupSAMSites, {self}, timer.getTime() + self.samSetupTime) +end + +function SkynetIADS.postSetupSAMSites(self) + local samSites = self:getSAMSites() + for i = 1, #samSites do + local sam = samSites[i] + --turn on the scan again otherwise SAMs that fired a missile while in setup will not turn off anymore + sam:scanForHarms() + end + self:activate() +end + +function SkynetIADS:deactivate() + mist.removeFunction(self.ewRadarScanMistTaskID) + self:deativateSAMSites() + self:deactivateEarlyWarningRadars() + self:deactivateCommandCenters() +end + +function SkynetIADS:deactivateCommandCenters() + for i = 1, #self.commandCenters do + local comCenter = self.commandCenters[i] + comCenter:cleanUp() + end +end + +function SkynetIADS:deativateSAMSites() + for i = 1, #self.samSites do + local samSite = self.samSites[i] + samSite:cleanUp() + end +end + +function SkynetIADS:deactivateEarlyWarningRadars() + for i = 1, #self.earlyWarningRadars do + local ewRadar = self.earlyWarningRadars[i] + ewRadar:cleanUp() + end +end + +function SkynetIADS:addRadioMenu() + self.radioMenu = missionCommands.addSubMenu('SKYNET IADS '..self:getCoalitionString()) + local displayIADSStatus = missionCommands.addCommand('show IADS Status', self.radioMenu, SkynetIADS.updateDisplay, {self = self, value = true, option = 'IADSStatus'}) + local displayIADSStatus = missionCommands.addCommand('hide IADS Status', self.radioMenu, SkynetIADS.updateDisplay, {self = self, value = false, option = 'IADSStatus'}) + local displayIADSStatus = missionCommands.addCommand('show contacts', self.radioMenu, SkynetIADS.updateDisplay, {self = self, value = true, option = 'contacts'}) + local displayIADSStatus = missionCommands.addCommand('hide contacts', self.radioMenu, SkynetIADS.updateDisplay, {self = self, value = false, option = 'contacts'}) +end + +function SkynetIADS:removeRadioMenu() + missionCommands.removeItem(self.radioMenu) +end + +function SkynetIADS.updateDisplay(params) + local option = params.option + local self = params.self + local value = params.value + if option == 'IADSStatus' then + self:getDebugSettings()[option] = value + elseif option == 'contacts' then + self:getDebugSettings()[option] = value + end +end + +function SkynetIADS:getCoalitionString() + local coalitionStr = "RED" + if self.coalitionID == coalition.side.BLUE then + coalitionStr = "BLUE" + elseif self.coalitionID == coalition.side.NEUTRAL then + coalitionStr = "NEUTRAL" + end + + if self.name then + coalitionStr = coalitionStr.." "..self.name + end + + return coalitionStr +end + +function SkynetIADS:getMooseConnector() + if self.mooseConnector == nil then + self.mooseConnector = SkynetMooseA2ADispatcherConnector:create(self) + end + return self.mooseConnector +end + +function SkynetIADS:addMooseSetGroup(mooseSetGroup) + self:getMooseConnector():addMooseSetGroup(mooseSetGroup) +end + +function SkynetIADS:printDetailedEarlyWarningRadarStatus() + local ewRadars = self:getEarlyWarningRadars() + env.info("------------------------------------------ EW RADAR STATUS: "..self:getCoalitionString().." -------------------------------") + for i = 1, #ewRadars do + local ewRadar = ewRadars[i] + local numConnectionNodes = #ewRadar:getConnectionNodes() + local numPowerSources = #ewRadar:getPowerSources() + local isActive = ewRadar:isActive() + local connectionNodes = ewRadar:getConnectionNodes() + local firstRadar = nil + local radars = ewRadar:getRadars() + + --get the first existing radar to prevent issues in calculating the distance later on: + for i = 1, #radars do + if radars[i]:isExist() then + firstRadar = radars[i] + break + end + + end + local numDamagedConnectionNodes = 0 + + + for j = 1, #connectionNodes do + local connectionNode = connectionNodes[j] + if connectionNode:isExist() == false then + numDamagedConnectionNodes = numDamagedConnectionNodes + 1 + end + end + local intactConnectionNodes = numConnectionNodes - numDamagedConnectionNodes + + local powerSources = ewRadar:getPowerSources() + local numDamagedPowerSources = 0 + for j = 1, #powerSources do + local powerSource = powerSources[j] + if powerSource:isExist() == false then + numDamagedPowerSources = numDamagedPowerSources + 1 + end + end + local intactPowerSources = numPowerSources - numDamagedPowerSources + + local detectedTargets = ewRadar:getDetectedTargets() + local samSitesInCoveredArea = ewRadar:getSAMSitesInCoveredArea() + + local unitName = "DESTROYED" + + if ewRadar:getDCSRepresentation():isExist() then + unitName = ewRadar:getDCSName() + end + + env.info("UNIT: "..unitName.." | TYPE: "..ewRadar:getNatoName()) + env.info("ACTIVE: "..tostring(isActive).."| DETECTED TARGETS: "..#detectedTargets.." | DEFENDING HARM: "..tostring(ewRadar:isDefendingHARM())) + if numConnectionNodes > 0 then + env.info("CONNECTION NODES: "..numConnectionNodes.." | DAMAGED: "..numDamagedConnectionNodes.." | INTACT: "..intactConnectionNodes) + else + env.info("NO CONNECTION NODES SET") + end + if numPowerSources > 0 then + env.info("POWER SOURCES : "..numPowerSources.." | DAMAGED:"..numDamagedPowerSources.." | INTACT: "..intactPowerSources) + else + env.info("NO POWER SOURCES SET") + end + + env.info("SAM SITES IN COVERED AREA: "..#samSitesInCoveredArea) + for j = 1, #samSitesInCoveredArea do + local samSiteCovered = samSitesInCoveredArea[j] + env.info(samSiteCovered:getDCSName()) + end + + for j = 1, #detectedTargets do + local contact = detectedTargets[j] + if firstRadar ~= nil and firstRadar:isExist() then + local distance = mist.utils.round(mist.utils.metersToNM(ewRadar:getDistanceInMetersToContact(firstRadar:getDCSRepresentation(), contact:getPosition().p)), 2) + env.info("CONTACT: "..contact:getName().." | TYPE: "..contact:getTypeName().." | DISTANCE NM: "..distance) + end + end + + env.info("---------------------------------------------------") + + end + +end + +function SkynetIADS:printDetailedSAMSiteStatus() + local samSites = self:getSAMSites() + + env.info("------------------------------------------ SAM STATUS: "..self:getCoalitionString().." -------------------------------") + for i = 1, #samSites do + local samSite = samSites[i] + local numConnectionNodes = #samSite:getConnectionNodes() + local numPowerSources = #samSite:getPowerSources() + local isAutonomous = samSite:getAutonomousState() + local isActive = samSite:isActive() + + local connectionNodes = samSite:getConnectionNodes() + local firstRadar = samSite:getRadars()[1] + local numDamagedConnectionNodes = 0 + for j = 1, #connectionNodes do + local connectionNode = connectionNodes[j] + if connectionNode:isExist() == false then + numDamagedConnectionNodes = numDamagedConnectionNodes + 1 + end + end + local intactConnectionNodes = numConnectionNodes - numDamagedConnectionNodes + + local powerSources = samSite:getPowerSources() + local numDamagedPowerSources = 0 + for j = 1, #powerSources do + local powerSource = powerSources[j] + if powerSource:isExist() == false then + numDamagedPowerSources = numDamagedPowerSources + 1 + end + end + local intactPowerSources = numPowerSources - numDamagedPowerSources + + local detectedTargets = samSite:getDetectedTargets() + + local samSitesInCoveredArea = samSite:getSAMSitesInCoveredArea() + + env.info("GROUP: "..samSite:getDCSName().." | TYPE: "..samSite:getNatoName()) + env.info("ACTIVE: "..tostring(isActive).." | AUTONOMOUS: "..tostring(isAutonomous).." | IS ACTING AS EW: "..tostring(samSite:getActAsEW()).." | DETECTED TARGETS: "..#detectedTargets.." | DEFENDING HARM: "..tostring(samSite:isDefendingHARM()).." | MISSILES IN FLIGHT:"..tostring(samSite:getNumberOfMissilesInFlight())) + + if numConnectionNodes > 0 then + env.info("CONNECTION NODES: "..numConnectionNodes.." | DAMAGED: "..numDamagedConnectionNodes.." | INTACT: "..intactConnectionNodes) + else + env.info("NO CONNECTION NODES SET") + end + if numPowerSources > 0 then + env.info("POWER SOURCES : "..numPowerSources.." | DAMAGED:"..numDamagedPowerSources.." | INTACT: "..intactPowerSources) + else + env.info("NO POWER SOURCES SET") + end + + env.info("SAM SITES IN COVERED AREA: "..#samSitesInCoveredArea) + for j = 1, #samSitesInCoveredArea do + local samSiteCovered = samSitesInCoveredArea[j] + env.info(samSiteCovered:getDCSName()) + end + + for j = 1, #detectedTargets do + local contact = detectedTargets[j] + if firstRadar ~= nil and firstRadar:isExist() then + local distance = mist.utils.round(mist.utils.metersToNM(samSite:getDistanceInMetersToContact(firstRadar:getDCSRepresentation(), contact:getPosition().p)), 2) + env.info("CONTACT: "..contact:getName().." | TYPE: "..contact:getTypeName().." | DISTANCE NM: "..distance) + end + end + + env.info("---------------------------------------------------") + end +end + +function SkynetIADS:printSystemStatus() + + if self:getDebugSettings().IADSStatus or self:getDebugSettings().contacts then + local coalitionStr = self:getCoalitionString() + self:printOutput("---- IADS: "..coalitionStr.." ------") + end + + if self:getDebugSettings().IADSStatus then + + local numComCenters = #self.commandCenters + local numIntactComCenters = 0 + local numDestroyedComCenters = 0 + local numComCentersNoPower = 0 + local numComCentersServingIADS = 0 + for i = 1, #self.commandCenters do + local commandCenter = self.commandCenters[i] + if commandCenter:hasWorkingPowerSource() == false then + numComCentersNoPower = numComCentersNoPower + 1 + end + if commandCenter:isDestroyed() == false then + numIntactComCenters = numIntactComCenters + 1 + end + if commandCenter:isDestroyed() == false and commandCenter:hasWorkingPowerSource() then + numComCentersServingIADS = numComCentersServingIADS + 1 + end + end + + numDestroyedComCenters = numComCenters - numIntactComCenters + + + self:printOutput("COMMAND CENTERS: Serving IADS: "..numComCentersServingIADS.." | Total: "..numComCenters.." | Intact: "..numIntactComCenters.." | Destroyed: "..numDestroyedComCenters.." | NoPower: "..numComCentersNoPower) + + local ewNoPower = 0 + local ewTotal = #self:getEarlyWarningRadars() + local ewNoConnectionNode = 0 + local ewActive = 0 + local ewRadarsInactive = 0 + + for i = 1, #self.earlyWarningRadars do + local ewRadar = self.earlyWarningRadars[i] + if ewRadar:hasWorkingPowerSource() == false then + ewNoPower = ewNoPower + 1 + end + if ewRadar:hasActiveConnectionNode() == false then + ewNoConnectionNode = ewNoConnectionNode + 1 + end + if ewRadar:isActive() then + ewActive = ewActive + 1 + end + end + + ewRadarsInactive = ewTotal - ewActive + local numEWRadarsDestroyed = #self:getDestroyedEarlyWarningRadars() + self:printOutput("EW: "..ewTotal.." | Act: "..ewActive.." | Inact: "..ewRadarsInactive.." | Destroyed: "..numEWRadarsDestroyed.." | NoPowr: "..ewNoPower.." | NoCon: "..ewNoConnectionNode) + + local samSitesInactive = 0 + local samSitesActive = 0 + local samSitesTotal = #self:getSAMSites() + local samSitesNoPower = 0 + local samSitesNoConnectionNode = 0 + local samSitesOutOfAmmo = 0 + local samSiteAutonomous = 0 + local samSiteRadarDestroyed = 0 + for i = 1, #self.samSites do + local samSite = self.samSites[i] + if samSite:hasWorkingPowerSource() == false then + samSitesNoPower = samSitesNoPower + 1 + end + if samSite:hasActiveConnectionNode() == false then + samSitesNoConnectionNode = samSitesNoConnectionNode + 1 + end + if samSite:isActive() then + samSitesActive = samSitesActive + 1 + end + if samSite:hasRemainingAmmo() == false then + samSitesOutOfAmmo = samSitesOutOfAmmo + 1 + end + if samSite:getAutonomousState() == true then + samSiteAutonomous = samSiteAutonomous + 1 + end + if samSite:hasWorkingRadar() == false then + samSiteRadarDestroyed = samSiteRadarDestroyed + 1 + end + end + + samSitesInactive = samSitesTotal - samSitesActive + self:printOutput("SAM: "..samSitesTotal.." | Act: "..samSitesActive.." | Inact: "..samSitesInactive.." | Autonm: "..samSiteAutonomous.." | Raddest: "..samSiteRadarDestroyed.." | NoPowr: "..samSitesNoPower.." | NoCon: "..samSitesNoConnectionNode.." | NoAmmo: "..samSitesOutOfAmmo) + end + if self:getDebugSettings().contacts then + for i = 1, #self.contacts do + local contact = self.contacts[i] + self:printOutput("CONTACT: "..contact:getName().." | TYPE: "..contact:getTypeName().." | GS: "..tostring(contact:getGroundSpeedInKnots()).." | LAST SEEN: "..contact:getAge()) + end + end + + if self:getDebugSettings().earlyWarningRadarStatusEnvOutput then + self:printDetailedEarlyWarningRadarStatus() + end + + if self:getDebugSettings().samSiteStatusEnvOutput then + self:printDetailedSAMSiteStatus() + end +end + +end +do + +SkynetMooseA2ADispatcherConnector = {} + +function SkynetMooseA2ADispatcherConnector:create(iads) + local instance = {} + setmetatable(instance, self) + self.__index = self + instance.iadsCollection = {} + instance.mooseGroups = {} + instance.ewRadarGroupNames = {} + instance.samSiteGroupNames = {} + table.insert(instance.iadsCollection, iads) + return instance +end + +function SkynetMooseA2ADispatcherConnector:addIADS(iads) + table.insert(self.iadsCollection, iads) +end + +function SkynetMooseA2ADispatcherConnector:addMooseSetGroup(mooseSetGroup) + table.insert(self.mooseGroups, mooseSetGroup) + self:update() +end + +function SkynetMooseA2ADispatcherConnector:getEarlyWarningRadarGroupNames() + self.ewRadarGroupNames = {} + for i = 1, #self.iadsCollection do + local ewRadars = self.iadsCollection[i]:getUsableEarlyWarningRadars() + for j = 1, #ewRadars do + local ewRadar = ewRadars[j] + table.insert(self.ewRadarGroupNames, ewRadar:getDCSRepresentation():getGroup():getName()) + end + end + return self.ewRadarGroupNames +end + +function SkynetMooseA2ADispatcherConnector:getSAMSiteGroupNames() + self.samSiteGroupNames = {} + for i = 1, #self.iadsCollection do + local samSites = self.iadsCollection[i]:getUsableSAMSites() + for j = 1, #samSites do + local samSite = samSites[j] + table.insert(self.samSiteGroupNames, samSite:getDCSName()) + end + end + return self.samSiteGroupNames +end + +function SkynetMooseA2ADispatcherConnector:update() + + --mooseGroup elements are type of: + --https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Core.Set.html##(SET_GROUP) + + --remove previously set group names: + for i = 1, #self.mooseGroups do + local mooseGroup = self.mooseGroups[i] + mooseGroup:RemoveGroupsByName(self.ewRadarGroupNames) + mooseGroup:RemoveGroupsByName(self.samSiteGroupNames) + end + + --add group names of IADS radars that are currently usable by the IADS: + for i = 1, #self.mooseGroups do + local mooseGroup = self.mooseGroups[i] + mooseGroup:AddGroupsByName(self:getEarlyWarningRadarGroupNames()) + mooseGroup:AddGroupsByName(self:getSAMSiteGroupNames()) + end +end + +end +do + + +SkynetIADSTableDelegator = {} + +function SkynetIADSTableDelegator:create() + local instance = {} + local forwarder = {} + forwarder.__index = function(tbl, name) + tbl[name] = function(self, ...) + for i = 1, #self do + self[i][name](self[i], ...) + end + return self + end + return tbl[name] + end + setmetatable(instance, forwarder) + instance.__index = forwarder + return instance +end + +end +do + +SkynetIADSAbstractDCSObjectWrapper = {} + +function SkynetIADSAbstractDCSObjectWrapper:create(dcsObject) + local instance = {} + setmetatable(instance, self) + self.__index = self + instance.dcsObject = dcsObject + if dcsObject and dcsObject:isExist() and getmetatable(dcsObject) == Unit then + --we store inital life here, because getLife0() returs a value that is lower that getLife() when no damage has happened... + instance.initialLife = dcsObject:getLife() + end + return instance +end + +function SkynetIADSAbstractDCSObjectWrapper:getName() + return self.dcsObject:getName() +end + +function SkynetIADSAbstractDCSObjectWrapper:getTypeName() + return self.dcsObject:getTypeName() +end + +function SkynetIADSAbstractDCSObjectWrapper:getPosition() + return self.dcsObject:getPosition() +end + +function SkynetIADSAbstractDCSObjectWrapper:isExist() + if self.dcsObject then + return self.dcsObject:isExist() + else + return false + end +end + +function SkynetIADSAbstractDCSObjectWrapper:getLifePercentage() + if self.dcsObject and self.dcsObject:isExist() then + return self.dcsObject:getLife() / self.initialLife * 100 + else + return 0 + end + +end + +function SkynetIADSAbstractDCSObjectWrapper:getDCSRepresentation() + return self.dcsObject +end + +end +do + +SkynetIADSAbstractElement = {} + +function SkynetIADSAbstractElement:create(dcsRepresentation, iads) + local instance = {} + setmetatable(instance, self) + self.__index = self + instance.connectionNodes = {} + instance.powerSources = {} + instance.iads = iads + instance.natoName = "UNKNOWN" + instance.dcsName = "" + instance:setDCSRepresentation(dcsRepresentation) + world.addEventHandler(instance) + return instance +end + +function SkynetIADSAbstractElement:removeEventHandlers() + world.removeEventHandler(self) +end + +function SkynetIADSAbstractElement:cleanUp() + self:removeEventHandlers() +end + +function SkynetIADSAbstractElement:isDestroyed() + return self:getDCSRepresentation():isExist() == false +end + +function SkynetIADSAbstractElement:addPowerSource(powerSource) + table.insert(self.powerSources, powerSource) + return self +end + +function SkynetIADSAbstractElement:getPowerSources() + return self.powerSources +end + +function SkynetIADSAbstractElement:addConnectionNode(connectionNode) + table.insert(self.connectionNodes, connectionNode) + self.iads:updateAutonomousStatesOfSAMSites() + return self +end + +function SkynetIADSAbstractElement:getConnectionNodes() + return self.connectionNodes +end + +function SkynetIADSAbstractElement:hasActiveConnectionNode() + local connectionNode = self:genericCheckOneObjectIsAlive(self.connectionNodes) + if connectionNode == false and self.iads:getDebugSettings().samNoConnection then + self.iads:printOutput(self:getDescription().." no connection to Command Center") + end + return connectionNode +end + +function SkynetIADSAbstractElement:hasWorkingPowerSource() + local power = self:genericCheckOneObjectIsAlive(self.powerSources) + if power == false and self.iads:getDebugSettings().hasNoPower then + self.iads:printOutput(self:getDescription().." has no power") + end + return power +end + +function SkynetIADSAbstractElement:getDCSName() + return self.dcsName +end + +-- generic function to theck if power plants, command centers, connection nodes are still alive +function SkynetIADSAbstractElement:genericCheckOneObjectIsAlive(objects) + local isAlive = (#objects == 0) + for i = 1, #objects do + local object = objects[i] + --if we find one object that is not fully destroyed we assume the IADS is still working + if object:isExist() then + isAlive = true + break + end + end + return isAlive +end + +function SkynetIADSAbstractElement:setDCSRepresentation(representation) + self.dcsRepresentation = representation + if self.dcsRepresentation then + self.dcsName = self:getDCSRepresentation():getName() + end +end + +function SkynetIADSAbstractElement:getDCSRepresentation() + return self.dcsRepresentation +end + +function SkynetIADSAbstractElement:getNatoName() + return self.natoName +end + +function SkynetIADSAbstractElement:getDescription() + return "IADS ELEMENT: "..self:getDCSName().." | Type : "..tostring(self:getNatoName()) +end + +function SkynetIADSAbstractElement:onEvent(event) + --if a unit is destroyed we check to see if its a power plant powering the unit or a connection node + if event.id == world.event.S_EVENT_DEAD then + if self:hasWorkingPowerSource() == false or self:isDestroyed() then + self:goDark() + self.iads:updateAutonomousStatesOfSAMSites(event.initiator) + end + if self:hasActiveConnectionNode() == false then + self:goAutonomous() + self.iads:updateAutonomousStatesOfSAMSites(event.initiator) + end + end + if event.id == world.event.S_EVENT_SHOT then + self:weaponFired(event) + end +end + +--placeholder method, can be implemented by subclasses +function SkynetIADSAbstractElement:weaponFired(event) + +end + +--placeholder method, can be implemented by subclasses +function SkynetIADSAbstractElement:goDark() + +end + +--placeholder method, can be implemented by subclasses +function SkynetIADSAbstractElement:goAutonomous() + +end + +-- helper code for class inheritance +function inheritsFrom( baseClass ) + + local new_class = {} + local class_mt = { __index = new_class } + + function new_class:create() + local newinst = {} + setmetatable( newinst, class_mt ) + return newinst + end + + if nil ~= baseClass then + setmetatable( new_class, { __index = baseClass } ) + end + + -- Implementation of additional OO properties starts here -- + + -- Return the class object of the instance + function new_class:class() + return new_class + end + + -- Return the super class object of the instance + function new_class:superClass() + return baseClass + end + + -- Return true if the caller is an instance of theClass + function new_class:isa( theClass ) + local b_isa = false + + local cur_class = new_class + + while ( nil ~= cur_class ) and ( false == b_isa ) do + if cur_class == theClass then + b_isa = true + else + cur_class = cur_class:superClass() + end + end + + return b_isa + end + + return new_class +end + +end +do + +SkynetIADSAbstractRadarElement = {} +SkynetIADSAbstractRadarElement = inheritsFrom(SkynetIADSAbstractElement) + +SkynetIADSAbstractRadarElement.AUTONOMOUS_STATE_DCS_AI = 1 +SkynetIADSAbstractRadarElement.AUTONOMOUS_STATE_DARK = 2 + +SkynetIADSAbstractRadarElement.GO_LIVE_WHEN_IN_KILL_ZONE = 1 +SkynetIADSAbstractRadarElement.GO_LIVE_WHEN_IN_SEARCH_RANGE = 2 + +function SkynetIADSAbstractRadarElement:create(dcsElementWithRadar, iads) + local instance = self:superClass():create(dcsElementWithRadar, iads) + setmetatable(instance, self) + self.__index = self + instance.aiState = false + instance.harmScanID = nil + instance.harmSilenceID = nil + instance.lastJammerUpdate = 0 + instance.objectsIdentifiedAsHarms = {} + instance.objectsIdentifiedAsHarmsMaxTargetAge = 60 + instance.launchers = {} + instance.trackingRadars = {} + instance.searchRadars = {} + instance.samSitesInCoveredArea = {} + instance.missilesInFlight = {} + instance.pointDefences = {} + instance.ingnoreHARMSWhilePointDefencesHaveAmmo = false + instance.autonomousBehaviour = SkynetIADSAbstractRadarElement.AUTONOMOUS_STATE_DCS_AI + instance.goLiveRange = SkynetIADSAbstractRadarElement.GO_LIVE_WHEN_IN_KILL_ZONE + instance.isAutonomous = false + instance.harmDetectionChance = 0 + instance.minHarmShutdownTime = 0 + instance.maxHarmShutDownTime = 0 + instance.minHarmPresetShutdownTime = 30 + instance.maxHarmPresetShutdownTime = 180 + instance.firingRangePercent = 100 + instance.actAsEW = false + instance.cachedTargets = {} + instance.cachedTargetsMaxAge = 1 + instance.cachedTargetsCurrentAge = 0 + instance.goLiveTime = 0 + -- 5 seconds seems to be a good value for the sam site to find the target with its organic radar + instance.noCacheActiveForSecondsAfterGoLive = 5 + return instance +end + +--TODO: this method could be updated to only return Radar weapons fired, this way a SAM firing an IR weapon could go dark faster in the goDark() method +function SkynetIADSAbstractRadarElement:weaponFired(event) + if event.id == world.event.S_EVENT_SHOT then + local weapon = event.weapon + local launcherFired = event.initiator + for i = 1, #self.launchers do + local launcher = self.launchers[i] + if launcher:getDCSRepresentation() == launcherFired then + table.insert(self.missilesInFlight, weapon) + end + end + end +end + +function SkynetIADSAbstractRadarElement:setCachedTargetsMaxAge(maxAge) + self.cachedTargetsMaxAge = maxAge +end + +function SkynetIADSAbstractRadarElement:cleanUp() + for i = 1, #self.pointDefences do + local pointDefence = self.pointDefences[i] + pointDefence:cleanUp() + end + mist.removeFunction(self.harmScanID) + mist.removeFunction(self.harmSilenceID) + --call method from super class + self:removeEventHandlers() +end + +function SkynetIADSAbstractRadarElement:addPointDefence(pointDefence) + table.insert(self.pointDefences, pointDefence) + return self +end + +function SkynetIADSAbstractRadarElement:getPointDefences() + return self.pointDefences +end + + +function SkynetIADSAbstractRadarElement:updateSAMSitesInCoveredArea() + local samSites = self.iads:getUsableSAMSites() + self.samSitesInCoveredArea = {} + for i = 1, #samSites do + local samSite = samSites[i] + if samSite:isInRadarDetectionRangeOf(self) and samSite ~= self then + table.insert(self.samSitesInCoveredArea, samSite) + end + end + return self.samSitesInCoveredArea +end + +function SkynetIADSAbstractRadarElement:getSAMSitesInCoveredArea() + return self.samSitesInCoveredArea +end + +function SkynetIADSAbstractRadarElement:pointDefencesHaveRemainingAmmo(minNumberOfMissiles) + local remainingMissiles = 0 + for i = 1, #self.pointDefences do + local pointDefence = self.pointDefences[i] + remainingMissiles = remainingMissiles + pointDefence:getRemainingNumberOfMissiles() + end + local returnValue = false + if ( remainingMissiles > 0 and remainingMissiles >= minNumberOfMissiles ) then + returnValue = true + end + return returnValue +end + +function SkynetIADSAbstractElement:pointDefencesHaveEnoughLaunchers(minNumberOfLaunchers) + local numOfLaunchers = 0 + for i = 1, #self.pointDefences do + local pointDefence = self.pointDefences[i] + numOfLaunchers = numOfLaunchers + #pointDefence:getLaunchers() + end + local returnValue = false + if ( numOfLaunchers > 0 and numOfLaunchers >= minNumberOfLaunchers ) then + returnValue = true + end + return returnValue +end + +function SkynetIADSAbstractElement:setIgnoreHARMSWhilePointDefencesHaveAmmo(state) + if state == true or state == false then + self.ingnoreHARMSWhilePointDefencesHaveAmmo = state + end + return self +end + +function SkynetIADSAbstractRadarElement:hasMissilesInFlight() + return #self.missilesInFlight > 0 +end + +function SkynetIADSAbstractRadarElement:getNumberOfMissilesInFlight() + return #self.missilesInFlight +end + +-- DCS does not send an event, when a missile is destroyed, so this method needs to be polled so that the missiles in flight are current, polling is done in the HARM Search call: evaluateIfTargetsContainHARMs +function SkynetIADSAbstractRadarElement:updateMissilesInFlight() + local missilesInFlight = {} + for i = 1, #self.missilesInFlight do + local missile = self.missilesInFlight[i] + if missile:isExist() then + table.insert(missilesInFlight, missile) + end + end + self.missilesInFlight = missilesInFlight + self:goDarkIfOutOfAmmo() +end + +function SkynetIADSAbstractRadarElement:goDarkIfOutOfAmmo() + if self:hasRemainingAmmo() == false and self:getActAsEW() == false then + self:goDark() + end +end + +function SkynetIADSAbstractRadarElement:getActAsEW() + return self.actAsEW +end + +function SkynetIADSAbstractRadarElement:setActAsEW(ewState) + if ewState == true or ewState == false then + self.actAsEW = ewState + end + if self.actAsEW == true then + self:goLive() + else + self:goDark() + end + return self +end + +function SkynetIADSAbstractRadarElement:getUnitsToAnalyse() + local units = {} + table.insert(units, self:getDCSRepresentation()) + if getmetatable(self:getDCSRepresentation()) == Group then + units = self:getDCSRepresentation():getUnits() + end + return units +end + +function SkynetIADSAbstractRadarElement:getRemainingNumberOfMissiles() + local remainingNumberOfMissiles = 0 + for i = 1, #self.launchers do + local launcher = self.launchers[i] + remainingNumberOfMissiles = remainingNumberOfMissiles + launcher:getRemainingNumberOfMissiles() + end + return remainingNumberOfMissiles +end + +function SkynetIADSAbstractRadarElement:getInitialNumberOfMissiles() + local initalNumberOfMissiles = 0 + for i = 1, #self.launchers do + local launcher = self.launchers[i] + initalNumberOfMissiles = launcher:getInitialNumberOfMissiles() + initalNumberOfMissiles + end + return initalNumberOfMissiles +end + +function SkynetIADSAbstractRadarElement:getRemainingNumberOfShells() + local remainingNumberOfShells = 0 + for i = 1, #self.launchers do + local launcher = self.launchers[i] + remainingNumberOfShells = remainingNumberOfShells + launcher:getRemainingNumberOfShells() + end + return remainingNumberOfShells +end + +function SkynetIADSAbstractRadarElement:getInitialNumberOfShells() + local initialNumberOfShells = 0 + for i = 1, #self.launchers do + local launcher = self.launchers[i] + initialNumberOfShells = initialNumberOfShells + launcher:getInitialNumberOfShells() + end + return initialNumberOfShells +end + +function SkynetIADSAbstractRadarElement:hasRemainingAmmo() + --the launcher check is due to ew radars they have no launcher and no ammo and therefore are never out of ammo + return ( #self.launchers == 0 ) or ((self:getRemainingNumberOfMissiles() > 0 ) or ( self:getRemainingNumberOfShells() > 0 ) ) +end + +function SkynetIADSAbstractRadarElement:getHARMDetectionChance() + return self.harmDetectionChance +end + +function SkynetIADSAbstractRadarElement:setHARMDetectionChance(chance) + self.harmDetectionChance = chance + return self +end + +function SkynetIADSAbstractRadarElement:setupElements() + local numUnits = #self:getUnitsToAnalyse() + for typeName, dataType in pairs(SkynetIADS.database) do + local hasSearchRadar = false + local hasTrackingRadar = false + local hasLauncher = false + self.searchRadars = {} + self.trackingRadars = {} + self.launchers = {} + for entry, unitData in pairs(dataType) do + if entry == 'searchRadar' then + self:analyseAndAddUnit(SkynetIADSSAMSearchRadar, self.searchRadars, unitData) + hasSearchRadar = true + end + if entry == 'launchers' then + self:analyseAndAddUnit(SkynetIADSSAMLauncher, self.launchers, unitData) + hasLauncher = true + end + if entry == 'trackingRadar' then + self:analyseAndAddUnit(SkynetIADSSAMTrackingRadar, self.trackingRadars, unitData) + hasTrackingRadar = true + end + end + + local numElementsCreated = #self.searchRadars + #self.trackingRadars + #self.launchers + --this check ensures a unit or group has all required elements for the specific sam or ew type: + if (hasLauncher and hasSearchRadar and hasTrackingRadar and #self.launchers > 0 and #self.searchRadars > 0 and #self.trackingRadars > 0 ) + or (hasSearchRadar and hasLauncher and #self.searchRadars > 0 and #self.launchers > 0) + or (hasSearchRadar and hasLauncher == false and hasTrackingRadar == false and #self.searchRadars > 0 and numUnits == 1) then + local harmDetection = dataType['harm_detection_chance'] + if harmDetection then + self.harmDetectionChance = harmDetection + end + local natoName = dataType['name']['NATO'] + --we shorten the SA-XX names and don't return their code names eg goa, gainful.. + local pos = natoName:find(" ") + local prefix = natoName:sub(1, 2) + if string.lower(prefix) == 'sa' and pos ~= nil then + self.natoName = natoName:sub(1, (pos-1)) + else + self.natoName = natoName + end + break + end + end +end + +function SkynetIADSAbstractRadarElement:analyseAndAddUnit(class, tableToAdd, unitData) + local units = self:getUnitsToAnalyse() + for i = 1, #units do + local unit = units[i] + local unitTypeName = unit:getTypeName() + for unitName, unitPerformanceData in pairs(unitData) do + if unitName == unitTypeName then + samElement = class:create(unit) + samElement:setupRangeData() + table.insert(tableToAdd, samElement) + end + end + end +end + +function SkynetIADSAbstractRadarElement:getController() + local dcsRepresentation = self:getDCSRepresentation() + if dcsRepresentation:isExist() then + return dcsRepresentation:getController() + else + return nil + end +end + +function SkynetIADSAbstractRadarElement:getLaunchers() + return self.launchers +end + +function SkynetIADSAbstractRadarElement:getSearchRadars() + return self.searchRadars +end + +function SkynetIADSAbstractRadarElement:getTrackingRadars() + return self.trackingRadars +end + +function SkynetIADSAbstractRadarElement:getRadars() + local radarUnits = {} + for i = 1, #self.searchRadars do + table.insert(radarUnits, self.searchRadars[i]) + end + for i = 1, #self.trackingRadars do + table.insert(radarUnits, self.trackingRadars[i]) + end + return radarUnits +end + +function SkynetIADSAbstractRadarElement:setGoLiveRangeInPercent(percent) + if percent ~= nil then + self.firingRangePercent = percent + for i = 1, #self.launchers do + local launcher = self.launchers[i] + launcher:setFiringRangePercent(self.firingRangePercent) + end + for i = 1, #self.searchRadars do + local radar = self.searchRadars[i] + radar:setFiringRangePercent(self.firingRangePercent) + end + end + return self +end + +function SkynetIADSAbstractRadarElement:getGoLiveRangeInPercent() + return self.firingRangePercent +end + +function SkynetIADSAbstractRadarElement:setEngagementZone(engagementZone) + if engagementZone == SkynetIADSAbstractRadarElement.GO_LIVE_WHEN_IN_KILL_ZONE then + self.goLiveRange = engagementZone + elseif engagementZone == SkynetIADSAbstractRadarElement.GO_LIVE_WHEN_IN_SEARCH_RANGE then + self.goLiveRange = engagementZone + end + return self +end + +function SkynetIADSAbstractRadarElement:getEngagementZone() + return self.goLiveRange +end + +function SkynetIADSAbstractRadarElement:goLive() + if ( self.aiState == false and self:hasWorkingPowerSource() and self.harmSilenceID == nil) + and ( (self.isAutonomous == false) or (self.isAutonomous == true and self.autonomousBehaviour == SkynetIADSAbstractRadarElement.AUTONOMOUS_STATE_DCS_AI ) ) + and (self:hasRemainingAmmo() == true ) + then + if self:isDestroyed() == false then + local cont = self:getController() + cont:setOnOff(true) + cont:setOption(AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.RED) + cont:setOption(AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_FREE) + self.goLiveTime = timer.getTime() + end + self.aiState = true + self:pointDefencesStopActingAsEW() + if self.iads:getDebugSettings().radarWentLive then + self.iads:printOutput(self:getDescription().." going live") + end + self:scanForHarms() + end +end + +function SkynetIADSAbstractRadarElement:pointDefencesStopActingAsEW() + for i = 1, #self.pointDefences do + local pointDefence = self.pointDefences[i] + pointDefence:setActAsEW(false) + end +end + + +function SkynetIADSAbstractRadarElement:noDamageToRadars() + local radars = self:getRadars() + for i = 1, #radars do + local radar = radars[i] + if radar:getLifePercentage() < 100 then + return false + end + end + return true +end + +function SkynetIADSAbstractRadarElement:goDark() + if ( self.aiState == true ) + and (self.harmSilenceID ~= nil or ( self.harmSilenceID == nil and #self:getDetectedTargets() == 0 and self:hasMissilesInFlight() == false) or ( self.harmSilenceID == nil and #self:getDetectedTargets() > 0 and self:hasMissilesInFlight() == false and self:hasRemainingAmmo() == false ) ) + and ( self.isAutonomous == false or ( self.isAutonomous == true and self.autonomousBehaviour == SkynetIADSAbstractRadarElement.AUTONOMOUS_STATE_DARK ) ) + then + if self:isDestroyed() == false then + local controller = self:getController() + -- if the SAM site still has ammo we turn off the controller, this prevents rearming, however this way the SAM site is frozen in a red state, on the next actication it will be up and running much faster, therefore it will instantaneously engage targets + -- also this is a better way to get the HARM to miss the target, if not set to false the HARM often sticks to the target + if self:hasRemainingAmmo() then + controller:setOnOff(false) + --if the SAM is out of ammo we set the state to green, and ROE to weapon hold, this way it will shut down its radar and it can be rearmed + else + controller:setOption(AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.GREEN) + controller:setOption(AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_HOLD) + end + end + -- point defence will only go live if the Radar Emitting site it is protecting goes dark and this is due to a it defending against a HARM + if (self.harmSilenceID ~= nil) then + self:pointDefencesGoLive() + end + self.aiState = false + self:stopScanningForHARMs() + if self.iads:getDebugSettings().samWentDark then + self.iads:printOutput(self:getDescription().." going dark") + end + end +end + +function SkynetIADSAbstractRadarElement:pointDefencesGoLive() + for i = 1, #self.pointDefences do + local pointDefence = self.pointDefences[i] + pointDefence:setActAsEW(true) + end +end + +function SkynetIADSAbstractRadarElement:isActive() + return self.aiState +end + +function SkynetIADSAbstractRadarElement:isTargetInRange(target) + + local isSearchRadarInRange = false + local isTrackingRadarInRange = false + local isLauncherInRange = false + + local isSearchRadarInRange = ( #self.searchRadars == 0 ) + for i = 1, #self.searchRadars do + local searchRadar = self.searchRadars[i] + if searchRadar:isInRange(target) then + isSearchRadarInRange = true + break + end + end + + if self.goLiveRange == SkynetIADSAbstractRadarElement.GO_LIVE_WHEN_IN_KILL_ZONE then + + isLauncherInRange = ( #self.launchers == 0 ) + for i = 1, #self.launchers do + local launcher = self.launchers[i] + if launcher:isInRange(target) then + isLauncherInRange = true + break + end + end + + isTrackingRadarInRange = ( #self.trackingRadars == 0 ) + for i = 1, #self.trackingRadars do + local trackingRadar = self.trackingRadars[i] + if trackingRadar:isInRange(target) then + isTrackingRadarInRange = true + break + end + end + else + isLauncherInRange = true + isTrackingRadarInRange = true + end + return (isSearchRadarInRange and isTrackingRadarInRange and isLauncherInRange ) +end + +function SkynetIADSAbstractRadarElement:isInRadarDetectionRangeOf(abstractRadarElement) + local radars = self:getRadars() + local abstractRadarElementRadars = abstractRadarElement:getRadars() + for i = 1, #radars do + local radar = radars[i] + for j = 1, #abstractRadarElementRadars do + local abstractRadarElementRadar = abstractRadarElementRadars[j] + if abstractRadarElementRadar:isExist() and radar:isExist() then + local distance = self:getDistanceToUnit(radar:getDCSRepresentation():getPosition().p, abstractRadarElementRadar:getDCSRepresentation():getPosition().p) + if abstractRadarElementRadar:getMaxRangeFindingTarget() >= distance then + return true + end + end + end + end + return false +end + +function SkynetIADSAbstractRadarElement:getDistanceToUnit(unitPosA, unitPosB) + return mist.utils.round(mist.utils.get2DDist(unitPosA, unitPosB, 0)) +end + +function SkynetIADSAbstractRadarElement:setAutonomousBehaviour(mode) + if mode ~= nil then + self.autonomousBehaviour = mode + end + return self +end + +function SkynetIADSAbstractRadarElement:getAutonomousBehaviour() + return self.autonomousBehaviour +end + +function SkynetIADSAbstractRadarElement:resetAutonomousState() + if self.isAutonomous == true then + self.isAutonomous = false + self:goDark() + end +end + +function SkynetIADSAbstractRadarElement:goAutonomous() + if self.isAutonomous == false then + self.isAutonomous = true + self:goDark() + self:goLive() + end +end + +function SkynetIADSAbstractRadarElement:getAutonomousState() + return self.isAutonomous +end + +function SkynetIADSAbstractRadarElement:hasWorkingRadar() + local radars = self:getRadars() + for i = 1, #radars do + local radar = radars[i] + if radar:isRadarWorking() == true then + return true + end + end + return false +end + +function SkynetIADSAbstractRadarElement:jam(successProbability) + if self:isDestroyed() == false then + local controller = self:getController() + local probability = math.random(1, 100) + if self.iads:getDebugSettings().jammerProbability then + self.iads:printOutput("JAMMER: "..self:getDescription()..": Probability: "..successProbability) + end + if successProbability > probability then + controller:setOption(AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_HOLD) + if self.iads:getDebugSettings().jammerProbability then + self.iads:printOutput("JAMMER: "..self:getDescription()..": jammed, setting to weapon hold") + end + else + controller:setOption(AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_FREE) + if self.iads:getDebugSettings().jammerProbability then + self.iads:printOutput("Jammer: "..self:getDescription()..": jammed, setting to weapon free") + end + end + self.lastJammerUpdate = timer:getTime() + end +end + +function SkynetIADSAbstractRadarElement:scanForHarms() + self:stopScanningForHARMs() + self.harmScanID = mist.scheduleFunction(SkynetIADSAbstractRadarElement.evaluateIfTargetsContainHARMs, {self}, 1, 2) +end + +function SkynetIADSAbstractElement:isScanningForHARMs() + return self.harmScanID ~= nil +end + +function SkynetIADSAbstractElement:isDefendingHARM() + return self.harmSilenceID ~= nil +end + +function SkynetIADSAbstractRadarElement:stopScanningForHARMs() + mist.removeFunction(self.harmScanID) + self.harmScanID = nil +end + +function SkynetIADSAbstractRadarElement:goSilentToEvadeHARM(timeToImpact) + self:finishHarmDefence(self) + self.objectsIdentifiedAsHarms = {} + local harmTime = self:getHarmShutDownTime() + if self.iads:getDebugSettings().harmDefence then + self.iads:printOutput("HARM DEFENCE: "..self:getDCSName().." shutting down | FOR: "..harmTime.." seconds | TTI: "..timeToImpact) + end + self.harmSilenceID = mist.scheduleFunction(SkynetIADSAbstractRadarElement.finishHarmDefence, {self}, timer.getTime() + harmTime, 1) + self:goDark() +end + +function SkynetIADSAbstractRadarElement:getHarmShutDownTime() + local shutDownTime = math.random(self.minHarmShutdownTime, self.maxHarmShutDownTime) + return shutDownTime +end + +function SkynetIADSAbstractRadarElement.finishHarmDefence(self) + mist.removeFunction(self.harmSilenceID) + self.harmSilenceID = nil +end + +function SkynetIADSAbstractRadarElement:getDetectedTargets() + if ( timer.getTime() - self.cachedTargetsCurrentAge > self.cachedTargetsMaxAge ) or ( timer.getTime() - self.goLiveTime < self.noCacheActiveForSecondsAfterGoLive ) then + self.cachedTargets = {} + self.cachedTargetsCurrentAge = timer.getTime() + if self:hasWorkingPowerSource() and self:isDestroyed() == false then + local targets = self:getController():getDetectedTargets(Controller.Detection.RADAR) + for i = 1, #targets do + local target = targets[i] + -- there are cases when a destroyed object is still visible as a target to the radar, don't add it, will cause errors everywhere the dcs object is accessed + if target.object then + local iadsTarget = SkynetIADSContact:create(target) + iadsTarget:refresh() + if self:isTargetInRange(iadsTarget) then + table.insert(self.cachedTargets, iadsTarget) + end + end + end + end + end + return self.cachedTargets +end + +function SkynetIADSAbstractRadarElement:getSecondsToImpact(distanceNM, speedKT) + local tti = 0 + if speedKT > 0 then + tti = mist.utils.round((distanceNM / speedKT) * 3600, 0) + if tti < 0 then + tti = 0 + end + end + return tti +end + +function SkynetIADSAbstractRadarElement:getDistanceInMetersToContact(radarUnit, point) + return mist.utils.round(mist.utils.get3DDist(radarUnit:getPosition().p, point), 0) +end + +function SkynetIADSAbstractRadarElement:calculateMinimalShutdownTimeInSeconds(timeToImpact) + return timeToImpact + self.minHarmPresetShutdownTime +end + +function SkynetIADSAbstractRadarElement:calculateMaximalShutdownTimeInSeconds(minShutdownTime) + return minShutdownTime + mist.random(1, self.maxHarmPresetShutdownTime) +end + +function SkynetIADSAbstractRadarElement:calculateImpactPoint(target, distanceInMeters) + -- distance needs to be incremented by a certain value for ip calculation to work, check why presumably due to rounding errors in the previous distance calculation + return land.getIP(target:getPosition().p, target:getPosition().x, distanceInMeters + 50) +end + +function SkynetIADSAbstractRadarElement:shallReactToHARM() + return self.harmDetectionChance >= math.random(1, 100) +end + +-- will only check for missiles, if DCS ads AAA than can engage HARMs then this code must be updated: +function SkynetIADSAbstractRadarElement:shallIgnoreHARMShutdown() + local numOfHarms = self:getNumberOfObjectsItentifiedAsHARMS() + return ( self:pointDefencesHaveRemainingAmmo(numOfHarms) and self:pointDefencesHaveEnoughLaunchers(numOfHarms) and self.ingnoreHARMSWhilePointDefencesHaveAmmo == true) +end + + +function SkynetIADSAbstractRadarElement:getNumberOfObjectsItentifiedAsHARMS() + local numFound = 0 + for unitName, unit in pairs(self.objectsIdentifiedAsHarms) do + numFound = numFound + 1 + end + return numFound +end + +function SkynetIADSAbstractRadarElement:cleanUpOldObjectsIdentifiedAsHARMS() + local validObjects = {} + for unitName, unit in pairs(self.objectsIdentifiedAsHarms) do + local harm = unit['target'] + if harm:getAge() <= self.objectsIdentifiedAsHarmsMaxTargetAge then + validObjects[harm:getName()] = {} + validObjects[harm:getName()]['target'] = harm + validObjects[harm:getName()]['count'] = unit['count'] + end + end + self.objectsIdentifiedAsHarms = validObjects + + --stop point defences acting as ew (always on), will occur of activated via shallIgnoreHARMShutdown() in evaluateIfTargetsContainHARMs + if self:getNumberOfObjectsItentifiedAsHARMS() == 0 then + self:pointDefencesStopActingAsEW() + end +end + + +function SkynetIADSAbstractRadarElement.evaluateIfTargetsContainHARMs(self) + + --if an emitter dies the SAM site being jammed will revert back to normal operation: + if self.lastJammerUpdate > 0 and ( timer:getTime() - self.lastJammerUpdate ) > 10 then + self:jam(0) + self.lastJammerUpdate = 0 + end + + --we use the regular interval of this method to update to other states: + self:updateMissilesInFlight() + self:cleanUpOldObjectsIdentifiedAsHARMS() + + + local targets = self:getDetectedTargets() + for i = 1, #targets do + local target = targets[i] + local radars = self:getRadars() + for j = 1, #radars do + local radar = radars[j] + if radar:isExist() == true then + local distance = self:getDistanceInMetersToContact(radar, target:getPosition().p) + local impactPoint = self:calculateImpactPoint(target, distance) + if impactPoint then + local harmImpactPointDistanceToSAM = self:getDistanceInMetersToContact(radar, impactPoint) + if harmImpactPointDistanceToSAM <= 100 then + if self.objectsIdentifiedAsHarms[target:getName()] then + self.objectsIdentifiedAsHarms[target:getName()]['count'] = self.objectsIdentifiedAsHarms[target:getName()]['count'] + 1 + else + self.objectsIdentifiedAsHarms[target:getName()] = {} + self.objectsIdentifiedAsHarms[target:getName()]['target'] = target + self.objectsIdentifiedAsHarms[target:getName()]['count'] = 1 + end + local savedTarget = self.objectsIdentifiedAsHarms[target:getName()]['target'] + savedTarget:refresh() + local numDetections = self.objectsIdentifiedAsHarms[target:getName()]['count'] + local speed = savedTarget:getGroundSpeedInKnots() + local timeToImpact = self:getSecondsToImpact(mist.utils.metersToNM(distance), speed) + local shallReactToHarm = self:shallReactToHARM() + + -- if self:getNumberOfObjectsItentifiedAsHARMS() > 0 then + -- env.info("detect as HARM: "..self:getDCSName().." "..self:getNumberOfObjectsItentifiedAsHARMS()) + -- end + + -- we use 2 detection cycles so a random object in the air pointing at the SAM site for a spilt second will not trigger a shutdown. shallReactToHarm adds some salt otherwise the SAM will always shut down 100% of the time. + if numDetections == 2 and shallReactToHarm then + if self:shallIgnoreHARMShutdown() == false then + self.minHarmShutdownTime = self:calculateMinimalShutdownTimeInSeconds(timeToImpact) + self.maxHarmShutDownTime = self:calculateMaximalShutdownTimeInSeconds(self.minHarmShutdownTime) + self:goSilentToEvadeHARM(timeToImpact) + else + self:pointDefencesGoLive() + end + end + if numDetections == 2 and shallReactToHarm == false then + if self.iads:getDebugSettings().harmDefence then + self.iads:printOutput("HARM DEFENCE: "..self:getDCSName().." will not react") + end + end + end + end + end + end + end +end + +end +do +--this class is currently used for AWACS and Ships, at a latter date a separate class for ships could be created, currently not needed +SkynetIADSAWACSRadar = {} +SkynetIADSAWACSRadar = inheritsFrom(SkynetIADSAbstractRadarElement) + +function SkynetIADSAWACSRadar:create(radarUnit, iads) + local instance = self:superClass():create(radarUnit, iads) + setmetatable(instance, self) + self.__index = self + instance.lastUpdatePosition = nil + return instance +end + +function SkynetIADSAWACSRadar:setupElements() + local unit = self:getDCSRepresentation() + local radar = SkynetIADSSAMSearchRadar:create(unit) + radar:setupRangeData() + table.insert(self.searchRadars, radar) +end + +function SkynetIADSAWACSRadar:getNatoName() + return self:getDCSRepresentation():getTypeName() +end + +-- AWACs will not scan for HARMS +function SkynetIADSAWACSRadar:scanForHarms() + +end + +function SkynetIADSAWACSRadar:getMaxAllowedMovementForAutonomousUpdateInNM() + local radarRange = mist.utils.metersToNM(self.searchRadars[1]:getMaxRangeFindingTarget()) + return mist.utils.round(radarRange / 10) +end + +function SkynetIADSAWACSRadar:isUpdateOfAutonomousStateOfSAMSitesRequired() + return self:getDistanceTraveledSinceLastUpdate() > self:getMaxAllowedMovementForAutonomousUpdateInNM() +end + +function SkynetIADSAWACSRadar:getDistanceTraveledSinceLastUpdate() + local currentPosition = nil + if self.lastUpdatePosition == nil and self:getDCSRepresentation():isExist() then + self.lastUpdatePosition = self:getDCSRepresentation():getPosition().p + end + if self:getDCSRepresentation():isExist() then + currentPosition = self:getDCSRepresentation():getPosition().p + end + return mist.utils.round(mist.utils.metersToNM(self:getDistanceToUnit(self.lastUpdatePosition, currentPosition))) +end + +end + +do +SkynetIADSCommandCenter = {} +SkynetIADSCommandCenter = inheritsFrom(SkynetIADSAbstractElement) + +function SkynetIADSCommandCenter:create(commandCenter, iads) + local instance = self:superClass():create(commandCenter, iads) + setmetatable(instance, self) + self.__index = self + instance.natoName = "Command Center" + return instance +end + +end +do + +SkynetIADSContact = {} +SkynetIADSContact = inheritsFrom(SkynetIADSAbstractDCSObjectWrapper) + +function SkynetIADSContact:create(dcsRadarTarget) + local instance = self:superClass():create(dcsRadarTarget.object) + setmetatable(instance, self) + self.__index = self + instance.firstContactTime = timer.getAbsTime() + instance.lastTimeSeen = 0 + instance.dcsRadarTarget = dcsRadarTarget + instance.name = instance.dcsObject:getName() + instance.typeName = instance.dcsObject:getTypeName() + instance.position = instance.dcsObject:getPosition() + instance.numOfTimesRefreshed = 0 + instance.speed = 0 + return instance +end + +function SkynetIADSContact:getName() + return self.name +end + +function SkynetIADSContact:getTypeName() + return self.typeName +end + +function SkynetIADSContact:isTypeKnown() + return self.dcsRadarTarget.type +end + +function SkynetIADSContact:isDistanceKnown() + return self.dcsRadarTarget.distance +end + +function SkynetIADSContact:getPosition() + return self.position +end + +function SkynetIADSContact:getGroundSpeedInKnots(decimals) + if decimals == nil then + decimals = 2 + end + return mist.utils.round(self.speed, decimals) +end + +function SkynetIADSContact:getDesc() + if self.dcsObject:isExist() then + return self.dcsObject:getDesc() + else + return {} + end +end + +function SkynetIADSContact:getNumberOfTimesHitByRadar() + return self.numOfTimesRefreshed +end + +function SkynetIADSContact:refresh() + self.numOfTimesRefreshed = self.numOfTimesRefreshed + 1 + if self.dcsObject and self.dcsObject:isExist() then + local distance = mist.utils.metersToNM(mist.utils.get2DDist(self.position.p, self.dcsObject:getPosition().p)) + local timeDelta = (timer.getAbsTime() - self.lastTimeSeen) + if timeDelta > 0 then + local hours = timeDelta / 3600 + self.speed = (distance / hours) + end + self.position = self.dcsObject:getPosition() + end + self.lastTimeSeen = timer.getAbsTime() +end + +function SkynetIADSContact:getAge() + return mist.utils.round(timer.getAbsTime() - self.lastTimeSeen) +end + +end + +do + +SkynetIADSEWRadar = {} +SkynetIADSEWRadar = inheritsFrom(SkynetIADSAbstractRadarElement) + +function SkynetIADSEWRadar:create(radarUnit, iads) + local instance = self:superClass():create(radarUnit, iads) + setmetatable(instance, self) + self.__index = self + instance.autonomousBehaviour = SkynetIADSAbstractRadarElement.AUTONOMOUS_STATE_DARK + return instance +end + +end +do + +SkynetIADSJammer = {} +SkynetIADSJammer.__index = SkynetIADSJammer + +function SkynetIADSJammer:create(emitter, iads) + local jammer = {} + setmetatable(jammer, SkynetIADSJammer) + jammer.radioMenu = nil + jammer.emitter = emitter + jammer.jammerTaskID = nil + jammer.iads = {iads} + jammer.maximumEffectiveDistanceNM = 200 + --jammer probability settings are stored here, visualisation, see: https://docs.google.com/spreadsheets/d/16rnaU49ZpOczPEsdGJ6nfD0SLPxYLEYKmmo4i2Vfoe0/edit#gid=0 + jammer.jammerTable = { + ['SA-2'] = { + ['function'] = function(distanceNauticalMiles) return ( 1.4 ^ distanceNauticalMiles ) + 90 end, + ['canjam'] = true, + }, + ['SA-3'] = { + ['function'] = function(distanceNauticalMiles) return ( 1.4 ^ distanceNauticalMiles ) + 80 end, + ['canjam'] = true, + }, + ['SA-6'] = { + ['function'] = function(distanceNauticalMiles) return ( 1.4 ^ distanceNauticalMiles ) + 23 end, + ['canjam'] = true, + }, + ['SA-8'] = { + ['function'] = function(distanceNauticalMiles) return ( 1.35 ^ distanceNauticalMiles ) + 30 end, + ['canjam'] = true, + }, + ['SA-10'] = { + ['function'] = function(distanceNauticalMiles) return ( 1.07 ^ (distanceNauticalMiles / 1.13) ) + 5 end, + ['canjam'] = true, + }, + ['SA-11'] = { + ['function'] = function(distanceNauticalMiles) return ( 1.25 ^ distanceNauticalMiles ) + 15 end, + ['canjam'] = true, + }, + ['SA-15'] = { + ['function'] = function(distanceNauticalMiles) return ( 1.15 ^ distanceNauticalMiles ) + 5 end, + ['canjam'] = true, + }, + } + return jammer +end + +function SkynetIADSJammer:masterArmOn() + self:masterArmSafe() + self.jammerTaskID = mist.scheduleFunction(SkynetIADSJammer.runCycle, {self}, 1, 10) +end + +function SkynetIADSJammer:addFunction(natoName, jammerFunction) + self.jammerTable[natoName] = { + ['function'] = jammerFunction, + ['canjam'] = true + } +end + +function SkynetIADSJammer:setMaximumEffectiveDistance(distance) + self.maximumEffectiveDistanceNM = distance +end + +function SkynetIADSJammer:disableFor(natoName) + self.jammerTable[natoName]['canjam'] = false +end + +function SkynetIADSJammer:isKnownRadarEmitter(natoName) + local isActive = false + for unitName, unit in pairs(self.jammerTable) do + if unitName == natoName and unit['canjam'] == true then + isActive = true + end + end + return isActive +end + +function SkynetIADSJammer:addIADS(iads) + table.insert(self.iads, iads) +end + +function SkynetIADSJammer:getSuccessProbability(distanceNauticalMiles, natoName) + local probability = 0 + local jammerSettings = self.jammerTable[natoName] + if jammerSettings ~= nil then + probability = jammerSettings['function'](distanceNauticalMiles) + end + return probability +end + +function SkynetIADSJammer:getDistanceNMToRadarUnit(radarUnit) + return mist.utils.metersToNM(mist.utils.get3DDist(self.emitter:getPosition().p, radarUnit:getPosition().p)) +end + +function SkynetIADSJammer.runCycle(self) + + if self.emitter:isExist() == false then + self:masterArmSafe() + return + end + + for i = 1, #self.iads do + local iads = self.iads[i] + local samSites = iads:getActiveSAMSites() + for j = 1, #samSites do + local samSite = samSites[j] + local radars = samSite:getRadars() + local hasLOS = false + local distance = 0 + local natoName = samSite:getNatoName() + for l = 1, #radars do + local radar = radars[l] + distance = self:getDistanceNMToRadarUnit(radar) + -- I try to emulate the system as it would work in real life, so a jammer can only jam a SAM site if has line of sight to at least one radar in the group + if self:isKnownRadarEmitter(natoName) and self:hasLineOfSightToRadar(radar) and distance <= self.maximumEffectiveDistanceNM then + if iads:getDebugSettings().jammerProbability then + iads:printOutput("JAMMER: Distance: "..distance) + end + samSite:jam(self:getSuccessProbability(distance, natoName)) + end + end + end + end +end + +function SkynetIADSJammer:hasLineOfSightToRadar(radar) + local radarPos = radar:getPosition().p + --lift the radar 30 meters off the ground, some 3d models are dug in to the ground, creating issues in calculating LOS + radarPos.y = radarPos.y + 30 + return land.isVisible(radarPos, self.emitter:getPosition().p) +end + +function SkynetIADSJammer:masterArmSafe() + mist.removeFunction(self.jammerTaskID) +end + +--TODO: Remove Menu when emitter dies: +function SkynetIADSJammer:addRadioMenu() + self.radioMenu = missionCommands.addSubMenu('Jammer: '..self.emitter:getName()) + missionCommands.addCommand('Master Arm On', self.radioMenu, SkynetIADSJammer.updateMasterArm, {self = self, option = 'masterArmOn'}) + missionCommands.addCommand('Master Arm Safe', self.radioMenu, SkynetIADSJammer.updateMasterArm, {self = self, option = 'masterArmSafe'}) +end + +function SkynetIADSJammer.updateMasterArm(params) + local option = params.option + local self = params.self + if option == 'masterArmOn' then + self:masterArmOn() + elseif option == 'masterArmSafe' then + self:masterArmSafe() + end +end + +function SkynetIADSJammer:removeRadioMenu() + missionCommands.removeItem(self.radioMenu) +end + +end +do + +SkynetIADSSAMSearchRadar = {} +SkynetIADSSAMSearchRadar = inheritsFrom(SkynetIADSAbstractDCSObjectWrapper) + +function SkynetIADSSAMSearchRadar:create(unit) + local instance = self:superClass():create(unit) + setmetatable(instance, self) + self.__index = self + instance.firingRangePercent = 100 + instance.maximumRange = 0 + instance.initialNumberOfMissiles = 0 + instance.remainingNumberOfMissiles = 0 + instance.initialNumberOfShells = 0 + instance.remainingNumberOfShells = 0 + instance.triedSensors = 0 + return instance +end + +--override in subclasses to match different datastructure of getSensors() +function SkynetIADSSAMSearchRadar:setupRangeData() + if self:isExist() then + local data = self:getDCSRepresentation():getSensors() + if data == nil then + --this is to prevent infinite calls between launcher and search radar + self.triedSensors = self.triedSensors + 1 + --the SA-13 does not have any sensor data, but is has launcher data, so we use the stuff from the launcher for the radar range. + SkynetIADSSAMLauncher.setupRangeData(self) + return + end + for i = 1, #data do + local subEntries = data[i] + for j = 1, #subEntries do + local sensorInformation = subEntries[j] + -- some sam sites have IR and passive EWR detection, we are just interested in the radar data + -- investigate if upperHemisphere and headOn is ok, I guess it will work for most detection cases + if sensorInformation.type == Unit.SensorType.RADAR then + local upperHemisphere = sensorInformation['detectionDistanceAir']['upperHemisphere']['headOn'] + local lowerHemisphere = sensorInformation['detectionDistanceAir']['lowerHemisphere']['headOn'] + self.maximumRange = upperHemisphere + if lowerHemisphere > upperHemisphere then + self.maximumRange = lowerHemisphere + end + end + end + end + end +end + +function SkynetIADSSAMSearchRadar:getMaxRangeFindingTarget() + return self.maximumRange +end + +function SkynetIADSSAMSearchRadar:isRadarWorking() + -- the ammo check is for the SA-13 which does not return any sensor data: + return (self:isExist() == true and ( self:getDCSRepresentation():getSensors() ~= nil or self:getDCSRepresentation():getAmmo() ~= nil ) ) +end + +function SkynetIADSSAMSearchRadar:setFiringRangePercent(percent) + self.firingRangePercent = percent +end + +function SkynetIADSSAMSearchRadar:getDistance(target) + return mist.utils.get2DDist(target:getPosition().p, self.dcsObject:getPosition().p) +end + +function SkynetIADSSAMSearchRadar:getHeight(target) + local radarElevation = self:getDCSRepresentation():getPosition().p.y + local targetElevation = target:getPosition().p.y + return math.abs(targetElevation - radarElevation) +end + +function SkynetIADSSAMSearchRadar:isInHorizontalRange(target) + return (self:getMaxRangeFindingTarget() / 100 * self.firingRangePercent) >= self:getDistance(target) +end + +function SkynetIADSSAMSearchRadar:isInRange(target) + if self:isExist() == false then + return false + end + return self:isInHorizontalRange(target) +end + +end + +do + +SkynetIADSSamSite = {} +SkynetIADSSamSite = inheritsFrom(SkynetIADSAbstractRadarElement) + +function SkynetIADSSamSite:create(samGroup, iads) + local sam = self:superClass():create(samGroup, iads) + setmetatable(sam, self) + self.__index = self + sam.targetsInRange = false + return sam +end + +function SkynetIADSSamSite:isDestroyed() + local isDestroyed = true + for i = 1, #self.launchers do + local launcher = self.launchers[i] + if launcher:isExist() == true then + isDestroyed = false + end + end + local radars = self:getRadars() + for i = 1, #radars do + local radar = radars[i] + if radar:isExist() == true then + isDestroyed = false + end + end + return isDestroyed +end + +function SkynetIADSSamSite:targetCycleUpdateStart() + self.targetsInRange = false +end + +function SkynetIADSSamSite:targetCycleUpdateEnd() + if self.targetsInRange == false and self.actAsEW == false then + self:goDark() + end +end + +function SkynetIADSSamSite:informOfContact(contact) + -- we make sure isTargetInRange (expensive call) is only triggered if no previous calls to this method resulted in targets in range + if self.targetsInRange == false and self:isTargetInRange(contact) then + self:goLive() + self.targetsInRange = true + end +end + +end +do + +SkynetIADSSAMTrackingRadar = {} +SkynetIADSSAMTrackingRadar = inheritsFrom(SkynetIADSSAMSearchRadar) + +function SkynetIADSSAMTrackingRadar:create(unit) + local instance = self:superClass():create(unit) + setmetatable(instance, self) + self.__index = self + return instance +end + +end +do + +SkynetIADSSAMLauncher = {} +SkynetIADSSAMLauncher = inheritsFrom(SkynetIADSSAMSearchRadar) + +function SkynetIADSSAMLauncher:create(unit) + local instance = self:superClass():create(unit) + setmetatable(instance, self) + self.__index = self + instance.maximumFiringAltitude = 0 + return instance +end + +function SkynetIADSSAMLauncher:setupRangeData() + self.remainingNumberOfMissiles = 0 + self.remainingNumberOfShells = 0 + if self:isExist() then + local data = self:getDCSRepresentation():getAmmo() + local initialNumberOfMissiles = 0 + local initialNumberOfShells = 0 + --data becomes nil, when all missiles are fired + if data then + for i = 1, #data do + local ammo = data[i] + --we ignore checks on radar guidance types, since we are not interested in how exactly the missile is guided by the SAM site. + if ammo.desc.category == Weapon.Category.MISSILE then + --TODO: see what the difference is between Max and Min values, SA-3 has higher Min value than Max?, most likely it has to do with the box parameters supplied by launcher + --to simplyfy we just use the larger value, sam sites need a few seconds of tracking time to fire, by that time contact has most likely closed in on the SAM site. + local altMin = ammo.desc.rangeMaxAltMin + local altMax = ammo.desc.rangeMaxAltMax + self.maximumRange = altMin + if altMin < altMax then + self.maximumRange = altMax + end + self.maximumFiringAltitude = ammo.desc.altMax + self.remainingNumberOfMissiles = self.remainingNumberOfMissiles + ammo.count + initialNumberOfMissiles = self.remainingNumberOfMissiles + end + if ammo.desc.category == Weapon.Category.SHELL then + self.remainingNumberOfShells = self.remainingNumberOfShells + ammo.count + initialNumberOfShells = self.remainingNumberOfShells + end + --if no distance was detected we run the code for the search radar. This happens when all in one units are passed like the shilka + if self.maximumRange == 0 then + --this is to prevent infinite calls between launcher and search radar + if self.triedSensors <= 2 then + SkynetIADSSAMSearchRadar.setupRangeData(self) + end + end + end + -- conditions here are because setupRangeData() is called multiple times in the code to update ammo status, we set initial values only the first time the method is called + if self.initialNumberOfMissiles == 0 then + self.initialNumberOfMissiles = initialNumberOfMissiles + end + if self.initialNumberOfShells == 0 then + self.initialNumberOfShells = initialNumberOfShells + end + end + end +end + +function SkynetIADSSAMLauncher:getInitialNumberOfShells() + return self.initialNumberOfShells +end + +function SkynetIADSSAMLauncher:getRemainingNumberOfShells() + self:setupRangeData() + return self.remainingNumberOfShells +end + +function SkynetIADSSAMLauncher:getInitialNumberOfMissiles() + return self.initialNumberOfMissiles +end + +function SkynetIADSSAMLauncher:getRemainingNumberOfMissiles() + self:setupRangeData() + return self.remainingNumberOfMissiles +end + +function SkynetIADSSAMLauncher:getRange() + return self.maximumRange +end + +function SkynetIADSSAMLauncher:getMaximumFiringAltitude() + return self.maximumFiringAltitude +end + +function SkynetIADSSAMLauncher:isWithinFiringHeight(target) + -- if no max firing height is set (radar quided AAA) then we use the vertical range, bit of a hack but probably ok for AAA + if self:getMaximumFiringAltitude() > 0 then + return self:getMaximumFiringAltitude() >= self:getHeight(target) + else + return self:getRange() >= self:getHeight(target) + end +end + +function SkynetIADSSAMLauncher:isInRange(target) + if self:isExist() == false then + return false + end + return self:isWithinFiringHeight(target) and self:isInHorizontalRange(target) +end + +end + +--[[ +SA-2 Launcher: + { + count=1, + desc={ + Nmax=17, + RCS=0.39669999480247, + _origin="", + altMax=25000, + altMin=100, + box={ + max={x=4.7303376197815, y=0.84564626216888, z=0.84564626216888}, + min={x=-5.8387970924377, y=-0.84564626216888, z=-0.84564626216888} + }, + category=1, + displayName="SA2V755", + fuseDist=20, + guidance=4, + life=2, + missileCategory=2, + rangeMaxAltMax=30000, + rangeMaxAltMin=40000, + rangeMin=7000, + typeName="SA2V755", + warhead={caliber=500, explosiveMass=196, mass=196, type=1} + } + } +} +--]] diff --git a/resources/plugins/skynetiads/skynetiads-config.lua b/resources/plugins/skynetiads/skynetiads-config.lua new file mode 100644 index 00000000..35c4d534 --- /dev/null +++ b/resources/plugins/skynetiads/skynetiads-config.lua @@ -0,0 +1,130 @@ +------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Mission configuration file for the Skynet-IADS framework +-- see https://github.com/walder/Skynet-IADS +-- +-- This configuration is tailored for a mission generated by DCS Liberation +-- see https://github.com/Khopa/dcs_liberation +------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- Skynet-IADS plugin - configuration +env.info("DCSLiberation|Skynet-IADS plugin - configuration") + +if dcsLiberation and SkynetIADS then + + -- specific options + local createRedIADS = false + local createBlueIADS = false + local actAsEwrRED = false + local actAsEwrBLUE = false + local includeRedInRadio = false + local includeBlueInRadio = false + local debugRED = false + local debugBLUE = false + + -- retrieve specific options values + if dcsLiberation.plugins then + if dcsLiberation.plugins.skynetiads then + createRedIADS = dcsLiberation.plugins.skynetiads.createRedIADS + createBlueIADS = dcsLiberation.plugins.skynetiads.createBlueIADS + actAsEwrRED = dcsLiberation.plugins.skynetiads.actAsEwrRED + actAsEwrBLUE = dcsLiberation.plugins.skynetiads.actAsEwrBLUE + includeRedInRadio = dcsLiberation.plugins.skynetiads.includeRedInRadio + includeBlueInRadio = dcsLiberation.plugins.skynetiads.includeBlueInRadio + debugRED = dcsLiberation.plugins.skynetiads.debugRED + debugBLUE = dcsLiberation.plugins.skynetiads.debugBLUE + end + end + + env.info(string.format("DCSLiberation|Skynet-IADS plugin - createRedIADS=%s",tostring(createRedIADS))) + env.info(string.format("DCSLiberation|Skynet-IADS plugin - createBlueIADS=%s",tostring(createBlueIADS))) + env.info(string.format("DCSLiberation|Skynet-IADS plugin - actAsEwrRED=%s",tostring(actAsEwrRED))) + env.info(string.format("DCSLiberation|Skynet-IADS plugin - actAsEwrBLUE=%s",tostring(actAsEwrBLUE))) + env.info(string.format("DCSLiberation|Skynet-IADS plugin - includeRedInRadio=%s",tostring(includeRedInRadio))) + env.info(string.format("DCSLiberation|Skynet-IADS plugin - includeBlueInRadio=%s",tostring(includeBlueInRadio))) + env.info(string.format("DCSLiberation|Skynet-IADS plugin - debugRED=%s",tostring(debugRED))) + env.info(string.format("DCSLiberation|Skynet-IADS plugin - debugBLUE=%s",tostring(debugBLUE))) + + -- actual configuration code + + local function initializeIADS(iads, coalition, actAsEwr, inRadio, debug) + + local coalitionPrefix = "BLUE" + if coalition == 1 then + coalitionPrefix = "RED" + end + + if debug then + env.info("adding debug information") + local iadsDebug = iads:getDebugSettings() + iadsDebug.IADSStatus = true + iadsDebug.samWentDark = true + iadsDebug.contacts = true + iadsDebug.radarWentLive = true + iadsDebug.noWorkingCommmandCenter = false + iadsDebug.ewRadarNoConnection = false + iadsDebug.samNoConnection = false + iadsDebug.jammerProbability = true + iadsDebug.addedEWRadar = false + iadsDebug.hasNoPower = false + iadsDebug.harmDefence = true + iadsDebug.samSiteStatusEnvOutput = true + iadsDebug.earlyWarningRadarStatusEnvOutput = true + end + + --add EW units to the IADS: + iads:addEarlyWarningRadarsByPrefix(coalitionPrefix .. " EW") + + --add SAM groups to the IADS: + iads:addSAMSitesByPrefix(coalitionPrefix .. " SAM") + + -- specific configurations, for each SAM type + if actAsEwr then + iads:getSAMSitesByNatoName('SA-10'):setActAsEW(true) + iads:getSAMSitesByNatoName('SA-6'):setActAsEW(true) + iads:getSAMSitesByNatoName('Patriot'):setActAsEW(true) + end + + -- add the AWACS + if dcsLiberation.AWACs then + for _, data in pairs(dcsLiberation.AWACs) do + env.info(string.format("DCSLiberation|Skynet-IADS plugin - processing AWACS %s", data.dcsGroupName)) + local group = Group.getByName(data.dcsGroupName) + if group then + if group:getCoalition() == coalition then + local unit = group:getUnit(1) + if unit then + local unitName = unit:getName() + env.info(string.format("DCSLiberation|Skynet-IADS plugin - adding AWACS %s", unitName)) + iads:addEarlyWarningRadar(unitName) + end + end + end + end + end + + if inRadio then + --activate the radio menu to toggle IADS Status output + env.info("DCSLiberation|Skynet-IADS plugin - adding in radio menu") + iads:addRadioMenu() + end + + --activate the IADS + iads:activate() + end + + ------------------------------------------------------------------------------------------------------------------------------------------------------------ + -- create the IADS networks + ------------------------------------------------------------------------------------------------------------------------------------------------------------- + if createRedIADS then + env.info("DCSLiberation|Skynet-IADS plugin - creating red IADS") + redIADS = SkynetIADS:create("IADS") + initializeIADS(redIADS, 1, actAsEwrRED, includeRedInRadio, debugRED) -- RED + end + + if createBlueIADS then + env.info("DCSLiberation|Skynet-IADS plugin - creating blue IADS") + blueIADS = SkynetIADS:create("IADS") + initializeIADS(blueIADS, 2, actAsEwrBLUE, includeBlueInRadio, debugBLUE) -- BLUE + end + +end \ No newline at end of file From 3ba2fb76aada52e79874e2d517036b8bda229c88 Mon Sep 17 00:00:00 2001 From: David Pierron Date: Thu, 22 Oct 2020 18:40:40 +0200 Subject: [PATCH 2/8] removed VEAF from the default plugins --- resources/plugins/plugins.json | 1 - 1 file changed, 1 deletion(-) diff --git a/resources/plugins/plugins.json b/resources/plugins/plugins.json index e313029b..6d4fb155 100644 --- a/resources/plugins/plugins.json +++ b/resources/plugins/plugins.json @@ -1,5 +1,4 @@ [ - "veaf", "skynetiads", "jtacautolase", "base" From c8955bdca737519ee042f7ca19a789a9f9150e74 Mon Sep 17 00:00:00 2001 From: David Pierron Date: Fri, 23 Oct 2020 07:49:26 +0200 Subject: [PATCH 3/8] activate the SAM at mission start, to haste their response --- resources/plugins/skynetiads/skynetiads-config.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/plugins/skynetiads/skynetiads-config.lua b/resources/plugins/skynetiads/skynetiads-config.lua index 35c4d534..ea6cb4a5 100644 --- a/resources/plugins/skynetiads/skynetiads-config.lua +++ b/resources/plugins/skynetiads/skynetiads-config.lua @@ -109,7 +109,7 @@ if dcsLiberation and SkynetIADS then end --activate the IADS - iads:activate() + iads:setupSAMSitesAndThenActivate() end ------------------------------------------------------------------------------------------------------------------------------------------------------------ From adb935290588af5402be521875b944210c5e9527 Mon Sep 17 00:00:00 2001 From: David Pierron Date: Sat, 24 Oct 2020 11:02:46 +0200 Subject: [PATCH 4/8] removed mist.lua from skynet plugin It's already included in the base plugin, which is mandatory --- resources/plugins/skynetiads/mist_4_3_74.lua | 6825 ------------------ resources/plugins/skynetiads/plugin.json | 4 - 2 files changed, 6829 deletions(-) delete mode 100644 resources/plugins/skynetiads/mist_4_3_74.lua diff --git a/resources/plugins/skynetiads/mist_4_3_74.lua b/resources/plugins/skynetiads/mist_4_3_74.lua deleted file mode 100644 index 4aa9db47..00000000 --- a/resources/plugins/skynetiads/mist_4_3_74.lua +++ /dev/null @@ -1,6825 +0,0 @@ ---[[-- -MIST Mission Scripting Tools. -## Description: -MIssion Scripting Tools (MIST) is a collection of Lua functions -and databases that is intended to be a supplement to the standard -Lua functions included in the simulator scripting engine. - -MIST functions and databases provide ready-made solutions to many common -scripting tasks and challenges, enabling easier scripting and saving -mission scripters time. The table mist.flagFuncs contains a set of -Lua functions (that are similar to Slmod functions) that do not -require detailed Lua knowledge to use. - -However, the majority of MIST does require knowledge of the Lua language, -and, if you are going to utilize these components of MIST, it is necessary -that you read the Simulator Scripting Engine guide on the official ED wiki. - -## Links: - -ED Forum Thread: - -##Github: - -Development - -Official Releases - -@script MIST -@author Speed -@author Grimes -@author lukrop -]] -mist = {} - --- don't change these -mist.majorVersion = 4 -mist.minorVersion = 3 -mist.build = 74 - --- forward declaration of log shorthand -local log - -do -- the main scope - local coroutines = {} - - local tempSpawnedUnits = {} -- birth events added here - local tempSpawnedGroups = {} - local tempSpawnGroupsCounter = 0 - - local mistAddedObjects = {} -- mist.dynAdd unit data added here - local mistAddedGroups = {} -- mist.dynAdd groupdata added here - local writeGroups = {} - local lastUpdateTime = 0 - - local updateAliveUnitsCounter = 0 - local updateTenthSecond = 0 - - local mistGpId = 7000 - local mistUnitId = 7000 - local mistDynAddIndex = {[' air '] = 0, [' hel '] = 0, [' gnd '] = 0, [' bld '] = 0, [' static '] = 0, [' shp '] = 0} - - local scheduledTasks = {} - local taskId = 0 - local idNum = 0 - - mist.nextGroupId = 1 - mist.nextUnitId = 1 - - local dbLog - - local function initDBs() -- mist.DBs scope - mist.DBs = {} - - mist.DBs.missionData = {} - if env.mission then - - mist.DBs.missionData.startTime = env.mission.start_time - mist.DBs.missionData.theatre = env.mission.theatre - mist.DBs.missionData.version = env.mission.version - mist.DBs.missionData.files = {} - if type(env.mission.resourceCounter) == 'table' then - for fIndex, fData in pairs (env.mission.resourceCounter) do - mist.DBs.missionData.files[#mist.DBs.missionData.files + 1] = mist.utils.deepCopy(fIndex) - end - end - -- if we add more coalition specific data then bullsye should be categorized by coaliton. For now its just the bullseye table - mist.DBs.missionData.bullseye = {red = {}, blue = {}} - mist.DBs.missionData.bullseye.red.x = env.mission.coalition.red.bullseye.x --should it be point.x? - mist.DBs.missionData.bullseye.red.y = env.mission.coalition.red.bullseye.y - mist.DBs.missionData.bullseye.blue.x = env.mission.coalition.blue.bullseye.x - mist.DBs.missionData.bullseye.blue.y = env.mission.coalition.blue.bullseye.y - end - - mist.DBs.zonesByName = {} - mist.DBs.zonesByNum = {} - - - if env.mission.triggers and env.mission.triggers.zones then - for zone_ind, zone_data in pairs(env.mission.triggers.zones) do - if type(zone_data) == 'table' then - local zone = mist.utils.deepCopy(zone_data) - zone.point = {} -- point is used by SSE - zone.point.x = zone_data.x - zone.point.y = 0 - zone.point.z = zone_data.y - - mist.DBs.zonesByName[zone_data.name] = zone - mist.DBs.zonesByNum[#mist.DBs.zonesByNum + 1] = mist.utils.deepCopy(zone) --[[deepcopy so that the zone in zones_by_name and the zone in - zones_by_num se are different objects.. don't want them linked.]] - end - end - end - - mist.DBs.navPoints = {} - mist.DBs.units = {} - --Build mist.db.units and mist.DBs.navPoints - for coa_name, coa_data in pairs(env.mission.coalition) do - - if (coa_name == 'red' or coa_name == 'blue') and type(coa_data) == 'table' then - mist.DBs.units[coa_name] = {} - - -- build nav points DB - mist.DBs.navPoints[coa_name] = {} - if coa_data.nav_points then --navpoints - --mist.debug.writeData (mist.utils.serialize,{'NavPoints',coa_data.nav_points}, 'NavPoints.txt') - for nav_ind, nav_data in pairs(coa_data.nav_points) do - - if type(nav_data) == 'table' then - mist.DBs.navPoints[coa_name][nav_ind] = mist.utils.deepCopy(nav_data) - - mist.DBs.navPoints[coa_name][nav_ind].name = nav_data.callsignStr -- name is a little bit more self-explanatory. - mist.DBs.navPoints[coa_name][nav_ind].point = {} -- point is used by SSE, support it. - mist.DBs.navPoints[coa_name][nav_ind].point.x = nav_data.x - mist.DBs.navPoints[coa_name][nav_ind].point.y = 0 - mist.DBs.navPoints[coa_name][nav_ind].point.z = nav_data.y - end - end - end - if coa_data.country then --there is a country table - for cntry_id, cntry_data in pairs(coa_data.country) do - - local countryName = string.lower(cntry_data.name) - mist.DBs.units[coa_name][countryName] = {} - mist.DBs.units[coa_name][countryName].countryId = cntry_data.id - - if type(cntry_data) == 'table' then --just making sure - - for obj_type_name, obj_type_data in pairs(cntry_data) do - - if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" or obj_type_name == "static" then --should be an unncessary check - - local category = obj_type_name - - if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then --there's a group! - - mist.DBs.units[coa_name][countryName][category] = {} - - for group_num, group_data in pairs(obj_type_data.group) do - - if group_data and group_data.units and type(group_data.units) == 'table' then --making sure again- this is a valid group - - mist.DBs.units[coa_name][countryName][category][group_num] = {} - local groupName = group_data.name - if env.mission.version > 7 then - groupName = env.getValueDictByKey(groupName) - end - mist.DBs.units[coa_name][countryName][category][group_num].groupName = groupName - mist.DBs.units[coa_name][countryName][category][group_num].groupId = group_data.groupId - mist.DBs.units[coa_name][countryName][category][group_num].category = category - mist.DBs.units[coa_name][countryName][category][group_num].coalition = coa_name - mist.DBs.units[coa_name][countryName][category][group_num].country = countryName - mist.DBs.units[coa_name][countryName][category][group_num].countryId = cntry_data.id - mist.DBs.units[coa_name][countryName][category][group_num].startTime = group_data.start_time - mist.DBs.units[coa_name][countryName][category][group_num].task = group_data.task - mist.DBs.units[coa_name][countryName][category][group_num].hidden = group_data.hidden - - mist.DBs.units[coa_name][countryName][category][group_num].units = {} - - mist.DBs.units[coa_name][countryName][category][group_num].radioSet = group_data.radioSet - mist.DBs.units[coa_name][countryName][category][group_num].uncontrolled = group_data.uncontrolled - mist.DBs.units[coa_name][countryName][category][group_num].frequency = group_data.frequency - mist.DBs.units[coa_name][countryName][category][group_num].modulation = group_data.modulation - - for unit_num, unit_data in pairs(group_data.units) do - local units_tbl = mist.DBs.units[coa_name][countryName][category][group_num].units --pointer to the units table for this group - - units_tbl[unit_num] = {} - if env.mission.version > 7 then - units_tbl[unit_num].unitName = env.getValueDictByKey(unit_data.name) - else - units_tbl[unit_num].unitName = unit_data.name - end - units_tbl[unit_num].type = unit_data.type - units_tbl[unit_num].skill = unit_data.skill --will be nil for statics - units_tbl[unit_num].unitId = unit_data.unitId - units_tbl[unit_num].category = category - units_tbl[unit_num].coalition = coa_name - units_tbl[unit_num].country = countryName - units_tbl[unit_num].countryId = cntry_data.id - units_tbl[unit_num].heading = unit_data.heading - units_tbl[unit_num].playerCanDrive = unit_data.playerCanDrive - units_tbl[unit_num].alt = unit_data.alt - units_tbl[unit_num].alt_type = unit_data.alt_type - units_tbl[unit_num].speed = unit_data.speed - units_tbl[unit_num].livery_id = unit_data.livery_id - if unit_data.point then --ME currently does not work like this, but it might one day - units_tbl[unit_num].point = unit_data.point - else - units_tbl[unit_num].point = {} - units_tbl[unit_num].point.x = unit_data.x - units_tbl[unit_num].point.y = unit_data.y - end - units_tbl[unit_num].x = unit_data.x - units_tbl[unit_num].y = unit_data.y - - units_tbl[unit_num].callsign = unit_data.callsign - units_tbl[unit_num].onboard_num = unit_data.onboard_num - units_tbl[unit_num].hardpoint_racks = unit_data.hardpoint_racks - units_tbl[unit_num].psi = unit_data.psi - - - units_tbl[unit_num].groupName = groupName - units_tbl[unit_num].groupId = group_data.groupId - - if unit_data.AddPropAircraft then - units_tbl[unit_num].AddPropAircraft = unit_data.AddPropAircraft - end - - if category == 'static' then - units_tbl[unit_num].categoryStatic = unit_data.category - units_tbl[unit_num].shape_name = unit_data.shape_name - if unit_data.mass then - units_tbl[unit_num].mass = unit_data.mass - end - - if unit_data.canCargo then - units_tbl[unit_num].canCargo = unit_data.canCargo - end - end - - end --for unit_num, unit_data in pairs(group_data.units) do - end --if group_data and group_data.units then - end --for group_num, group_data in pairs(obj_type_data.group) do - end --if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then - end --if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" or obj_type_name == "static" then - end --for obj_type_name, obj_type_data in pairs(cntry_data) do - end --if type(cntry_data) == 'table' then - end --for cntry_id, cntry_data in pairs(coa_data.country) do - end --if coa_data.country then --there is a country table - end --if coa_name == 'red' or coa_name == 'blue' and type(coa_data) == 'table' then - end --for coa_name, coa_data in pairs(mission.coalition) do - - mist.DBs.unitsByName = {} - mist.DBs.unitsById = {} - mist.DBs.unitsByCat = {} - - mist.DBs.unitsByCat.helicopter = {} -- adding default categories - mist.DBs.unitsByCat.plane = {} - mist.DBs.unitsByCat.ship = {} - mist.DBs.unitsByCat.static = {} - mist.DBs.unitsByCat.vehicle = {} - - mist.DBs.unitsByNum = {} - - mist.DBs.groupsByName = {} - mist.DBs.groupsById = {} - mist.DBs.humansByName = {} - mist.DBs.humansById = {} - - mist.DBs.dynGroupsAdded = {} -- will be filled by mist.dbUpdate from dynamically spawned groups - mist.DBs.activeHumans = {} - - mist.DBs.aliveUnits = {} -- will be filled in by the "updateAliveUnits" coroutine in mist.main. - - mist.DBs.removedAliveUnits = {} -- will be filled in by the "updateAliveUnits" coroutine in mist.main. - - mist.DBs.const = {} - - -- not accessible by SSE, must use static list :-/ - mist.DBs.const.callsigns = { - ['NATO'] = { - ['rules'] = { - ['groupLimit'] = 9, - }, - ['AWACS'] = { - ['Overlord'] = 1, - ['Magic'] = 2, - ['Wizard'] = 3, - ['Focus'] = 4, - ['Darkstar'] = 5, - }, - ['TANKER'] = { - ['Texaco'] = 1, - ['Arco'] = 2, - ['Shell'] = 3, - }, - ['JTAC'] = { - ['Axeman'] = 1, - ['Darknight'] = 2, - ['Warrior'] = 3, - ['Pointer'] = 4, - ['Eyeball'] = 5, - ['Moonbeam'] = 6, - ['Whiplash'] = 7, - ['Finger'] = 8, - ['Pinpoint'] = 9, - ['Ferret'] = 10, - ['Shaba'] = 11, - ['Playboy'] = 12, - ['Hammer'] = 13, - ['Jaguar'] = 14, - ['Deathstar'] = 15, - ['Anvil'] = 16, - ['Firefly'] = 17, - ['Mantis'] = 18, - ['Badger'] = 19, - }, - ['aircraft'] = { - ['Enfield'] = 1, - ['Springfield'] = 2, - ['Uzi'] = 3, - ['Colt'] = 4, - ['Dodge'] = 5, - ['Ford'] = 6, - ['Chevy'] = 7, - ['Pontiac'] = 8, - }, - - ['unique'] = { - ['A10'] = { - ['Hawg'] = 9, - ['Boar'] = 10, - ['Pig'] = 11, - ['Tusk'] = 12, - ['rules'] = { - ['canUseAircraft'] = true, - ['appliesTo'] = { - 'A-10C', - 'A-10A', - }, - }, - }, - }, - }, - } - mist.DBs.const.shapeNames = { - ["Landmine"] = "landmine", - ["FARP CP Blindage"] = "kp_ug", - ["Subsidiary structure C"] = "saray-c", - ["Barracks 2"] = "kazarma2", - ["Small house 2C"] = "dom2c", - ["Military staff"] = "aviashtab", - ["Tech hangar A"] = "ceh_ang_a", - ["Oil derrick"] = "neftevyshka", - ["Tech combine"] = "kombinat", - ["Garage B"] = "garage_b", - ["Airshow_Crowd"] = "Crowd1", - ["Hangar A"] = "angar_a", - ["Repair workshop"] = "tech", - ["Subsidiary structure D"] = "saray-d", - ["FARP Ammo Dump Coating"] = "SetkaKP", - ["Small house 1C area"] = "dom2c-all", - ["Tank 2"] = "airbase_tbilisi_tank_01", - ["Boiler-house A"] = "kotelnaya_a", - ["Workshop A"] = "tec_a", - ["Small werehouse 1"] = "s1", - ["Garage small B"] = "garagh-small-b", - ["Small werehouse 4"] = "s4", - ["Shop"] = "magazin", - ["Subsidiary structure B"] = "saray-b", - ["FARP Fuel Depot"] = "GSM Rus", - ["Coach cargo"] = "wagon-gruz", - ["Electric power box"] = "tr_budka", - ["Tank 3"] = "airbase_tbilisi_tank_02", - ["Red_Flag"] = "H-flag_R", - ["Container red 3"] = "konteiner_red3", - ["Garage A"] = "garage_a", - ["Hangar B"] = "angar_b", - ["Black_Tyre"] = "H-tyre_B", - ["Cafe"] = "stolovaya", - ["Restaurant 1"] = "restoran1", - ["Subsidiary structure A"] = "saray-a", - ["Container white"] = "konteiner_white", - ["Warehouse"] = "sklad", - ["Tank"] = "bak", - ["Railway crossing B"] = "pereezd_small", - ["Subsidiary structure F"] = "saray-f", - ["Farm A"] = "ferma_a", - ["Small werehouse 3"] = "s3", - ["Water tower A"] = "wodokachka_a", - ["Railway station"] = "r_vok_sd", - ["Coach a tank blue"] = "wagon-cisterna_blue", - ["Supermarket A"] = "uniwersam_a", - ["Coach a platform"] = "wagon-platforma", - ["Garage small A"] = "garagh-small-a", - ["TV tower"] = "tele_bash", - ["Comms tower M"] = "tele_bash_m", - ["Small house 1A"] = "domik1a", - ["Farm B"] = "ferma_b", - ["GeneratorF"] = "GeneratorF", - ["Cargo1"] = "ab-212_cargo", - ["Container red 2"] = "konteiner_red2", - ["Subsidiary structure E"] = "saray-e", - ["Coach a passenger"] = "wagon-pass", - ["Black_Tyre_WF"] = "H-tyre_B_WF", - ["Electric locomotive"] = "elektrowoz", - ["Shelter"] = "ukrytie", - ["Coach a tank yellow"] = "wagon-cisterna_yellow", - ["Railway crossing A"] = "pereezd_big", - [".Ammunition depot"] = "SkladC", - ["Small werehouse 2"] = "s2", - ["Windsock"] = "H-Windsock_RW", - ["Shelter B"] = "ukrytie_b", - ["Fuel tank"] = "toplivo-bak", - ["Locomotive"] = "teplowoz", - [".Command Center"] = "ComCenter", - ["Pump station"] = "nasos", - ["Black_Tyre_RF"] = "H-tyre_B_RF", - ["Coach cargo open"] = "wagon-gruz-otkr", - ["Subsidiary structure 3"] = "hozdomik3", - ["FARP Tent"] = "PalatkaB", - ["White_Tyre"] = "H-tyre_W", - ["Subsidiary structure G"] = "saray-g", - ["Container red 1"] = "konteiner_red1", - ["Small house 1B area"] = "domik1b-all", - ["Subsidiary structure 1"] = "hozdomik1", - ["Container brown"] = "konteiner_brown", - ["Small house 1B"] = "domik1b", - ["Subsidiary structure 2"] = "hozdomik2", - ["Chemical tank A"] = "him_bak_a", - ["WC"] = "WC", - ["Small house 1A area"] = "domik1a-all", - ["White_Flag"] = "H-Flag_W", - ["Airshow_Cone"] = "Comp_cone", - } - - - -- create mist.DBs.oldAliveUnits - -- do - -- local intermediate_alive_units = {} -- between 0 and 0.5 secs old - -- local function make_old_alive_units() -- called every 0.5 secs, makes the old_alive_units DB which is just a copy of alive_units that is 0.5 to 1 sec old - -- if intermediate_alive_units then - -- mist.DBs.oldAliveUnits = mist.utils.deepCopy(intermediate_alive_units) - -- end - -- intermediate_alive_units = mist.utils.deepCopy(mist.DBs.aliveUnits) - -- timer.scheduleFunction(make_old_alive_units, nil, timer.getTime() + 0.5) - -- end - - -- make_old_alive_units() - -- end - - --Build DBs - for coa_name, coa_data in pairs(mist.DBs.units) do - for cntry_name, cntry_data in pairs(coa_data) do - for category_name, category_data in pairs(cntry_data) do - if type(category_data) == 'table' then - for group_ind, group_data in pairs(category_data) do - if type(group_data) == 'table' and group_data.units and type(group_data.units) == 'table' and #group_data.units > 0 then -- OCD paradigm programming - mist.DBs.groupsByName[group_data.groupName] = mist.utils.deepCopy(group_data) - mist.DBs.groupsById[group_data.groupId] = mist.utils.deepCopy(group_data) - for unit_ind, unit_data in pairs(group_data.units) do - mist.DBs.unitsByName[unit_data.unitName] = mist.utils.deepCopy(unit_data) - mist.DBs.unitsById[unit_data.unitId] = mist.utils.deepCopy(unit_data) - - mist.DBs.unitsByCat[unit_data.category] = mist.DBs.unitsByCat[unit_data.category] or {} -- future-proofing against new categories... - table.insert(mist.DBs.unitsByCat[unit_data.category], mist.utils.deepCopy(unit_data)) - dbLog:info('inserting $1', unit_data.unitName) - table.insert(mist.DBs.unitsByNum, mist.utils.deepCopy(unit_data)) - - if unit_data.skill and (unit_data.skill == "Client" or unit_data.skill == "Player") then - mist.DBs.humansByName[unit_data.unitName] = mist.utils.deepCopy(unit_data) - mist.DBs.humansById[unit_data.unitId] = mist.utils.deepCopy(unit_data) - --if Unit.getByName(unit_data.unitName) then - -- mist.DBs.activeHumans[unit_data.unitName] = mist.utils.deepCopy(unit_data) - -- mist.DBs.activeHumans[unit_data.unitName].playerName = Unit.getByName(unit_data.unitName):getPlayerName() - --end - end - end - end - end - end - end - end - end - - --DynDBs - mist.DBs.MEunits = mist.utils.deepCopy(mist.DBs.units) - mist.DBs.MEunitsByName = mist.utils.deepCopy(mist.DBs.unitsByName) - mist.DBs.MEunitsById = mist.utils.deepCopy(mist.DBs.unitsById) - mist.DBs.MEunitsByCat = mist.utils.deepCopy(mist.DBs.unitsByCat) - mist.DBs.MEunitsByNum = mist.utils.deepCopy(mist.DBs.unitsByNum) - mist.DBs.MEgroupsByName = mist.utils.deepCopy(mist.DBs.groupsByName) - mist.DBs.MEgroupsById = mist.utils.deepCopy(mist.DBs.groupsById) - - mist.DBs.deadObjects = {} - - do - local mt = {} - - function mt.__newindex(t, key, val) - local original_key = key --only for duplicate runtime IDs. - local key_ind = 1 - while mist.DBs.deadObjects[key] do - dbLog:warn('duplicate runtime id of previously dead object key: $1', key) - key = tostring(original_key) .. ' #' .. tostring(key_ind) - key_ind = key_ind + 1 - end - - if mist.DBs.aliveUnits and mist.DBs.aliveUnits[val.object.id_] then - --dbLog:info('object found in alive_units') - val.objectData = mist.utils.deepCopy(mist.DBs.aliveUnits[val.object.id_]) - local pos = Object.getPosition(val.object) - if pos then - val.objectPos = pos.p - end - val.objectType = mist.DBs.aliveUnits[val.object.id_].category - - elseif mist.DBs.removedAliveUnits and mist.DBs.removedAliveUnits[val.object.id_] then -- it didn't exist in alive_units, check old_alive_units - --dbLog:info('object found in old_alive_units') - val.objectData = mist.utils.deepCopy(mist.DBs.removedAliveUnits[val.object.id_]) - local pos = Object.getPosition(val.object) - if pos then - val.objectPos = pos.p - end - val.objectType = mist.DBs.removedAliveUnits[val.object.id_].category - - else --attempt to determine if static object... - --dbLog:info('object not found in alive units or old alive units') - local pos = Object.getPosition(val.object) - if pos then - local static_found = false - for ind, static in pairs(mist.DBs.unitsByCat.static) do - if ((pos.p.x - static.point.x)^2 + (pos.p.z - static.point.y)^2)^0.5 < 0.1 then --really, it should be zero... - dbLog:info('correlated dead static object to position') - val.objectData = static - val.objectPos = pos.p - val.objectType = 'static' - static_found = true - break - end - end - if not static_found then - val.objectPos = pos.p - val.objectType = 'building' - end - else - val.objectType = 'unknown' - end - end - rawset(t, key, val) - end - - setmetatable(mist.DBs.deadObjects, mt) - end - - do -- mist unitID funcs - for id, idData in pairs(mist.DBs.unitsById) do - if idData.unitId > mist.nextUnitId then - mist.nextUnitId = mist.utils.deepCopy(idData.unitId) - end - if idData.groupId > mist.nextGroupId then - mist.nextGroupId = mist.utils.deepCopy(idData.groupId) - end - end - end - - - end - - local function updateAliveUnits() -- coroutine function - local lalive_units = mist.DBs.aliveUnits -- local references for faster execution - local lunits = mist.DBs.unitsByNum - local ldeepcopy = mist.utils.deepCopy - local lUnit = Unit - local lremovedAliveUnits = mist.DBs.removedAliveUnits - local updatedUnits = {} - - if #lunits > 0 then - local units_per_run = math.ceil(#lunits/20) - if units_per_run < 5 then - units_per_run = 5 - end - - for i = 1, #lunits do - if lunits[i].category ~= 'static' then -- can't get statics with Unit.getByName :( - local unit = lUnit.getByName(lunits[i].unitName) - if unit then - --dbLog:info("unit named $1 alive!", lunits[i].unitName) -- spammy - local pos = unit:getPosition() - local newtbl = ldeepcopy(lunits[i]) - if pos then - newtbl.pos = pos.p - end - newtbl.unit = unit - --newtbl.rt_id = unit.id_ - lalive_units[unit.id_] = newtbl - updatedUnits[unit.id_] = true - end - end - if i%units_per_run == 0 then - coroutine.yield() - end - end - -- All units updated, remove any "alive" units that were not updated- they are dead! - for unit_id, unit in pairs(lalive_units) do - if not updatedUnits[unit_id] then - lremovedAliveUnits[unit_id] = unit - lalive_units[unit_id] = nil - end - end - end - end - - local function dbUpdate(event, objType) - dbLog:info('dbUpdate') - local newTable = {} - newTable.startTime = 0 - if type(event) == 'string' then -- if name of an object. - local newObject - if Group.getByName(event) then - newObject = Group.getByName(event) - elseif StaticObject.getByName(event) then - newObject = StaticObject.getByName(event) - -- log:info('its static') - else - log:warn('$1 is not a Unit or Static Object. This should not be possible', event) - return false - end - - newTable.name = newObject:getName() - newTable.groupId = tonumber(newObject:getID()) - newTable.groupName = newObject:getName() - local unitOneRef - if objType == 'static' then - unitOneRef = newObject - newTable.countryId = tonumber(newObject:getCountry()) - newTable.coalitionId = tonumber(newObject:getCoalition()) - newTable.category = 'static' - else - unitOneRef = newObject:getUnits() - newTable.countryId = tonumber(unitOneRef[1]:getCountry()) - newTable.coalitionId = tonumber(unitOneRef[1]:getCoalition()) - newTable.category = tonumber(newObject:getCategory()) - end - for countryData, countryId in pairs(country.id) do - if newTable.country and string.upper(countryData) == string.upper(newTable.country) or countryId == newTable.countryId then - newTable.countryId = countryId - newTable.country = string.lower(countryData) - for coaData, coaId in pairs(coalition.side) do - if coaId == coalition.getCountryCoalition(countryId) then - newTable.coalition = string.lower(coaData) - end - end - end - end - for catData, catId in pairs(Unit.Category) do - if objType == 'group' and Group.getByName(newTable.groupName):isExist() then - if catId == Group.getByName(newTable.groupName):getCategory() then - newTable.category = string.lower(catData) - end - elseif objType == 'static' and StaticObject.getByName(newTable.groupName):isExist() then - if catId == StaticObject.getByName(newTable.groupName):getCategory() then - newTable.category = string.lower(catData) - end - - end - end - local gfound = false - for index, data in pairs(mistAddedGroups) do - if mist.stringMatch(data.name, newTable.groupName) == true then - gfound = true - newTable.task = data.task - newTable.modulation = data.modulation - newTable.uncontrolled = data.uncontrolled - newTable.radioSet = data.radioSet - newTable.hidden = data.hidden - newTable.startTime = data.start_time - mistAddedGroups[index] = nil - end - end - - if gfound == false then - newTable.uncontrolled = false - newTable.hidden = false - end - - newTable.units = {} - if objType == 'group' then - for unitId, unitData in pairs(unitOneRef) do - newTable.units[unitId] = {} - newTable.units[unitId].unitName = unitData:getName() - - newTable.units[unitId].x = mist.utils.round(unitData:getPosition().p.x) - newTable.units[unitId].y = mist.utils.round(unitData:getPosition().p.z) - newTable.units[unitId].point = {} - newTable.units[unitId].point.x = newTable.units[unitId].x - newTable.units[unitId].point.y = newTable.units[unitId].y - newTable.units[unitId].alt = mist.utils.round(unitData:getPosition().p.y) - newTable.units[unitId].speed = mist.vec.mag(unitData:getVelocity()) - - newTable.units[unitId].heading = mist.getHeading(unitData, true) - - newTable.units[unitId].type = unitData:getTypeName() - newTable.units[unitId].unitId = tonumber(unitData:getID()) - - - newTable.units[unitId].groupName = newTable.groupName - newTable.units[unitId].groupId = newTable.groupId - newTable.units[unitId].countryId = newTable.countryId - newTable.units[unitId].coalitionId = newTable.coalitionId - newTable.units[unitId].coalition = newTable.coalition - newTable.units[unitId].country = newTable.country - local found = false - for index, data in pairs(mistAddedObjects) do - if mist.stringMatch(data.name, newTable.units[unitId].unitName) == true then - found = true - newTable.units[unitId].livery_id = data.livery_id - newTable.units[unitId].skill = data.skill - newTable.units[unitId].alt_type = data.alt_type - newTable.units[unitId].callsign = data.callsign - newTable.units[unitId].psi = data.psi - mistAddedObjects[index] = nil - end - if found == false then - newTable.units[unitId].skill = "High" - newTable.units[unitId].alt_type = "BARO" - end - end - - end - else -- its a static - newTable.category = 'static' - newTable.units[1] = {} - newTable.units[1].unitName = newObject:getName() - newTable.units[1].category = 'static' - newTable.units[1].x = mist.utils.round(newObject:getPosition().p.x) - newTable.units[1].y = mist.utils.round(newObject:getPosition().p.z) - newTable.units[1].point = {} - newTable.units[1].point.x = newTable.units[1].x - newTable.units[1].point.y = newTable.units[1].y - newTable.units[1].alt = mist.utils.round(newObject:getPosition().p.y) - newTable.units[1].heading = mist.getHeading(newObject, true) - newTable.units[1].type = newObject:getTypeName() - newTable.units[1].unitId = tonumber(newObject:getID()) - newTable.units[1].groupName = newTable.name - newTable.units[1].groupId = newTable.groupId - newTable.units[1].countryId = newTable.countryId - newTable.units[1].country = newTable.country - newTable.units[1].coalitionId = newTable.coalitionId - newTable.units[1].coalition = newTable.coalition - if newObject:getCategory() == 6 and newObject:getCargoDisplayName() then - local mass = newObject:getCargoDisplayName() - mass = string.gsub(mass, ' ', '') - mass = string.gsub(mass, 'kg', '') - newTable.units[1].mass = tonumber(mass) - newTable.units[1].categoryStatic = 'Cargos' - newTable.units[1].canCargo = true - newTable.units[1].shape_name = 'ab-212_cargo' - end - - ----- search mist added objects for extra data if applicable - for index, data in pairs(mistAddedObjects) do - if mist.stringMatch(data.name, newTable.units[1].unitName) == true then - newTable.units[1].shape_name = data.shape_name -- for statics - newTable.units[1].livery_id = data.livery_id - newTable.units[1].airdromeId = data.airdromeId - newTable.units[1].mass = data.mass - newTable.units[1].canCargo = data.canCargo - newTable.units[1].categoryStatic = data.categoryStatic - newTable.units[1].type = 'cargo1' - mistAddedObjects[index] = nil - end - end - end - end - --mist.debug.writeData(mist.utils.serialize,{'msg', newTable}, timer.getAbsTime() ..'Group.lua') - newTable.timeAdded = timer.getAbsTime() -- only on the dynGroupsAdded table. For other reference, see start time - --mist.debug.dumpDBs() - --end - dbLog:info('endDbUpdate') - return newTable - end - - --[[DB update code... FRACK. I need to refactor some of it. - - The problem is that the DBs need to account better for shared object names. Needs to write over some data and outright remove other. - - If groupName is used then entire group needs to be rewritten - what to do with old groups units DB entries?. Names cant be assumed to be the same. - - - -- new spawn event check. - -- event handler filters everything into groups: tempSpawnedGroups - -- this function then checks DBs to see if data has changed - ]] - local function checkSpawnedEventsNew() - if tempSpawnGroupsCounter > 0 then - --[[local updatesPerRun = math.ceil(#tempSpawnedGroupsCounter/20) - if updatesPerRun < 5 then - updatesPerRun = 5 - end]] - - dbLog:info('iterate') - for name, gType in pairs(tempSpawnedGroups) do - dbLog:info(name) - local updated = false - - if mist.DBs.groupsByName[name] then - -- first check group level properties, groupId, countryId, coalition - dbLog:info('Found in DBs, check if updated') - local dbTable = mist.DBs.groupsByName[name] - dbLog:info(dbTable) - if gType ~= 'static' then - dbLog:info('Not static') - local _g = Group.getByName(name) - local _u = _g:getUnit(1) - if dbTable.groupId ~= tonumber(_g:getID()) or _u:getCountry() ~= dbTable.countryId or _u:getCoalition() ~= dbTable.coaltionId then - dbLog:info('Group Data mismatch') - updated = true - else - dbLog:info('No Mismatch') - end - - end - end - dbLog:info('Updated: $1', updated) - if updated == false and gType ~= 'static' then -- time to check units - dbLog:info('No Group Mismatch, Check Units') - for index, uObject in pairs(Group.getByName(name):getUnits()) do - dbLog:info(index) - if mist.DBs.unitsByName[uObject:getName()] then - dbLog:info('UnitByName table exists') - local uTable = mist.DBs.unitsByName[uObject:getName()] - if tonumber(uObject:getID()) ~= uTable.unitId or uObject:getTypeName() ~= uTable.type then - dbLog:info('Unit Data mismatch') - updated = true - break - end - end - end - end - - if updated == true or not mist.DBs.groupsByName[name] then - dbLog:info('Get Table') - writeGroups[#writeGroups+1] = {data = dbUpdate(name, gType), isUpdated = updated} - - end - -- Work done, so remove - tempSpawnedGroups[name] = nil - tempSpawnGroupsCounter = tempSpawnGroupsCounter - 1 - end - end - end - - local function updateDBTables() - local i = #writeGroups - - local savesPerRun = math.ceil(i/10) - if savesPerRun < 5 then - savesPerRun = 5 - end - if i > 0 then - dbLog:info('updateDBTables') - local ldeepCopy = mist.utils.deepCopy - for x = 1, i do - dbLog:info(writeGroups[x]) - local newTable = writeGroups[x].data - local updated = writeGroups[x].isUpdated - local mistCategory - if type(newTable.category) == 'string' then - mistCategory = string.lower(newTable.category) - end - - if string.upper(newTable.category) == 'GROUND_UNIT' then - mistCategory = 'vehicle' - newTable.category = mistCategory - elseif string.upper(newTable.category) == 'AIRPLANE' then - mistCategory = 'plane' - newTable.category = mistCategory - elseif string.upper(newTable.category) == 'HELICOPTER' then - mistCategory = 'helicopter' - newTable.category = mistCategory - elseif string.upper(newTable.category) == 'SHIP' then - mistCategory = 'ship' - newTable.category = mistCategory - end - dbLog:info('Update unitsBy') - for newId, newUnitData in pairs(newTable.units) do - dbLog:info(newId) - newUnitData.category = mistCategory - if newUnitData.unitId then - dbLog:info('byId') - mist.DBs.unitsById[tonumber(newUnitData.unitId)] = ldeepCopy(newUnitData) - end - dbLog:info(updated) - if mist.DBs.unitsByName[newUnitData.unitName] and updated == true then--if unit existed before and something was updated, write over the entry for a given unit name just in case. - dbLog:info('Updating Unit Tables') - for i = 1, #mist.DBs.unitsByCat[mistCategory] do - if mist.DBs.unitsByCat[mistCategory][i].unitName == newUnitData.unitName then - dbLog:info('Entry Found, Rewriting for unitsByCat') - mist.DBs.unitsByCat[mistCategory][i] = ldeepCopy(newUnitData) - break - end - end - for i = 1, #mist.DBs.unitsByNum do - if mist.DBs.unitsByNum[i].unitName == newUnitData.unitName then - dbLog:info('Entry Found, Rewriting for unitsByNum') - mist.DBs.unitsByNum[i] = ldeepCopy(newUnitData) - break - end - end - - else - dbLog:info('Unitname not in use, add as normal') - mist.DBs.unitsByCat[mistCategory][#mist.DBs.unitsByCat[mistCategory] + 1] = ldeepCopy(newUnitData) - mist.DBs.unitsByNum[#mist.DBs.unitsByNum + 1] = ldeepCopy(newUnitData) - end - mist.DBs.unitsByName[newUnitData.unitName] = ldeepCopy(newUnitData) - - - end - -- this is a really annoying DB to populate. Gotta create new tables in case its missing - dbLog:info('write mist.DBs.units') - if not mist.DBs.units[newTable.coalition] then - mist.DBs.units[newTable.coalition] = {} - end - - if not mist.DBs.units[newTable.coalition][newTable.country] then - mist.DBs.units[newTable.coalition][(newTable.country)] = {} - mist.DBs.units[newTable.coalition][(newTable.country)].countryId = newTable.countryId - end - if not mist.DBs.units[newTable.coalition][newTable.country][mistCategory] then - mist.DBs.units[newTable.coalition][(newTable.country)][mistCategory] = {} - end - - if updated == true then - dbLog:info('Updating DBsUnits') - for i = 1, #mist.DBs.units[newTable.coalition][(newTable.country)][mistCategory] do - if mist.DBs.units[newTable.coalition][(newTable.country)][mistCategory][i].groupName == newTable.groupName then - dbLog:info('Entry Found, Rewriting') - mist.DBs.units[newTable.coalition][(newTable.country)][mistCategory][i] = ldeepCopy(newTable) - break - end - end - else - mist.DBs.units[newTable.coalition][(newTable.country)][mistCategory][#mist.DBs.units[newTable.coalition][(newTable.country)][mistCategory] + 1] = ldeepCopy(newTable) - end - - - if newTable.groupId then - mist.DBs.groupsById[newTable.groupId] = ldeepCopy(newTable) - end - - mist.DBs.groupsByName[newTable.name] = ldeepCopy(newTable) - mist.DBs.dynGroupsAdded[#mist.DBs.dynGroupsAdded + 1] = ldeepCopy(newTable) - - writeGroups[x] = nil - if x%savesPerRun == 0 then - coroutine.yield() - end - end - if timer.getTime() > lastUpdateTime then - lastUpdateTime = timer.getTime() - end - dbLog:info('endUpdateTables') - end - end - - local function groupSpawned(event) - -- dont need to add units spawned in at the start of the mission if mist is loaded in init line - if event.id == world.event.S_EVENT_BIRTH and timer.getTime0() < timer.getAbsTime() then - dbLog:info('unitSpawnEvent') - - --table.insert(tempSpawnedUnits,(event.initiator)) - ------- - -- New functionality below. - ------- - if Object.getCategory(event.initiator) == 1 and not Unit.getPlayerName(event.initiator) then -- simple player check, will need to later check to see if unit was spawned with a player in a flight - dbLog:info('Object is a Unit') - dbLog:info(Unit.getGroup(event.initiator):getName()) - if not tempSpawnedGroups[Unit.getGroup(event.initiator):getName()] then - dbLog:info('added') - tempSpawnedGroups[Unit.getGroup(event.initiator):getName()] = 'group' - tempSpawnGroupsCounter = tempSpawnGroupsCounter + 1 - end - elseif Object.getCategory(event.initiator) == 3 or Object.getCategory(event.initiator) == 6 then - dbLog:info('Object is Static') - tempSpawnedGroups[StaticObject.getName(event.initiator)] = 'static' - tempSpawnGroupsCounter = tempSpawnGroupsCounter + 1 - end - - - end - end - - local function doScheduledFunctions() - local i = 1 - while i <= #scheduledTasks do - if not scheduledTasks[i].rep then -- not a repeated process - if scheduledTasks[i].t <= timer.getTime() then - local task = scheduledTasks[i] -- local reference - table.remove(scheduledTasks, i) - local err, errmsg = pcall(task.f, unpack(task.vars, 1, table.maxn(task.vars))) - if not err then - log:error('Error in scheduled function: $1', errmsg) - end - --task.f(unpack(task.vars, 1, table.maxn(task.vars))) -- do the task, do not increment i - else - i = i + 1 - end - else - if scheduledTasks[i].st and scheduledTasks[i].st <= timer.getTime() then --if a stoptime was specified, and the stop time exceeded - table.remove(scheduledTasks, i) -- stop time exceeded, do not execute, do not increment i - elseif scheduledTasks[i].t <= timer.getTime() then - local task = scheduledTasks[i] -- local reference - task.t = timer.getTime() + task.rep --schedule next run - local err, errmsg = pcall(task.f, unpack(task.vars, 1, table.maxn(task.vars))) - if not err then - log:error('Error in scheduled function: $1' .. errmsg) - end - --scheduledTasks[i].f(unpack(scheduledTasks[i].vars, 1, table.maxn(scheduledTasks[i].vars))) -- do the task - i = i + 1 - else - i = i + 1 - end - end - end - end - - -- Event handler to start creating the dead_objects table - local function addDeadObject(event) - if event.id == world.event.S_EVENT_DEAD or event.id == world.event.S_EVENT_CRASH then - if event.initiator and event.initiator.id_ and event.initiator.id_ > 0 then - - local id = event.initiator.id_ -- initial ID, could change if there is a duplicate id_ already dead. - local val = {object = event.initiator} -- the new entry in mist.DBs.deadObjects. - - local original_id = id --only for duplicate runtime IDs. - local id_ind = 1 - while mist.DBs.deadObjects[id] do - --log:info('duplicate runtime id of previously dead object id: $1', id) - id = tostring(original_id) .. ' #' .. tostring(id_ind) - id_ind = id_ind + 1 - end - - if mist.DBs.aliveUnits and mist.DBs.aliveUnits[val.object.id_] then - --log:info('object found in alive_units') - val.objectData = mist.utils.deepCopy(mist.DBs.aliveUnits[val.object.id_]) - local pos = Object.getPosition(val.object) - if pos then - val.objectPos = pos.p - end - val.objectType = mist.DBs.aliveUnits[val.object.id_].category - --[[if mist.DBs.activeHumans[Unit.getName(val.object)] then - --trigger.action.outText('remove via death: ' .. Unit.getName(val.object),20) - mist.DBs.activeHumans[Unit.getName(val.object)] = nil - end]] - elseif mist.DBs.removedAliveUnits and mist.DBs.removedAliveUnits[val.object.id_] then -- it didn't exist in alive_units, check old_alive_units - --log:info('object found in old_alive_units') - val.objectData = mist.utils.deepCopy(mist.DBs.removedAliveUnits[val.object.id_]) - local pos = Object.getPosition(val.object) - if pos then - val.objectPos = pos.p - end - val.objectType = mist.DBs.removedAliveUnits[val.object.id_].category - - else --attempt to determine if static object... - --log:info('object not found in alive units or old alive units') - local pos = Object.getPosition(val.object) - if pos then - local static_found = false - for ind, static in pairs(mist.DBs.unitsByCat.static) do - if ((pos.p.x - static.point.x)^2 + (pos.p.z - static.point.y)^2)^0.5 < 0.1 then --really, it should be zero... - --log:info('correlated dead static object to position') - val.objectData = static - val.objectPos = pos.p - val.objectType = 'static' - static_found = true - break - end - end - if not static_found then - val.objectPos = pos.p - val.objectType = 'building' - end - else - val.objectType = 'unknown' - end - end - mist.DBs.deadObjects[id] = val - end - end - end - - --[[ - local function addClientsToActive(event) - if event.id == world.event.S_EVENT_PLAYER_ENTER_UNIT or event.id == world.event.S_EVENT_BIRTH then - log:info(event) - if Unit.getPlayerName(event.initiator) then - log:info(Unit.getPlayerName(event.initiator)) - local newU = mist.utils.deepCopy(mist.DBs.unitsByName[Unit.getName(event.initiator)]) - newU.playerName = Unit.getPlayerName(event.initiator) - mist.DBs.activeHumans[Unit.getName(event.initiator)] = newU - --trigger.action.outText('added: ' .. Unit.getName(event.initiator), 20) - end - elseif event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT and event.initiator then - if mist.DBs.activeHumans[Unit.getName(event.initiator)] then - mist.DBs.activeHumans[Unit.getName(event.initiator)] = nil - -- trigger.action.outText('removed via control: ' .. Unit.getName(event.initiator), 20) - end - end - end - - mist.addEventHandler(addClientsToActive) - ]] - - --- init function. - -- creates logger, adds default event handler - -- and calls main the first time. - -- @function mist.init - function mist.init() - -- create logger - mist.log = mist.Logger:new("MIST") - dbLog = mist.Logger:new('MISTDB', 'warning') - - log = mist.log -- log shorthand - -- set warning log level, showing only - -- warnings and errors - log:setLevel("warning") - - log:info("initializing databases") - initDBs() - - -- add event handler for group spawns - mist.addEventHandler(groupSpawned) - mist.addEventHandler(addDeadObject) - - -- call main the first time therafter it reschedules itself. - mist.main() - --log:msg('MIST version $1.$2.$3 loaded', mist.majorVersion, mist.minorVersion, mist.build) - return - end - - --- The main function. - -- Run 100 times per second. - -- You shouldn't call this function. - function mist.main() - timer.scheduleFunction(mist.main, {}, timer.getTime() + 0.01) --reschedule first in case of Lua error - - updateTenthSecond = updateTenthSecond + 1 - if updateTenthSecond == 10 then - updateTenthSecond = 0 - - checkSpawnedEventsNew() - - if not coroutines.updateDBTables then - coroutines.updateDBTables = coroutine.create(updateDBTables) - end - - coroutine.resume(coroutines.updateDBTables) - - if coroutine.status(coroutines.updateDBTables) == 'dead' then - coroutines.updateDBTables = nil - end - end - - --updating alive units - updateAliveUnitsCounter = updateAliveUnitsCounter + 1 - if updateAliveUnitsCounter == 5 then - updateAliveUnitsCounter = 0 - - if not coroutines.updateAliveUnits then - coroutines.updateAliveUnits = coroutine.create(updateAliveUnits) - end - - coroutine.resume(coroutines.updateAliveUnits) - - if coroutine.status(coroutines.updateAliveUnits) == 'dead' then - coroutines.updateAliveUnits = nil - end - end - - doScheduledFunctions() - end -- end of mist.main - - --- Returns next unit id. - -- @treturn number next unit id. - function mist.getNextUnitId() - mist.nextUnitId = mist.nextUnitId + 1 - if mist.nextUnitId > 6900 then - mist.nextUnitId = 14000 - end - return mist.nextUnitId - end - - --- Returns next group id. - -- @treturn number next group id. - function mist.getNextGroupId() - mist.nextGroupId = mist.nextGroupId + 1 - if mist.nextGroupId > 6900 then - mist.nextGroupId = 14000 - end - return mist.nextGroupId - end - - --- Returns timestamp of last database update. - -- @treturn timestamp of last database update - function mist.getLastDBUpdateTime() - return lastUpdateTime - end - - --- Spawns a static object to the game world. - -- @todo write good docs - -- @tparam table staticObj table containing data needed for the object creation - function mist.dynAddStatic(newObj) - - if newObj.units and newObj.units[1] then -- if its mist format - for entry, val in pairs(newObj.units[1]) do - if newObj[entry] and newObj[entry] ~= val or not newObj[entry] then - newObj[entry] = val - end - end - end - --log:info(newObj) - - local cntry = newObj.country - if newObj.countryId then - cntry = newObj.countryId - end - - local newCountry = '' - - for countryId, countryName in pairs(country.name) do - if type(cntry) == 'string' then - cntry = cntry:gsub("%s+", "_") - if tostring(countryName) == string.upper(cntry) then - newCountry = countryName - end - elseif type(cntry) == 'number' then - if countryId == cntry then - newCountry = countryName - end - end - end - - if newCountry == '' then - log:error("Country not found: $1", cntry) - return false - end - - if newObj.clone or not newObj.groupId then - mistGpId = mistGpId + 1 - newObj.groupId = mistGpId - end - - if newObj.clone or not newObj.unitId then - mistUnitId = mistUnitId + 1 - newObj.unitId = mistUnitId - end - - if newObj.clone or not newObj.name then - mistDynAddIndex[' static '] = mistDynAddIndex[' static '] + 1 - newObj.name = (newCountry .. ' static ' .. mistDynAddIndex[' static ']) - end - - if not newObj.dead then - newObj.dead = false - end - - if not newObj.heading then - newObj.heading = math.random(360) - end - - if newObj.categoryStatic then - newObj.category = newObj.categoryStatic - end - if newObj.mass then - newObj.category = 'Cargos' - end - - if newObj.shapeName then - newObj.shape_name = newObj.shapeName - end - - if not newObj.shape_name then - log:info('shape_name not present') - if mist.DBs.const.shapeNames[newObj.type] then - newObj.shape_name = mist.DBs.const.shapeNames[newObj.type] - end - end - - mistAddedObjects[#mistAddedObjects + 1] = mist.utils.deepCopy(newObj) - if newObj.x and newObj.y and newObj.type and type(newObj.x) == 'number' and type(newObj.y) == 'number' and type(newObj.type) == 'string' then - --log:info('addStaticObject') - coalition.addStaticObject(country.id[newCountry], newObj) - - return newObj - end - log:error("Failed to add static object due to missing or incorrect value. X: $1, Y: $2, Type: $3", newObj.x, newObj.y, newObj.type) - return false - end - - --- Spawns a dynamic group into the game world. - -- Same as coalition.add function in SSE. checks the passed data to see if its valid. - -- Will generate groupId, groupName, unitId, and unitName if needed - -- @tparam table newGroup table containting values needed for spawning a group. - function mist.dynAdd(newGroup) - - --mist.debug.writeData(mist.utils.serialize,{'msg', newGroup}, 'newGroupOrig.lua') - local cntry = newGroup.country - if newGroup.countryId then - cntry = newGroup.countryId - end - - local groupType = newGroup.category - local newCountry = '' - -- validate data - for countryId, countryName in pairs(country.name) do - if type(cntry) == 'string' then - cntry = cntry:gsub("%s+", "_") - if tostring(countryName) == string.upper(cntry) then - newCountry = countryName - end - elseif type(cntry) == 'number' then - if countryId == cntry then - newCountry = countryName - end - end - end - - if newCountry == '' then - log:error("Country not found: $1", cntry) - return false - end - - local newCat = '' - for catName, catId in pairs(Unit.Category) do - if type(groupType) == 'string' then - if tostring(catName) == string.upper(groupType) then - newCat = catName - end - elseif type(groupType) == 'number' then - if catId == groupType then - newCat = catName - end - end - - if catName == 'GROUND_UNIT' and (string.upper(groupType) == 'VEHICLE' or string.upper(groupType) == 'GROUND') then - newCat = 'GROUND_UNIT' - elseif catName == 'AIRPLANE' and string.upper(groupType) == 'PLANE' then - newCat = 'AIRPLANE' - end - end - local typeName - if newCat == 'GROUND_UNIT' then - typeName = ' gnd ' - elseif newCat == 'AIRPLANE' then - typeName = ' air ' - elseif newCat == 'HELICOPTER' then - typeName = ' hel ' - elseif newCat == 'SHIP' then - typeName = ' shp ' - elseif newCat == 'BUILDING' then - typeName = ' bld ' - end - if newGroup.clone or not newGroup.groupId then - mistDynAddIndex[typeName] = mistDynAddIndex[typeName] + 1 - mistGpId = mistGpId + 1 - newGroup.groupId = mistGpId - end - if newGroup.groupName or newGroup.name then - if newGroup.groupName then - newGroup.name = newGroup.groupName - elseif newGroup.name then - newGroup.name = newGroup.name - end - end - - if newGroup.clone and mist.DBs.groupsByName[newGroup.name] or not newGroup.name then - newGroup.name = tostring(newCountry .. tostring(typeName) .. mistDynAddIndex[typeName]) - end - - if not newGroup.hidden then - newGroup.hidden = false - end - - if not newGroup.visible then - newGroup.visible = false - end - - if (newGroup.start_time and type(newGroup.start_time) ~= 'number') or not newGroup.start_time then - if newGroup.startTime then - newGroup.start_time = mist.utils.round(newGroup.startTime) - else - newGroup.start_time = 0 - end - end - - - for unitIndex, unitData in pairs(newGroup.units) do - local originalName = newGroup.units[unitIndex].unitName or newGroup.units[unitIndex].name - if newGroup.clone or not unitData.unitId then - mistUnitId = mistUnitId + 1 - newGroup.units[unitIndex].unitId = mistUnitId - end - if newGroup.units[unitIndex].unitName or newGroup.units[unitIndex].name then - if newGroup.units[unitIndex].unitName then - newGroup.units[unitIndex].name = newGroup.units[unitIndex].unitName - elseif newGroup.units[unitIndex].name then - newGroup.units[unitIndex].name = newGroup.units[unitIndex].name - end - end - if newGroup.clone or not unitData.name then - newGroup.units[unitIndex].name = tostring(newGroup.name .. ' unit' .. unitIndex) - end - - if not unitData.skill then - newGroup.units[unitIndex].skill = 'Random' - end - - if not unitData.alt then - if newCat == 'AIRPLANE' then - newGroup.units[unitIndex].alt = 2000 - newGroup.units[unitIndex].alt_type = 'RADIO' - newGroup.units[unitIndex].speed = 150 - elseif newCat == 'HELICOPTER' then - newGroup.units[unitIndex].alt = 500 - newGroup.units[unitIndex].alt_type = 'RADIO' - newGroup.units[unitIndex].speed = 60 - else - --[[log:info('check height') - newGroup.units[unitIndex].alt = land.getHeight({x = newGroup.units[unitIndex].x, y = newGroup.units[unitIndex].y}) - newGroup.units[unitIndex].alt_type = 'BARO']] - end - - - end - - if newCat == 'AIRPLANE' or newCat == 'HELICOPTER' then - if newGroup.units[unitIndex].alt_type and newGroup.units[unitIndex].alt_type ~= 'BARO' or not newGroup.units[unitIndex].alt_type then - newGroup.units[unitIndex].alt_type = 'RADIO' - end - if not unitData.speed then - if newCat == 'AIRPLANE' then - newGroup.units[unitIndex].speed = 150 - elseif newCat == 'HELICOPTER' then - newGroup.units[unitIndex].speed = 60 - end - end - if not unitData.payload then - newGroup.units[unitIndex].payload = mist.getPayload(originalName) - end - end - mistAddedObjects[#mistAddedObjects + 1] = mist.utils.deepCopy(newGroup.units[unitIndex]) - end - mistAddedGroups[#mistAddedGroups + 1] = mist.utils.deepCopy(newGroup) - if newGroup.route and not newGroup.route.points then - if not newGroup.route.points and newGroup.route[1] then - local copyRoute = newGroup.route - newGroup.route = {} - newGroup.route.points = copyRoute - end - end - newGroup.country = newCountry - - - --mist.debug.writeData(mist.utils.serialize,{'msg', newGroup}, 'newGroup.lua') - - -- sanitize table - newGroup.groupName = nil - newGroup.clone = nil - newGroup.category = nil - newGroup.country = nil - - newGroup.tasks = {} - - for unitIndex, unitData in pairs(newGroup.units) do - newGroup.units[unitIndex].unitName = nil - end - - coalition.addGroup(country.id[newCountry], Unit.Category[newCat], newGroup) - - return newGroup - - end - - --- Schedules a function. - -- Modified Slmod task scheduler, superior to timer.scheduleFunction - -- @tparam function f function to schedule - -- @tparam table vars array containing all parameters passed to the function - -- @tparam number t time in seconds from mission start to schedule the function to. - -- @tparam[opt] number rep time between repetitions of the function - -- @tparam[opt] number st time in seconds from mission start at which the function - -- should stop to be rescheduled. - -- @treturn number scheduled function id. - function mist.scheduleFunction(f, vars, t, rep, st) - --verify correct types - assert(type(f) == 'function', 'variable 1, expected function, got ' .. type(f)) - assert(type(vars) == 'table' or vars == nil, 'variable 2, expected table or nil, got ' .. type(f)) - assert(type(t) == 'number', 'variable 3, expected number, got ' .. type(t)) - assert(type(rep) == 'number' or rep == nil, 'variable 4, expected number or nil, got ' .. type(rep)) - assert(type(st) == 'number' or st == nil, 'variable 5, expected number or nil, got ' .. type(st)) - if not vars then - vars = {} - end - taskId = taskId + 1 - table.insert(scheduledTasks, {f = f, vars = vars, t = t, rep = rep, st = st, id = taskId}) - return taskId - end - - --- Removes a scheduled function. - -- @tparam number id function id - -- @treturn boolean true if function was successfully removed, false otherwise. - function mist.removeFunction(id) - local i = 1 - local removedFunction = false - while i <= #scheduledTasks do - if scheduledTasks[i].id == id then - table.remove(scheduledTasks, i) - removedFunction = true - else - i = i + 1 - end - end - return removedFunction - end - - --- Registers an event handler. - -- @tparam function f function handling event - -- @treturn number id of the event handler - function mist.addEventHandler(f) --id is optional! - local handler = {} - idNum = idNum + 1 - handler.id = idNum - handler.f = f - function handler:onEvent(event) - self.f(event) - end - world.addEventHandler(handler) - return handler.id - end - - --- Removes event handler with given id. - -- @tparam number id event handler id - -- @treturn boolean true on success, false otherwise - function mist.removeEventHandler(id) - for key, handler in pairs(world.eventHandlers) do - if handler.id and handler.id == id then - world.eventHandlers[key] = nil - return true - end - end - return false - end -end - --- Begin common funcs -do - --- Returns MGRS coordinates as string. - -- @tparam string MGRS MGRS coordinates - -- @tparam number acc the accuracy of each easting/northing. - -- Can be: 0, 1, 2, 3, 4, or 5. - function mist.tostringMGRS(MGRS, acc) - if acc == 0 then - return MGRS.UTMZone .. ' ' .. MGRS.MGRSDigraph - else - return MGRS.UTMZone .. ' ' .. MGRS.MGRSDigraph .. ' ' .. string.format('%0' .. acc .. 'd', mist.utils.round(MGRS.Easting/(10^(5-acc)), 0)) - .. ' ' .. string.format('%0' .. acc .. 'd', mist.utils.round(MGRS.Northing/(10^(5-acc)), 0)) - end - end - - --[[acc: - in DM: decimal point of minutes. - In DMS: decimal point of seconds. - position after the decimal of the least significant digit: - So: - 42.32 - acc of 2. - ]] - function mist.tostringLL(lat, lon, acc, DMS) - - local latHemi, lonHemi - if lat > 0 then - latHemi = 'N' - else - latHemi = 'S' - end - - if lon > 0 then - lonHemi = 'E' - else - lonHemi = 'W' - end - - lat = math.abs(lat) - lon = math.abs(lon) - - local latDeg = math.floor(lat) - local latMin = (lat - latDeg)*60 - - local lonDeg = math.floor(lon) - local lonMin = (lon - lonDeg)*60 - - if DMS then -- degrees, minutes, and seconds. - local oldLatMin = latMin - latMin = math.floor(latMin) - local latSec = mist.utils.round((oldLatMin - latMin)*60, acc) - - local oldLonMin = lonMin - lonMin = math.floor(lonMin) - local lonSec = mist.utils.round((oldLonMin - lonMin)*60, acc) - - if latSec == 60 then - latSec = 0 - latMin = latMin + 1 - end - - if lonSec == 60 then - lonSec = 0 - lonMin = lonMin + 1 - end - - local secFrmtStr -- create the formatting string for the seconds place - if acc <= 0 then -- no decimal place. - secFrmtStr = '%02d' - else - local width = 3 + acc -- 01.310 - that's a width of 6, for example. - secFrmtStr = '%0' .. width .. '.' .. acc .. 'f' - end - - return string.format('%02d', latDeg) .. ' ' .. string.format('%02d', latMin) .. '\' ' .. string.format(secFrmtStr, latSec) .. '"' .. latHemi .. ' ' - .. string.format('%02d', lonDeg) .. ' ' .. string.format('%02d', lonMin) .. '\' ' .. string.format(secFrmtStr, lonSec) .. '"' .. lonHemi - - else -- degrees, decimal minutes. - latMin = mist.utils.round(latMin, acc) - lonMin = mist.utils.round(lonMin, acc) - - if latMin == 60 then - latMin = 0 - latDeg = latDeg + 1 - end - - if lonMin == 60 then - lonMin = 0 - lonDeg = lonDeg + 1 - end - - local minFrmtStr -- create the formatting string for the minutes place - if acc <= 0 then -- no decimal place. - minFrmtStr = '%02d' - else - local width = 3 + acc -- 01.310 - that's a width of 6, for example. - minFrmtStr = '%0' .. width .. '.' .. acc .. 'f' - end - - return string.format('%02d', latDeg) .. ' ' .. string.format(minFrmtStr, latMin) .. '\'' .. latHemi .. ' ' - .. string.format('%02d', lonDeg) .. ' ' .. string.format(minFrmtStr, lonMin) .. '\'' .. lonHemi - - end - end - - --[[ required: az - radian - required: dist - meters - optional: alt - meters (set to false or nil if you don't want to use it). - optional: metric - set true to get dist and alt in km and m. - precision will always be nearest degree and NM or km.]] - function mist.tostringBR(az, dist, alt, metric) - az = mist.utils.round(mist.utils.toDegree(az), 0) - - if metric then - dist = mist.utils.round(dist/1000, 0) - else - dist = mist.utils.round(mist.utils.metersToNM(dist), 0) - end - - local s = string.format('%03d', az) .. ' for ' .. dist - - if alt then - if metric then - s = s .. ' at ' .. mist.utils.round(alt, 0) - else - s = s .. ' at ' .. mist.utils.round(mist.utils.metersToFeet(alt), 0) - end - end - return s - end - - function mist.getNorthCorrection(gPoint) --gets the correction needed for true north - local point = mist.utils.deepCopy(gPoint) - if not point.z then --Vec2; convert to Vec3 - point.z = point.y - point.y = 0 - end - local lat, lon = coord.LOtoLL(point) - local north_posit = coord.LLtoLO(lat + 1, lon) - return math.atan2(north_posit.z - point.z, north_posit.x - point.x) - end - - --- Returns skill of the given unit. - -- @tparam string unitName unit name - -- @return skill of the unit - function mist.getUnitSkill(unitName) - if mist.DBs.unitsByName[unitName] then - if Unit.getByName(unitName) then - local lunit = Unit.getByName(unitName) - local data = mist.DBs.unitsByName[unitName] - if data.unitName == unitName and data.type == lunit:getTypeName() and data.unitId == tonumber(lunit:getID()) and data.skill then - return data.skill - end - end - end - log:error("Unit not found in DB: $1", unitName) - return false - end - - --- Returns an array containing a group's units positions. - -- e.g. - -- { - -- [1] = {x = 299435.224, y = -1146632.6773}, - -- [2] = {x = 663324.6563, y = 322424.1112} - -- } - -- @tparam number|string groupIdent group id or name - -- @treturn table array containing positions of each group member - function mist.getGroupPoints(groupIdent) - -- search by groupId and allow groupId and groupName as inputs - local gpId = groupIdent - if type(groupIdent) == 'string' and not tonumber(groupIdent) then - if mist.DBs.MEgroupsByName[groupIdent] then - gpId = mist.DBs.MEgroupsByName[groupIdent].groupId - else - log:error("Group not found in mist.DBs.MEgroupsByName: $1", groupIdent) - end - end - - for coa_name, coa_data in pairs(env.mission.coalition) do - if (coa_name == 'red' or coa_name == 'blue') and type(coa_data) == 'table' then - if coa_data.country then --there is a country table - for cntry_id, cntry_data in pairs(coa_data.country) do - for obj_type_name, obj_type_data in pairs(cntry_data) do - if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" then -- only these types have points - if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then --there's a group! - for group_num, group_data in pairs(obj_type_data.group) do - if group_data and group_data.groupId == gpId then -- this is the group we are looking for - if group_data.route and group_data.route.points and #group_data.route.points > 0 then - local points = {} - for point_num, point in pairs(group_data.route.points) do - if not point.point then - points[point_num] = { x = point.x, y = point.y } - else - points[point_num] = point.point --it's possible that the ME could move to the point = Vec2 notation. - end - end - return points - end - return - end --if group_data and group_data.name and group_data.name == 'groupname' - end --for group_num, group_data in pairs(obj_type_data.group) do - end --if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then - end --if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" or obj_type_name == "static" then - end --for obj_type_name, obj_type_data in pairs(cntry_data) do - end --for cntry_id, cntry_data in pairs(coa_data.country) do - end --if coa_data.country then --there is a country table - end --if coa_name == 'red' or coa_name == 'blue' and type(coa_data) == 'table' then - end --for coa_name, coa_data in pairs(mission.coalition) do - end - - --- getUnitAttitude(unit) return values. - -- Yaw, AoA, ClimbAngle - relative to earth reference - -- DOES NOT TAKE INTO ACCOUNT WIND. - -- @table attitude - -- @tfield number Heading in radians, range of 0 to 2*pi, - -- relative to true north. - -- @tfield number Pitch in radians, range of -pi/2 to pi/2 - -- @tfield number Roll in radians, range of 0 to 2*pi, - -- right roll is positive direction. - -- @tfield number Yaw in radians, range of -pi to pi, - -- right yaw is positive direction. - -- @tfield number AoA in radians, range of -pi to pi, - -- rotation of aircraft to the right in comparison to - -- flight direction being positive. - -- @tfield number ClimbAngle in radians, range of -pi/2 to pi/2 - - --- Returns the attitude of a given unit. - -- Will work on any unit, even if not an aircraft. - -- @tparam Unit unit unit whose attitude is returned. - -- @treturn table @{attitude} - function mist.getAttitude(unit) - local unitpos = unit:getPosition() - if unitpos then - - local Heading = math.atan2(unitpos.x.z, unitpos.x.x) - - Heading = Heading + mist.getNorthCorrection(unitpos.p) - - if Heading < 0 then - Heading = Heading + 2*math.pi -- put heading in range of 0 to 2*pi - end - ---- heading complete.---- - - local Pitch = math.asin(unitpos.x.y) - ---- pitch complete.---- - - -- now get roll: - --maybe not the best way to do it, but it works. - - --first, make a vector that is perpendicular to y and unitpos.x with cross product - local cp = mist.vec.cp(unitpos.x, {x = 0, y = 1, z = 0}) - - --now, get dot product of of this cross product with unitpos.z - local dp = mist.vec.dp(cp, unitpos.z) - - --now get the magnitude of the roll (magnitude of the angle between two vectors is acos(vec1.vec2/|vec1||vec2|) - local Roll = math.acos(dp/(mist.vec.mag(cp)*mist.vec.mag(unitpos.z))) - - --now, have to get sign of roll. - -- by convention, making right roll positive - -- to get sign of roll, use the y component of unitpos.z. For right roll, y component is negative. - - if unitpos.z.y > 0 then -- left roll, flip the sign of the roll - Roll = -Roll - end - ---- roll complete. ---- - - --now, work on yaw, AoA, climb, and abs velocity - local Yaw - local AoA - local ClimbAngle - - -- get unit velocity - local unitvel = unit:getVelocity() - if mist.vec.mag(unitvel) ~= 0 then --must have non-zero velocity! - local AxialVel = {} --unit velocity transformed into aircraft axes directions - - --transform velocity components in direction of aircraft axes. - AxialVel.x = mist.vec.dp(unitpos.x, unitvel) - AxialVel.y = mist.vec.dp(unitpos.y, unitvel) - AxialVel.z = mist.vec.dp(unitpos.z, unitvel) - - --Yaw is the angle between unitpos.x and the x and z velocities - --define right yaw as positive - Yaw = math.acos(mist.vec.dp({x = 1, y = 0, z = 0}, {x = AxialVel.x, y = 0, z = AxialVel.z})/mist.vec.mag({x = AxialVel.x, y = 0, z = AxialVel.z})) - - --now set correct direction: - if AxialVel.z > 0 then - Yaw = -Yaw - end - - -- AoA is angle between unitpos.x and the x and y velocities - AoA = math.acos(mist.vec.dp({x = 1, y = 0, z = 0}, {x = AxialVel.x, y = AxialVel.y, z = 0})/mist.vec.mag({x = AxialVel.x, y = AxialVel.y, z = 0})) - - --now set correct direction: - if AxialVel.y > 0 then - AoA = -AoA - end - - ClimbAngle = math.asin(unitvel.y/mist.vec.mag(unitvel)) - end - return { Heading = Heading, Pitch = Pitch, Roll = Roll, Yaw = Yaw, AoA = AoA, ClimbAngle = ClimbAngle} - else - log:error("Couldn't get unit's position") - end - end - - --- Returns heading of given unit. - -- @tparam Unit unit unit whose heading is returned. - -- @param rawHeading - -- @treturn number heading of the unit, in range - -- of 0 to 2*pi. - function mist.getHeading(unit, rawHeading) - local unitpos = unit:getPosition() - if unitpos then - local Heading = math.atan2(unitpos.x.z, unitpos.x.x) - if not rawHeading then - Heading = Heading + mist.getNorthCorrection(unitpos.p) - end - if Heading < 0 then - Heading = Heading + 2*math.pi -- put heading in range of 0 to 2*pi - end - return Heading - end - end - - --- Returns given unit's pitch - -- @tparam Unit unit unit whose pitch is returned. - -- @treturn number pitch of given unit - function mist.getPitch(unit) - local unitpos = unit:getPosition() - if unitpos then - return math.asin(unitpos.x.y) - end - end - - --- Returns given unit's roll. - -- @tparam Unit unit unit whose roll is returned. - -- @treturn number roll of given unit - function mist.getRoll(unit) - local unitpos = unit:getPosition() - if unitpos then - -- now get roll: - --maybe not the best way to do it, but it works. - - --first, make a vector that is perpendicular to y and unitpos.x with cross product - local cp = mist.vec.cp(unitpos.x, {x = 0, y = 1, z = 0}) - - --now, get dot product of of this cross product with unitpos.z - local dp = mist.vec.dp(cp, unitpos.z) - - --now get the magnitude of the roll (magnitude of the angle between two vectors is acos(vec1.vec2/|vec1||vec2|) - local Roll = math.acos(dp/(mist.vec.mag(cp)*mist.vec.mag(unitpos.z))) - - --now, have to get sign of roll. - -- by convention, making right roll positive - -- to get sign of roll, use the y component of unitpos.z. For right roll, y component is negative. - - if unitpos.z.y > 0 then -- left roll, flip the sign of the roll - Roll = -Roll - end - return Roll - end - end - - --- Returns given unit's yaw. - -- @tparam Unit unit unit whose yaw is returned. - -- @treturn number yaw of given unit. - function mist.getYaw(unit) - local unitpos = unit:getPosition() - if unitpos then - -- get unit velocity - local unitvel = unit:getVelocity() - if mist.vec.mag(unitvel) ~= 0 then --must have non-zero velocity! - local AxialVel = {} --unit velocity transformed into aircraft axes directions - - --transform velocity components in direction of aircraft axes. - AxialVel.x = mist.vec.dp(unitpos.x, unitvel) - AxialVel.y = mist.vec.dp(unitpos.y, unitvel) - AxialVel.z = mist.vec.dp(unitpos.z, unitvel) - - --Yaw is the angle between unitpos.x and the x and z velocities - --define right yaw as positive - local Yaw = math.acos(mist.vec.dp({x = 1, y = 0, z = 0}, {x = AxialVel.x, y = 0, z = AxialVel.z})/mist.vec.mag({x = AxialVel.x, y = 0, z = AxialVel.z})) - - --now set correct direction: - if AxialVel.z > 0 then - Yaw = -Yaw - end - return Yaw - end - end - end - - --- Returns given unit's angle of attack. - -- @tparam Unit unit unit to get AoA from. - -- @treturn number angle of attack of the given unit. - function mist.getAoA(unit) - local unitpos = unit:getPosition() - if unitpos then - local unitvel = unit:getVelocity() - if mist.vec.mag(unitvel) ~= 0 then --must have non-zero velocity! - local AxialVel = {} --unit velocity transformed into aircraft axes directions - - --transform velocity components in direction of aircraft axes. - AxialVel.x = mist.vec.dp(unitpos.x, unitvel) - AxialVel.y = mist.vec.dp(unitpos.y, unitvel) - AxialVel.z = mist.vec.dp(unitpos.z, unitvel) - - -- AoA is angle between unitpos.x and the x and y velocities - local AoA = math.acos(mist.vec.dp({x = 1, y = 0, z = 0}, {x = AxialVel.x, y = AxialVel.y, z = 0})/mist.vec.mag({x = AxialVel.x, y = AxialVel.y, z = 0})) - - --now set correct direction: - if AxialVel.y > 0 then - AoA = -AoA - end - return AoA - end - end - end - - --- Returns given unit's climb angle. - -- @tparam Unit unit unit to get climb angle from. - -- @treturn number climb angle of given unit. - function mist.getClimbAngle(unit) - local unitpos = unit:getPosition() - if unitpos then - local unitvel = unit:getVelocity() - if mist.vec.mag(unitvel) ~= 0 then --must have non-zero velocity! - return math.asin(unitvel.y/mist.vec.mag(unitvel)) - end - end - end - - --[[-- - Unit name table. - Many Mist functions require tables of unit names, which are known - in Mist as UnitNameTables. These follow a special set of shortcuts - borrowed from Slmod. These shortcuts alleviate the problem of entering - huge lists of unit names by hand, and in many cases, they remove the - need to even know the names of the units in the first place! - - These are the unit table "short-cut" commands: - - Prefixes: - "[-u]" - subtract this unit if its in the table - "[g]" - add this group to the table - "[-g]" - subtract this group from the table - "[c]" - add this country's units - "[-c]" - subtract this country's units if any are in the table - - Stand-alone identifiers - "[all]" - add all units - "[-all]" - subtract all units (not very useful by itself) - "[blue]" - add all blue units - "[-blue]" - subtract all blue units - "[red]" - add all red coalition units - "[-red]" - subtract all red units - - Compound Identifiers: - "[c][helicopter]" - add all of this country's helicopters - "[-c][helicopter]" - subtract all of this country's helicopters - "[c][plane]" - add all of this country's planes - "[-c][plane]" - subtract all of this country's planes - "[c][ship]" - add all of this country's ships - "[-c][ship]" - subtract all of this country's ships - "[c][vehicle]" - add all of this country's vehicles - "[-c][vehicle]" - subtract all of this country's vehicles - - "[all][helicopter]" - add all helicopters - "[-all][helicopter]" - subtract all helicopters - "[all][plane]" - add all planes - "[-all][plane]" - subtract all planes - "[all][ship]" - add all ships - "[-all][ship]" - subtract all ships - "[all][vehicle]" - add all vehicles - "[-all][vehicle]" - subtract all vehicles - - "[blue][helicopter]" - add all blue coalition helicopters - "[-blue][helicopter]" - subtract all blue coalition helicopters - "[blue][plane]" - add all blue coalition planes - "[-blue][plane]" - subtract all blue coalition planes - "[blue][ship]" - add all blue coalition ships - "[-blue][ship]" - subtract all blue coalition ships - "[blue][vehicle]" - add all blue coalition vehicles - "[-blue][vehicle]" - subtract all blue coalition vehicles - - "[red][helicopter]" - add all red coalition helicopters - "[-red][helicopter]" - subtract all red coalition helicopters - "[red][plane]" - add all red coalition planes - "[-red][plane]" - subtract all red coalition planes - "[red][ship]" - add all red coalition ships - "[-red][ship]" - subtract all red coalition ships - "[red][vehicle]" - add all red coalition vehicles - "[-red][vehicle]" - subtract all red coalition vehicles - - Country names to be used in [c] and [-c] short-cuts: - Turkey - Norway - The Netherlands - Spain - 11 - UK - Denmark - USA - Georgia - Germany - Belgium - Canada - France - Israel - Ukraine - Russia - South Ossetia - Abkhazia - Italy - Australia - Austria - Belarus - Bulgaria - Czech Republic - China - Croatia - Finland - Greece - Hungary - India - Iran - Iraq - Japan - Kazakhstan - North Korea - Pakistan - Poland - Romania - Saudi Arabia - Serbia, Slovakia - South Korea - Sweden - Switzerland - Syria - USAF Aggressors - - Do NOT use a '[u]' notation for single units. Single units are referenced - the same way as before: Simply input their names as strings. - - These unit tables are evaluated in order, and you cannot subtract a unit - from a table before it is added. For example: - - {'[blue]', '[-c]Georgia'} - - will evaluate to all of blue coalition except those units owned by the - country named "Georgia"; however: - - {'[-c]Georgia', '[blue]'} - - will evaluate to all of the units in blue coalition, because the addition - of all units owned by blue coalition occurred AFTER the subtraction of all - units owned by Georgia (which actually subtracted nothing at all, since - there were no units in the table when the subtraction occurred). - - More examples: - - {'[blue][plane]', '[-c]Georgia', '[-g]Hawg 1'} - - Evaluates to all blue planes, except those blue units owned by the country - named "Georgia" and the units in the group named "Hawg1". - - - {'[g]arty1', '[g]arty2', '[-u]arty1_AD', '[-u]arty2_AD', 'Shark 11' } - - Evaluates to the unit named "Shark 11", plus all the units in groups named - "arty1" and "arty2" except those that are named "arty1\_AD" and "arty2\_AD". - - @table UnitNameTable - ]] - - --- Returns a table containing unit names. - -- @tparam table tbl sequential strings - -- @treturn table @{UnitNameTable} - function mist.makeUnitTable(tbl) - --Assumption: will be passed a table of strings, sequential - log:info(tbl) - local units_by_name = {} - - local l_munits = mist.DBs.units --local reference for faster execution - for i = 1, #tbl do - local unit = tbl[i] - if unit:sub(1,4) == '[-u]' then --subtract a unit - if units_by_name[unit:sub(5)] then -- 5 to end - units_by_name[unit:sub(5)] = nil --remove - end - elseif unit:sub(1,3) == '[g]' then -- add a group - for coa, coa_tbl in pairs(l_munits) do - for country, country_table in pairs(coa_tbl) do - for unit_type, unit_type_tbl in pairs(country_table) do - if type(unit_type_tbl) == 'table' then - for group_ind, group_tbl in pairs(unit_type_tbl) do - if type(group_tbl) == 'table' and group_tbl.groupName == unit:sub(4) then - -- index 4 to end - for unit_ind, unit in pairs(group_tbl.units) do - units_by_name[unit.unitName] = true --add - end - end - end - end - end - end - end - elseif unit:sub(1,4) == '[-g]' then -- subtract a group - for coa, coa_tbl in pairs(l_munits) do - for country, country_table in pairs(coa_tbl) do - for unit_type, unit_type_tbl in pairs(country_table) do - if type(unit_type_tbl) == 'table' then - for group_ind, group_tbl in pairs(unit_type_tbl) do - if type(group_tbl) == 'table' and group_tbl.groupName == unit:sub(5) then - -- index 5 to end - for unit_ind, unit in pairs(group_tbl.units) do - if units_by_name[unit.unitName] then - units_by_name[unit.unitName] = nil --remove - end - end - end - end - end - end - end - end - elseif unit:sub(1,3) == '[c]' then -- add a country - local category = '' - local country_start = 4 - if unit:sub(4,15) == '[helicopter]' then - category = 'helicopter' - country_start = 16 - elseif unit:sub(4,10) == '[plane]' then - category = 'plane' - country_start = 11 - elseif unit:sub(4,9) == '[ship]' then - category = 'ship' - country_start = 10 - elseif unit:sub(4,12) == '[vehicle]' then - category = 'vehicle' - country_start = 13 - end - for coa, coa_tbl in pairs(l_munits) do - for country, country_table in pairs(coa_tbl) do - if country == string.lower(unit:sub(country_start)) then -- match - for unit_type, unit_type_tbl in pairs(country_table) do - if type(unit_type_tbl) == 'table' and (category == '' or unit_type == category) then - for group_ind, group_tbl in pairs(unit_type_tbl) do - if type(group_tbl) == 'table' then - for unit_ind, unit in pairs(group_tbl.units) do - units_by_name[unit.unitName] = true --add - end - end - end - end - end - end - end - end - elseif unit:sub(1,4) == '[-c]' then -- subtract a country - local category = '' - local country_start = 5 - if unit:sub(5,16) == '[helicopter]' then - category = 'helicopter' - country_start = 17 - elseif unit:sub(5,11) == '[plane]' then - category = 'plane' - country_start = 12 - elseif unit:sub(5,10) == '[ship]' then - category = 'ship' - country_start = 11 - elseif unit:sub(5,13) == '[vehicle]' then - category = 'vehicle' - country_start = 14 - end - for coa, coa_tbl in pairs(l_munits) do - for country, country_table in pairs(coa_tbl) do - if country == string.lower(unit:sub(country_start)) then -- match - for unit_type, unit_type_tbl in pairs(country_table) do - if type(unit_type_tbl) == 'table' and (category == '' or unit_type == category) then - for group_ind, group_tbl in pairs(unit_type_tbl) do - if type(group_tbl) == 'table' then - for unit_ind, unit in pairs(group_tbl.units) do - if units_by_name[unit.unitName] then - units_by_name[unit.unitName] = nil --remove - end - end - end - end - end - end - end - end - end - elseif unit:sub(1,6) == '[blue]' then -- add blue coalition - local category = '' - if unit:sub(7) == '[helicopter]' then - category = 'helicopter' - elseif unit:sub(7) == '[plane]' then - category = 'plane' - elseif unit:sub(7) == '[ship]' then - category = 'ship' - elseif unit:sub(7) == '[vehicle]' then - category = 'vehicle' - end - for coa, coa_tbl in pairs(l_munits) do - if coa == 'blue' then - for country, country_table in pairs(coa_tbl) do - for unit_type, unit_type_tbl in pairs(country_table) do - if type(unit_type_tbl) == 'table' and (category == '' or unit_type == category) then - for group_ind, group_tbl in pairs(unit_type_tbl) do - if type(group_tbl) == 'table' then - for unit_ind, unit in pairs(group_tbl.units) do - units_by_name[unit.unitName] = true --add - end - end - end - end - end - end - end - end - elseif unit:sub(1,7) == '[-blue]' then -- subtract blue coalition - local category = '' - if unit:sub(8) == '[helicopter]' then - category = 'helicopter' - elseif unit:sub(8) == '[plane]' then - category = 'plane' - elseif unit:sub(8) == '[ship]' then - category = 'ship' - elseif unit:sub(8) == '[vehicle]' then - category = 'vehicle' - end - for coa, coa_tbl in pairs(l_munits) do - if coa == 'blue' then - for country, country_table in pairs(coa_tbl) do - for unit_type, unit_type_tbl in pairs(country_table) do - if type(unit_type_tbl) == 'table' and (category == '' or unit_type == category) then - for group_ind, group_tbl in pairs(unit_type_tbl) do - if type(group_tbl) == 'table' then - for unit_ind, unit in pairs(group_tbl.units) do - if units_by_name[unit.unitName] then - units_by_name[unit.unitName] = nil --remove - end - end - end - end - end - end - end - end - end - elseif unit:sub(1,5) == '[red]' then -- add red coalition - local category = '' - if unit:sub(6) == '[helicopter]' then - category = 'helicopter' - elseif unit:sub(6) == '[plane]' then - category = 'plane' - elseif unit:sub(6) == '[ship]' then - category = 'ship' - elseif unit:sub(6) == '[vehicle]' then - category = 'vehicle' - end - for coa, coa_tbl in pairs(l_munits) do - if coa == 'red' then - for country, country_table in pairs(coa_tbl) do - for unit_type, unit_type_tbl in pairs(country_table) do - if type(unit_type_tbl) == 'table' and (category == '' or unit_type == category) then - for group_ind, group_tbl in pairs(unit_type_tbl) do - if type(group_tbl) == 'table' then - for unit_ind, unit in pairs(group_tbl.units) do - units_by_name[unit.unitName] = true --add - end - end - end - end - end - end - end - end - elseif unit:sub(1,6) == '[-red]' then -- subtract red coalition - local category = '' - if unit:sub(7) == '[helicopter]' then - category = 'helicopter' - elseif unit:sub(7) == '[plane]' then - category = 'plane' - elseif unit:sub(7) == '[ship]' then - category = 'ship' - elseif unit:sub(7) == '[vehicle]' then - category = 'vehicle' - end - for coa, coa_tbl in pairs(l_munits) do - if coa == 'red' then - for country, country_table in pairs(coa_tbl) do - for unit_type, unit_type_tbl in pairs(country_table) do - if type(unit_type_tbl) == 'table' and (category == '' or unit_type == category) then - for group_ind, group_tbl in pairs(unit_type_tbl) do - if type(group_tbl) == 'table' then - for unit_ind, unit in pairs(group_tbl.units) do - if units_by_name[unit.unitName] then - units_by_name[unit.unitName] = nil --remove - end - end - end - end - end - end - end - end - end - elseif unit:sub(1,5) == '[all]' then -- add all of a certain category (or all categories) - local category = '' - if unit:sub(6) == '[helicopter]' then - category = 'helicopter' - elseif unit:sub(6) == '[plane]' then - category = 'plane' - elseif unit:sub(6) == '[ship]' then - category = 'ship' - elseif unit:sub(6) == '[vehicle]' then - category = 'vehicle' - end - for coa, coa_tbl in pairs(l_munits) do - for country, country_table in pairs(coa_tbl) do - for unit_type, unit_type_tbl in pairs(country_table) do - if type(unit_type_tbl) == 'table' and (category == '' or unit_type == category) then - for group_ind, group_tbl in pairs(unit_type_tbl) do - if type(group_tbl) == 'table' then - for unit_ind, unit in pairs(group_tbl.units) do - units_by_name[unit.unitName] = true --add - end - end - end - end - end - end - end - elseif unit:sub(1,6) == '[-all]' then -- subtract all of a certain category (or all categories) - local category = '' - if unit:sub(7) == '[helicopter]' then - category = 'helicopter' - elseif unit:sub(7) == '[plane]' then - category = 'plane' - elseif unit:sub(7) == '[ship]' then - category = 'ship' - elseif unit:sub(7) == '[vehicle]' then - category = 'vehicle' - end - for coa, coa_tbl in pairs(l_munits) do - for country, country_table in pairs(coa_tbl) do - for unit_type, unit_type_tbl in pairs(country_table) do - if type(unit_type_tbl) == 'table' and (category == '' or unit_type == category) then - for group_ind, group_tbl in pairs(unit_type_tbl) do - if type(group_tbl) == 'table' then - for unit_ind, unit in pairs(group_tbl.units) do - if units_by_name[unit.unitName] then - units_by_name[unit.unitName] = nil --remove - end - end - end - end - end - end - end - end - else -- just a regular unit - units_by_name[unit] = true --add - end - end - - local units_tbl = {} -- indexed sequentially - for unit_name, val in pairs(units_by_name) do - if val then - units_tbl[#units_tbl + 1] = unit_name -- add all the units to the table - end - end - - - units_tbl.processed = timer.getTime() --add the processed flag - return units_tbl -end - -function mist.getDeadMapObjsInZones(zone_names) - -- zone_names: table of zone names - -- returns: table of dead map objects (indexed numerically) - local map_objs = {} - local zones = {} - for i = 1, #zone_names do - if mist.DBs.zonesByName[zone_names[i]] then - zones[#zones + 1] = mist.DBs.zonesByName[zone_names[i]] - end - end - for obj_id, obj in pairs(mist.DBs.deadObjects) do - if obj.objectType and obj.objectType == 'building' then --dead map object - for i = 1, #zones do - if ((zones[i].point.x - obj.objectPos.x)^2 + (zones[i].point.z - obj.objectPos.z)^2)^0.5 <= zones[i].radius then - map_objs[#map_objs + 1] = mist.utils.deepCopy(obj) - end - end - end - end - return map_objs -end - -function mist.getDeadMapObjsInPolygonZone(zone) - -- zone_names: table of zone names - -- returns: table of dead map objects (indexed numerically) - local map_objs = {} - for obj_id, obj in pairs(mist.DBs.deadObjects) do - if obj.objectType and obj.objectType == 'building' then --dead map object - if mist.pointInPolygon(obj.objectPos, zone) then - map_objs[#map_objs + 1] = mist.utils.deepCopy(obj) - end - end - end - return map_objs -end - -function mist.pointInPolygon(point, poly, maxalt) --raycasting point in polygon. Code from http://softsurfer.com/Archive/algorithm_0103/algorithm_0103.htm - --[[local type_tbl = { - point = {'table'}, - poly = {'table'}, - maxalt = {'number', 'nil'}, - } - - local err, errmsg = mist.utils.typeCheck('mist.pointInPolygon', type_tbl, {point, poly, maxalt}) - assert(err, errmsg) - ]] - point = mist.utils.makeVec3(point) - local px = point.x - local pz = point.z - local cn = 0 - local newpoly = mist.utils.deepCopy(poly) - - if not maxalt or (point.y <= maxalt) then - local polysize = #newpoly - newpoly[#newpoly + 1] = newpoly[1] - - newpoly[1] = mist.utils.makeVec3(newpoly[1]) - - for k = 1, polysize do - newpoly[k+1] = mist.utils.makeVec3(newpoly[k+1]) - if ((newpoly[k].z <= pz) and (newpoly[k+1].z > pz)) or ((newpoly[k].z > pz) and (newpoly[k+1].z <= pz)) then - local vt = (pz - newpoly[k].z) / (newpoly[k+1].z - newpoly[k].z) - if (px < newpoly[k].x + vt*(newpoly[k+1].x - newpoly[k].x)) then - cn = cn + 1 - end - end - end - - return cn%2 == 1 - else - return false - end -end - -function mist.getUnitsInPolygon(unit_names, polyZone, max_alt) - local units = {} - - for i = 1, #unit_names do - units[#units + 1] = Unit.getByName(unitNames[i]) - end - - local inZoneUnits = {} - for i =1, #units do - if units[i]:isActive() and mist.pointInPolygon(units[i]:getPosition().p, polyZone, max_alt) then - inZoneUnits[inZoneUnits + 1] = units[i] - end - end - - return inZoneUnits -end - -function mist.getUnitsInZones(unit_names, zone_names, zone_type) - - zone_type = zone_type or 'cylinder' - if zone_type == 'c' or zone_type == 'cylindrical' or zone_type == 'C' then - zone_type = 'cylinder' - end - if zone_type == 's' or zone_type == 'spherical' or zone_type == 'S' then - zone_type = 'sphere' - end - - assert(zone_type == 'cylinder' or zone_type == 'sphere', 'invalid zone_type: ' .. tostring(zone_type)) - - local units = {} - local zones = {} - - for k = 1, #unit_names do - local unit = Unit.getByName(unit_names[k]) - if unit then - units[#units + 1] = unit - end - end - - - for k = 1, #zone_names do - local zone = trigger.misc.getZone(zone_names[k]) - if zone then - zones[#zones + 1] = {radius = zone.radius, x = zone.point.x, y = zone.point.y, z = zone.point.z} - end - end - - local in_zone_units = {} - - for units_ind = 1, #units do - for zones_ind = 1, #zones do - if zone_type == 'sphere' then --add land height value for sphere zone type - local alt = land.getHeight({x = zones[zones_ind].x, y = zones[zones_ind].z}) - if alt then - zones[zones_ind].y = alt - end - end - local unit_pos = units[units_ind]:getPosition().p - if unit_pos and units[units_ind]:isActive() == true then - if zone_type == 'cylinder' and (((unit_pos.x - zones[zones_ind].x)^2 + (unit_pos.z - zones[zones_ind].z)^2)^0.5 <= zones[zones_ind].radius) then - in_zone_units[#in_zone_units + 1] = units[units_ind] - break - elseif zone_type == 'sphere' and (((unit_pos.x - zones[zones_ind].x)^2 + (unit_pos.y - zones[zones_ind].y)^2 + (unit_pos.z - zones[zones_ind].z)^2)^0.5 <= zones[zones_ind].radius) then - in_zone_units[#in_zone_units + 1] = units[units_ind] - break - end - end - end - end - return in_zone_units -end - -function mist.getUnitsInMovingZones(unit_names, zone_unit_names, radius, zone_type) - - zone_type = zone_type or 'cylinder' - if zone_type == 'c' or zone_type == 'cylindrical' or zone_type == 'C' then - zone_type = 'cylinder' - end - if zone_type == 's' or zone_type == 'spherical' or zone_type == 'S' then - zone_type = 'sphere' - end - - assert(zone_type == 'cylinder' or zone_type == 'sphere', 'invalid zone_type: ' .. tostring(zone_type)) - - local units = {} - local zone_units = {} - - for k = 1, #unit_names do - local unit = Unit.getByName(unit_names[k]) - if unit then - units[#units + 1] = unit - end - end - - for k = 1, #zone_unit_names do - local unit = Unit.getByName(zone_unit_names[k]) - if unit then - zone_units[#zone_units + 1] = unit - end - end - - local in_zone_units = {} - - for units_ind = 1, #units do - for zone_units_ind = 1, #zone_units do - local unit_pos = units[units_ind]:getPosition().p - local zone_unit_pos = zone_units[zone_units_ind]:getPosition().p - if unit_pos and zone_unit_pos and units[units_ind]:isActive() == true then - if zone_type == 'cylinder' and (((unit_pos.x - zone_unit_pos.x)^2 + (unit_pos.z - zone_unit_pos.z)^2)^0.5 <= radius) then - in_zone_units[#in_zone_units + 1] = units[units_ind] - break - elseif zone_type == 'sphere' and (((unit_pos.x - zone_unit_pos.x)^2 + (unit_pos.y - zone_unit_pos.y)^2 + (unit_pos.z - zone_unit_pos.z)^2)^0.5 <= radius) then - in_zone_units[#in_zone_units + 1] = units[units_ind] - break - end - end - end - end - return in_zone_units -end - -function mist.getUnitsLOS(unitset1, altoffset1, unitset2, altoffset2, radius) - log:info("$1, $2, $3, $4, $5", unitset1, altoffset1, unitset2, altoffset2, radius) - radius = radius or math.huge - local unit_info1 = {} - local unit_info2 = {} - - -- get the positions all in one step, saves execution time. - for unitset1_ind = 1, #unitset1 do - local unit1 = Unit.getByName(unitset1[unitset1_ind]) - if unit1 and unit1:isActive() == true then - unit_info1[#unit_info1 + 1] = {} - unit_info1[#unit_info1].unit = unit1 - unit_info1[#unit_info1].pos = unit1:getPosition().p - end - end - - for unitset2_ind = 1, #unitset2 do - local unit2 = Unit.getByName(unitset2[unitset2_ind]) - if unit2 and unit2:isActive() == true then - unit_info2[#unit_info2 + 1] = {} - unit_info2[#unit_info2].unit = unit2 - unit_info2[#unit_info2].pos = unit2:getPosition().p - end - end - - local LOS_data = {} - -- now compute los - for unit1_ind = 1, #unit_info1 do - local unit_added = false - for unit2_ind = 1, #unit_info2 do - if radius == math.huge or (mist.vec.mag(mist.vec.sub(unit_info1[unit1_ind].pos, unit_info2[unit2_ind].pos)) < radius) then -- inside radius - local point1 = { x = unit_info1[unit1_ind].pos.x, y = unit_info1[unit1_ind].pos.y + altoffset1, z = unit_info1[unit1_ind].pos.z} - local point2 = { x = unit_info2[unit2_ind].pos.x, y = unit_info2[unit2_ind].pos.y + altoffset2, z = unit_info2[unit2_ind].pos.z} - if land.isVisible(point1, point2) then - if unit_added == false then - unit_added = true - LOS_data[#LOS_data + 1] = {} - LOS_data[#LOS_data].unit = unit_info1[unit1_ind].unit - LOS_data[#LOS_data].vis = {} - LOS_data[#LOS_data].vis[#LOS_data[#LOS_data].vis + 1] = unit_info2[unit2_ind].unit - else - LOS_data[#LOS_data].vis[#LOS_data[#LOS_data].vis + 1] = unit_info2[unit2_ind].unit - end - end - end - end - end - - return LOS_data -end - -function mist.getAvgPoint(points) - local avgX, avgY, avgZ, totNum = 0, 0, 0, 0 - for i = 1, #points do - local nPoint = mist.utils.makeVec3(points[i]) - if nPoint.z then - avgX = avgX + nPoint.x - avgY = avgY + nPoint.y - avgZ = avgZ + nPoint.z - totNum = totNum + 1 - end - end - if totNum ~= 0 then - return {x = avgX/totNum, y = avgY/totNum, z = avgZ/totNum} - end -end - ---Gets the average position of a group of units (by name) -function mist.getAvgPos(unitNames) - local avgX, avgY, avgZ, totNum = 0, 0, 0, 0 - for i = 1, #unitNames do - local unit - if Unit.getByName(unitNames[i]) then - unit = Unit.getByName(unitNames[i]) - elseif StaticObject.getByName(unitNames[i]) then - unit = StaticObject.getByName(unitNames[i]) - end - if unit then - local pos = unit:getPosition().p - if pos then -- you never know O.o - avgX = avgX + pos.x - avgY = avgY + pos.y - avgZ = avgZ + pos.z - totNum = totNum + 1 - end - end - end - if totNum ~= 0 then - return {x = avgX/totNum, y = avgY/totNum, z = avgZ/totNum} - end -end - -function mist.getAvgGroupPos(groupName) - if type(groupName) == 'string' and Group.getByName(groupName) and Group.getByName(groupName):isExist() == true then - groupName = Group.getByName(groupName) - end - local units = {} - for i = 1, #groupName:getSize() do - table.insert(units, groupName.getUnit(i):getName()) - end - - return mist.getAvgPos(units) - -end - ---[[ vars for mist.getMGRSString: -vars.units - table of unit names (NOT unitNameTable- maybe this should change). -vars.acc - integer between 0 and 5, inclusive -]] -function mist.getMGRSString(vars) - local units = vars.units - local acc = vars.acc or 5 - local avgPos = mist.getAvgPos(units) - if avgPos then - return mist.tostringMGRS(coord.LLtoMGRS(coord.LOtoLL(avgPos)), acc) - end -end - ---[[ vars for mist.getLLString -vars.units - table of unit names (NOT unitNameTable- maybe this should change). -vars.acc - integer, number of numbers after decimal place -vars.DMS - if true, output in degrees, minutes, seconds. Otherwise, output in degrees, minutes. -]] -function mist.getLLString(vars) - local units = vars.units - local acc = vars.acc or 3 - local DMS = vars.DMS - local avgPos = mist.getAvgPos(units) - if avgPos then - local lat, lon = coord.LOtoLL(avgPos) - return mist.tostringLL(lat, lon, acc, DMS) - end -end - ---[[ -vars.units- table of unit names (NOT unitNameTable- maybe this should change). -vars.ref - vec3 ref point, maybe overload for vec2 as well? -vars.alt - boolean, if used, includes altitude in string -vars.metric - boolean, gives distance in km instead of NM. -]] -function mist.getBRString(vars) - local units = vars.units - local ref = mist.utils.makeVec3(vars.ref, 0) -- turn it into Vec3 if it is not already. - local alt = vars.alt - local metric = vars.metric - local avgPos = mist.getAvgPos(units) - if avgPos then - local vec = {x = avgPos.x - ref.x, y = avgPos.y - ref.y, z = avgPos.z - ref.z} - local dir = mist.utils.getDir(vec, ref) - local dist = mist.utils.get2DDist(avgPos, ref) - if alt then - alt = avgPos.y - end - return mist.tostringBR(dir, dist, alt, metric) - end -end - --- Returns the Vec3 coordinates of the average position of the concentration of units most in the heading direction. ---[[ vars for mist.getLeadingPos: -vars.units - table of unit names -vars.heading - direction -vars.radius - number -vars.headingDegrees - boolean, switches heading to degrees -]] -function mist.getLeadingPos(vars) - local units = vars.units - local heading = vars.heading - local radius = vars.radius - if vars.headingDegrees then - heading = mist.utils.toRadian(vars.headingDegrees) - end - - local unitPosTbl = {} - for i = 1, #units do - local unit = Unit.getByName(units[i]) - if unit and unit:isExist() then - unitPosTbl[#unitPosTbl + 1] = unit:getPosition().p - end - end - if #unitPosTbl > 0 then -- one more more units found. - -- first, find the unit most in the heading direction - local maxPos = -math.huge - - local maxPosInd -- maxPos - the furthest in direction defined by heading; maxPosInd = - for i = 1, #unitPosTbl do - local rotatedVec2 = mist.vec.rotateVec2(mist.utils.makeVec2(unitPosTbl[i]), heading) - if (not maxPos) or maxPos < rotatedVec2.x then - maxPos = rotatedVec2.x - maxPosInd = i - end - end - - --now, get all the units around this unit... - local avgPos - if radius then - local maxUnitPos = unitPosTbl[maxPosInd] - local avgx, avgy, avgz, totNum = 0, 0, 0, 0 - for i = 1, #unitPosTbl do - if mist.utils.get2DDist(maxUnitPos, unitPosTbl[i]) <= radius then - avgx = avgx + unitPosTbl[i].x - avgy = avgy + unitPosTbl[i].y - avgz = avgz + unitPosTbl[i].z - totNum = totNum + 1 - end - end - avgPos = { x = avgx/totNum, y = avgy/totNum, z = avgz/totNum} - else - avgPos = unitPosTbl[maxPosInd] - end - - return avgPos - end -end - ---[[ vars for mist.getLeadingMGRSString: -vars.units - table of unit names -vars.heading - direction -vars.radius - number -vars.headingDegrees - boolean, switches heading to degrees -vars.acc - number, 0 to 5. -]] -function mist.getLeadingMGRSString(vars) - local pos = mist.getLeadingPos(vars) - if pos then - local acc = vars.acc or 5 - return mist.tostringMGRS(coord.LLtoMGRS(coord.LOtoLL(pos)), acc) - end -end - ---[[ vars for mist.getLeadingLLString: -vars.units - table of unit names -vars.heading - direction, number -vars.radius - number -vars.headingDegrees - boolean, switches heading to degrees -vars.acc - number of digits after decimal point (can be negative) -vars.DMS - boolean, true if you want DMS. -]] -function mist.getLeadingLLString(vars) - local pos = mist.getLeadingPos(vars) - if pos then - local acc = vars.acc or 3 - local DMS = vars.DMS - local lat, lon = coord.LOtoLL(pos) - return mist.tostringLL(lat, lon, acc, DMS) - end -end - ---[[ vars for mist.getLeadingBRString: -vars.units - table of unit names -vars.heading - direction, number -vars.radius - number -vars.headingDegrees - boolean, switches heading to degrees -vars.metric - boolean, if true, use km instead of NM. -vars.alt - boolean, if true, include altitude. -vars.ref - vec3/vec2 reference point. -]] -function mist.getLeadingBRString(vars) - local pos = mist.getLeadingPos(vars) - if pos then - local ref = vars.ref - local alt = vars.alt - local metric = vars.metric - - local vec = {x = pos.x - ref.x, y = pos.y - ref.y, z = pos.z - ref.z} - local dir = mist.utils.getDir(vec, ref) - local dist = mist.utils.get2DDist(pos, ref) - if alt then - alt = pos.y - end - return mist.tostringBR(dir, dist, alt, metric) - end -end - -end - ---- Group functions. --- @section groups -do -- group functions scope - - --- Check table used for group creation. - -- @tparam table groupData table to check. - -- @treturn boolean true if a group can be spawned using - -- this table, false otherwise. - function mist.groupTableCheck(groupData) - -- return false if country, category - -- or units are missing - if not groupData.country or - not groupData.category or - not groupData.units then - return false - end - -- return false if unitData misses - -- x, y or type - for unitId, unitData in pairs(groupData.units) do - if not unitData.x or - not unitData.y or - not unitData.type then - return false - end - end - -- everything we need is here return true - return true - end - - --- Returns group data table of give group. - function mist.getCurrentGroupData(gpName) - local dbData = mist.getGroupData(gpName) - - if Group.getByName(gpName) and Group.getByName(gpName):isExist() == true then - local newGroup = Group.getByName(gpName) - local newData = {} - newData.name = gpName - newData.groupId = tonumber(newGroup:getID()) - newData.category = newGroup:getCategory() - newData.groupName = gpName - newData.hidden = dbData.hidden - - if newData.category == 2 then - newData.category = 'vehicle' - elseif newData.category == 3 then - newData.category = 'ship' - end - - newData.units = {} - local newUnits = newGroup:getUnits() - for unitNum, unitData in pairs(newGroup:getUnits()) do - newData.units[unitNum] = {} - local uName = unitData:getName() - - if mist.DBs.unitsByName[uName] and unitData:getTypeName() == mist.DBs.unitsByName[uName].type and mist.DBs.unitsByName[uName].unitId == tonumber(unitData:getID()) then -- If old data matches most of new data - newData.units[unitNum] = mist.utils.deepCopy(mist.DBs.unitsByName[uName]) - else - newData.units[unitNum].unitId = tonumber(unitData:getID()) - newData.units[unitNum].type = unitData:getTypeName() - newData.units[unitNum].skill = mist.getUnitSkill(uName) - newData.country = string.lower(country.name[unitData:getCountry()]) - newData.units[unitNum].callsign = unitData:getCallsign() - newData.units[unitNum].unitName = uName - end - - newData.units[unitNum].x = unitData:getPosition().p.x - newData.units[unitNum].y = unitData:getPosition().p.z - newData.units[unitNum].point = {x = newData.units[unitNum].x, y = newData.units[unitNum].y} - newData.units[unitNum].heading = mist.getHeading(unitData, true) -- added to DBs - newData.units[unitNum].alt = unitData:getPosition().p.y - newData.units[unitNum].speed = mist.vec.mag(unitData:getVelocity()) - - end - - return newData - elseif StaticObject.getByName(gpName) and StaticObject.getByName(gpName):isExist() == true then - local staticObj = StaticObject.getByName(gpName) - dbData.units[1].x = staticObj:getPosition().p.x - dbData.units[1].y = staticObj:getPosition().p.z - dbData.units[1].alt = staticObj:getPosition().p.y - dbData.units[1].heading = mist.getHeading(staticObj, true) - - return dbData - end - - end - - function mist.getGroupData(gpName) - local found = false - local newData = {} - if mist.DBs.groupsByName[gpName] then - newData = mist.utils.deepCopy(mist.DBs.groupsByName[gpName]) - found = true - end - - if found == false then - for groupName, groupData in pairs(mist.DBs.groupsByName) do - if mist.stringMatch(groupName, gpName) == true then - newData = mist.utils.deepCopy(groupData) - newData.groupName = groupName - found = true - break - end - end - end - - local payloads - if newData.category == 'plane' or newData.category == 'helicopter' then - payloads = mist.getGroupPayload(newData.groupName) - end - if found == true then - --newData.hidden = false -- maybe add this to DBs - - for unitNum, unitData in pairs(newData.units) do - newData.units[unitNum] = {} - - newData.units[unitNum].unitId = unitData.unitId - --newData.units[unitNum].point = unitData.point - newData.units[unitNum].x = unitData.point.x - newData.units[unitNum].y = unitData.point.y - newData.units[unitNum].alt = unitData.alt - newData.units[unitNum].alt_type = unitData.alt_type - newData.units[unitNum].speed = unitData.speed - newData.units[unitNum].type = unitData.type - newData.units[unitNum].skill = unitData.skill - newData.units[unitNum].unitName = unitData.unitName - newData.units[unitNum].heading = unitData.heading -- added to DBs - newData.units[unitNum].playerCanDrive = unitData.playerCanDrive -- added to DBs - - - if newData.category == 'plane' or newData.category == 'helicopter' then - newData.units[unitNum].payload = payloads[unitNum] - newData.units[unitNum].livery_id = unitData.livery_id - newData.units[unitNum].onboard_num = unitData.onboard_num - newData.units[unitNum].callsign = unitData.callsign - newData.units[unitNum].AddPropAircraft = unitData.AddPropAircraft - end - if newData.category == 'static' then - newData.units[unitNum].categoryStatic = unitData.categoryStatic - newData.units[unitNum].mass = unitData.mass - newData.units[unitNum].canCargo = unitData.canCargo - newData.units[unitNum].shape_name = unitData.shape_name - end - end - --log:info(newData) - return newData - else - log:error('$1 not found in MIST database', gpName) - return - end - end - - function mist.getPayload(unitIdent) - -- refactor to search by groupId and allow groupId and groupName as inputs - local unitId = unitIdent - if type(unitIdent) == 'string' and not tonumber(unitIdent) then - if mist.DBs.MEunitsByName[unitIdent] then - unitId = mist.DBs.MEunitsByName[unitIdent].unitId - else - log:error("Unit not found in mist.DBs.MEunitsByName: $1", unitIdent) - end - end - local gpId = mist.DBs.MEunitsById[unitId].groupId - - if gpId and unitId then - for coa_name, coa_data in pairs(env.mission.coalition) do - if (coa_name == 'red' or coa_name == 'blue') and type(coa_data) == 'table' then - if coa_data.country then --there is a country table - for cntry_id, cntry_data in pairs(coa_data.country) do - for obj_type_name, obj_type_data in pairs(cntry_data) do - if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" then -- only these types have points - if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then --there's a group! - for group_num, group_data in pairs(obj_type_data.group) do - if group_data and group_data.groupId == gpId then - for unitIndex, unitData in pairs(group_data.units) do --group index - if unitData.unitId == unitId then - return unitData.payload - end - end - end - end - end - end - end - end - end - end - end - else - log:error('Need string or number. Got: $1', type(unitIdent)) - return false - end - log:warn("Couldn't find payload for unit: $1", unitIdent) - return - end - - function mist.getGroupPayload(groupIdent) - local gpId = groupIdent - if type(groupIdent) == 'string' and not tonumber(groupIdent) then - if mist.DBs.MEgroupsByName[groupIdent] then - gpId = mist.DBs.MEgroupsByName[groupIdent].groupId - else - log:error('$1 not found in mist.DBs.MEgroupsByName', groupIdent) - end - end - - if gpId then - for coa_name, coa_data in pairs(env.mission.coalition) do - if (coa_name == 'red' or coa_name == 'blue') and type(coa_data) == 'table' then - if coa_data.country then --there is a country table - for cntry_id, cntry_data in pairs(coa_data.country) do - for obj_type_name, obj_type_data in pairs(cntry_data) do - if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" then -- only these types have points - if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then --there's a group! - for group_num, group_data in pairs(obj_type_data.group) do - if group_data and group_data.groupId == gpId then - local payloads = {} - for unitIndex, unitData in pairs(group_data.units) do --group index - payloads[unitIndex] = unitData.payload - end - return payloads - end - end - end - end - end - end - end - end - end - else - log:error('Need string or number. Got: $1', type(groupIdent)) - return false - end - log:warn("Couldn't find payload for group: $1", groupIdent) - return - - end - - function mist.teleportToPoint(vars) -- main teleport function that all of teleport/respawn functions call - local point = vars.point - - local gpName - if vars.gpName then - gpName = vars.gpName - elseif vars.groupName then - gpName = vars.groupName - else - log:error('Missing field groupName or gpName in variable table') - end - - local action = vars.action - - local disperse = vars.disperse or false - local maxDisp = vars.maxDisp - if not vars.maxDisp then - maxDisp = 200 - else - maxDisp = vars.maxDisp - end - local radius = vars.radius or 0 - local innerRadius = vars.innerRadius - - local route = vars.route - local dbData = false - - local newGroupData - if gpName and not vars.groupData then - if string.lower(action) == 'teleport' or string.lower(action) == 'tele' then - newGroupData = mist.getCurrentGroupData(gpName) - elseif string.lower(action) == 'respawn' then - newGroupData = mist.getGroupData(gpName) - dbData = true - elseif string.lower(action) == 'clone' then - newGroupData = mist.getGroupData(gpName) - newGroupData.clone = 'order66' - dbData = true - else - action = 'tele' - newGroupData = mist.getCurrentGroupData(gpName) - end - else - action = 'tele' - newGroupData = vars.groupData - end - - --log:info('get Randomized Point') - local diff = {x = 0, y = 0} - local newCoord, origCoord - if point then - local valid = false - - local validTerrain - if string.lower(newGroupData.category) == 'ship' then - validTerrain = {'SHALLOW_WATER' , 'WATER'} - elseif string.lower(newGroupData.category) == 'vehicle' then - validTerrain = {'LAND', 'ROAD'} - else - validTerrain = {'LAND', 'ROAD', 'SHALLOW_WATER', 'WATER', 'RUNWAY'} - end - - for i = 1, 100 do - newCoord = mist.getRandPointInCircle(point, radius, innerRadius) - if mist.isTerrainValid(newCoord, validTerrain) then - origCoord = mist.utils.deepCopy(newCoord) - diff = {x = (newCoord.x - newGroupData.units[1].x), y = (newCoord.y - newGroupData.units[1].y)} - valid = true - break - end - end - if valid == false then - log:error('Point supplied in variable table is not a valid coordinate. Valid coords: $1', validTerrain) - return false - end - end - if not newGroupData.country and mist.DBs.groupsByName[newGroupData.groupName].country then - newGroupData.country = mist.DBs.groupsByName[newGroupData.groupName].country - end - if not newGroupData.category and mist.DBs.groupsByName[newGroupData.groupName].category then - newGroupData.category = mist.DBs.groupsByName[newGroupData.groupName].category - end - - for unitNum, unitData in pairs(newGroupData.units) do - if disperse then - if maxDisp and type(maxDisp) == 'number' and unitNum ~= 1 then - newCoord = mist.getRandPointInCircle(origCoord, maxDisp) - --else - --newCoord = mist.getRandPointInCircle(zone.point, zone.radius) - end - - newGroupData.units[unitNum].x = newCoord.x - newGroupData.units[unitNum].y = newCoord.y - else - newGroupData.units[unitNum].x = unitData.x + diff.x - newGroupData.units[unitNum].y = unitData.y + diff.y - end - if point then - if (newGroupData.category == 'plane' or newGroupData.category == 'helicopter') then - if point.z and point.y > 0 and point.y > land.getHeight({newGroupData.units[unitNum].x, newGroupData.units[unitNum].y}) + 10 then - newGroupData.units[unitNum].alt = point.y - else - if newGroupData.category == 'plane' then - newGroupData.units[unitNum].alt = land.getHeight({newGroupData.units[unitNum].x, newGroupData.units[unitNum].y}) + math.random(300, 9000) - else - newGroupData.units[unitNum].alt = land.getHeight({newGroupData.units[unitNum].x, newGroupData.units[unitNum].y}) + math.random(200, 3000) - end - end - end - end - end - - if newGroupData.start_time then - newGroupData.startTime = newGroupData.start_time - end - - if newGroupData.startTime and newGroupData.startTime ~= 0 and dbData == true then - local timeDif = timer.getAbsTime() - timer.getTime0() - if timeDif > newGroupData.startTime then - newGroupData.startTime = 0 - else - newGroupData.startTime = newGroupData.startTime - timeDif - end - - end - - if route then - newGroupData.route = route - end - --mist.debug.writeData(mist.utils.serialize,{'teleportToPoint', newGroupData}, 'newGroupData.lua') - if string.lower(newGroupData.category) == 'static' then - --log:info(newGroupData) - return mist.dynAddStatic(newGroupData) - end - return mist.dynAdd(newGroupData) - - end - - function mist.respawnInZone(gpName, zone, disperse, maxDisp) - - if type(gpName) == 'table' and gpName:getName() then - gpName = gpName:getName() - elseif type(gpName) == 'table' and gpName[1]:getName() then - gpName = math.random(#gpName) - else - gpName = tostring(gpName) - end - - if type(zone) == 'string' then - zone = trigger.misc.getZone(zone) - elseif type(zone) == 'table' and not zone.radius then - zone = trigger.misc.getZone(zone[math.random(1, #zone)]) - end - local vars = {} - vars.gpName = gpName - vars.action = 'respawn' - vars.point = zone.point - vars.radius = zone.radius - vars.disperse = disperse - vars.maxDisp = maxDisp - return mist.teleportToPoint(vars) - end - - function mist.cloneInZone(gpName, zone, disperse, maxDisp) - --log:info('cloneInZone') - if type(gpName) == 'table' then - gpName = gpName:getName() - else - gpName = tostring(gpName) - end - - if type(zone) == 'string' then - zone = trigger.misc.getZone(zone) - elseif type(zone) == 'table' and not zone.radius then - zone = trigger.misc.getZone(zone[math.random(1, #zone)]) - end - local vars = {} - vars.gpName = gpName - vars.action = 'clone' - vars.point = zone.point - vars.radius = zone.radius - vars.disperse = disperse - vars.maxDisp = maxDisp - --log:info('do teleport') - return mist.teleportToPoint(vars) - end - - function mist.teleportInZone(gpName, zone, disperse, maxDisp) -- groupName, zoneName or table of Zone Names, keepForm is a boolean - if type(gpName) == 'table' and gpName:getName() then - gpName = gpName:getName() - else - gpName = tostring(gpName) - end - - if type(zone) == 'string' then - zone = trigger.misc.getZone(zone) - elseif type(zone) == 'table' and not zone.radius then - zone = trigger.misc.getZone(zone[math.random(1, #zone)]) - end - - local vars = {} - vars.gpName = gpName - vars.action = 'tele' - vars.point = zone.point - vars.radius = zone.radius - vars.disperse = disperse - vars.maxDisp = maxDisp - return mist.teleportToPoint(vars) - end - - function mist.respawnGroup(gpName, task) - local vars = {} - vars.gpName = gpName - vars.action = 'respawn' - if task and type(task) ~= 'number' then - vars.route = mist.getGroupRoute(gpName, 'task') - end - local newGroup = mist.teleportToPoint(vars) - if task and type(task) == 'number' then - local newRoute = mist.getGroupRoute(gpName, 'task') - mist.scheduleFunction(mist.goRoute, {newGroup, newRoute}, timer.getTime() + task) - end - return newGroup - end - - function mist.cloneGroup(gpName, task) - local vars = {} - vars.gpName = gpName - vars.action = 'clone' - if task and type(task) ~= 'number' then - vars.route = mist.getGroupRoute(gpName, 'task') - end - local newGroup = mist.teleportToPoint(vars) - if task and type(task) == 'number' then - local newRoute = mist.getGroupRoute(gpName, 'task') - mist.scheduleFunction(mist.goRoute, {newGroup, newRoute}, timer.getTime() + task) - end - return newGroup - end - - function mist.teleportGroup(gpName, task) - local vars = {} - vars.gpName = gpName - vars.action = 'teleport' - if task and type(task) ~= 'number' then - vars.route = mist.getGroupRoute(gpName, 'task') - end - local newGroup = mist.teleportToPoint(vars) - if task and type(task) == 'number' then - local newRoute = mist.getGroupRoute(gpName, 'task') - mist.scheduleFunction(mist.goRoute, {newGroup, newRoute}, timer.getTime() + task) - end - return newGroup - end - - function mist.spawnRandomizedGroup(groupName, vars) -- need to debug - if Group.getByName(groupName) and Group.getByName(groupName):isExist() == true then - local gpData = mist.getGroupData(groupName) - gpData.units = mist.randomizeGroupOrder(gpData.units, vars) - gpData.route = mist.getGroupRoute(groupName, 'task') - - mist.dynAdd(gpData) - end - - return true - end - - function mist.randomizeNumTable(vars) - local newTable = {} - - local excludeIndex = {} - local randomTable = {} - - if vars and vars.exclude and type(vars.exclude) == 'table' then - for index, data in pairs(vars.exclude) do - excludeIndex[data] = true - end - end - - local low, hi, size - - if vars.size then - size = vars.size - end - - if vars and vars.lowerLimit and type(vars.lowerLimit) == 'number' then - low = mist.utils.round(vars.lowerLimit) - else - low = 1 - end - - if vars and vars.upperLimit and type(vars.upperLimit) == 'number' then - hi = mist.utils.round(vars.upperLimit) - else - hi = size - end - - local choices = {} - -- add to exclude list and create list of what to randomize - for i = 1, size do - if not (i >= low and i <= hi) then - - excludeIndex[i] = true - end - if not excludeIndex[i] then - table.insert(choices, i) - else - newTable[i] = i - end - end - - for ind, num in pairs(choices) do - local found = false - local x = 0 - while found == false do - x = mist.random(size) -- get random number from list - local addNew = true - for index, _ in pairs(excludeIndex) do - if index == x then - addNew = false - break - end - end - if addNew == true then - excludeIndex[x] = true - found = true - end - excludeIndex[x] = true - - end - newTable[num] = x - end - --[[ - for i = 1, #newTable do - log:info(newTable[i]) - end - ]] - return newTable - end - - function mist.randomizeGroupOrder(passedUnits, vars) - -- figure out what to exclude, and send data to other func - local units = passedUnits - - if passedUnits.units then - units = passUnits.units - end - - local exclude = {} - local excludeNum = {} - if vars and vars.excludeType and type(vars.excludeType) == 'table' then - exclude = vars.excludeType - end - - if vars and vars.excludeNum and type(vars.excludeNum) == 'table' then - excludeNum = vars.excludeNum - end - - local low, hi - - if vars and vars.lowerLimit and type(vars.lowerLimit) == 'number' then - low = mist.utils.round(vars.lowerLimit) - else - low = 1 - end - - if vars and vars.upperLimit and type(vars.upperLimit) == 'number' then - hi = mist.utils.round(vars.upperLimit) - else - hi = #units - end - - - local excludeNum = {} - for unitIndex, unitData in pairs(units) do - if unitIndex >= low and unitIndex <= hi then -- if within range - local found = false - if #exclude > 0 then - for excludeType, index in pairs(exclude) do -- check if excluded - if mist.stringMatch(excludeType, unitData.type) then -- if excluded - excludeNum[unitIndex] = unitIndex - found = true - end - end - end - else -- unitIndex is either to low, or to high: added to exclude list - excludeNum[unitIndex] = unitId - end - end - - local newGroup = {} - local newOrder = mist.randomizeNumTable({exclude = excludeNum, size = #units}) - - for unitIndex, unitData in pairs(units) do - for i = 1, #newOrder do - if newOrder[i] == unitIndex then - newGroup[i] = mist.utils.deepCopy(units[i]) -- gets all of the unit data - newGroup[i].type = mist.utils.deepCopy(unitData.type) - newGroup[i].skill = mist.utils.deepCopy(unitData.skill) - newGroup[i].unitName = mist.utils.deepCopy(unitData.unitName) - newGroup[i].unitIndex = mist.utils.deepCopy(unitData.unitIndex) -- replaces the units data with a new type - end - end - end - return newGroup - end - - function mist.random(firstNum, secondNum) -- no support for decimals - local lowNum, highNum - if not secondNum then - highNum = firstNum - lowNum = 1 - else - lowNum = firstNum - highNum = secondNum - end - local total = 1 - if math.abs(highNum - lowNum + 1) < 50 then -- if total values is less than 50 - total = math.modf(50/math.abs(highNum - lowNum + 1)) -- make x copies required to be above 50 - end - local choices = {} - for i = 1, total do -- iterate required number of times - for x = lowNum, highNum do -- iterate between the range - choices[#choices +1] = x -- add each entry to a table - end - end - local rtnVal = math.random(#choices) -- will now do a math.random of at least 50 choices - for i = 1, 10 do - rtnVal = math.random(#choices) -- iterate a few times for giggles - end - return choices[rtnVal] - end - - function mist.stringMatch(s1, s2, bool) - local exclude = {'%-', '%(', '%)', '%_', '%[', '%]', '%.', '%#', '% ', '%{', '%}', '%$', '%%', '%?', '%+', '%^'} - if type(s1) == 'string' and type(s2) == 'string' then - for i , str in pairs(exclude) do - s1 = string.gsub(s1, str, '') - s2 = string.gsub(s2, str, '') - end - if not bool then - s1 = string.lower(s1) - s2 = string.lower(s2) - end - log:info('Comparing: $1 and $2', s1, s2) - if s1 == s2 then - return true - else - return false - end - else - log:error('Either the first or second variable were not a string') - return false - end - end - - mist.matchString = mist.stringMatch -- both commands work because order out type of I - - --[[ scope: -{ - units = {...}, -- unit names. - coa = {...}, -- coa names - countries = {...}, -- country names - CA = {...}, -- looks just like coa. - unitTypes = { red = {}, blue = {}, all = {}, Russia = {},} -} - - -scope examples: - -{ units = { 'Hawg11', 'Hawg12' }, CA = {'blue'} } - -{ countries = {'Georgia'}, unitTypes = {blue = {'A-10C', 'A-10A'}}} - -{ coa = {'all'}} - -{unitTypes = { blue = {'A-10C'}}} -]] -end - ---- Utility functions. --- E.g. conversions between units etc. --- @section mist.utils -do -- mist.util scope - mist.utils = {} - - --- Converts angle in radians to degrees. - -- @param angle angle in radians - -- @return angle in degrees - function mist.utils.toDegree(angle) - return angle*180/math.pi - end - - --- Converts angle in degrees to radians. - -- @param angle angle in degrees - -- @return angle in degrees - function mist.utils.toRadian(angle) - return angle*math.pi/180 - end - - --- Converts meters to nautical miles. - -- @param meters distance in meters - -- @return distance in nautical miles - function mist.utils.metersToNM(meters) - return meters/1852 - end - - --- Converts meters to feet. - -- @param meters distance in meters - -- @return distance in feet - function mist.utils.metersToFeet(meters) - return meters/0.3048 - end - - --- Converts nautical miles to meters. - -- @param nm distance in nautical miles - -- @return distance in meters - function mist.utils.NMToMeters(nm) - return nm*1852 - end - - --- Converts feet to meters. - -- @param feet distance in feet - -- @return distance in meters - function mist.utils.feetToMeters(feet) - return feet*0.3048 - end - - --- Converts meters per second to knots. - -- @param mps speed in m/s - -- @return speed in knots - function mist.utils.mpsToKnots(mps) - return mps*3600/1852 - end - - --- Converts meters per second to kilometers per hour. - -- @param mps speed in m/s - -- @return speed in km/h - function mist.utils.mpsToKmph(mps) - return mps*3.6 - end - - --- Converts knots to meters per second. - -- @param knots speed in knots - -- @return speed in m/s - function mist.utils.knotsToMps(knots) - return knots*1852/3600 - end - - --- Converts kilometers per hour to meters per second. - -- @param kmph speed in km/h - -- @return speed in m/s - function mist.utils.kmphToMps(kmph) - return kmph/3.6 - end - - --- Converts a Vec3 to a Vec2. - -- @tparam Vec3 vec the 3D vector - -- @return vector converted to Vec2 - function mist.utils.makeVec2(vec) - if vec.z then - return {x = vec.x, y = vec.z} - else - return {x = vec.x, y = vec.y} -- it was actually already vec2. - end - end - - --- Converts a Vec2 to a Vec3. - -- @tparam Vec2 vec the 2D vector - -- @param y optional new y axis (altitude) value. If omitted it's 0. - function mist.utils.makeVec3(vec, y) - if not vec.z then - if vec.alt and not y then - y = vec.alt - elseif not y then - y = 0 - end - return {x = vec.x, y = y, z = vec.y} - else - return {x = vec.x, y = vec.y, z = vec.z} -- it was already Vec3, actually. - end - end - - --- Converts a Vec2 to a Vec3 using ground level as altitude. - -- The ground level at the specific point is used as altitude (y-axis) - -- for the new vector. Optionally a offset can be specified. - -- @tparam Vec2 vec the 2D vector - -- @param[opt] offset offset to be applied to the ground level - -- @return new 3D vector - function mist.utils.makeVec3GL(vec, offset) - local adj = offset or 0 - - if not vec.z then - return {x = vec.x, y = (land.getHeight(vec) + adj), z = vec.y} - else - return {x = vec.x, y = (land.getHeight({x = vec.x, y = vec.z}) + adj), z = vec.z} - end - end - - --- Returns the center of a zone as Vec3. - -- @tparam string|table zone trigger zone name or table - -- @treturn Vec3 center of the zone - function mist.utils.zoneToVec3(zone) - local new = {} - if type(zone) == 'table' then - if zone.point then - new.x = zone.point.x - new.y = zone.point.y - new.z = zone.point.z - elseif zone.x and zone.y and zone.z then - return zone - end - return new - elseif type(zone) == 'string' then - zone = trigger.misc.getZone(zone) - if zone then - new.x = zone.point.x - new.y = zone.point.y - new.z = zone.point.z - return new - end - end - end - - --- Returns heading-error corrected direction. - -- True-north corrected direction from point along vector vec. - -- @tparam Vec3 vec - -- @tparam Vec2 point - -- @return heading-error corrected direction from point. - function mist.utils.getDir(vec, point) - local dir = math.atan2(vec.z, vec.x) - if point then - dir = dir + mist.getNorthCorrection(point) - end - if dir < 0 then - dir = dir + 2 * math.pi -- put dir in range of 0 to 2*pi - end - return dir - end - - --- Returns distance in meters between two points. - -- @tparam Vec2|Vec3 point1 first point - -- @tparam Vec2|Vec3 point2 second point - -- @treturn number distance between given points. - function mist.utils.get2DDist(point1, point2) - point1 = mist.utils.makeVec3(point1) - point2 = mist.utils.makeVec3(point2) - return mist.vec.mag({x = point1.x - point2.x, y = 0, z = point1.z - point2.z}) - end - - --- Returns distance in meters between two points in 3D space. - -- @tparam Vec3 point1 first point - -- @tparam Vec3 point2 second point - -- @treturn number distancen between given points in 3D space. - function mist.utils.get3DDist(point1, point2) - return mist.vec.mag({x = point1.x - point2.x, y = point1.y - point2.y, z = point1.z - point2.z}) - end - - --- Creates a waypoint from a vector. - -- @tparam Vec2|Vec3 vec position of the new waypoint - -- @treturn Waypoint a new waypoint to be used inside paths. - function mist.utils.vecToWP(vec) - local newWP = {} - newWP.x = vec.x - newWP.y = vec.y - if vec.z then - newWP.alt = vec.y - newWP.y = vec.z - else - newWP.alt = land.getHeight({x = vec.x, y = vec.y}) - end - return newWP - end - - --- Creates a waypoint from a unit. - -- This function also considers the units speed. - -- The alt_type of this waypoint is set to "BARO". - -- @tparam Unit pUnit Unit whose position and speed will be used. - -- @treturn Waypoint new waypoint. - function mist.utils.unitToWP(pUnit) - local unit = mist.utils.deepCopy(pUnit) - if type(unit) == 'string' then - if Unit.getByName(unit) then - unit = Unit.getByName(unit) - end - end - if unit:isExist() == true then - local new = mist.utils.vecToWP(unit:getPosition().p) - new.speed = mist.vec.mag(unit:getVelocity()) - new.alt_type = "BARO" - - return new - end - log:error("$1 not found or doesn't exist", pUnit) - return false - end - - --- Creates a deep copy of a object. - -- Usually this object is a table. - -- See also: from http://lua-users.org/wiki/CopyTable - -- @param object object to copy - -- @return copy of object - function mist.utils.deepCopy(object) - local lookup_table = {} - local function _copy(object) - if type(object) ~= "table" then - return object - elseif lookup_table[object] then - return lookup_table[object] - end - local new_table = {} - lookup_table[object] = new_table - for index, value in pairs(object) do - new_table[_copy(index)] = _copy(value) - end - return setmetatable(new_table, getmetatable(object)) - end - return _copy(object) - end - - --- Simple rounding function. - -- From http://lua-users.org/wiki/SimpleRound - -- use negative idp for rounding ahead of decimal place, positive for rounding after decimal place - -- @tparam number num number to round - -- @param idp - function mist.utils.round(num, idp) - local mult = 10^(idp or 0) - return math.floor(num * mult + 0.5) / mult - end - - --- Rounds all numbers inside a table. - -- @tparam table tbl table in which to round numbers - -- @param idp - function mist.utils.roundTbl(tbl, idp) - for id, val in pairs(tbl) do - if type(val) == 'number' then - tbl[id] = mist.utils.round(val, idp) - end - end - return tbl - end - - --- Executes the given string. - -- borrowed from Slmod - -- @tparam string s string containing LUA code. - -- @treturn boolean true if successfully executed, false otherwise - function mist.utils.dostring(s) - local f, err = loadstring(s) - if f then - return true, f() - else - return false, err - end - end - - --- Checks a table's types. - -- This function checks a tables types against a specifically forged type table. - -- @param fname - -- @tparam table type_tbl - -- @tparam table var_tbl - -- @usage -- specifically forged type table - -- type_tbl = { - -- {'table', 'number'}, - -- 'string', - -- 'number', - -- 'number', - -- {'string','nil'}, - -- {'number', 'nil'} - -- } - -- -- my_tbl index 1 must be a table or a number; - -- -- index 2, a string; index 3, a number; - -- -- index 4, a number; index 5, either a string or nil; - -- -- and index 6, either a number or nil. - -- mist.utils.typeCheck(type_tbl, my_tb) - -- @return true if table passes the check, false otherwise. - function mist.utils.typeCheck(fname, type_tbl, var_tbl) - -- log:info('type check') - for type_key, type_val in pairs(type_tbl) do - -- log:info('type_key: $1 type_val: $2', type_key, type_val) - - --type_key can be a table of accepted keys- so try to find one that is not nil - local type_key_str = '' - local act_key = type_key -- actual key within var_tbl - necessary to use for multiple possible key variables. Initialize to type_key - if type(type_key) == 'table' then - - for i = 1, #type_key do - if i ~= 1 then - type_key_str = type_key_str .. '/' - end - type_key_str = type_key_str .. tostring(type_key[i]) - if var_tbl[type_key[i]] ~= nil then - act_key = type_key[i] -- found a non-nil entry, make act_key now this val. - end - end - else - type_key_str = tostring(type_key) - end - - local err_msg = 'Error in function ' .. fname .. ', parameter "' .. type_key_str .. '", expected: ' - local passed_check = false - - if type(type_tbl[type_key]) == 'table' then - -- log:info('err_msg, before: $1', err_msg) - for j = 1, #type_tbl[type_key] do - - if j == 1 then - err_msg = err_msg .. type_tbl[type_key][j] - else - err_msg = err_msg .. ' or ' .. type_tbl[type_key][j] - end - - if type(var_tbl[act_key]) == type_tbl[type_key][j] then - passed_check = true - end - end - -- log:info('err_msg, after: $1', err_msg) - else - -- log:info('err_msg, before: $1', err_msg) - err_msg = err_msg .. type_tbl[type_key] - -- log:info('err_msg, after: $1', err_msg) - if type(var_tbl[act_key]) == type_tbl[type_key] then - passed_check = true - end - - end - - if not passed_check then - err_msg = err_msg .. ', got ' .. type(var_tbl[act_key]) - return false, err_msg - end - end - return true - end - - --- Serializes the give variable to a string. - -- borrowed from slmod - -- @param var variable to serialize - -- @treturn string variable serialized to string - function mist.utils.basicSerialize(var) - if var == nil then - return "\"\"" - else - if ((type(var) == 'number') or - (type(var) == 'boolean') or - (type(var) == 'function') or - (type(var) == 'table') or - (type(var) == 'userdata') ) then - return tostring(var) - elseif type(var) == 'string' then - var = string.format('%q', var) - return var - end - end -end - ---- Serialize value --- borrowed from slmod (serialize_slmod) --- @param name --- @param value value to serialize --- @param level -function mist.utils.serialize(name, value, level) - --Based on ED's serialize_simple2 - local function basicSerialize(o) - if type(o) == "number" then - return tostring(o) - elseif type(o) == "boolean" then - return tostring(o) - else -- assume it is a string - return mist.utils.basicSerialize(o) - end - end - - local function serializeToTbl(name, value, level) - local var_str_tbl = {} - if level == nil then level = "" end - if level ~= "" then level = level.." " end - - table.insert(var_str_tbl, level .. name .. " = ") - - if type(value) == "number" or type(value) == "string" or type(value) == "boolean" then - table.insert(var_str_tbl, basicSerialize(value) .. ",\n") - elseif type(value) == "table" then - table.insert(var_str_tbl, "\n"..level.."{\n") - - for k,v in pairs(value) do -- serialize its fields - local key - if type(k) == "number" then - key = string.format("[%s]", k) - else - key = string.format("[%q]", k) - end - - table.insert(var_str_tbl, mist.utils.serialize(key, v, level.." ")) - - end - if level == "" then - table.insert(var_str_tbl, level.."} -- end of "..name.."\n") - - else - table.insert(var_str_tbl, level.."}, -- end of "..name.."\n") - - end - else - log:error('Cannot serialize a $1', type(value)) - end - return var_str_tbl - end - - local t_str = serializeToTbl(name, value, level) - - return table.concat(t_str) -end - ---- Serialize value supporting cycles. --- borrowed from slmod (serialize_wcycles) --- @param name --- @param value value to serialize --- @param saved -function mist.utils.serializeWithCycles(name, value, saved) - --mostly straight out of Programming in Lua - local function basicSerialize(o) - if type(o) == "number" then - return tostring(o) - elseif type(o) == "boolean" then - return tostring(o) - else -- assume it is a string - return mist.utils.basicSerialize(o) - end - end - - local t_str = {} - saved = saved or {} -- initial value - if ((type(value) == 'string') or (type(value) == 'number') or (type(value) == 'table') or (type(value) == 'boolean')) then - table.insert(t_str, name .. " = ") - if type(value) == "number" or type(value) == "string" or type(value) == "boolean" then - table.insert(t_str, basicSerialize(value) .. "\n") - else - - if saved[value] then -- value already saved? - table.insert(t_str, saved[value] .. "\n") - else - saved[value] = name -- save name for next time - table.insert(t_str, "{}\n") - for k,v in pairs(value) do -- save its fields - local fieldname = string.format("%s[%s]", name, basicSerialize(k)) - table.insert(t_str, mist.utils.serializeWithCycles(fieldname, v, saved)) - end - end - end - return table.concat(t_str) - else - return "" - end -end - ---- Serialize a table to a single line string. --- serialization of a table all on a single line, no comments, made to replace old get_table_string function --- borrowed from slmod --- @tparam table tbl table to serialize. --- @treturn string string containing serialized table -function mist.utils.oneLineSerialize(tbl) - if type(tbl) == 'table' then --function only works for tables! - - local tbl_str = {} - - tbl_str[#tbl_str + 1] = '{ ' - - for ind,val in pairs(tbl) do -- serialize its fields - if type(ind) == "number" then - tbl_str[#tbl_str + 1] = '[' - tbl_str[#tbl_str + 1] = tostring(ind) - tbl_str[#tbl_str + 1] = '] = ' - else --must be a string - tbl_str[#tbl_str + 1] = '[' - tbl_str[#tbl_str + 1] = mist.utils.basicSerialize(ind) - tbl_str[#tbl_str + 1] = '] = ' - end - - if ((type(val) == 'number') or (type(val) == 'boolean')) then - tbl_str[#tbl_str + 1] = tostring(val) - tbl_str[#tbl_str + 1] = ', ' - elseif type(val) == 'string' then - tbl_str[#tbl_str + 1] = mist.utils.basicSerialize(val) - tbl_str[#tbl_str + 1] = ', ' - elseif type(val) == 'nil' then -- won't ever happen, right? - tbl_str[#tbl_str + 1] = 'nil, ' - elseif type(val) == 'table' then - tbl_str[#tbl_str + 1] = mist.utils.oneLineSerialize(val) - tbl_str[#tbl_str + 1] = ', ' --I think this is right, I just added it - else - log:war('Unable to serialize value type $1 at index $2', mist.utils.basicSerialize(type(val)), tostring(ind)) - end - - end - tbl_str[#tbl_str + 1] = '}' - return table.concat(tbl_str) - end -end - ---- Returns table in a easy readable string representation. --- this function is not meant for serialization because it uses --- newlines for better readability. --- @param tbl table to show --- @param loc --- @param indent --- @param tableshow_tbls --- @return human readable string representation of given table -function mist.utils.tableShow(tbl, loc, indent, tableshow_tbls) --based on serialize_slmod, this is a _G serialization - tableshow_tbls = tableshow_tbls or {} --create table of tables - loc = loc or "" - indent = indent or "" - if type(tbl) == 'table' then --function only works for tables! - tableshow_tbls[tbl] = loc - - local tbl_str = {} - - tbl_str[#tbl_str + 1] = indent .. '{\n' - - for ind,val in pairs(tbl) do -- serialize its fields - if type(ind) == "number" then - tbl_str[#tbl_str + 1] = indent - tbl_str[#tbl_str + 1] = loc .. '[' - tbl_str[#tbl_str + 1] = tostring(ind) - tbl_str[#tbl_str + 1] = '] = ' - else - tbl_str[#tbl_str + 1] = indent - tbl_str[#tbl_str + 1] = loc .. '[' - tbl_str[#tbl_str + 1] = mist.utils.basicSerialize(ind) - tbl_str[#tbl_str + 1] = '] = ' - end - - if ((type(val) == 'number') or (type(val) == 'boolean')) then - tbl_str[#tbl_str + 1] = tostring(val) - tbl_str[#tbl_str + 1] = ',\n' - elseif type(val) == 'string' then - tbl_str[#tbl_str + 1] = mist.utils.basicSerialize(val) - tbl_str[#tbl_str + 1] = ',\n' - elseif type(val) == 'nil' then -- won't ever happen, right? - tbl_str[#tbl_str + 1] = 'nil,\n' - elseif type(val) == 'table' then - if tableshow_tbls[val] then - tbl_str[#tbl_str + 1] = tostring(val) .. ' already defined: ' .. tableshow_tbls[val] .. ',\n' - else - tableshow_tbls[val] = loc .. '[' .. mist.utils.basicSerialize(ind) .. ']' - tbl_str[#tbl_str + 1] = tostring(val) .. ' ' - tbl_str[#tbl_str + 1] = mist.utils.tableShow(val, loc .. '[' .. mist.utils.basicSerialize(ind).. ']', indent .. ' ', tableshow_tbls) - tbl_str[#tbl_str + 1] = ',\n' - end - elseif type(val) == 'function' then - if debug and debug.getinfo then - local fcnname = tostring(val) - local info = debug.getinfo(val, "S") - if info.what == "C" then - tbl_str[#tbl_str + 1] = string.format('%q', fcnname .. ', C function') .. ',\n' - else - if (string.sub(info.source, 1, 2) == [[./]]) then - tbl_str[#tbl_str + 1] = string.format('%q', fcnname .. ', defined in (' .. info.linedefined .. '-' .. info.lastlinedefined .. ')' .. info.source) ..',\n' - else - tbl_str[#tbl_str + 1] = string.format('%q', fcnname .. ', defined in (' .. info.linedefined .. '-' .. info.lastlinedefined .. ')') ..',\n' - end - end - - else - tbl_str[#tbl_str + 1] = 'a function,\n' - end - else - tbl_str[#tbl_str + 1] = 'unable to serialize value type ' .. mist.utils.basicSerialize(type(val)) .. ' at index ' .. tostring(ind) - end - end - - tbl_str[#tbl_str + 1] = indent .. '}' - return table.concat(tbl_str) - end -end -end - ---- Debug functions --- @section mist.debug -do -- mist.debug scope - mist.debug = {} - - --- Dumps the global table _G. - -- This dumps the global table _G to a file in - -- the DCS\Logs directory. - -- This function requires you to disable script sanitization - -- in $DCS_ROOT\Scripts\MissionScripting.lua to access lfs and io - -- libraries. - -- @param fname - function mist.debug.dump_G(fname) - if lfs and io then - local fdir = lfs.writedir() .. [[Logs\]] .. fname - local f = io.open(fdir, 'w') - f:write(mist.utils.tableShow(_G)) - f:close() - log:info('Wrote debug data to $1', fdir) - --trigger.action.outText(errmsg, 10) - else - log:alert('insufficient libraries to run mist.debug.dump_G, you must disable the sanitization of the io and lfs libraries in ./Scripts/MissionScripting.lua') - --trigger.action.outText(errmsg, 10) - end - end - - --- Write debug data to file. - -- This function requires you to disable script sanitization - -- in $DCS_ROOT\Scripts\MissionScripting.lua to access lfs and io - -- libraries. - -- @param fcn - -- @param fcnVars - -- @param fname - function mist.debug.writeData(fcn, fcnVars, fname) - if lfs and io then - local fdir = lfs.writedir() .. [[Logs\]] .. fname - local f = io.open(fdir, 'w') - f:write(fcn(unpack(fcnVars, 1, table.maxn(fcnVars)))) - f:close() - log:info('Wrote debug data to $1', fdir) - local errmsg = 'mist.debug.writeData wrote data to ' .. fdir - trigger.action.outText(errmsg, 10) - else - local errmsg = 'Error: insufficient libraries to run mist.debug.writeData, you must disable the sanitization of the io and lfs libraries in ./Scripts/MissionScripting.lua' - log:alert(errmsg) - trigger.action.outText(errmsg, 10) - end - end - - --- Write mist databases to file. - -- This function requires you to disable script sanitization - -- in $DCS_ROOT\Scripts\MissionScripting.lua to access lfs and io - -- libraries. - function mist.debug.dumpDBs() - for DBname, DB in pairs(mist.DBs) do - if type(DB) == 'table' and type(DBname) == 'string' then - mist.debug.writeData(mist.utils.serialize, {DBname, DB}, 'mist_DBs_' .. DBname .. '.lua') - end - end - end -end - ---- 3D Vector functions --- @section mist.vec -do -- mist.vec scope - mist.vec = {} - - --- Vector addition. - -- @tparam Vec3 vec1 first vector - -- @tparam Vec3 vec2 second vector - -- @treturn Vec3 new vector, sum of vec1 and vec2. - function mist.vec.add(vec1, vec2) - return {x = vec1.x + vec2.x, y = vec1.y + vec2.y, z = vec1.z + vec2.z} - end - - --- Vector substraction. - -- @tparam Vec3 vec1 first vector - -- @tparam Vec3 vec2 second vector - -- @treturn Vec3 new vector, vec2 substracted from vec1. - function mist.vec.sub(vec1, vec2) - return {x = vec1.x - vec2.x, y = vec1.y - vec2.y, z = vec1.z - vec2.z} - end - - --- Vector scalar multiplication. - -- @tparam Vec3 vec vector to multiply - -- @tparam number mult scalar multiplicator - -- @treturn Vec3 new vector multiplied with the given scalar - function mist.vec.scalarMult(vec, mult) - return {x = vec.x*mult, y = vec.y*mult, z = vec.z*mult} - end - - mist.vec.scalar_mult = mist.vec.scalarMult - - --- Vector dot product. - -- @tparam Vec3 vec1 first vector - -- @tparam Vec3 vec2 second vector - -- @treturn number dot product of given vectors - function mist.vec.dp (vec1, vec2) - return vec1.x*vec2.x + vec1.y*vec2.y + vec1.z*vec2.z - end - - --- Vector cross product. - -- @tparam Vec3 vec1 first vector - -- @tparam Vec3 vec2 second vector - -- @treturn Vec3 new vector, cross product of vec1 and vec2. - function mist.vec.cp(vec1, vec2) - return { x = vec1.y*vec2.z - vec1.z*vec2.y, y = vec1.z*vec2.x - vec1.x*vec2.z, z = vec1.x*vec2.y - vec1.y*vec2.x} - end - - --- Vector magnitude - -- @tparam Vec3 vec vector - -- @treturn number magnitude of vector vec - function mist.vec.mag(vec) - return (vec.x^2 + vec.y^2 + vec.z^2)^0.5 - end - - --- Unit vector - -- @tparam Vec3 vec - -- @treturn Vec3 unit vector of vec - function mist.vec.getUnitVec(vec) - local mag = mist.vec.mag(vec) - return { x = vec.x/mag, y = vec.y/mag, z = vec.z/mag } - end - - --- Rotate vector. - -- @tparam Vec2 vec2 to rotoate - -- @tparam number theta - -- @return Vec2 rotated vector. - function mist.vec.rotateVec2(vec2, theta) - return { x = vec2.x*math.cos(theta) - vec2.y*math.sin(theta), y = vec2.x*math.sin(theta) + vec2.y*math.cos(theta)} - end -end - ---- Flag functions. --- The mist "Flag functions" are functions that are similar to Slmod functions --- that detect a game condition and set a flag when that game condition is met. --- --- They are intended to be used by persons with little or no experience in Lua --- programming, but with a good knowledge of the DCS mission editor. --- @section mist.flagFunc -do -- mist.flagFunc scope - mist.flagFunc = {} - - --- Sets a flag if map objects are destroyed inside a zone. - -- Once this function is run, it will start a continuously evaluated process - -- that will set a flag true if map objects (such as bridges, buildings in - -- town, etc.) die (or have died) in a mission editor zone (or set of zones). - -- This will only happen once; once the flag is set true, the process ends. - -- @usage - -- -- Example vars table - -- vars = { - -- zones = { "zone1", "zone2" }, -- can also be a single string - -- flag = 3, -- number of the flag - -- stopflag = 4, -- optional number of the stop flag - -- req_num = 10, -- optional minimum amount of map objects needed to die - -- } - -- mist.flagFuncs.mapobjs_dead_zones(vars) - -- @tparam table vars table containing parameters. - function mist.flagFunc.mapobjs_dead_zones(vars) - --[[vars needs to be: -zones = table or string, -flag = number, -stopflag = number or nil, -req_num = number or nil - -AND used by function, -initial_number - -]] - -- type_tbl - local type_tbl = { - [{'zones', 'zone'}] = {'table', 'string'}, - flag = {'number', 'string'}, - [{'stopflag', 'stopFlag'}] = {'number', 'string', 'nil'}, - [{'req_num', 'reqnum'}] = {'number', 'nil'}, - } - - local err, errmsg = mist.utils.typeCheck('mist.flagFunc.mapobjs_dead_zones', type_tbl, vars) - assert(err, errmsg) - local zones = vars.zones or vars.zone - local flag = vars.flag - local stopflag = vars.stopflag or vars.stopFlag or -1 - local req_num = vars.req_num or vars.reqnum or 1 - local initial_number = vars.initial_number - - if type(zones) == 'string' then - zones = {zones} - end - - if not initial_number then - initial_number = #mist.getDeadMapObjsInZones(zones) - end - - if stopflag == -1 or (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then - if (#mist.getDeadMapObjsInZones(zones) - initial_number) >= req_num and trigger.misc.getUserFlag(flag) == 0 then - trigger.action.setUserFlag(flag, true) - return - else - mist.scheduleFunction(mist.flagFunc.mapobjs_dead_zones, {{zones = zones, flag = flag, stopflag = stopflag, req_num = req_num, initial_number = initial_number}}, timer.getTime() + 1) - end - end - end - - --- Sets a flag if map objects are destroyed inside a polygon. - -- Once this function is run, it will start a continuously evaluated process - -- that will set a flag true if map objects (such as bridges, buildings in - -- town, etc.) die (or have died) in a polygon. - -- This will only happen once; once the flag is set true, the process ends. - -- @usage - -- -- Example vars table - -- vars = { - -- zone = { - -- [1] = mist.DBs.unitsByName['NE corner'].point, - -- [2] = mist.DBs.unitsByName['SE corner'].point, - -- [3] = mist.DBs.unitsByName['SW corner'].point, - -- [4] = mist.DBs.unitsByName['NW corner'].point - -- } - -- flag = 3, -- number of the flag - -- stopflag = 4, -- optional number of the stop flag - -- req_num = 10, -- optional minimum amount of map objects needed to die - -- } - -- mist.flagFuncs.mapobjs_dead_zones(vars) - -- @tparam table vars table containing parameters. - function mist.flagFunc.mapobjs_dead_polygon(vars) - --[[vars needs to be: -zone = table, -flag = number, -stopflag = number or nil, -req_num = number or nil - -AND used by function, -initial_number - -]] - -- type_tbl - local type_tbl = { - [{'zone', 'polyzone'}] = 'table', - flag = {'number', 'string'}, - [{'stopflag', 'stopFlag'}] = {'number', 'string', 'nil'}, - [{'req_num', 'reqnum'}] = {'number', 'nil'}, - } - - local err, errmsg = mist.utils.typeCheck('mist.flagFunc.mapobjs_dead_polygon', type_tbl, vars) - assert(err, errmsg) - local zone = vars.zone or vars.polyzone - local flag = vars.flag - local stopflag = vars.stopflag or vars.stopFlag or -1 - local req_num = vars.req_num or vars.reqnum or 1 - local initial_number = vars.initial_number - - if not initial_number then - initial_number = #mist.getDeadMapObjsInPolygonZone(zone) - end - - if stopflag == -1 or (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then - if (#mist.getDeadMapObjsInPolygonZone(zone) - initial_number) >= req_num and trigger.misc.getUserFlag(flag) == 0 then - trigger.action.setUserFlag(flag, true) - return - else - mist.scheduleFunction(mist.flagFunc.mapobjs_dead_polygon, {{zone = zone, flag = flag, stopflag = stopflag, req_num = req_num, initial_number = initial_number}}, timer.getTime() + 1) - end - end - end - - --- Sets a flag if unit(s) is/are inside a polygon. - -- @tparam table vars @{unitsInPolygonVars} - -- @usage -- set flag 11 to true as soon as any blue vehicles - -- -- are inside the polygon shape created off of the waypoints - -- -- of the group forest1 - -- mist.flagFunc.units_in_polygon { - -- units = {'[blue][vehicle]'}, - -- zone = mist.getGroupPoints('forest1'), - -- flag = 11 - -- } - function mist.flagFunc.units_in_polygon(vars) - --[[vars needs to be: -units = table, -zone = table, -flag = number, -stopflag = number or nil, -maxalt = number or nil, -interval = number or nil, -req_num = number or nil -toggle = boolean or nil -unitTableDef = table or nil -]] - -- type_tbl - local type_tbl = { - [{'units', 'unit'}] = 'table', - [{'zone', 'polyzone'}] = 'table', - flag = {'number', 'string'}, - [{'stopflag', 'stopFlag'}] = {'number', 'string', 'nil'}, - [{'maxalt', 'alt'}] = {'number', 'nil'}, - interval = {'number', 'nil'}, - [{'req_num', 'reqnum'}] = {'number', 'nil'}, - toggle = {'boolean', 'nil'}, - unitTableDef = {'table', 'nil'}, - } - - local err, errmsg = mist.utils.typeCheck('mist.flagFunc.units_in_polygon', type_tbl, vars) - assert(err, errmsg) - local units = vars.units or vars.unit - local zone = vars.zone or vars.polyzone - local flag = vars.flag - local stopflag = vars.stopflag or vars.stopFlag or -1 - local interval = vars.interval or 1 - local maxalt = vars.maxalt or vars.alt - local req_num = vars.req_num or vars.reqnum or 1 - local toggle = vars.toggle or nil - local unitTableDef = vars.unitTableDef - - if not units.processed then - unitTableDef = mist.utils.deepCopy(units) - end - - if (units.processed and units.processed < mist.getLastDBUpdateTime()) or not units.processed then -- run unit table short cuts - if unitTableDef then - units = mist.makeUnitTable(unitTableDef) - end - end - - if stopflag == -1 or (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == 0) then - local num_in_zone = 0 - for i = 1, #units do - local unit = Unit.getByName(units[i]) - if unit then - local pos = unit:getPosition().p - if mist.pointInPolygon(pos, zone, maxalt) then - num_in_zone = num_in_zone + 1 - if num_in_zone >= req_num and trigger.misc.getUserFlag(flag) == 0 then - trigger.action.setUserFlag(flag, true) - break - end - end - end - end - if toggle and (num_in_zone < req_num) and trigger.misc.getUserFlag(flag) > 0 then - trigger.action.setUserFlag(flag, false) - end - -- do another check in case stopflag was set true by this function - if (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == 0) then - mist.scheduleFunction(mist.flagFunc.units_in_polygon, {{units = units, zone = zone, flag = flag, stopflag = stopflag, interval = interval, req_num = req_num, maxalt = maxalt, toggle = toggle, unitTableDef = unitTableDef}}, timer.getTime() + interval) - end - end - - end - - --- Sets a flag if unit(s) is/are inside a trigger zone. - -- @todo document - function mist.flagFunc.units_in_zones(vars) - --[[vars needs to be: - units = table, - zones = table, - flag = number, - stopflag = number or nil, - zone_type = string or nil, - req_num = number or nil, - interval = number or nil - toggle = boolean or nil - ]] - -- type_tbl - local type_tbl = { - units = 'table', - zones = 'table', - flag = {'number', 'string'}, - [{'stopflag', 'stopFlag'}] = {'number', 'string', 'nil'}, - [{'zone_type', 'zonetype'}] = {'string', 'nil'}, - [{'req_num', 'reqnum'}] = {'number', 'nil'}, - interval = {'number', 'nil'}, - toggle = {'boolean', 'nil'}, - unitTableDef = {'table', 'nil'}, - } - - local err, errmsg = mist.utils.typeCheck('mist.flagFunc.units_in_zones', type_tbl, vars) - assert(err, errmsg) - local units = vars.units - local zones = vars.zones - local flag = vars.flag - local stopflag = vars.stopflag or vars.stopFlag or -1 - local zone_type = vars.zone_type or vars.zonetype or 'cylinder' - local req_num = vars.req_num or vars.reqnum or 1 - local interval = vars.interval or 1 - local toggle = vars.toggle or nil - local unitTableDef = vars.unitTableDef - - if not units.processed then - unitTableDef = mist.utils.deepCopy(units) - end - - if (units.processed and units.processed < mist.getLastDBUpdateTime()) or not units.processed then -- run unit table short cuts - if unitTableDef then - units = mist.makeUnitTable(unitTableDef) - end - end - - if stopflag == -1 or (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then - - local in_zone_units = mist.getUnitsInZones(units, zones, zone_type) - - if #in_zone_units >= req_num and trigger.misc.getUserFlag(flag) == 0 then - trigger.action.setUserFlag(flag, true) - elseif #in_zone_units < req_num and toggle then - trigger.action.setUserFlag(flag, false) - end - -- do another check in case stopflag was set true by this function - if (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then - mist.scheduleFunction(mist.flagFunc.units_in_zones, {{units = units, zones = zones, flag = flag, stopflag = stopflag, zone_type = zone_type, req_num = req_num, interval = interval, toggle = toggle, unitTableDef = unitTableDef}}, timer.getTime() + interval) - end - end - - end - - --- Sets a flag if unit(s) is/are inside a moving zone. - -- @todo document - function mist.flagFunc.units_in_moving_zones(vars) - --[[vars needs to be: - units = table, - zone_units = table, - radius = number, - flag = number, - stopflag = number or nil, - zone_type = string or nil, - req_num = number or nil, - interval = number or nil - toggle = boolean or nil - ]] - -- type_tbl - local type_tbl = { - units = 'table', - [{'zone_units', 'zoneunits'}] = 'table', - radius = 'number', - flag = {'number', 'string'}, - [{'stopflag', 'stopFlag'}] = {'number', 'string', 'nil'}, - [{'zone_type', 'zonetype'}] = {'string', 'nil'}, - [{'req_num', 'reqnum'}] = {'number', 'nil'}, - interval = {'number', 'nil'}, - toggle = {'boolean', 'nil'}, - unitTableDef = {'table', 'nil'}, - zUnitTableDef = {'table', 'nil'}, - } - - local err, errmsg = mist.utils.typeCheck('mist.flagFunc.units_in_moving_zones', type_tbl, vars) - assert(err, errmsg) - local units = vars.units - local zone_units = vars.zone_units or vars.zoneunits - local radius = vars.radius - local flag = vars.flag - local stopflag = vars.stopflag or vars.stopFlag or -1 - local zone_type = vars.zone_type or vars.zonetype or 'cylinder' - local req_num = vars.req_num or vars.reqnum or 1 - local interval = vars.interval or 1 - local toggle = vars.toggle or nil - local unitTableDef = vars.unitTableDef - local zUnitTableDef = vars.zUnitTableDef - - if not units.processed then - unitTableDef = mist.utils.deepCopy(units) - end - - if not zone_units.processed then - zUnitTableDef = mist.utils.deepCopy(zone_units) - end - - if (units.processed and units.processed < mist.getLastDBUpdateTime()) or not units.processed then -- run unit table short cuts - if unitTableDef then - units = mist.makeUnitTable(unitTableDef) - end - end - - if (zone_units.processed and zone_units.processed < mist.getLastDBUpdateTime()) or not zone_units.processed then -- run unit table short cuts - if zUnitTableDef then - zone_units = mist.makeUnitTable(zUnitTableDef) - end - - end - - if stopflag == -1 or (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then - - local in_zone_units = mist.getUnitsInMovingZones(units, zone_units, radius, zone_type) - - if #in_zone_units >= req_num and trigger.misc.getUserFlag(flag) == 0 then - trigger.action.setUserFlag(flag, true) - elseif #in_zone_units < req_num and toggle then - trigger.action.setUserFlag(flag, false) - end - -- do another check in case stopflag was set true by this function - if (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then - mist.scheduleFunction(mist.flagFunc.units_in_moving_zones, {{units = units, zone_units = zone_units, radius = radius, flag = flag, stopflag = stopflag, zone_type = zone_type, req_num = req_num, interval = interval, toggle = toggle, unitTableDef = unitTableDef, zUnitTableDef = zUnitTableDef}}, timer.getTime() + interval) - end - end - - end - - --- Sets a flag if units have line of sight to each other. - -- @todo document - function mist.flagFunc.units_LOS(vars) - --[[vars needs to be: -unitset1 = table, -altoffset1 = number, -unitset2 = table, -altoffset2 = number, -flag = number, -stopflag = number or nil, -radius = number or nil, -interval = number or nil, -req_num = number or nil -toggle = boolean or nil -]] - -- type_tbl - local type_tbl = { - [{'unitset1', 'units1'}] = 'table', - [{'altoffset1', 'alt1'}] = 'number', - [{'unitset2', 'units2'}] = 'table', - [{'altoffset2', 'alt2'}] = 'number', - flag = {'number', 'string'}, - [{'stopflag', 'stopFlag'}] = {'number', 'string', 'nil'}, - [{'req_num', 'reqnum'}] = {'number', 'nil'}, - interval = {'number', 'nil'}, - radius = {'number', 'nil'}, - toggle = {'boolean', 'nil'}, - unitTableDef1 = {'table', 'nil'}, - unitTableDef2 = {'table', 'nil'}, - } - - local err, errmsg = mist.utils.typeCheck('mist.flagFunc.units_LOS', type_tbl, vars) - assert(err, errmsg) - local unitset1 = vars.unitset1 or vars.units1 - local altoffset1 = vars.altoffset1 or vars.alt1 - local unitset2 = vars.unitset2 or vars.units2 - local altoffset2 = vars.altoffset2 or vars.alt2 - local flag = vars.flag - local stopflag = vars.stopflag or vars.stopFlag or -1 - local interval = vars.interval or 1 - local radius = vars.radius or math.huge - local req_num = vars.req_num or vars.reqnum or 1 - local toggle = vars.toggle or nil - local unitTableDef1 = vars.unitTableDef1 - local unitTableDef2 = vars.unitTableDef2 - - if not unitset1.processed then - unitTableDef1 = mist.utils.deepCopy(unitset1) - end - - if not unitset2.processed then - unitTableDef2 = mist.utils.deepCopy(unitset2) - end - - if (unitset1.processed and unitset1.processed < mist.getLastDBUpdateTime()) or not unitset1.processed then -- run unit table short cuts - if unitTableDef1 then - unitset1 = mist.makeUnitTable(unitTableDef1) - end - end - - if (unitset2.processed and unitset2.processed < mist.getLastDBUpdateTime()) or not unitset2.processed then -- run unit table short cuts - if unitTableDef2 then - unitset2 = mist.makeUnitTable(unitTableDef2) - end - end - - - if stopflag == -1 or (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then - - local unitLOSdata = mist.getUnitsLOS(unitset1, altoffset1, unitset2, altoffset2, radius) - - if #unitLOSdata >= req_num and trigger.misc.getUserFlag(flag) == 0 then - trigger.action.setUserFlag(flag, true) - elseif #unitLOSdata < req_num and toggle then - trigger.action.setUserFlag(flag, false) - end - -- do another check in case stopflag was set true by this function - if (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then - mist.scheduleFunction(mist.flagFunc.units_LOS, {{unitset1 = unitset1, altoffset1 = altoffset1, unitset2 = unitset2, altoffset2 = altoffset2, flag = flag, stopflag = stopflag, radius = radius, req_num = req_num, interval = interval, toggle = toggle, unitTableDef1 = unitTableDef1, unitTableDef2 = unitTableDef2}}, timer.getTime() + interval) - end - end - end - - --- Sets a flag if group is alive. - -- @todo document - function mist.flagFunc.group_alive(vars) - --[[vars -groupName -flag -toggle -interval -stopFlag - -]] - local type_tbl = { - [{'group', 'groupname', 'gp', 'groupName'}] = 'string', - flag = {'number', 'string'}, - [{'stopflag', 'stopFlag'}] = {'number', 'string', 'nil'}, - interval = {'number', 'nil'}, - toggle = {'boolean', 'nil'}, - } - - local err, errmsg = mist.utils.typeCheck('mist.flagFunc.group_alive', type_tbl, vars) - assert(err, errmsg) - - local groupName = vars.groupName or vars.group or vars.gp or vars.Groupname - local flag = vars.flag - local stopflag = vars.stopflag or vars.stopFlag or -1 - local interval = vars.interval or 1 - local toggle = vars.toggle or nil - - - if stopflag == -1 or (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then - if Group.getByName(groupName) and Group.getByName(groupName):isExist() == true and #Group.getByName(groupName):getUnits() > 0 then - if trigger.misc.getUserFlag(flag) == 0 then - trigger.action.setUserFlag(flag, true) - end - else - if toggle then - trigger.action.setUserFlag(flag, false) - end - end - end - - if (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then - mist.scheduleFunction(mist.flagFunc.group_alive, {{groupName = groupName, flag = flag, stopflag = stopflag, interval = interval, toggle = toggle}}, timer.getTime() + interval) - end - - end - - --- Sets a flag if group is dead. - -- @todo document - function mist.flagFunc.group_dead(vars) - local type_tbl = { - [{'group', 'groupname', 'gp', 'groupName'}] = 'string', - flag = {'number', 'string'}, - [{'stopflag', 'stopFlag'}] = {'number', 'string', 'nil'}, - interval = {'number', 'nil'}, - toggle = {'boolean', 'nil'}, - } - - local err, errmsg = mist.utils.typeCheck('mist.flagFunc.group_dead', type_tbl, vars) - assert(err, errmsg) - - local groupName = vars.groupName or vars.group or vars.gp or vars.Groupname - local flag = vars.flag - local stopflag = vars.stopflag or vars.stopFlag or -1 - local interval = vars.interval or 1 - local toggle = vars.toggle or nil - - - if stopflag == -1 or (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then - if (Group.getByName(groupName) and Group.getByName(groupName):isExist() == false) or (Group.getByName(groupName) and #Group.getByName(groupName):getUnits() < 1) or not Group.getByName(groupName) then - if trigger.misc.getUserFlag(flag) == 0 then - trigger.action.setUserFlag(flag, true) - end - else - if toggle then - trigger.action.setUserFlag(flag, false) - end - end - end - - if (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then - mist.scheduleFunction(mist.flagFunc.group_dead, {{groupName = groupName, flag = flag, stopflag = stopflag, interval = interval, toggle = toggle}}, timer.getTime() + interval) - end - end - - --- Sets a flag if less than given percent of group is alive. - -- @todo document - function mist.flagFunc.group_alive_less_than(vars) - local type_tbl = { - [{'group', 'groupname', 'gp', 'groupName'}] = 'string', - percent = 'number', - flag = {'number', 'string'}, - [{'stopflag', 'stopFlag'}] = {'number', 'string', 'nil'}, - interval = {'number', 'nil'}, - toggle = {'boolean', 'nil'}, - } - - local err, errmsg = mist.utils.typeCheck('mist.flagFunc.group_alive_less_than', type_tbl, vars) - assert(err, errmsg) - - local groupName = vars.groupName or vars.group or vars.gp or vars.Groupname - local flag = vars.flag - local percent = vars.percent - local stopflag = vars.stopflag or vars.stopFlag or -1 - local interval = vars.interval or 1 - local toggle = vars.toggle or nil - - - if stopflag == -1 or (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then - if Group.getByName(groupName) and Group.getByName(groupName):isExist() == true then - if Group.getByName(groupName):getSize()/Group.getByName(groupName):getInitialSize() < percent/100 then - if trigger.misc.getUserFlag(flag) == 0 then - trigger.action.setUserFlag(flag, true) - end - else - if toggle then - trigger.action.setUserFlag(flag, false) - end - end - else - if trigger.misc.getUserFlag(flag) == 0 then - trigger.action.setUserFlag(flag, true) - end - end - end - - if (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then - mist.scheduleFunction(mist.flagFunc.group_alive_less_than, {{groupName = groupName, flag = flag, stopflag = stopflag, interval = interval, toggle = toggle, percent = percent}}, timer.getTime() + interval) - end - end - - --- Sets a flag if more than given percent of group is alive. - -- @todo document - function mist.flagFunc.group_alive_more_than(vars) - local type_tbl = { - [{'group', 'groupname', 'gp', 'groupName'}] = 'string', - percent = 'number', - flag = {'number', 'string'}, - [{'stopflag', 'stopFlag'}] = {'number', 'string', 'nil'}, - interval = {'number', 'nil'}, - toggle = {'boolean', 'nil'}, - } - - local err, errmsg = mist.utils.typeCheck('mist.flagFunc.group_alive_more_than', type_tbl, vars) - assert(err, errmsg) - - local groupName = vars.groupName or vars.group or vars.gp or vars.Groupname - local flag = vars.flag - local percent = vars.percent - local stopflag = vars.stopflag or vars.stopFlag or -1 - local interval = vars.interval or 1 - local toggle = vars.toggle or nil - - - if stopflag == -1 or (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then - if Group.getByName(groupName) and Group.getByName(groupName):isExist() == true then - if Group.getByName(groupName):getSize()/Group.getByName(groupName):getInitialSize() > percent/100 then - if trigger.misc.getUserFlag(flag) == 0 then - trigger.action.setUserFlag(flag, true) - end - else - if toggle and trigger.misc.getUserFlag(flag) == 1 then - trigger.action.setUserFlag(flag, false) - end - end - else --- just in case - if toggle and trigger.misc.getUserFlag(flag) == 1 then - trigger.action.setUserFlag(flag, false) - end - end - end - - if (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then - mist.scheduleFunction(mist.flagFunc.group_alive_more_than, {{groupName = groupName, flag = flag, stopflag = stopflag, interval = interval, toggle = toggle, percent = percent}}, timer.getTime() + interval) - end - end - - mist.flagFunc.mapobjsDeadPolygon = mist.flagFunc.mapobjs_dead_polygon - mist.flagFunc.mapobjsDeadZones = mist.flagFunc.Mapobjs_dead_zones - mist.flagFunc.unitsInZones = mist.flagFunc.units_in_zones - mist.flagFunc.unitsInMovingZones = mist.flagFunc.units_in_moving_zones - mist.flagFunc.unitsInPolygon = mist.flagFunc.units_in_polygon - mist.flagFunc.unitsLOS = mist.flagFunc.units_LOS - mist.flagFunc.groupAlive = mist.flagFunc.group_alive - mist.flagFunc.groupDead = mist.flagFunc.group_dead - mist.flagFunc.groupAliveMoreThan = mist.flagFunc.group_alive_more_than - mist.flagFunc.groupAliveLessThan = mist.flagFunc.group_alive_less_than - -end - ---- Message functions. --- Messaging system --- @section mist.msg -do -- mist.msg scope - local messageList = {} - -- this defines the max refresh rate of the message box it honestly only needs to - -- go faster than this for precision timing stuff (which could be its own function) - local messageDisplayRate = 0.1 - local messageID = 0 - local displayActive = false - local displayFuncId = 0 - - local caSlots = false - local caMSGtoGroup = false - - if env.mission.groundControl then -- just to be sure? - for index, value in pairs(env.mission.groundControl) do - if type(value) == 'table' then - for roleName, roleVal in pairs(value) do - for rIndex, rVal in pairs(roleVal) do - if rIndex == 'red' or rIndex == 'blue' then - if env.mission.groundControl[index][roleName][rIndex] > 0 then - caSlots = true - break - end - end - end - end - elseif type(value) == 'boolean' and value == true then - caSlots = true - break - end - end - end - - local function mistdisplayV5() - --[[thoughts to improve upon - event handler based activeClients table. - display messages only when there is an update - possibly co-routine it. - ]] - end - - local function mistdisplayV4() - local activeClients = {} - - for clientId, clientData in pairs(mist.DBs.humansById) do - if Unit.getByName(clientData.unitName) and Unit.getByName(clientData.unitName):isExist() == true then - activeClients[clientData.groupId] = clientData.groupName - end - end - - --[[if caSlots == true and caMSGtoGroup == true then - - end]] - - - if #messageList > 0 then - if displayActive == false then - displayActive = true - end - --mist.debug.writeData(mist.utils.serialize,{'msg', messageList}, 'messageList.lua') - local msgTableText = {} - local msgTableSound = {} - - for messageId, messageData in pairs(messageList) do - if messageData.displayedFor > messageData.displayTime then - messageData:remove() -- now using the remove/destroy function. - else - if messageData.displayedFor then - messageData.displayedFor = messageData.displayedFor + messageDisplayRate - end - local nextSound = 1000 - local soundIndex = 0 - - if messageData.multSound and #messageData.multSound > 0 then - for index, sData in pairs(messageData.multSound) do - if sData.time <= messageData.displayedFor and sData.played == false and sData.time < nextSound then -- find index of the next sound to be played - nextSound = sData.time - soundIndex = index - end - end - if soundIndex ~= 0 then - messageData.multSound[soundIndex].played = true - end - end - - for recIndex, recData in pairs(messageData.msgFor) do -- iterate recipiants - if recData == 'RED' or recData == 'BLUE' or activeClients[recData] then -- rec exists - if messageData.text then -- text - if not msgTableText[recData] then -- create table entry for text - msgTableText[recData] = {} - msgTableText[recData].text = {} - if recData == 'RED' or recData == 'BLUE' then - msgTableText[recData].text[1] = '-------Combined Arms Message-------- \n' - end - msgTableText[recData].text[#msgTableText[recData].text + 1] = messageData.text - msgTableText[recData].displayTime = messageData.displayTime - messageData.displayedFor - else -- add to table entry and adjust display time if needed - if recData == 'RED' or recData == 'BLUE' then - msgTableText[recData].text[#msgTableText[recData].text + 1] = '\n ---------------- Combined Arms Message: \n' - else - msgTableText[recData].text[#msgTableText[recData].text + 1] = '\n ---------------- \n' - end - msgTableText[recData].text[#msgTableText[recData].text + 1] = messageData.text - if msgTableText[recData].displayTime < messageData.displayTime - messageData.displayedFor then - msgTableText[recData].displayTime = messageData.displayTime - messageData.displayedFor - else - msgTableText[recData].displayTime = 1 - end - end - end - if soundIndex ~= 0 then - msgTableSound[recData] = messageData.multSound[soundIndex].file - end - end - end - - - end - end - ------- new display - - if caSlots == true and caMSGtoGroup == false then - if msgTableText.RED then - trigger.action.outTextForCoalition(coalition.side.RED, table.concat(msgTableText.RED.text), msgTableText.RED.displayTime, true) - - end - if msgTableText.BLUE then - trigger.action.outTextForCoalition(coalition.side.BLUE, table.concat(msgTableText.BLUE.text), msgTableText.BLUE.displayTime, true) - end - end - - for index, msgData in pairs(msgTableText) do - if type(index) == 'number' then -- its a groupNumber - trigger.action.outTextForGroup(index, table.concat(msgData.text), msgData.displayTime, true) - end - end - --- new audio - if msgTableSound.RED then - trigger.action.outSoundForCoalition(coalition.side.RED, msgTableSound.RED) - end - if msgTableSound.BLUE then - trigger.action.outSoundForCoalition(coalition.side.BLUE, msgTableSound.BLUE) - end - - - for index, file in pairs(msgTableSound) do - if type(index) == 'number' then -- its a groupNumber - trigger.action.outSoundForGroup(index, file) - end - end - else - mist.removeFunction(displayFuncId) - displayActive = false - end - - end - - local typeBase = { - ['Mi-8MT'] = {'Mi-8MTV2', 'Mi-8MTV', 'Mi-8'}, - ['MiG-21Bis'] = {'Mig-21'}, - ['MiG-15bis'] = {'Mig-15'}, - ['FW-190D9'] = {'FW-190'}, - ['Bf-109K-4'] = {'Bf-109'}, - } - - --[[function mist.setCAGroupMSG(val) - if type(val) == 'boolean' then - caMSGtoGroup = val - return true - end - return false -end]] - - mist.message = { - - add = function(vars) - local function msgSpamFilter(recList, spamBlockOn) - for id, name in pairs(recList) do - if name == spamBlockOn then - -- log:info('already on recList') - return recList - end - end - --log:info('add to recList') - table.insert(recList, spamBlockOn) - return recList - end - - --[[ - local vars = {} - vars.text = 'Hello World' - vars.displayTime = 20 - vars.msgFor = {coa = {'red'}, countries = {'Ukraine', 'Georgia'}, unitTypes = {'A-10C'}} - mist.message.add(vars) - - Displays the message for all red coalition players. Players belonging to Ukraine and Georgia, and all A-10Cs on the map - - ]] - - - local new = {} - new.text = vars.text -- The actual message - new.displayTime = vars.displayTime -- How long will the message appear for - new.displayedFor = 0 -- how long the message has been displayed so far - new.name = vars.name -- ID to overwrite the older message (if it exists) Basically it replaces a message that is displayed with new text. - new.addedAt = timer.getTime() - new.update = true - - if vars.multSound and vars.multSound[1] then - new.multSound = vars.multSound - else - new.multSound = {} - end - - if vars.sound or vars.fileName then -- converts old sound file system into new multSound format - local sound = vars.sound - if vars.fileName then - sound = vars.fileName - end - new.multSound[#new.multSound+1] = {time = 0.1, file = sound} - end - - if #new.multSound > 0 then - for i, data in pairs(new.multSound) do - data.played = false - end - end - - local newMsgFor = {} -- list of all groups message displays for - for forIndex, forData in pairs(vars.msgFor) do - for list, listData in pairs(forData) do - for clientId, clientData in pairs(mist.DBs.humansById) do - forIndex = string.lower(forIndex) - if type(listData) == 'string' then - listData = string.lower(listData) - end - if (forIndex == 'coa' and (listData == string.lower(clientData.coalition) or listData == 'all')) or (forIndex == 'countries' and string.lower(clientData.country) == listData) or (forIndex == 'units' and string.lower(clientData.unitName) == listData) then -- - newMsgFor = msgSpamFilter(newMsgFor, clientData.groupId) -- so units dont get the same message twice if complex rules are given - --table.insert(newMsgFor, clientId) - elseif forIndex == 'unittypes' then - for typeId, typeData in pairs(listData) do - local found = false - for clientDataEntry, clientDataVal in pairs(clientData) do - if type(clientDataVal) == 'string' then - if mist.matchString(list, clientDataVal) == true or list == 'all' then - local sString = typeData - for rName, pTbl in pairs(typeBase) do -- just a quick check to see if the user may have meant something and got the specific type of the unit wrong - for pIndex, pName in pairs(pTbl) do - if mist.stringMatch(sString, pName) then - sString = rName - end - end - end - if sString == clientData.type then - found = true - newMsgFor = msgSpamFilter(newMsgFor, clientData.groupId) -- sends info oto other function to see if client is already recieving the current message. - --table.insert(newMsgFor, clientId) - end - end - end - if found == true then -- shouldn't this be elsewhere too? - break - end - end - end - - end - end - for coaData, coaId in pairs(coalition.side) do - if string.lower(forIndex) == 'coa' or string.lower(forIndex) == 'ca' then - if listData == string.lower(coaData) or listData == 'all' then - newMsgFor = msgSpamFilter(newMsgFor, coaData) - end - end - end - end - end - - if #newMsgFor > 0 then - new.msgFor = newMsgFor -- I swear its not confusing - - else - return false - end - - - if vars.name and type(vars.name) == 'string' then - for i = 1, #messageList do - if messageList[i].name then - if messageList[i].name == vars.name then - --log:info('updateMessage') - messageList[i].displayedFor = 0 - messageList[i].addedAt = timer.getTime() - messageList[i].sound = new.sound - messageList[i].text = new.text - messageList[i].msgFor = new.msgFor - messageList[i].multSound = new.multSound - messageList[i].update = true - return messageList[i].messageID - end - end - end - end - - messageID = messageID + 1 - new.messageID = messageID - - --mist.debug.writeData(mist.utils.serialize,{'msg', new}, 'newMsg.lua') - - - messageList[#messageList + 1] = new - - local mt = { __index = mist.message} - setmetatable(new, mt) - - if displayActive == false then - displayActive = true - displayFuncId = mist.scheduleFunction(mistdisplayV4, {}, timer.getTime() + messageDisplayRate, messageDisplayRate) - end - - return messageID - - end, - - remove = function(self) -- Now a self variable; the former functionality taken up by mist.message.removeById. - for i, msgData in pairs(messageList) do - if messageList[i] == self then - table.remove(messageList, i) - return true --removal successful - end - end - return false -- removal not successful this script fails at life! - end, - - removeById = function(id) -- This function is NOT passed a self variable; it is the remove by id function. - for i, msgData in pairs(messageList) do - if messageList[i].messageID == id then - table.remove(messageList, i) - return true --removal successful - end - end - return false -- removal not successful this script fails at life! - end, - } - - --[[ vars for mist.msgMGRS -vars.units - table of unit names (NOT unitNameTable- maybe this should change). -vars.acc - integer between 0 and 5, inclusive -vars.text - text in the message -vars.displayTime - self explanatory -vars.msgFor - scope -]] - function mist.msgMGRS(vars) - local units = vars.units - local acc = vars.acc - local text = vars.text - local displayTime = vars.displayTime - local msgFor = vars.msgFor - - local s = mist.getMGRSString{units = units, acc = acc} - local newText - if text then - if string.find(text, '%%s') then -- look for %s - newText = string.format(text, s) -- insert the coordinates into the message - else - -- just append to the end. - newText = text .. s - end - else - newText = s - end - mist.message.add{ - text = newText, - displayTime = displayTime, - msgFor = msgFor - } - end - - --[[ vars for mist.msgLL -vars.units - table of unit names (NOT unitNameTable- maybe this should change) (Yes). -vars.acc - integer, number of numbers after decimal place -vars.DMS - if true, output in degrees, minutes, seconds. Otherwise, output in degrees, minutes. -vars.text - text in the message -vars.displayTime - self explanatory -vars.msgFor - scope -]] - function mist.msgLL(vars) - local units = vars.units -- technically, I don't really need to do this, but it helps readability. - local acc = vars.acc - local DMS = vars.DMS - local text = vars.text - local displayTime = vars.displayTime - local msgFor = vars.msgFor - - local s = mist.getLLString{units = units, acc = acc, DMS = DMS} - local newText - if text then - if string.find(text, '%%s') then -- look for %s - newText = string.format(text, s) -- insert the coordinates into the message - else - -- just append to the end. - newText = text .. s - end - else - newText = s - end - - mist.message.add{ - text = newText, - displayTime = displayTime, - msgFor = msgFor - } - - end - - --[[ -vars.units- table of unit names (NOT unitNameTable- maybe this should change). -vars.ref - vec3 ref point, maybe overload for vec2 as well? -vars.alt - boolean, if used, includes altitude in string -vars.metric - boolean, gives distance in km instead of NM. -vars.text - text of the message -vars.displayTime -vars.msgFor - scope -]] - function mist.msgBR(vars) - local units = vars.units -- technically, I don't really need to do this, but it helps readability. - local ref = vars.ref -- vec2/vec3 will be handled in mist.getBRString - local alt = vars.alt - local metric = vars.metric - local text = vars.text - local displayTime = vars.displayTime - local msgFor = vars.msgFor - - local s = mist.getBRString{units = units, ref = ref, alt = alt, metric = metric} - local newText - if text then - if string.find(text, '%%s') then -- look for %s - newText = string.format(text, s) -- insert the coordinates into the message - else - -- just append to the end. - newText = text .. s - end - else - newText = s - end - - mist.message.add{ - text = newText, - displayTime = displayTime, - msgFor = msgFor - } - - end - - -- basically, just sub-types of mist.msgBR... saves folks the work of getting the ref point. - --[[ -vars.units- table of unit names (NOT unitNameTable- maybe this should change). -vars.ref - string red, blue -vars.alt - boolean, if used, includes altitude in string -vars.metric - boolean, gives distance in km instead of NM. -vars.text - text of the message -vars.displayTime -vars.msgFor - scope -]] - function mist.msgBullseye(vars) - if string.lower(vars.ref) == 'red' then - vars.ref = mist.DBs.missionData.bullseye.red - mist.msgBR(vars) - elseif string.lower(vars.ref) == 'blue' then - vars.ref = mist.DBs.missionData.bullseye.blue - mist.msgBR(vars) - end - end - - --[[ -vars.units- table of unit names (NOT unitNameTable- maybe this should change). -vars.ref - unit name of reference point -vars.alt - boolean, if used, includes altitude in string -vars.metric - boolean, gives distance in km instead of NM. -vars.text - text of the message -vars.displayTime -vars.msgFor - scope -]] - function mist.msgBRA(vars) - if Unit.getByName(vars.ref) and Unit.getByName(vars.ref):isExist() == true then - vars.ref = Unit.getByName(vars.ref):getPosition().p - if not vars.alt then - vars.alt = true - end - mist.msgBR(vars) - end - end - - --[[ vars for mist.msgLeadingMGRS: -vars.units - table of unit names -vars.heading - direction -vars.radius - number -vars.headingDegrees - boolean, switches heading to degrees (optional) -vars.acc - number, 0 to 5. -vars.text - text of the message -vars.displayTime -vars.msgFor - scope -]] - function mist.msgLeadingMGRS(vars) - local units = vars.units -- technically, I don't really need to do this, but it helps readability. - local heading = vars.heading - local radius = vars.radius - local headingDegrees = vars.headingDegrees - local acc = vars.acc - local text = vars.text - local displayTime = vars.displayTime - local msgFor = vars.msgFor - - local s = mist.getLeadingMGRSString{units = units, heading = heading, radius = radius, headingDegrees = headingDegrees, acc = acc} - local newText - if text then - if string.find(text, '%%s') then -- look for %s - newText = string.format(text, s) -- insert the coordinates into the message - else - -- just append to the end. - newText = text .. s - end - else - newText = s - end - - mist.message.add{ - text = newText, - displayTime = displayTime, - msgFor = msgFor - } - - - end - - --[[ vars for mist.msgLeadingLL: -vars.units - table of unit names -vars.heading - direction, number -vars.radius - number -vars.headingDegrees - boolean, switches heading to degrees (optional) -vars.acc - number of digits after decimal point (can be negative) -vars.DMS - boolean, true if you want DMS. (optional) -vars.text - text of the message -vars.displayTime -vars.msgFor - scope -]] - function mist.msgLeadingLL(vars) - local units = vars.units -- technically, I don't really need to do this, but it helps readability. - local heading = vars.heading - local radius = vars.radius - local headingDegrees = vars.headingDegrees - local acc = vars.acc - local DMS = vars.DMS - local text = vars.text - local displayTime = vars.displayTime - local msgFor = vars.msgFor - - local s = mist.getLeadingLLString{units = units, heading = heading, radius = radius, headingDegrees = headingDegrees, acc = acc, DMS = DMS} - local newText - - if text then - if string.find(text, '%%s') then -- look for %s - newText = string.format(text, s) -- insert the coordinates into the message - else - -- just append to the end. - newText = text .. s - end - else - newText = s - end - - mist.message.add{ - text = newText, - displayTime = displayTime, - msgFor = msgFor - } - - end - - --[[ -vars.units - table of unit names -vars.heading - direction, number -vars.radius - number -vars.headingDegrees - boolean, switches heading to degrees (optional) -vars.metric - boolean, if true, use km instead of NM. (optional) -vars.alt - boolean, if true, include altitude. (optional) -vars.ref - vec3/vec2 reference point. -vars.text - text of the message -vars.displayTime -vars.msgFor - scope -]] - function mist.msgLeadingBR(vars) - local units = vars.units -- technically, I don't really need to do this, but it helps readability. - local heading = vars.heading - local radius = vars.radius - local headingDegrees = vars.headingDegrees - local metric = vars.metric - local alt = vars.alt - local ref = vars.ref -- vec2/vec3 will be handled in mist.getBRString - local text = vars.text - local displayTime = vars.displayTime - local msgFor = vars.msgFor - - local s = mist.getLeadingBRString{units = units, heading = heading, radius = radius, headingDegrees = headingDegrees, metric = metric, alt = alt, ref = ref} - local newText - - if text then - if string.find(text, '%%s') then -- look for %s - newText = string.format(text, s) -- insert the coordinates into the message - else - -- just append to the end. - newText = text .. s - end - else - newText = s - end - - mist.message.add{ - text = newText, - displayTime = displayTime, - msgFor = msgFor - } - end -end - ---- Demo functions. --- @section mist.demos -do -- mist.demos scope - mist.demos = {} - - function mist.demos.printFlightData(unit) - if unit:isExist() then - local function printData(unit, prevVel, prevE, prevTime) - local angles = mist.getAttitude(unit) - if angles then - local Heading = angles.Heading - local Pitch = angles.Pitch - local Roll = angles.Roll - local Yaw = angles.Yaw - local AoA = angles.AoA - local ClimbAngle = angles.ClimbAngle - - if not Heading then - Heading = 'NA' - else - Heading = string.format('%12.2f', mist.utils.toDegree(Heading)) - end - - if not Pitch then - Pitch = 'NA' - else - Pitch = string.format('%12.2f', mist.utils.toDegree(Pitch)) - end - - if not Roll then - Roll = 'NA' - else - Roll = string.format('%12.2f', mist.utils.toDegree(Roll)) - end - - local AoAplusYaw = 'NA' - if AoA and Yaw then - AoAplusYaw = string.format('%12.2f', mist.utils.toDegree((AoA^2 + Yaw^2)^0.5)) - end - - if not Yaw then - Yaw = 'NA' - else - Yaw = string.format('%12.2f', mist.utils.toDegree(Yaw)) - end - - if not AoA then - AoA = 'NA' - else - AoA = string.format('%12.2f', mist.utils.toDegree(AoA)) - end - - if not ClimbAngle then - ClimbAngle = 'NA' - else - ClimbAngle = string.format('%12.2f', mist.utils.toDegree(ClimbAngle)) - end - local unitPos = unit:getPosition() - local unitVel = unit:getVelocity() - local curTime = timer.getTime() - local absVel = string.format('%12.2f', mist.vec.mag(unitVel)) - - - local unitAcc = 'NA' - local Gs = 'NA' - local axialGs = 'NA' - local transGs = 'NA' - if prevVel and prevTime then - local xAcc = (unitVel.x - prevVel.x)/(curTime - prevTime) - local yAcc = (unitVel.y - prevVel.y)/(curTime - prevTime) - local zAcc = (unitVel.z - prevVel.z)/(curTime - prevTime) - - unitAcc = string.format('%12.2f', mist.vec.mag({x = xAcc, y = yAcc, z = zAcc})) - Gs = string.format('%12.2f', mist.vec.mag({x = xAcc, y = yAcc + 9.81, z = zAcc})/9.81) - axialGs = string.format('%12.2f', mist.vec.dp({x = xAcc, y = yAcc + 9.81, z = zAcc}, unitPos.x)/9.81) - transGs = string.format('%12.2f', mist.vec.mag(mist.vec.cp({x = xAcc, y = yAcc + 9.81, z = zAcc}, unitPos.x))/9.81) - end - - local E = 0.5*mist.vec.mag(unitVel)^2 + 9.81*unitPos.p.y - - local energy = string.format('%12.2e', E) - - local dEdt = 'NA' - if prevE and prevTime then - dEdt = string.format('%12.2e', (E - prevE)/(curTime - prevTime)) - end - - trigger.action.outText(string.format('%-25s', 'Heading: ') .. Heading .. ' degrees\n' .. string.format('%-25s', 'Roll: ') .. Roll .. ' degrees\n' .. string.format('%-25s', 'Pitch: ') .. Pitch - .. ' degrees\n' .. string.format('%-25s', 'Yaw: ') .. Yaw .. ' degrees\n' .. string.format('%-25s', 'AoA: ') .. AoA .. ' degrees\n' .. string.format('%-25s', 'AoA plus Yaw: ') .. AoAplusYaw .. ' degrees\n' .. string.format('%-25s', 'Climb Angle: ') .. - ClimbAngle .. ' degrees\n' .. string.format('%-25s', 'Absolute Velocity: ') .. absVel .. ' m/s\n' .. string.format('%-25s', 'Absolute Acceleration: ') .. unitAcc ..' m/s^2\n' - .. string.format('%-25s', 'Axial G loading: ') .. axialGs .. ' g\n' .. string.format('%-25s', 'Transverse G loading: ') .. transGs .. ' g\n' .. string.format('%-25s', 'Absolute G loading: ') .. Gs .. ' g\n' .. string.format('%-25s', 'Energy: ') .. energy .. ' J/kg\n' .. string.format('%-25s', 'dE/dt: ') .. dEdt ..' J/(kg*s)', 1) - return unitVel, E, curTime - end - end - - local function frameFinder(unit, prevVel, prevE, prevTime) - if unit:isExist() then - local currVel = unit:getVelocity() - if prevVel and (prevVel.x ~= currVel.x or prevVel.y ~= currVel.y or prevVel.z ~= currVel.z) or (prevTime and (timer.getTime() - prevTime) > 0.25) then - prevVel, prevE, prevTime = printData(unit, prevVel, prevE, prevTime) - end - mist.scheduleFunction(frameFinder, {unit, prevVel, prevE, prevTime}, timer.getTime() + 0.005) -- it can't go this fast, limited to the 100 times a sec check right now. - end - end - - - local curVel = unit:getVelocity() - local curTime = timer.getTime() - local curE = 0.5*mist.vec.mag(curVel)^2 + 9.81*unit:getPosition().p.y - frameFinder(unit, curVel, curE, curTime) - - end - - end - -end - ---- Time conversion functions. --- @section mist.time -do -- mist.time scope - mist.time = {} - -- returns a string for specified military time - -- theTime is optional - -- if present current time in mil time is returned - -- if number or table the time is converted into mil tim - function mist.time.convertToSec(timeTable) - - timeInSec = 0 - if timeTable and type(timeTable) == 'number' then - timeInSec = timeTable - elseif timeTable and type(timeTable) == 'table' and (timeTable.d or timeTable.h or timeTable.m or timeTable.s) then - if timeTable.d and type(timeTable.d) == 'number' then - timeInSec = timeInSec + (timeTable.d*86400) - end - if timeTable.h and type(timeTable.h) == 'number' then - timeInSec = timeInSec + (timeTable.h*3600) - end - if timeTable.m and type(timeTable.m) == 'number' then - timeInSec = timeInSec + (timeTable.m*60) - end - if timeTable.s and type(timeTable.s) == 'number' then - timeInSec = timeInSec + timeTable.s - end - - end - return timeInSec - end - - function mist.time.getDHMS(timeInSec) - if timeInSec and type(timeInSec) == 'number' then - local tbl = {d = 0, h = 0, m = 0, s = 0} - if timeInSec > 86400 then - while timeInSec > 86400 do - tbl.d = tbl.d + 1 - timeInSec = timeInSec - 86400 - end - end - if timeInSec > 3600 then - while timeInSec > 3600 do - tbl.h = tbl.h + 1 - timeInSec = timeInSec - 3600 - end - end - if timeInSec > 60 then - while timeInSec > 60 do - tbl.m = tbl.m + 1 - timeInSec = timeInSec - 60 - end - end - tbl.s = timeInSec - return tbl - else - log:error("Didn't recieve number") - return - end - end - - function mist.getMilString(theTime) - local timeInSec = 0 - if theTime then - timeInSec = mist.time.convertToSec(theTime) - else - timeInSec = mist.utils.round(timer.getAbsTime(), 0) - end - - local DHMS = mist.time.getDHMS(timeInSec) - - return tostring(string.format('%02d', DHMS.h) .. string.format('%02d',DHMS.m)) - end - - function mist.getClockString(theTime, hour) - local timeInSec = 0 - if theTime then - timeInSec = mist.time.convertToSec(theTime) - else - timeInSec = mist.utils.round(timer.getAbsTime(), 0) - end - local DHMS = mist.time.getDHMS(timeInSec) - if hour then - if DHMS.h > 12 then - DHMS.h = DHMS.h - 12 - return tostring(string.format('%02d', DHMS.h) .. ':' .. string.format('%02d',DHMS.m) .. ':' .. string.format('%02d',DHMS.s) .. ' PM') - else - return tostring(string.format('%02d', DHMS.h) .. ':' .. string.format('%02d',DHMS.m) .. ':' .. string.format('%02d',DHMS.s) .. ' AM') - end - else - return tostring(string.format('%02d', DHMS.h) .. ':' .. string.format('%02d',DHMS.m) .. ':' .. string.format('%02d',DHMS.s)) - end - end - - -- returns the date in string format - -- both variables optional - -- first val returns with the month as a string - -- 2nd val defins if it should be written the American way or the wrong way. - function mist.time.getDate(convert) - local cal = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} -- - local date = {} - - if not env.mission.date then -- Not likely to happen. Resaving mission auto updates this to remove it. - date.d = 0 - date.m = 6 - date.y = 2011 - else - date.d = env.mission.date.Day - date.m = env.mission.date.Month - date.y = env.mission.date.Year - end - local start = 86400 - local timeInSec = mist.utils.round(timer.getAbsTime()) - if convert and type(convert) == 'number' then - timeInSec = convert - end - if timeInSec > 86400 then - while start < timeInSec do - if date.d >= cal[date.m] then - if date.m == 2 and date.d == 28 then -- HOLY COW we can edit years now. Gotta re-add this! - if date.y % 4 == 0 and date.y % 100 == 0 and date.y % 400 ~= 0 or date.y % 4 > 0 then - date.m = date.m + 1 - date.d = 0 - end - --date.d = 29 - else - date.m = date.m + 1 - date.d = 0 - end - end - if date.m == 13 then - date.m = 1 - date.y = date.y + 1 - end - date.d = date.d + 1 - start = start + 86400 - - end - end - return date - end - - function mist.time.relativeToStart(time) - if type(time) == 'number' then - return time - timer.getTime0() - end - end - - function mist.getDateString(rtnType, murica, oTime) -- returns date based on time - local word = {'January', 'Feburary', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' } -- 'etc - local curTime = 0 - if oTime then - curTime = oTime - else - curTime = mist.utils.round(timer.getAbsTime()) - end - local tbl = mist.time.getDate(curTime) - - if rtnType then - if murica then - return tostring(word[tbl.m] .. ' ' .. tbl.d .. ' ' .. tbl.y) - else - return tostring(tbl.d .. ' ' .. word[tbl.m] .. ' ' .. tbl.y) - end - else - if murica then - return tostring(tbl.m .. '.' .. tbl.d .. '.' .. tbl.y) - else - return tostring(tbl.d .. '.' .. tbl.m .. '.' .. tbl.y) - end - end - end - --WIP - function mist.time.milToGame(milString, rtnType) --converts a military time. By default returns the abosolute time that event would occur. With optional value it returns how many seconds from time of call till that time. - local curTime = mist.utils.round(timer.getAbsTime()) - local milTimeInSec = 0 - - if milString and type(milString) == 'string' and string.len(milString) >= 4 then - local hr = tonumber(string.sub(milString, 1, 2)) - local mi = tonumber(string.sub(milString, 3)) - milTimeInSec = milTimeInSec + (mi*60) + (hr*3600) - elseif milString and type(milString) == 'table' and (milString.d or milString.h or milString.m or milString.s) then - milTimeInSec = mist.time.convertToSec(milString) - end - - local startTime = timer.getTime0() - local daysOffset = 0 - if startTime > 86400 then - daysOffset = mist.utils.round(startTime/86400) - if daysOffset > 0 then - milTimeInSec = milTimeInSec *daysOffset - end - end - - if curTime > milTimeInSec then - milTimeInSec = milTimeInSec + 86400 - end - if rtnType then - milTimeInSec = milTimeInSec - startTime - end - return milTimeInSec - end - - -end - ---- Group task functions. --- @section tasks -do -- group tasks scope - mist.ground = {} - mist.fixedWing = {} - mist.heli = {} - mist.air = {} - mist.air.fixedWing = {} - mist.air.heli = {} - - --- Tasks group to follow a route. - -- This sets the mission task for the given group. - -- Any wrapped actions inside the path (like enroute - -- tasks) will be executed. - -- @tparam Group group group to task. - -- @tparam table path containing - -- points defining a route. - function mist.goRoute(group, path) - local misTask = { - id = 'Mission', - params = { - route = { - points = mist.utils.deepCopy(path), - }, - }, - } - if type(group) == 'string' then - group = Group.getByName(group) - end - if group then - local groupCon = group:getController() - if groupCon then - groupCon:setTask(misTask) - return true - end - end - return false - end - - -- same as getGroupPoints but returns speed and formation type along with vec2 of point} - function mist.getGroupRoute(groupIdent, task) - -- refactor to search by groupId and allow groupId and groupName as inputs - local gpId = groupIdent - if mist.DBs.MEgroupsByName[groupIdent] then - gpId = mist.DBs.MEgroupsByName[groupIdent].groupId - else - log:error('$1 not found in mist.DBs.MEgroupsByName', groupIdent) - end - - for coa_name, coa_data in pairs(env.mission.coalition) do - if (coa_name == 'red' or coa_name == 'blue') and type(coa_data) == 'table' then - if coa_data.country then --there is a country table - for cntry_id, cntry_data in pairs(coa_data.country) do - for obj_type_name, obj_type_data in pairs(cntry_data) do - if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" then -- only these types have points - if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then --there's a group! - for group_num, group_data in pairs(obj_type_data.group) do - if group_data and group_data.groupId == gpId then -- this is the group we are looking for - if group_data.route and group_data.route.points and #group_data.route.points > 0 then - local points = {} - - for point_num, point in pairs(group_data.route.points) do - local routeData = {} - if not point.point then - routeData.x = point.x - routeData.y = point.y - else - routeData.point = point.point --it's possible that the ME could move to the point = Vec2 notation. - end - routeData.form = point.action - routeData.speed = point.speed - routeData.alt = point.alt - routeData.alt_type = point.alt_type - routeData.airdromeId = point.airdromeId - routeData.helipadId = point.helipadId - routeData.type = point.type - routeData.action = point.action - if task then - routeData.task = point.task - end - points[point_num] = routeData - end - - return points - end - log:error('Group route not defined in mission editor for groupId: $1', gpId) - return - end --if group_data and group_data.name and group_data.name == 'groupname' - end --for group_num, group_data in pairs(obj_type_data.group) do - end --if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then - end --if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" or obj_type_name == "static" then - end --for obj_type_name, obj_type_data in pairs(cntry_data) do - end --for cntry_id, cntry_data in pairs(coa_data.country) do - end --if coa_data.country then --there is a country table - end --if coa_name == 'red' or coa_name == 'blue' and type(coa_data) == 'table' then - end --for coa_name, coa_data in pairs(mission.coalition) do - end - - -- function mist.ground.buildPath() end -- ???? - - function mist.ground.patrolRoute(vars) - log:info('patrol') - local tempRoute = {} - local useRoute = {} - local gpData = vars.gpData - if type(gpData) == 'string' then - gpData = Group.getByName(gpData) - end - - local useGroupRoute - if not vars.useGroupRoute then - useGroupRoute = vars.gpData - else - useGroupRoute = vars.useGroupRoute - end - local routeProvided = false - if not vars.route then - if useGroupRoute then - tempRoute = mist.getGroupRoute(useGroupRoute) - end - else - useRoute = vars.route - local posStart = mist.getLeadPos(gpData) - useRoute[1] = mist.ground.buildWP(posStart, useRoute[1].action, useRoute[1].speed) - routeProvided = true - end - - - local overRideSpeed = vars.speed or 'default' - local pType = vars.pType - local offRoadForm = vars.offRoadForm or 'default' - local onRoadForm = vars.onRoadForm or 'default' - - if routeProvided == false and #tempRoute > 0 then - local posStart = mist.getLeadPos(gpData) - - - useRoute[#useRoute + 1] = mist.ground.buildWP(posStart, offRoadForm, overRideSpeed) - for i = 1, #tempRoute do - local tempForm = tempRoute[i].action - local tempSpeed = tempRoute[i].speed - - if offRoadForm == 'default' then - tempForm = tempRoute[i].action - end - if onRoadForm == 'default' then - onRoadForm = 'On Road' - end - if (string.lower(tempRoute[i].action) == 'on road' or string.lower(tempRoute[i].action) == 'onroad' or string.lower(tempRoute[i].action) == 'on_road') then - tempForm = onRoadForm - else - tempForm = offRoadForm - end - - if type(overRideSpeed) == 'number' then - tempSpeed = overRideSpeed - end - - - useRoute[#useRoute + 1] = mist.ground.buildWP(tempRoute[i], tempForm, tempSpeed) - end - - if pType and string.lower(pType) == 'doubleback' then - local curRoute = mist.utils.deepCopy(useRoute) - for i = #curRoute, 2, -1 do - useRoute[#useRoute + 1] = mist.ground.buildWP(curRoute[i], curRoute[i].action, curRoute[i].speed) - end - end - - useRoute[1].action = useRoute[#useRoute].action -- make it so the first WP matches the last WP - end - - local cTask3 = {} - local newPatrol = {} - newPatrol.route = useRoute - newPatrol.gpData = gpData:getName() - cTask3[#cTask3 + 1] = 'mist.ground.patrolRoute(' - cTask3[#cTask3 + 1] = mist.utils.oneLineSerialize(newPatrol) - cTask3[#cTask3 + 1] = ')' - cTask3 = table.concat(cTask3) - local tempTask = { - id = 'WrappedAction', - params = { - action = { - id = 'Script', - params = { - command = cTask3, - - }, - }, - }, - } - - useRoute[#useRoute].task = tempTask - log:info(useRoute) - mist.goRoute(gpData, useRoute) - - return - end - - function mist.ground.patrol(gpData, pType, form, speed) - local vars = {} - - if type(gpData) == 'table' and gpData:getName() then - gpData = gpData:getName() - end - - vars.useGroupRoute = gpData - vars.gpData = gpData - vars.pType = pType - vars.offRoadForm = form - vars.speed = speed - - mist.ground.patrolRoute(vars) - - return - end - - -- No longer accepts path - function mist.ground.buildWP(point, overRideForm, overRideSpeed) - - local wp = {} - wp.x = point.x - - if point.z then - wp.y = point.z - else - wp.y = point.y - end - local form, speed - - if point.speed and not overRideSpeed then - wp.speed = point.speed - elseif type(overRideSpeed) == 'number' then - wp.speed = overRideSpeed - else - wp.speed = mist.utils.kmphToMps(20) - end - - if point.form and not overRideForm then - form = point.form - else - form = overRideForm - end - - if not form then - wp.action = 'Cone' - else - form = string.lower(form) - if form == 'off_road' or form == 'off road' then - wp.action = 'Off Road' - elseif form == 'on_road' or form == 'on road' then - wp.action = 'On Road' - elseif form == 'rank' or form == 'line_abrest' or form == 'line abrest' or form == 'lineabrest'then - wp.action = 'Rank' - elseif form == 'cone' then - wp.action = 'Cone' - elseif form == 'diamond' then - wp.action = 'Diamond' - elseif form == 'vee' then - wp.action = 'Vee' - elseif form == 'echelon_left' or form == 'echelon left' or form == 'echelonl' then - wp.action = 'EchelonL' - elseif form == 'echelon_right' or form == 'echelon right' or form == 'echelonr' then - wp.action = 'EchelonR' - else - wp.action = 'Cone' -- if nothing matched - end - end - - wp.type = 'Turning Point' - - return wp - - end - - function mist.fixedWing.buildWP(point, WPtype, speed, alt, altType) - - local wp = {} - wp.x = point.x - - if point.z then - wp.y = point.z - else - wp.y = point.y - end - - if alt and type(alt) == 'number' then - wp.alt = alt - else - wp.alt = 2000 - end - - if altType then - altType = string.lower(altType) - if altType == 'radio' or altType == 'agl' then - wp.alt_type = 'RADIO' - elseif altType == 'baro' or altType == 'asl' then - wp.alt_type = 'BARO' - end - else - wp.alt_type = 'RADIO' - end - - if point.speed then - speed = point.speed - end - - if point.type then - WPtype = point.type - end - - if not speed then - wp.speed = mist.utils.kmphToMps(500) - else - wp.speed = speed - end - - if not WPtype then - wp.action = 'Turning Point' - else - WPtype = string.lower(WPtype) - if WPtype == 'flyover' or WPtype == 'fly over' or WPtype == 'fly_over' then - wp.action = 'Fly Over Point' - elseif WPtype == 'turningpoint' or WPtype == 'turning point' or WPtype == 'turning_point' then - wp.action = 'Turning Point' - else - wp.action = 'Turning Point' - end - end - - wp.type = 'Turning Point' - return wp - end - - function mist.heli.buildWP(point, WPtype, speed, alt, altType) - - local wp = {} - wp.x = point.x - - if point.z then - wp.y = point.z - else - wp.y = point.y - end - - if alt and type(alt) == 'number' then - wp.alt = alt - else - wp.alt = 500 - end - - if altType then - altType = string.lower(altType) - if altType == 'radio' or altType == 'agl' then - wp.alt_type = 'RADIO' - elseif altType == 'baro' or altType == 'asl' then - wp.alt_type = 'BARO' - end - else - wp.alt_type = 'RADIO' - end - - if point.speed then - speed = point.speed - end - - if point.type then - WPtype = point.type - end - - if not speed then - wp.speed = mist.utils.kmphToMps(200) - else - wp.speed = speed - end - - if not WPtype then - wp.action = 'Turning Point' - else - WPtype = string.lower(WPtype) - if WPtype == 'flyover' or WPtype == 'fly over' or WPtype == 'fly_over' then - wp.action = 'Fly Over Point' - elseif WPtype == 'turningpoint' or WPtype == 'turning point' or WPtype == 'turning_point' then - wp.action = 'Turning Point' - else - wp.action = 'Turning Point' - end - end - - wp.type = 'Turning Point' - return wp - end - - -- need to return a Vec3 or Vec2? - function mist.getRandPointInCircle(point, radius, innerRadius) - local theta = 2*math.pi*math.random() - local rad = math.random() + math.random() - if rad > 1 then - rad = 2 - rad - end - - local radMult - if innerRadius and innerRadius <= radius then - radMult = (radius - innerRadius)*rad + innerRadius - else - radMult = radius*rad - end - - if not point.z then --might as well work with vec2/3 - point.z = point.y - end - - local rndCoord - if radius > 0 then - rndCoord = {x = math.cos(theta)*radMult + point.x, y = math.sin(theta)*radMult + point.z} - else - rndCoord = {x = point.x, y = point.z} - end - return rndCoord - end - - function mist.getRandomPointInZone(zoneName, innerRadius) - if type(zoneName) == 'string' and type(trigger.misc.getZone(zoneName)) == 'table' then - return mist.getRandPointInCircle(trigger.misc.getZone(zoneName).point, trigger.misc.getZone(zoneName).radius, innerRadius) - end - return false - end - - function mist.groupToRandomPoint(vars) - local group = vars.group --Required - local point = vars.point --required - local radius = vars.radius or 0 - local innerRadius = vars.innerRadius - local form = vars.form or 'Cone' - local heading = vars.heading or math.random()*2*math.pi - local headingDegrees = vars.headingDegrees - local speed = vars.speed or mist.utils.kmphToMps(20) - - - local useRoads - if not vars.disableRoads then - useRoads = true - else - useRoads = false - end - - local path = {} - - if headingDegrees then - heading = headingDegrees*math.pi/180 - end - - if heading >= 2*math.pi then - heading = heading - 2*math.pi - end - - local rndCoord = mist.getRandPointInCircle(point, radius, innerRadius) - - local offset = {} - local posStart = mist.getLeadPos(group) - - offset.x = mist.utils.round(math.sin(heading - (math.pi/2)) * 50 + rndCoord.x, 3) - offset.z = mist.utils.round(math.cos(heading + (math.pi/2)) * 50 + rndCoord.y, 3) - path[#path + 1] = mist.ground.buildWP(posStart, form, speed) - - - if useRoads == true and ((point.x - posStart.x)^2 + (point.z - posStart.z)^2)^0.5 > radius * 1.3 then - path[#path + 1] = mist.ground.buildWP({x = posStart.x + 11, z = posStart.z + 11}, 'off_road', speed) - path[#path + 1] = mist.ground.buildWP(posStart, 'on_road', speed) - path[#path + 1] = mist.ground.buildWP(offset, 'on_road', speed) - else - path[#path + 1] = mist.ground.buildWP({x = posStart.x + 25, z = posStart.z + 25}, form, speed) - end - - path[#path + 1] = mist.ground.buildWP(offset, form, speed) - path[#path + 1] = mist.ground.buildWP(rndCoord, form, speed) - - mist.goRoute(group, path) - - return - end - - function mist.groupRandomDistSelf(gpData, dist, form, heading, speed) - local pos = mist.getLeadPos(gpData) - local fakeZone = {} - fakeZone.radius = dist or math.random(300, 1000) - fakeZone.point = {x = pos.x, y = pos.y, z = pos.z} - mist.groupToRandomZone(gpData, fakeZone, form, heading, speed) - - return - end - - function mist.groupToRandomZone(gpData, zone, form, heading, speed) - if type(gpData) == 'string' then - gpData = Group.getByName(gpData) - end - - if type(zone) == 'string' then - zone = trigger.misc.getZone(zone) - elseif type(zone) == 'table' and not zone.radius then - zone = trigger.misc.getZone(zone[math.random(1, #zone)]) - end - - if speed then - speed = mist.utils.kmphToMps(speed) - end - - local vars = {} - vars.group = gpData - vars.radius = zone.radius - vars.form = form - vars.headingDegrees = heading - vars.speed = speed - vars.point = mist.utils.zoneToVec3(zone) - - mist.groupToRandomPoint(vars) - - return - end - - function mist.isTerrainValid(coord, terrainTypes) -- vec2/3 and enum or table of acceptable terrain types - if coord.z then - coord.y = coord.z - end - local typeConverted = {} - - if type(terrainTypes) == 'string' then -- if its a string it does this check - for constId, constData in pairs(land.SurfaceType) do - if string.lower(constId) == string.lower(terrainTypes) or string.lower(constData) == string.lower(terrainTypes) then - table.insert(typeConverted, constId) - end - end - elseif type(terrainTypes) == 'table' then -- if its a table it does this check - for typeId, typeData in pairs(terrainTypes) do - for constId, constData in pairs(land.SurfaceType) do - if string.lower(constId) == string.lower(typeData) or string.lower(constData) == string.lower(typeId) then - table.insert(typeConverted, constId) - end - end - end - end - for validIndex, validData in pairs(typeConverted) do - if land.getSurfaceType(coord) == land.SurfaceType[validData] then - return true - end - end - return false - end - - function mist.terrainHeightDiff(coord, searchSize) - local samples = {} - local searchRadius = 5 - if searchSize then - searchRadius = searchSize - end - if type(coord) == 'string' then - coord = mist.utils.zoneToVec3(coord) - end - - coord = mist.utils.makeVec2(coord) - - samples[#samples + 1] = land.getHeight(coord) - for i = 0, 360, 30 do - samples[#samples + 1] = land.getHeight({x = (coord.x + (math.sin(math.rad(i))*searchRadius)), y = (coord.y + (math.cos(math.rad(i))*searchRadius))}) - if searchRadius >= 20 then -- if search radius is sorta large, take a sample halfway between center and outer edge - samples[#samples + 1] = land.getHeight({x = (coord.x + (math.sin(math.rad(i))*(searchRadius/2))), y = (coord.y + (math.cos(math.rad(i))*(searchRadius/2)))}) - end - end - local tMax, tMin = 0, 1000000 - for index, height in pairs(samples) do - if height > tMax then - tMax = height - end - if height < tMin then - tMin = height - end - end - return mist.utils.round(tMax - tMin, 2) - end - - function mist.groupToPoint(gpData, point, form, heading, speed, useRoads) - if type(point) == 'string' then - point = trigger.misc.getZone(point) - end - if speed then - speed = mist.utils.kmphToMps(speed) - end - - local vars = {} - vars.group = gpData - vars.form = form - vars.headingDegrees = heading - vars.speed = speed - vars.disableRoads = useRoads - vars.point = mist.utils.zoneToVec3(point) - mist.groupToRandomPoint(vars) - - return - end - - function mist.getLeadPos(group) - if type(group) == 'string' then -- group name - group = Group.getByName(group) - end - - local units = group:getUnits() - - local leader = units[1] - if not Unit.isExist(leader) then -- SHOULD be good, but if there is a bug, this code future-proofs it then. - local lowestInd = math.huge - for ind, unit in pairs(units) do - if Unit.isExist(unit) and ind < lowestInd then - lowestInd = ind - return unit:getPosition().p - end - end - end - if leader and Unit.isExist(leader) then -- maybe a little too paranoid now... - return leader:getPosition().p - end - end - -end - ---- Database tables. --- @section mist.DBs - ---- Mission data --- @table mist.DBs.missionData --- @field startTime mission start time --- @field theatre mission theatre/map e.g. Caucasus --- @field version mission version --- @field files mission resources - ---- Tables used as parameters. --- @section varTables - ---- mist.flagFunc.units_in_polygon parameter table. --- @table unitsInPolygonVars --- @tfield table unit name table @{UnitNameTable}. --- @tfield table zone table defining a polygon. --- @tfield number|string flag flag to set to true. --- @tfield[opt] number|string stopflag if set to true the function --- will stop evaluating. --- @tfield[opt] number maxalt maximum altitude (MSL) for the --- polygon. --- @tfield[opt] number req_num minimum number of units that have --- to be in the polygon. --- @tfield[opt] number interval sets the interval for --- checking if units are inside of the polygon in seconds. Default: 1. --- @tfield[opt] boolean toggle switch the flag to false if required --- conditions are not met. Default: false. --- @tfield[opt] table unitTableDef - ---- Logger class. --- @type mist.Logger -do -- mist.Logger scope - mist.Logger = {} - - --- parses text and substitutes keywords with values from given array. - -- @param text string containing keywords to substitute with values - -- or a variable. - -- @param ... variables to use for substitution in string. - -- @treturn string new string with keywords substituted or - -- value of variable as string. - local function formatText(text, ...) - if type(text) ~= 'string' then - if type(text) == 'table' then - text = mist.utils.oneLineSerialize(text) - else - text = tostring(text) - end - else - for index,value in ipairs(arg) do - -- TODO: check for getmetatabel(value).__tostring - if type(value) == 'table' then - value = mist.utils.oneLineSerialize(value) - else - value = tostring(value) - end - text = text:gsub('$' .. index, value) - end - end - local fName = nil - local cLine = nil - if debug then - local dInfo = debug.getinfo(3) - fName = dInfo.name - cLine = dInfo.currentline - -- local fsrc = dinfo.short_src - --local fLine = dInfo.linedefined - end - if fName and cLine then - return fName .. '|' .. cLine .. ': ' .. text - elseif cLine then - return cLine .. ': ' .. text - else - return ' ' .. text - end - end - - local function splitText(text) - local tbl = {} - while text:len() > 4000 do - local sub = text:sub(1, 4000) - text = text:sub(4001) - table.insert(tbl, sub) - end - table.insert(tbl, text) - return tbl - end - - --- Creates a new logger. - -- Each logger has it's own tag and log level. - -- @tparam string tag tag which appears at the start of - -- every log line produced by this logger. - -- @tparam[opt] number|string level the log level defines which messages - -- will be logged and which will be omitted. Log level 3 beeing the most verbose - -- and 0 disabling all output. This can also be a string. Allowed strings are: - -- "none" (0), "error" (1), "warning" (2) and "info" (3). - -- @usage myLogger = mist.Logger:new("MyScript") - -- @usage myLogger = mist.Logger:new("MyScript", 2) - -- @usage myLogger = mist.Logger:new("MyScript", "info") - -- @treturn mist.Logger - function mist.Logger:new(tag, level) - local l = {} - l.tag = tag - setmetatable(l, self) - self.__index = self - self:setLevel(level) - return l - end - - --- Sets the level of verbosity for this logger. - -- @tparam[opt] number|string level the log level defines which messages - -- will be logged and which will be omitted. Log level 3 beeing the most verbose - -- and 0 disabling all output. This can also be a string. Allowed strings are: - -- "none" (0), "error" (1), "warning" (2) and "info" (3). - -- @usage myLogger:setLevel("info") - -- @usage -- log everything - --myLogger:setLevel(3) - function mist.Logger:setLevel(level) - if not level then - self.level = 2 - else - if type(level) == 'string' then - if level == 'none' or level == 'off' then - self.level = 0 - elseif level == 'error' then - self.level = 1 - elseif level == 'warning' or level == 'warn' then - self.level = 2 - elseif level == 'info' then - self.level = 3 - end - elseif type(level) == 'number' then - self.level = level - else - self.level = 2 - end - end - end - - --- Logs error and shows alert window. - -- This logs an error to the dcs.log and shows a popup window, - -- pausing the simulation. This works always even if logging is - -- disabled by setting a log level of "none" or 0. - -- @tparam string text the text with keywords to substitute. - -- @param ... variables to be used for substitution. - -- @usage myLogger:alert("Shit just hit the fan! WEEEE!!!11") - function mist.Logger:alert(text, ...) - text = formatText(text, unpack(arg)) - if text:len() > 4000 then - local texts = splitText(text) - for i = 1, #texts do - if i == 1 then - env.error(self.tag .. '|' .. texts[i], true) - else - env.error(texts[i]) - end - end - else - env.error(self.tag .. '|' .. text, true) - end - end - - --- Logs a message, disregarding the log level. - -- @tparam string text the text with keywords to substitute. - -- @param ... variables to be used for substitution. - -- @usage myLogger:msg("Always logged!") - function mist.Logger:msg(text, ...) - text = formatText(text, unpack(arg)) - if text:len() > 4000 then - local texts = splitText(text) - for i = 1, #texts do - if i == 1 then - env.info(self.tag .. '|' .. texts[i]) - else - env.info(texts[i]) - end - end - else - env.info(self.tag .. '|' .. text) - end - end - - --- Logs an error. - -- logs a message prefixed with this loggers tag to dcs.log as - -- long as at least the "error" log level (1) is set. - -- @tparam string text the text with keywords to substitute. - -- @param ... variables to be used for substitution. - -- @usage myLogger:error("Just an error!") - -- @usage myLogger:error("Foo is $1 instead of $2", foo, "bar") - function mist.Logger:error(text, ...) - if self.level >= 1 then - text = formatText(text, unpack(arg)) - if text:len() > 4000 then - local texts = splitText(text) - for i = 1, #texts do - if i == 1 then - env.error(self.tag .. '|' .. texts[i]) - else - env.error(texts[i]) - end - end - else - env.error(self.tag .. '|' .. text) - end - end - end - - --- Logs a warning. - -- logs a message prefixed with this loggers tag to dcs.log as - -- long as at least the "warning" log level (2) is set. - -- @tparam string text the text with keywords to substitute. - -- @param ... variables to be used for substitution. - -- @usage myLogger:warn("Mother warned you! Those $1 from the interwebs are $2", {"geeks", 1337}) - function mist.Logger:warn(text, ...) - if self.level >= 2 then - text = formatText(text, unpack(arg)) - if text:len() > 4000 then - local texts = splitText(text) - for i = 1, #texts do - if i == 1 then - env.warning(self.tag .. '|' .. texts[i]) - else - env.warning(texts[i]) - end - end - else - env.warning(self.tag .. '|' .. text) - end - end - end - - --- Logs a info. - -- logs a message prefixed with this loggers tag to dcs.log as - -- long as the highest log level (3) "info" is set. - -- @tparam string text the text with keywords to substitute. - -- @param ... variables to be used for substitution. - -- @see warn - function mist.Logger:info(text, ...) - if self.level >= 3 then - text = formatText(text, unpack(arg)) - if text:len() > 4000 then - local texts = splitText(text) - for i = 1, #texts do - if i == 1 then - env.info(self.tag .. '|' .. texts[i]) - else - env.info(texts[i]) - end - end - else - env.info(self.tag .. '|' .. text) - end - end - end - -end - --- initialize mist -mist.init() -env.info(('Mist version ' .. mist.majorVersion .. '.' .. mist.minorVersion .. '.' .. mist.build .. ' loaded.')) - --- vim: noet:ts=2:sw=2 diff --git a/resources/plugins/skynetiads/plugin.json b/resources/plugins/skynetiads/plugin.json index bcfe83c5..7fe1e176 100644 --- a/resources/plugins/skynetiads/plugin.json +++ b/resources/plugins/skynetiads/plugin.json @@ -45,10 +45,6 @@ } ], "scriptsWorkOrders": [ - { - "file": "mist_4_3_74.lua", - "mnemonic": "mist" - }, { "file": "skynet-iads-compiled.lua", "mnemonic": "skynetiads-script" From e0319a4047fb620f6a89de06163fafe1d5b484a5 Mon Sep 17 00:00:00 2001 From: David Pierron Date: Mon, 26 Oct 2020 18:05:07 +0100 Subject: [PATCH 5/8] Merge remote-tracking branch 'upstream/develop' into skynet-iads-plugin --- .github/workflows/build.yml | 14 +- .github/workflows/release.yml | 14 +- .gitignore | 1 - game/db.py | 173 +-------- game/event/event.py | 16 +- game/factions/australia_2005.py | 70 ---- game/factions/bluefor_coldwar.py | 84 ---- game/factions/bluefor_coldwar_a4.py | 92 ----- game/factions/bluefor_coldwar_mods.py | 95 ----- game/factions/bluefor_modern.py | 128 ------- game/factions/canada_2005.py | 62 --- game/factions/china_2010.py | 111 ------ game/factions/faction.py | 252 ++++++++++++ game/factions/faction_loader.py | 28 ++ game/factions/france_1995.py | 68 ---- game/factions/france_2005.py | 86 ----- game/factions/france_modded.py | 103 ----- game/factions/germany_1944.py | 58 --- game/factions/germany_1944_easy.py | 51 --- game/factions/germany_1990.py | 66 ---- game/factions/india_2010.py | 80 ---- game/factions/insurgent.py | 34 -- game/factions/insurgent_modded.py | 46 --- game/factions/iran_2015.py | 86 ----- game/factions/israel_1948.py | 47 --- game/factions/israel_1973.py | 131 ------- game/factions/israel_2000.py | 66 ---- game/factions/italy_1990.py | 69 ---- game/factions/italy_1990_mb339.py | 73 ---- game/factions/japan_2005.py | 71 ---- game/factions/libya_2011.py | 68 ---- game/factions/netherlands_1990.py | 56 --- game/factions/north_korea_2000.py | 79 ---- game/factions/pakistan_2015.py | 60 --- game/factions/private_miltary_companies.py | 109 ------ game/factions/russia_1955.py | 53 --- game/factions/russia_1965.py | 73 ---- game/factions/russia_1975.py | 99 ----- game/factions/russia_1990.py | 113 ------ game/factions/russia_2010.py | 119 ------ game/factions/russia_2020.py | 124 ------ game/factions/spain_1990.py | 70 ---- game/factions/sweden_1990.py | 45 --- game/factions/syria.py | 296 --------------- game/factions/turkey_2005.py | 60 --- game/factions/uae_2005.py | 58 --- game/factions/uk_1944.py | 57 --- game/factions/uk_1990.py | 74 ---- game/factions/ukraine_2010.py | 79 ---- game/factions/us_aggressors.py | 108 ------ game/factions/usa_1944.py | 100 ----- game/factions/usa_1955.py | 51 --- game/factions/usa_1960.py | 57 --- game/factions/usa_1965.py | 60 --- game/factions/usa_1990.py | 99 ----- game/factions/usa_2005.py | 109 ------ game/game.py | 50 ++- game/operation/operation.py | 8 +- gen/armor.py | 6 +- gen/briefinggen.py | 10 +- gen/defenses/armor_group_generator.py | 3 +- gen/fleet/carrier_group.py | 12 +- gen/fleet/lha_group.py | 8 +- gen/fleet/ship_group_generator.py | 18 +- gen/flights/flightplan.py | 2 +- gen/missiles/missiles_group_generator.py | 4 +- gen/sam/genericsam_group_generator.py | 14 +- gen/sam/group_generator.py | 13 +- gen/sam/sam_group_generator.py | 94 ++--- gen/visualgen.py | 4 +- pydcs_extensions/mod_units.py | 43 +++ qt_ui/widgets/QTopPanel.py | 4 +- qt_ui/widgets/map/QMapControlPoint.py | 26 ++ qt_ui/widgets/map/QMapObject.py | 5 + qt_ui/windows/newgame/QCampaignList.py | 3 +- qt_ui/windows/newgame/QNewGameWizard.py | 26 +- resources/campaigns/battle_of_britain.json | 164 ++++---- resources/campaigns/desert_war.json | 114 +++--- resources/campaigns/dunkirk.json | 146 +++---- resources/campaigns/emirates.json | 204 +++++----- resources/campaigns/full_caucasus.json | 168 ++++++++ resources/campaigns/full_map.json | 358 +++++++++--------- resources/campaigns/golan_heights_battle.json | 154 ++++---- resources/campaigns/inherent_resolve.json | 156 ++++---- resources/campaigns/invasion_from_turkey.json | 162 ++++---- resources/campaigns/invasion_of_iran.json | 274 +++++++------- .../campaigns/invasion_of_iran_[lite].json | 142 +++---- resources/campaigns/normandy.json | 158 ++++---- resources/campaigns/normandy_small.json | 98 ++--- resources/campaigns/north_caucasus.json | 190 +++++----- resources/campaigns/north_nevada.json | 134 +++---- resources/campaigns/russia_small.json | 68 ++-- resources/campaigns/syrian_civil_war.json | 174 ++++----- resources/campaigns/western_georgia.json | 214 +++++------ resources/factions/allies_1944.json | 65 ++++ resources/factions/australia_2005.json | 63 +++ resources/factions/bluefor_coldwar.json | 78 ++++ resources/factions/bluefor_coldwar_a4.json | 81 ++++ .../factions/bluefor_coldwar_a4_mb339.json | 82 ++++ resources/factions/bluefor_modern.json | 96 +++++ resources/factions/canada_2005.json | 61 +++ resources/factions/china_2010.json | 83 ++++ resources/factions/france_1995.json | 72 ++++ resources/factions/france_2005_modded.json | 86 +++++ resources/factions/germany_1942.json | 59 +++ resources/factions/germany_1944.json | 68 ++++ resources/factions/germany_1990.json | 63 +++ resources/factions/india_2010.json | 71 ++++ resources/factions/insurgents.json | 35 ++ resources/factions/insurgents_modded.json | 39 ++ resources/factions/iran_2015.json | 78 ++++ resources/factions/israel_1948.json | 53 +++ resources/factions/israel_1973.json | 60 +++ resources/factions/israel_1982.json | 62 +++ resources/factions/israel_2000.json | 65 ++++ resources/factions/italy_1990.json | 64 ++++ resources/factions/italy_1990_mb339.json | 65 ++++ resources/factions/japan_2005.json | 70 ++++ resources/factions/libya_2011.json | 64 ++++ resources/factions/netherlands_1990.json | 55 +++ resources/factions/north_korea_2000.json | 72 ++++ resources/factions/pakistan_2015.json | 69 ++++ resources/factions/pmc_russian.json | 36 ++ resources/factions/pmc_us.json | 33 ++ resources/factions/pmc_us_with_mb339.json | 37 ++ resources/factions/russia_1955.json | 60 +++ resources/factions/russia_1965.json | 64 ++++ resources/factions/russia_1975.json | 70 ++++ resources/factions/russia_1990.json | 77 ++++ resources/factions/russia_2010.json | 83 ++++ resources/factions/russia_2020.json | 80 ++++ resources/factions/spain_1990.json | 64 ++++ resources/factions/sweden_1990.json | 42 ++ resources/factions/syria_1948.json | 49 +++ resources/factions/syria_1967.json | 59 +++ .../factions/syria_1967_with_ww2_weapons.json | 64 ++++ resources/factions/syria_1973.json | 63 +++ resources/factions/syria_1982.json | 65 ++++ resources/factions/syria_2011.json | 81 ++++ resources/factions/turkey_2005.json | 49 +++ resources/factions/uae_2005.json | 47 +++ resources/factions/uk_1944.json | 60 +++ resources/factions/uk_1990.json | 70 ++++ resources/factions/ukraine_2010.json | 59 +++ resources/factions/us_aggressors.json | 62 +++ resources/factions/usa_1944.json | 59 +++ resources/factions/usa_1955.json | 38 ++ resources/factions/usa_1960.json | 33 ++ resources/factions/usa_1965.json | 36 ++ resources/factions/usa_1975.json | 40 ++ resources/factions/usa_1990.json | 86 +++++ resources/factions/usa_2005.json | 87 +++++ resources/tools/generate_loadout_check.py | 2 +- tests/resources/invalid_faction_country.json | 86 +++++ tests/resources/valid_faction.json | 88 +++++ tests/test_factions.py | 95 +++++ theater/base.py | 3 + theater/conflicttheater.py | 11 +- theater/controlpoint.py | 30 +- theater/start_generator.py | 34 +- 160 files changed, 6125 insertions(+), 5838 deletions(-) delete mode 100644 game/factions/australia_2005.py delete mode 100644 game/factions/bluefor_coldwar.py delete mode 100644 game/factions/bluefor_coldwar_a4.py delete mode 100644 game/factions/bluefor_coldwar_mods.py delete mode 100644 game/factions/bluefor_modern.py delete mode 100644 game/factions/canada_2005.py delete mode 100644 game/factions/china_2010.py create mode 100644 game/factions/faction.py create mode 100644 game/factions/faction_loader.py delete mode 100644 game/factions/france_1995.py delete mode 100644 game/factions/france_2005.py delete mode 100644 game/factions/france_modded.py delete mode 100644 game/factions/germany_1944.py delete mode 100644 game/factions/germany_1944_easy.py delete mode 100644 game/factions/germany_1990.py delete mode 100644 game/factions/india_2010.py delete mode 100644 game/factions/insurgent.py delete mode 100644 game/factions/insurgent_modded.py delete mode 100644 game/factions/iran_2015.py delete mode 100644 game/factions/israel_1948.py delete mode 100644 game/factions/israel_1973.py delete mode 100644 game/factions/israel_2000.py delete mode 100644 game/factions/italy_1990.py delete mode 100644 game/factions/italy_1990_mb339.py delete mode 100644 game/factions/japan_2005.py delete mode 100644 game/factions/libya_2011.py delete mode 100644 game/factions/netherlands_1990.py delete mode 100644 game/factions/north_korea_2000.py delete mode 100644 game/factions/pakistan_2015.py delete mode 100644 game/factions/private_miltary_companies.py delete mode 100644 game/factions/russia_1955.py delete mode 100644 game/factions/russia_1965.py delete mode 100644 game/factions/russia_1975.py delete mode 100644 game/factions/russia_1990.py delete mode 100644 game/factions/russia_2010.py delete mode 100644 game/factions/russia_2020.py delete mode 100644 game/factions/spain_1990.py delete mode 100644 game/factions/sweden_1990.py delete mode 100644 game/factions/syria.py delete mode 100644 game/factions/turkey_2005.py delete mode 100644 game/factions/uae_2005.py delete mode 100644 game/factions/uk_1944.py delete mode 100644 game/factions/uk_1990.py delete mode 100644 game/factions/ukraine_2010.py delete mode 100644 game/factions/us_aggressors.py delete mode 100644 game/factions/usa_1944.py delete mode 100644 game/factions/usa_1955.py delete mode 100644 game/factions/usa_1960.py delete mode 100644 game/factions/usa_1965.py delete mode 100644 game/factions/usa_1990.py delete mode 100644 game/factions/usa_2005.py create mode 100644 pydcs_extensions/mod_units.py create mode 100644 resources/campaigns/full_caucasus.json create mode 100644 resources/factions/allies_1944.json create mode 100644 resources/factions/australia_2005.json create mode 100644 resources/factions/bluefor_coldwar.json create mode 100644 resources/factions/bluefor_coldwar_a4.json create mode 100644 resources/factions/bluefor_coldwar_a4_mb339.json create mode 100644 resources/factions/bluefor_modern.json create mode 100644 resources/factions/canada_2005.json create mode 100644 resources/factions/china_2010.json create mode 100644 resources/factions/france_1995.json create mode 100644 resources/factions/france_2005_modded.json create mode 100644 resources/factions/germany_1942.json create mode 100644 resources/factions/germany_1944.json create mode 100644 resources/factions/germany_1990.json create mode 100644 resources/factions/india_2010.json create mode 100644 resources/factions/insurgents.json create mode 100644 resources/factions/insurgents_modded.json create mode 100644 resources/factions/iran_2015.json create mode 100644 resources/factions/israel_1948.json create mode 100644 resources/factions/israel_1973.json create mode 100644 resources/factions/israel_1982.json create mode 100644 resources/factions/israel_2000.json create mode 100644 resources/factions/italy_1990.json create mode 100644 resources/factions/italy_1990_mb339.json create mode 100644 resources/factions/japan_2005.json create mode 100644 resources/factions/libya_2011.json create mode 100644 resources/factions/netherlands_1990.json create mode 100644 resources/factions/north_korea_2000.json create mode 100644 resources/factions/pakistan_2015.json create mode 100644 resources/factions/pmc_russian.json create mode 100644 resources/factions/pmc_us.json create mode 100644 resources/factions/pmc_us_with_mb339.json create mode 100644 resources/factions/russia_1955.json create mode 100644 resources/factions/russia_1965.json create mode 100644 resources/factions/russia_1975.json create mode 100644 resources/factions/russia_1990.json create mode 100644 resources/factions/russia_2010.json create mode 100644 resources/factions/russia_2020.json create mode 100644 resources/factions/spain_1990.json create mode 100644 resources/factions/sweden_1990.json create mode 100644 resources/factions/syria_1948.json create mode 100644 resources/factions/syria_1967.json create mode 100644 resources/factions/syria_1967_with_ww2_weapons.json create mode 100644 resources/factions/syria_1973.json create mode 100644 resources/factions/syria_1982.json create mode 100644 resources/factions/syria_2011.json create mode 100644 resources/factions/turkey_2005.json create mode 100644 resources/factions/uae_2005.json create mode 100644 resources/factions/uk_1944.json create mode 100644 resources/factions/uk_1990.json create mode 100644 resources/factions/ukraine_2010.json create mode 100644 resources/factions/us_aggressors.json create mode 100644 resources/factions/usa_1944.json create mode 100644 resources/factions/usa_1955.json create mode 100644 resources/factions/usa_1960.json create mode 100644 resources/factions/usa_1965.json create mode 100644 resources/factions/usa_1975.json create mode 100644 resources/factions/usa_1990.json create mode 100644 resources/factions/usa_2005.json create mode 100644 tests/resources/invalid_faction_country.json create mode 100644 tests/resources/valid_faction.json create mode 100644 tests/test_factions.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 48e79b89..8e04aa8f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,12 +27,24 @@ jobs: # For some reason the shiboken2.abi3.dll is not found properly, so I copy it instead Copy-Item .\venv\Lib\site-packages\shiboken2\shiboken2.abi3.dll .\venv\Lib\site-packages\PySide2\ -Force - - name: Build binaries + - name: mypy game run: | ./venv/scripts/activate mypy game + + - name: mypy gen + run: | + ./venv/scripts/activate mypy gen + + - name: mypy theater + run: | + ./venv/scripts/activate mypy theater + + - name: Build binaries + run: | + ./venv/scripts/activate $env:PYTHONPATH=".;./pydcs" pyinstaller pyinstaller.spec diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 33d78ae3..559d19a5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,12 +29,24 @@ jobs: # For some reason the shiboken2.abi3.dll is not found properly, so I copy it instead Copy-Item .\venv\Lib\site-packages\shiboken2\shiboken2.abi3.dll .\venv\Lib\site-packages\PySide2\ -Force - - name: Build binaries + - name: mypy game run: | ./venv/scripts/activate mypy game + + - name: mypy gen + run: | + ./venv/scripts/activate mypy gen + + - name: mypy theater + run: | + ./venv/scripts/activate mypy theater + + - name: Build binaries + run: | + ./venv/scripts/activate $env:PYTHONPATH=".;./pydcs" pyinstaller pyinstaller.spec diff --git a/.gitignore b/.gitignore index 26831339..e53921ce 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,6 @@ logs.txt dist/** a.py resources/tools/a.miz -tests/** # User-specific stuff .idea/ diff --git a/game/db.py b/game/db.py index bc221df2..948a37ac 100644 --- a/game/db.py +++ b/game/db.py @@ -1,6 +1,6 @@ from datetime import datetime from enum import Enum -from typing import Any, Dict, List, Optional, Tuple, Type, Union +from typing import Dict, List, Optional, Tuple, Type, Union from dcs.countries import country_dict from dcs.helicopters import ( @@ -153,71 +153,9 @@ from dcs.vehicles import ( ) import pydcs_extensions.frenchpack.frenchpack as frenchpack -from game.factions.australia_2005 import Australia_2005 -from game.factions.bluefor_coldwar import BLUEFOR_COLDWAR -from game.factions.bluefor_coldwar_a4 import BLUEFOR_COLDWAR_A4 -from game.factions.bluefor_coldwar_mods import BLUEFOR_COLDWAR_MODS -from game.factions.bluefor_modern import BLUEFOR_MODERN -from game.factions.canada_2005 import Canada_2005 -from game.factions.china_2010 import China_2010 -from game.factions.france_1995 import France_1995 -from game.factions.france_2005 import France_2005 -from game.factions.france_modded import France_2005_Modded -from game.factions.germany_1944 import Germany_1944 -from game.factions.germany_1944_easy import Germany_1944_Easy -from game.factions.germany_1990 import Germany_1990 -from game.factions.india_2010 import India_2010 -from game.factions.insurgent import Insurgent -from game.factions.insurgent_modded import Insurgent_modded -from game.factions.iran_2015 import Iran_2015 -from game.factions.israel_1948 import Israel_1948 -from game.factions.israel_1973 import ( - Israel_1973, - Israel_1973_NO_WW2_UNITS, - Israel_1982, -) -from game.factions.israel_2000 import Israel_2000 -from game.factions.italy_1990 import Italy_1990 -from game.factions.italy_1990_mb339 import Italy_1990_MB339 -from game.factions.japan_2005 import Japan_2005 -from game.factions.libya_2011 import Libya_2011 -from game.factions.netherlands_1990 import Netherlands_1990 -from game.factions.north_korea_2000 import NorthKorea_2000 -from game.factions.pakistan_2015 import Pakistan_2015 -from game.factions.private_miltary_companies import ( - PMC_RUSSIAN, - PMC_WESTERN_A, - PMC_WESTERN_B, -) -from game.factions.russia_1955 import Russia_1955 -from game.factions.russia_1965 import Russia_1965 -from game.factions.russia_1975 import Russia_1975 -from game.factions.russia_1990 import Russia_1990 -from game.factions.russia_2010 import Russia_2010 -from game.factions.russia_2020 import Russia_2020 -from game.factions.spain_1990 import Spain_1990 -from game.factions.sweden_1990 import Sweden_1990 -from game.factions.syria import ( - Arab_Armies_1948, - Syria_1967, - Syria_1967_WW2_Weapons, - Syria_1973, - Syria_1982, - Syria_2011, -) -from game.factions.turkey_2005 import Turkey_2005 -from game.factions.uae_2005 import UAE_2005 -from game.factions.uk_1944 import UK_1944 -from game.factions.uk_1990 import UnitedKingdom_1990 -from game.factions.ukraine_2010 import Ukraine_2010 -from game.factions.us_aggressors import US_Aggressors -from game.factions.usa_1944 import ALLIES_1944, USA_1944 -from game.factions.usa_1955 import USA_1955 -from game.factions.usa_1960 import USA_1960 -from game.factions.usa_1965 import USA_1965 -from game.factions.usa_1990 import USA_1990 -from game.factions.usa_2005 import USA_2005 +from game.factions.faction import Faction # PATCH pydcs data with MODS +from game.factions.faction_loader import FactionLoader from pydcs_extensions.a4ec.a4ec import A_4E_C from pydcs_extensions.mb339.mb339 import MB_339PAN from pydcs_extensions.rafale.rafale import Rafale_A_S, Rafale_M @@ -973,99 +911,7 @@ CARRIER_TAKEOFF_BAN: List[Type[FlyingType]] = [ Units separated by country. country : DCS Country name """ -FACTIONS: Dict[str, Dict[str, Any]] = { - - "Bluefor Modern": BLUEFOR_MODERN, - "Bluefor Cold War 1970s": BLUEFOR_COLDWAR, - "Bluefor Cold War (with A-4)": BLUEFOR_COLDWAR_A4, - "Bluefor Cold War (with A-4 and MB339)": BLUEFOR_COLDWAR_MODS, - - "USA 1955 (WW2 Pack)": USA_1955, - "USA 1960": USA_1960, - "USA 1965": USA_1965, - "USA 1990": USA_1990, - "USA 2005": USA_2005, - "USA Aggressors 2005": US_Aggressors, - - "Russia 1955": Russia_1955, - "Russia 1965": Russia_1965, - "Russia 1975": Russia_1975, - "Russia 1990": Russia_1990, - "Russia 2010": Russia_2010, - "Russia 2020 (Modded)": Russia_2020, - - "France 1995": France_1995, - "France 2005": France_2005, - "France 2005 (Modded)": France_2005_Modded, - - "Germany 1990": Germany_1990, - - "Netherlands 1990": Netherlands_1990, - - "United Kingdom 1990": UnitedKingdom_1990, - - "Spain 1990": Spain_1990, - - "Italy 1990": Italy_1990, - "Italy 1990 (With MB339)": Italy_1990_MB339, - - "Israel 2000": Israel_2000, - "Israel 1982": Israel_1982, - "Israel 1973 (WW2 Pack)": Israel_1973, - "Israel 1973": Israel_1973_NO_WW2_UNITS, - "Israel 1948": Israel_1948, - - "Arab Armies 1982": Syria_1982, - "Arab Armies 1973": Syria_1973, - "Arab Armies 1967 (WW2 Pack)": Syria_1967_WW2_Weapons, - "Arab Armies 1967": Syria_1967, - "Arab League 1948": Arab_Armies_1948, - - "China 2010": China_2010, - - "Sweden 1990": Sweden_1990, - - "Australia 2005": Australia_2005, - - "Canada 2005": Canada_2005, - - "Japan 2005": Japan_2005, - - "Turkey 2005": Turkey_2005, - - "United Arab Emirates 2005": UAE_2005, - - "Ukraine 2010": Ukraine_2010, - - "India 2010": India_2010, - - "Libya 2011": Libya_2011, - - "Syria 2011": Syria_2011, - - - "Pakistan 2015": Pakistan_2015, - - "Iran 2015": Iran_2015, - - "North Korea 2000": NorthKorea_2000, - - "Insurgent": Insurgent, - "Insurgent (Modded)": Insurgent_modded, - - "PMC (American)": PMC_WESTERN_A, - "PMC (American) - MB339": PMC_WESTERN_B, - "PMC (Russian)": PMC_RUSSIAN, - - "Allies 1944 (WW2 Pack)": USA_1944, - "USA 1944 (WW2 Pack)": ALLIES_1944, - "UK 1944 (WW2 Pack)": UK_1944, - - "Germany 1944 (WW2 Pack)": Germany_1944, - "Germany 1944 Easy (WW2 Pack)": Germany_1944_Easy, - -} - +FACTIONS: Dict[str, Faction] = FactionLoader.load_factions() CARRIER_TYPE_BY_PLANE = { FA_18C_hornet: CVN_74_John_C__Stennis, @@ -1295,6 +1141,7 @@ CARRIER_CAPABLE = [ AV8BNA, Su_33, A_4E_C, + Rafale_M, UH_1H, Mi_8MT, @@ -1358,6 +1205,7 @@ def upgrade_to_supercarrier(unit, name: str): else: return unit + def unit_task(unit: UnitType) -> Optional[Task]: for task, units in UNIT_BY_TASK.items(): if unit in units: @@ -1369,8 +1217,10 @@ def unit_task(unit: UnitType) -> Optional[Task]: print(unit.name + " cause issue") return None + def find_unittype(for_task: Task, country_name: str) -> List[UnitType]: - return [x for x in UNIT_BY_TASK[for_task] if x in FACTIONS[country_name]["units"]] + return [x for x in UNIT_BY_TASK[for_task] if x in FACTIONS[country_name].units] + def find_infantry(country_name: str) -> List[UnitType]: inf = [ @@ -1387,14 +1237,17 @@ def find_infantry(country_name: str) -> List[UnitType]: Infantry.Infantry_M1_Garand, Infantry.Infantry_M1_Garand, Infantry.Infantry_M1_Garand, Infantry.Infantry_Soldier_Insurgents, Infantry.Infantry_Soldier_Insurgents, Infantry.Infantry_Soldier_Insurgents ] - return [x for x in inf if x in FACTIONS[country_name]["units"]] + return [x for x in inf if x in FACTIONS[country_name].infantry_units] + def unit_type_name(unit_type) -> str: return unit_type.id and unit_type.id or unit_type.name + def unit_type_name_2(unit_type) -> str: return unit_type.name and unit_type.name or unit_type.id + def unit_type_from_name(name: str) -> Optional[UnitType]: if name in vehicle_map: return vehicle_map[name] diff --git a/game/event/event.py b/game/event/event.py index 8f7ac1b8..e6b7d142 100644 --- a/game/event/event.py +++ b/game/event/event.py @@ -195,29 +195,19 @@ class Event: if cp.id == id: if cp.captured and new_owner_coalition != coalition: - cp.captured = False + for_player = False info = Information(cp.name + " lost !", "The ennemy took control of " + cp.name + "\nShame on us !", self.game.turn) self.game.informations.append(info) - pname = self.game.enemy_name captured_cps.append(cp) elif not(cp.captured) and new_owner_coalition == coalition: - cp.captured = True + for_player = True info = Information(cp.name + " captured !", "We took control of " + cp.name + "! Great job !", self.game.turn) self.game.informations.append(info) - pname = self.game.player_name captured_cps.append(cp) else: continue - cp.base.aircraft = {} - cp.base.armor = {} - - airbase_def_id = 0 - for g in cp.ground_objects: - g.groups = [] - if g.airbase_group and pname != "": - generate_airbase_defense_group(airbase_def_id, g, pname, self.game, cp) - airbase_def_id = airbase_def_id + 1 + cp.capture(self.game, for_player) for cp in captured_cps: logging.info("Will run redeploy for " + cp.name) diff --git a/game/factions/australia_2005.py b/game/factions/australia_2005.py deleted file mode 100644 index c9cfb320..00000000 --- a/game/factions/australia_2005.py +++ /dev/null @@ -1,70 +0,0 @@ -from dcs.helicopters import ( - AH_1W, - UH_1H, -) -from dcs.planes import ( - C_130, - E_3A, - FA_18C_hornet, - KC130, - KC_135, -) -from dcs.ships import ( - Armed_speedboat, - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Ticonderoga_class, - USS_Arleigh_Burke_IIa, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Infantry, - Unarmed, -) - -Australia_2005 = { - "country": "Australia", - "side": "blue", - "units": [ - FA_18C_hornet, - - KC_135, - KC130, - C_130, - E_3A, - - Armor.MBT_M1A2_Abrams, - Armor.MBT_Leopard_1A3, - Armor.APC_M113, - Armor.IFV_LAV_25, - Armor.IFV_MCV_80, - - UH_1H, - AH_1W, # Standing as EC Tiger - - Unarmed.Transport_M818, - Infantry.Infantry_M4, - Infantry.Soldier_M249, - - AirDefence.SAM_Hawk_PCP, - AirDefence.Rapier_FSA_Launcher, - - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Armed_speedboat, - ], "shorad": [ - AirDefence.Rapier_FSA_Launcher, - ], "helicopter_carrier": [ - LHA_1_Tarawa, - ], "destroyer": [ - USS_Arleigh_Burke_IIa, - ], "cruiser": [ - Ticonderoga_class, - ], "lhanames": [ - "HMAS Canberra", - "HMAS Adelaide" - ], "boat":[ - "ArleighBurkeGroupGenerator" - ], "has_jtac": True -} diff --git a/game/factions/bluefor_coldwar.py b/game/factions/bluefor_coldwar.py deleted file mode 100644 index b2a4a494..00000000 --- a/game/factions/bluefor_coldwar.py +++ /dev/null @@ -1,84 +0,0 @@ -from dcs.helicopters import ( - SA342L, - SA342M, - UH_1H, -) -from dcs.planes import ( - AJS37, - A_10A, - B_52H, - C_130, - E_3A, - F_14B, - F_4E, - F_5E_3, - KC130, - KC_135, -) -from dcs.ships import ( - Armed_speedboat, - CVN_74_John_C__Stennis, - LHA_1_Tarawa, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Infantry, - Unarmed, -) - -BLUEFOR_COLDWAR = { - "country": "Combined Joint Task Forces Blue", - "side": "blue", - "units": [ - - F_14B, - F_4E, - F_5E_3, - A_10A, - AJS37, - - B_52H, - - KC_135, - KC130, - C_130, - E_3A, - - UH_1H, - SA342M, - SA342L, - - Armor.MBT_M60A3_Patton, - Armor.APC_M113, - - Unarmed.Transport_M818, - Infantry.Infantry_M4, - Infantry.Soldier_M249, - - AirDefence.SAM_Hawk_PCP, - AirDefence.SAM_Chaparral_M48, - - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Armed_speedboat, - ], "shorad": [ - AirDefence.AAA_Vulcan_M163, - ], "aircraft_carrier": [ - CVN_74_John_C__Stennis, - ], "helicopter_carrier": [ - LHA_1_Tarawa, - ], "carrier_names": [ - "CVN-71 Theodore Roosevelt", - "CVN-72 Abraham Lincoln", - "CVN-73 George Washington", - "CVN-74 John C. Stennis", - ], "lhanames": [ - "LHA-1 Tarawa", - "LHA-2 Saipan", - "LHA-3 Belleau Wood", - "LHA-4 Nassau", - "LHA-5 Peleliu" - ], "boat": [ - ], "has_jtac": True -} diff --git a/game/factions/bluefor_coldwar_a4.py b/game/factions/bluefor_coldwar_a4.py deleted file mode 100644 index 1244ffbd..00000000 --- a/game/factions/bluefor_coldwar_a4.py +++ /dev/null @@ -1,92 +0,0 @@ -from dcs.helicopters import ( - SA342L, - SA342M, - UH_1H, -) -from dcs.planes import ( - AJS37, - A_10A, - B_52H, - C_130, - E_3A, - F_14B, - F_4E, - F_5E_3, - KC130, - KC_135, -) -from dcs.ships import ( - Armed_speedboat, - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Ticonderoga_class, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Infantry, - Unarmed, -) - -from pydcs_extensions.a4ec.a4ec import A_4E_C - -BLUEFOR_COLDWAR_A4 = { - "country": "Combined Joint Task Forces Blue", - "side": "blue", - "units": [ - - F_14B, - F_4E, - F_5E_3, - A_10A, - AJS37, - A_4E_C, - - B_52H, - - KC_135, - KC130, - C_130, - E_3A, - - UH_1H, - SA342M, - SA342L, - - Armor.MBT_M60A3_Patton, - Armor.APC_M113, - - Unarmed.Transport_M818, - Infantry.Infantry_M4, - Infantry.Soldier_M249, - - AirDefence.SAM_Hawk_PCP, - AirDefence.SAM_Chaparral_M48, - - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Armed_speedboat, - ], "shorad": [ - AirDefence.AAA_Vulcan_M163, - ], "aircraft_carrier": [ - CVN_74_John_C__Stennis, - ], "helicopter_carrier": [ - LHA_1_Tarawa, - ], "cruiser": [ - Ticonderoga_class, - ], "carrier_names": [ - "CVN-71 Theodore Roosevelt", - "CVN-72 Abraham Lincoln", - "CVN-73 George Washington", - "CVN-74 John C. Stennis", - ], "lhanames": [ - "LHA-1 Tarawa", - "LHA-2 Saipan", - "LHA-3 Belleau Wood", - "LHA-4 Nassau", - "LHA-5 Peleliu" - ], "boat": [ - ], "requirements": { - "Community A-4E": "https://heclak.github.io/community-a4e-c/", - }, "has_jtac": True -} diff --git a/game/factions/bluefor_coldwar_mods.py b/game/factions/bluefor_coldwar_mods.py deleted file mode 100644 index 2eed5f31..00000000 --- a/game/factions/bluefor_coldwar_mods.py +++ /dev/null @@ -1,95 +0,0 @@ -from dcs.helicopters import ( - SA342L, - SA342M, - UH_1H, -) -from dcs.planes import ( - AJS37, - A_10A, - B_52H, - C_130, - E_3A, - F_14B, - F_4E, - F_5E_3, - KC130, - KC_135, -) -from dcs.ships import ( - Armed_speedboat, - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Ticonderoga_class, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Infantry, - Unarmed, -) - -from pydcs_extensions.a4ec.a4ec import A_4E_C -from pydcs_extensions.mb339.mb339 import MB_339PAN - -BLUEFOR_COLDWAR_MODS = { - "country": "USA", - "side": "blue", - "units": [ - - F_14B, - F_4E, - F_5E_3, - A_10A, - AJS37, - A_4E_C, - MB_339PAN, - - B_52H, - - KC_135, - KC130, - C_130, - E_3A, - - UH_1H, - SA342M, - SA342L, - - Armor.MBT_M60A3_Patton, - Armor.APC_M113, - - Unarmed.Transport_M818, - Infantry.Infantry_M4, - Infantry.Soldier_M249, - - AirDefence.SAM_Hawk_PCP, - AirDefence.SAM_Chaparral_M48, - - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Armed_speedboat, - ], "shorad": [ - AirDefence.AAA_Vulcan_M163, - ], "aircraft_carrier": [ - CVN_74_John_C__Stennis, - ], "helicopter_carrier": [ - LHA_1_Tarawa, - ], "cruiser": [ - Ticonderoga_class, - ], "carrier_names": [ - "CVN-71 Theodore Roosevelt", - "CVN-72 Abraham Lincoln", - "CVN-73 George Washington", - "CVN-74 John C. Stennis", - ], "lhanames": [ - "LHA-1 Tarawa", - "LHA-2 Saipan", - "LHA-3 Belleau Wood", - "LHA-4 Nassau", - "LHA-5 Peleliu" - ], "boat": [ - ], "requirements": { - "MB-339A": "http://www.freccetricolorivirtuali.net/", - "Community A-4E": "https://heclak.github.io/community-a4e-c/", - }, "has_jtac": True -} diff --git a/game/factions/bluefor_modern.py b/game/factions/bluefor_modern.py deleted file mode 100644 index 0798f02f..00000000 --- a/game/factions/bluefor_modern.py +++ /dev/null @@ -1,128 +0,0 @@ -from dcs.helicopters import ( - AH_64D, - Ka_50, - SA342L, - SA342M, - UH_1H, -) -from dcs.planes import ( - AJS37, - AV8BNA, - A_10A, - A_10C, - A_10C_2, - B_1B, - B_52H, - C_130, - E_3A, - FA_18C_hornet, - F_14B, - F_15C, - F_15E, - F_16C_50, - F_5E_3, - JF_17, - KC130, - KC_135, - M_2000C, - Su_25T, - Su_27, -) -from dcs.ships import ( - Armed_speedboat, - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Oliver_Hazzard_Perry_class, - Ticonderoga_class, - USS_Arleigh_Burke_IIa, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Artillery, - Infantry, - Unarmed, -) - -BLUEFOR_MODERN = { - "country": "Combined Joint Task Forces Blue", - "side": "blue", - "units": [ - - F_15C, - F_15E, - F_14B, - FA_18C_hornet, - F_16C_50, - JF_17, - M_2000C, - F_5E_3, - Su_27, - - Su_25T, - A_10A, - A_10C, - A_10C_2, - AV8BNA, - AJS37, - - B_1B, - B_52H, - - KC_135, - KC130, - C_130, - E_3A, - - UH_1H, - AH_64D, - Ka_50, - SA342M, - SA342L, - - Armor.MBT_M1A2_Abrams, - Armor.MBT_Leopard_2, - Armor.ATGM_M1134_Stryker, - Armor.IFV_M2A2_Bradley, - Armor.IFV_Marder, - Armor.APC_M1043_HMMWV_Armament, - - Artillery.MLRS_M270, - Artillery.SPH_M109_Paladin, - - Unarmed.Transport_M818, - Infantry.Infantry_M4, - Infantry.Soldier_M249, - - AirDefence.SAM_Hawk_PCP, - AirDefence.SAM_Patriot_EPP_III, - - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Armed_speedboat, - ], "shorad": [ - AirDefence.SAM_Avenger_M1097, - ], "aircraft_carrier": [ - CVN_74_John_C__Stennis, - ], "helicopter_carrier": [ - LHA_1_Tarawa, - ], "destroyer": [ - Oliver_Hazzard_Perry_class, - USS_Arleigh_Burke_IIa, - ], "cruiser": [ - Ticonderoga_class, - ], "carrier_names": [ - "CVN-71 Theodore Roosevelt", - "CVN-72 Abraham Lincoln", - "CVN-73 George Washington", - "CVN-74 John C. Stennis", - ], "lhanames": [ - "LHA-1 Tarawa", - "LHA-2 Saipan", - "LHA-3 Belleau Wood", - "LHA-4 Nassau", - "LHA-5 Peleliu" - ], "boat":[ - "ArleighBurkeGroupGenerator", "OliverHazardPerryGroupGenerator" - ], "has_jtac": True -} diff --git a/game/factions/canada_2005.py b/game/factions/canada_2005.py deleted file mode 100644 index 7a664709..00000000 --- a/game/factions/canada_2005.py +++ /dev/null @@ -1,62 +0,0 @@ -from dcs.helicopters import ( - UH_1H, -) -from dcs.planes import ( - C_130, - E_3A, - FA_18C_hornet, - KC130, - KC_135, -) -from dcs.ships import ( - Armed_speedboat, - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Ticonderoga_class, - USS_Arleigh_Burke_IIa, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Infantry, - Unarmed, -) - -Canada_2005 = { - "country": "Canada", - "side": "blue", - "units": [ - FA_18C_hornet, - - KC_135, - KC130, - C_130, - E_3A, - - Armor.MBT_Leopard_1A3, - Armor.MBT_Leopard_2, - Armor.IFV_LAV_25, - Armor.APC_M113, - Armor.IFV_MCV_80, - - UH_1H, - - Unarmed.Transport_M818, - Infantry.Infantry_M4, - Infantry.Soldier_M249, - - AirDefence.SAM_Avenger_M1097, - - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Armed_speedboat, - ], "shorad": [ - AirDefence.SAM_Avenger_M1097, - ], "destroyer": [ - USS_Arleigh_Burke_IIa, - ], "cruiser": [ - Ticonderoga_class, - ], "boat":[ - "ArleighBurkeGroupGenerator" - ], "has_jtac": True -} diff --git a/game/factions/china_2010.py b/game/factions/china_2010.py deleted file mode 100644 index 577fb33d..00000000 --- a/game/factions/china_2010.py +++ /dev/null @@ -1,111 +0,0 @@ -from dcs.helicopters import ( - Mi_28N, - Mi_8MT, -) -from dcs.planes import ( - An_26B, - An_30M, - IL_76MD, - IL_78M, - JF_17, - J_11A, - KJ_2000, - MiG_21Bis, - Su_30, - Su_33, - WingLoong_I, - Yak_40, -) -from dcs.ships import ( - Bulk_cargo_ship_Yakushev, - CV_1143_5_Admiral_Kuznetsov, - Dry_cargo_ship_Ivanov, - Tanker_Elnya_160, - Type_052B_Destroyer, - Type_052C_Destroyer, - Type_054A_Frigate, - Type_071_Amphibious_Transport_Dock, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Artillery, - Infantry, - Unarmed, -) - -China_2010 = { - "country": "China", - "side": "red", - "units": [ - - MiG_21Bis, # Standing as J-7 - Su_30, - Su_33, - J_11A, - JF_17, - - IL_76MD, - IL_78M, - An_26B, - An_30M, - Yak_40, - - KJ_2000, - - Mi_8MT, - Mi_28N, - - AirDefence.SAM_SA_10_S_300PS_LN_5P85C, # Standing as HQ-9+ - AirDefence.SAM_SA_6_Kub_LN_2P25, - AirDefence.HQ_7_Self_Propelled_LN, - - Armor.ZTZ_96B, - Armor.MBT_T_55, - Armor.ZBD_04A, - Armor.IFV_BMP_1, - Artillery.MLRS_9A52_Smerch, - Artillery.SPH_2S9_Nona, - - Unarmed.Transport_Ural_375, - Unarmed.Transport_UAZ_469, - - Infantry.Paratrooper_AKS, - Infantry.Infantry_Soldier_Rus, - Infantry.Paratrooper_RPG_16, - - CV_1143_5_Admiral_Kuznetsov, - Bulk_cargo_ship_Yakushev, - Dry_cargo_ship_Ivanov, - Tanker_Elnya_160 - ], - "shorad":[ - AirDefence.SPAAA_ZSU_23_4_Shilka, - AirDefence.Rapier_FSA_Launcher, # Standing as PL-9C Shorad - AirDefence.HQ_7_Self_Propelled_LN - ], "aircraft_carrier": [ - CV_1143_5_Admiral_Kuznetsov, - ], "destroyer": [ - Type_052B_Destroyer, - Type_052C_Destroyer - ], "cruiser": [ - Type_054A_Frigate, - ], "helicopter_carrier": [ - Type_071_Amphibious_Transport_Dock, - ], "lhanames": [ - "Kunlun Shan", - "Jinggang Shan", - "Changbai Shan", - "Yimeng Shan", - "Longhu Shan", - "Wuzhi Shan", - "Wudang Shan" - ], "carrier_names": [ - "001 Liaoning", - "002 Shandong", - ], "boat":[ - "Type54GroupGenerator" - ], - "has_jtac": True, - "jtac_unit": WingLoong_I -} \ No newline at end of file diff --git a/game/factions/faction.py b/game/factions/faction.py new file mode 100644 index 00000000..1ecb20fd --- /dev/null +++ b/game/factions/faction.py @@ -0,0 +1,252 @@ +from __future__ import annotations + +import logging +from dataclasses import dataclass, field +from typing import Optional, Dict, Type, List, Any, cast + +import dcs +from dcs.countries import country_dict +from dcs.planes import plane_map +from dcs.unittype import FlyingType, ShipType, VehicleType, UnitType +from dcs.vehicles import Armor, Unarmed, Infantry, Artillery, AirDefence + +from game.data.building_data import WW2_ALLIES_BUILDINGS, DEFAULT_AVAILABLE_BUILDINGS, WW2_GERMANY_BUILDINGS +from game.data.doctrine import Doctrine, MODERN_DOCTRINE, COLDWAR_DOCTRINE, WWII_DOCTRINE +from pydcs_extensions.mod_units import MODDED_VEHICLES, MODDED_AIRPLANES + + +@dataclass +class Faction: + + # Country used by this faction + country: str = field(default="") + + # Nice name of the faction + name: str = field(default="") + + # List of faction file authors + authors: str = field(default="") + + # A description of the faction + description: str = field(default="") + + # Available aircraft + aircrafts: List[UnitType] = field(default_factory=list) + + # Available awacs aircraft + awacs: List[UnitType] = field(default_factory=list) + + # Available tanker aircraft + tankers: List[UnitType] = field(default_factory=list) + + # Available frontline units + frontline_units: List[VehicleType] = field(default_factory=list) + + # Available artillery units + artillery_units: List[VehicleType] = field(default_factory=list) + + # Infantry units used + infantry_units: List[VehicleType] = field(default_factory=list) + + # Logistics units used + logistics_units: List[VehicleType] = field(default_factory=list) + + # List of units that can be deployed as SHORAD + shorads: List[str] = field(default_factory=list) + + # Possible SAMS site generators for this faction + sams: List[str] = field(default_factory=list) + + # Possible Missile site generators for this faction + missiles: List[str] = field(default_factory=list) + + # Required mods or asset packs + requirements: Dict[str, str] = field(default_factory=dict) + + # possible aircraft carrier units + aircraft_carrier: List[UnitType] = field(default_factory=list) + + # possible helicopter carrier units + helicopter_carrier: List[UnitType] = field(default_factory=list) + + # Possible carrier names + carrier_names: List[str] = field(default_factory=list) + + # Possible helicopter carrier names + helicopter_carrier_names: List[str] = field(default_factory=list) + + # Navy group generators + navy_generators: List[str] = field(default_factory=list) + + # Available destroyers + destroyers: List[str] = field(default_factory=list) + + # Available cruisers + cruisers: List[str] = field(default_factory=list) + + # How many navy group should we try to generate per CP on startup for this faction + navy_group_count: int = field(default=1) + + # How many missiles group should we try to generate per CP on startup for this faction + missiles_group_count: int = field(default=1) + + # Whether this faction has JTAC access + has_jtac: bool = field(default=False) + + # Unit to use as JTAC for this faction + jtac_unit: Optional[FlyingType] = field(default=None) + + # doctrine + doctrine: Doctrine = field(default=MODERN_DOCTRINE) + + # List of available buildings for this faction + building_set: List[str] = field(default_factory=list) + + @classmethod + def from_json(cls: Type[Faction], json: Dict[str, Any]) -> Faction: + + faction = Faction() + + faction.country = json.get("country", "/") + if faction.country not in [c.name for c in country_dict.values()]: + raise AssertionError("Faction's country (\"{}\") is not a valid DCS country ID".format(faction.country)) + + faction.name = json.get("name", "") + if not faction.name: + raise AssertionError("Faction has no valid name") + + faction.authors = json.get("authors", "") + faction.description = json.get("description", "") + + faction.aircrafts = load_all_aircraft(json.get("aircrafts", [])) + faction.awacs = load_all_aircraft(json.get("awacs", [])) + faction.tankers = load_all_aircraft(json.get("tankers", [])) + + faction.frontline_units = load_all_vehicles( + json.get("frontline_units", [])) + faction.artillery_units = load_all_vehicles( + json.get("artillery_units", [])) + faction.infantry_units = load_all_vehicles( + json.get("infantry_units", [])) + faction.logistics_units = load_all_vehicles( + json.get("logistics_units", [])) + + faction.sams = json.get("sams", []) + faction.shorads = json.get("shorads", []) + faction.missiles = json.get("missiles", []) + faction.requirements = json.get("requirements", {}) + + faction.carrier_names = json.get("carrier_names", []) + faction.helicopter_carrier_names = json.get( + "helicopter_carrier_names", []) + faction.navy_generators = json.get("navy_generators", []) + faction.aircraft_carrier = load_all_ships( + json.get("aircraft_carrier", [])) + faction.helicopter_carrier = load_all_ships( + json.get("helicopter_carrier", [])) + faction.destroyers = load_all_ships(json.get("destroyers", [])) + faction.cruisers = load_all_ships(json.get("cruisers", [])) + faction.has_jtac = json.get("has_jtac", False) + jtac_name = json.get("jtac_unit", None) + if jtac_name is not None: + faction.jtac_unit = load_aircraft(jtac_name) + else: + faction.jtac_unit = None + faction.navy_group_count = int(json.get("navy_group_count", 1)) + faction.missiles_group_count = int(json.get("missiles_group_count", 0)) + + # Load doctrine + doctrine = json.get("doctrine", "modern") + if doctrine == "modern": + faction.doctrine = MODERN_DOCTRINE + elif doctrine == "coldwar": + faction.doctrine = COLDWAR_DOCTRINE + elif doctrine == "ww2": + faction.doctrine = WWII_DOCTRINE + else: + faction.doctrine = MODERN_DOCTRINE + + # Load the building set + building_set = json.get("building_set", "default") + if building_set == "default": + faction.building_set = DEFAULT_AVAILABLE_BUILDINGS + elif building_set == "ww2ally": + faction.building_set = WW2_ALLIES_BUILDINGS + elif building_set == "ww2germany": + faction.building_set = WW2_GERMANY_BUILDINGS + else: + faction.building_set = DEFAULT_AVAILABLE_BUILDINGS + + return faction + + @property + def units(self) -> List[UnitType]: + return (self.infantry_units + self.aircrafts + self.awacs + + self.artillery_units + self.frontline_units + + self.tankers + self.logistics_units) + + +def unit_loader(unit: str, class_repository: List[Any]) -> Optional[UnitType]: + """ + Find unit by name + :param unit: Unit name as string + :param class_repository: Repository of classes (Either a module, a class, or a list of classes) + :return: The unit as a PyDCS type + """ + if unit is None: + return None + elif unit in plane_map.keys(): + return plane_map[unit] + else: + for mother_class in class_repository: + if getattr(mother_class, unit, None) is not None: + return getattr(mother_class, unit) + if type(mother_class) is list: + for m in mother_class: + if m.__name__ == unit: + return m + logging.error(f"FACTION ERROR : Unable to find {unit} in pydcs") + return None + + +def load_aircraft(name: str) -> Optional[FlyingType]: + return cast(Optional[FlyingType], unit_loader( + name, [dcs.planes, dcs.helicopters, MODDED_AIRPLANES] + )) + + +def load_all_aircraft(data) -> List[FlyingType]: + items = [] + for name in data: + item = load_aircraft(name) + if item is not None: + items.append(item) + return items + + +def load_vehicle(name: str) -> Optional[VehicleType]: + return cast(Optional[FlyingType], unit_loader( + name, [Infantry, Unarmed, Armor, AirDefence, Artillery, MODDED_VEHICLES] + )) + + +def load_all_vehicles(data) -> List[VehicleType]: + items = [] + for name in data: + item = load_vehicle(name) + if item is not None: + items.append(item) + return items + + +def load_ship(name: str) -> Optional[ShipType]: + return cast(Optional[FlyingType], unit_loader(name, [dcs.ships])) + + +def load_all_ships(data) -> List[ShipType]: + items = [] + for name in data: + item = load_ship(name) + if item is not None: + items.append(item) + return items diff --git a/game/factions/faction_loader.py b/game/factions/faction_loader.py new file mode 100644 index 00000000..f20f5a68 --- /dev/null +++ b/game/factions/faction_loader.py @@ -0,0 +1,28 @@ +from __future__ import annotations +import json +import logging +from pathlib import Path +from typing import Dict, Type + +from game.factions.faction import Faction + +FACTION_DIRECTORY = Path("./resources/factions/") + + +class FactionLoader: + + @classmethod + def load_factions(cls: Type[FactionLoader]) -> Dict[str, Faction]: + files = [f for f in FACTION_DIRECTORY.glob("*.json") if f.is_file()] + factions = {} + + for f in files: + try: + with f.open("r", encoding="utf-8") as fdata: + data = json.load(fdata, encoding="utf-8") + factions[data["name"]] = Faction.from_json(data) + logging.info("Loaded faction : " + str(f)) + except Exception: + logging.exception(f"Unable to load faction : {f}") + + return factions diff --git a/game/factions/france_1995.py b/game/factions/france_1995.py deleted file mode 100644 index a14f24e5..00000000 --- a/game/factions/france_1995.py +++ /dev/null @@ -1,68 +0,0 @@ -from dcs.helicopters import ( - SA342L, - SA342M, - SA342Mistral, -) -from dcs.planes import ( - C_130, - E_3A, - KC130, - KC_135, - M_2000C, - Mirage_2000_5, -) -from dcs.ships import ( - Armed_speedboat, - CVN_74_John_C__Stennis, - LHA_1_Tarawa, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Artillery, - Infantry, - Unarmed, -) - -France_1995 = { - "country": "France", - "side": "blue", - "units": [ - M_2000C, - Mirage_2000_5, - - KC_135, - KC130, - C_130, - E_3A, - - SA342M, - SA342L, - SA342Mistral, - - Armor.MBT_Leclerc, - Armor.TPz_Fuchs, # Standing as VAB - Armor.APC_Cobra, # Standing as VBL - Armor.ATGM_M1134_Stryker, # Standing as VAB Mephisto - Artillery.SPH_M109_Paladin, # Standing as AMX30 AuF1 - Artillery.MLRS_M270, - - Unarmed.Transport_M818, - Infantry.Infantry_M4, - Infantry.Soldier_M249, - - AirDefence.SAM_Roland_ADS, - AirDefence.SAM_Hawk_PCP, - AirDefence.HQ_7_Self_Propelled_LN, # Standing as Crotale - - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Armed_speedboat, - - ], "shorad": [ - AirDefence.HQ_7_Self_Propelled_LN, - AirDefence.SAM_Roland_ADS - ], "boat":[ - "ArleighBurkeGroupGenerator", "OliverHazardPerryGroupGenerator" - ], "has_jtac": True -} \ No newline at end of file diff --git a/game/factions/france_2005.py b/game/factions/france_2005.py deleted file mode 100644 index 28c00ae4..00000000 --- a/game/factions/france_2005.py +++ /dev/null @@ -1,86 +0,0 @@ -from dcs.helicopters import ( - SA342L, - SA342M, - SA342Mistral, -) -from dcs.planes import ( - C_130, - E_3A, - FA_18C_hornet, - KC130, - KC_135, - M_2000C, - Mirage_2000_5, -) -from dcs.ships import ( - Armed_speedboat, - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Oliver_Hazzard_Perry_class, - Ticonderoga_class, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Artillery, - Infantry, - Unarmed, -) - -France_2005 = { - "country": "France", - "side": "blue", - "units":[ - M_2000C, - Mirage_2000_5, - FA_18C_hornet, # Standing as Rafale M - - KC_135, - KC130, - C_130, - E_3A, - - SA342M, - SA342L, - SA342Mistral, - - Armor.MBT_Leclerc, - Armor.TPz_Fuchs, # Standing as VAB - Armor.APC_Cobra, # Standing as VBL - Armor.ATGM_M1134_Stryker, # Standing as VAB Mephisto - Artillery.SPH_M109_Paladin, # Standing as AMX30 AuF1 - Artillery.MLRS_M270, - - Unarmed.Transport_M818, - Infantry.Infantry_M4, - Infantry.Soldier_M249, - - AirDefence.SAM_Roland_ADS, - AirDefence.SAM_Hawk_PCP, - AirDefence.HQ_7_Self_Propelled_LN, # Standing as Crotale - - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Armed_speedboat, - - ], "shorad":[ - AirDefence.HQ_7_Self_Propelled_LN, - AirDefence.SAM_Roland_ADS - ], "aircraft_carrier": [ - CVN_74_John_C__Stennis, # Standing as CDG Aircraft Carrier - ], "helicopter_carrier": [ - LHA_1_Tarawa, # Standing as Mistral Class - ], "destroyer": [ - Oliver_Hazzard_Perry_class, - ], "cruiser": [ - Ticonderoga_class, - ], "carrier_names": [ - "PA Charles de Gaulle", - ], "lhanames": [ - "L9013 Mistral", - "L9014 Tonerre", - "L9015 Dixmude" - ], "boat":[ - "ArleighBurkeGroupGenerator", "OliverHazardPerryGroupGenerator" - ], "has_jtac": True -} \ No newline at end of file diff --git a/game/factions/france_modded.py b/game/factions/france_modded.py deleted file mode 100644 index 8283d090..00000000 --- a/game/factions/france_modded.py +++ /dev/null @@ -1,103 +0,0 @@ -from dcs.helicopters import ( - SA342L, - SA342M, - SA342Mistral, -) -from dcs.planes import ( - C_130, - E_3A, - KC130, - KC_135, - M_2000C, - Mirage_2000_5, -) -from dcs.ships import ( - Armed_speedboat, - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Oliver_Hazzard_Perry_class, - Ticonderoga_class, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Artillery, - Infantry, - Unarmed, -) - -import pydcs_extensions.frenchpack.frenchpack as frenchpack -from pydcs_extensions.rafale.rafale import Rafale_A_S, Rafale_M - -France_2005_Modded = { - "country": "France", - "side": "blue", - "units": [ - M_2000C, - Mirage_2000_5, - Rafale_M, - Rafale_A_S, - - KC_135, - KC130, - C_130, - E_3A, - - SA342M, - SA342L, - SA342Mistral, - - Armor.MBT_Leclerc, - Artillery.SPH_M109_Paladin, # Standing as AMX30 AuF1 - Artillery.MLRS_M270, - - frenchpack.AMX_10RCR, - frenchpack.AMX_10RCR_SEPAR, - frenchpack.ERC_90, - frenchpack.TRM_2000_PAMELA, - frenchpack.VAB__50, - frenchpack.VAB_MEPHISTO, - frenchpack.VAB_T20_13, - frenchpack.VBL__50, - frenchpack.VBL_AANF1, - frenchpack.VBAE_CRAB, - frenchpack.VBAE_CRAB_MMP, - frenchpack.AMX_30B2, - frenchpack.Leclerc_Serie_XXI, - - Unarmed.Transport_M818, - Infantry.Infantry_M4, - Infantry.Soldier_M249, - - AirDefence.SAM_Roland_ADS, - AirDefence.SAM_Hawk_PCP, - AirDefence.HQ_7_Self_Propelled_LN, # Standing as Crotale - - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Armed_speedboat, - - ], "shorad": [ - AirDefence.HQ_7_Self_Propelled_LN, - AirDefence.SAM_Roland_ADS - ], "aircraft_carrier": [ - CVN_74_John_C__Stennis, # Standing as CDG Aircraft Carrier - ], "helicopter_carrier": [ - LHA_1_Tarawa, # Standing as Mistral Class - ], "destroyer": [ - Oliver_Hazzard_Perry_class, - ], "cruiser": [ - Ticonderoga_class, - ], "carrier_names": [ - "PA Charles de Gaulle", - ], "lhanames": [ - "L9013 Mistral", - "L9014 Tonerre", - "L9015 Dixmude" - ], "boat": [ - "ArleighBurkeGroupGenerator", "OliverHazardPerryGroupGenerator" - ], "requirements": { - "frenchpack V3.5": "https://forums.eagle.ru/showthread.php?t=279974", - "RAFALE 2.5.5": "https://www.digitalcombatsimulator.com/fr/files/3307478/", - }, "has_jtac": True -} \ No newline at end of file diff --git a/game/factions/germany_1944.py b/game/factions/germany_1944.py deleted file mode 100644 index c63f88f4..00000000 --- a/game/factions/germany_1944.py +++ /dev/null @@ -1,58 +0,0 @@ -from dcs.planes import ( - Bf_109K_4, - FW_190A8, - FW_190D9, - Ju_88A4, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Artillery, - Infantry, - Unarmed, -) - -from game.data.building_data import WW2_GERMANY_BUILDINGS -from game.data.doctrine import WWII_DOCTRINE - -Germany_1944 = { - "country": "Third Reich", - "side": "red", - "units": [ - - FW_190A8, - FW_190D9, - Bf_109K_4, - Ju_88A4, - - Armor.MT_Pz_Kpfw_V_Panther_Ausf_G, - Armor.MT_Pz_Kpfw_IV_Ausf_H, - Armor.HT_Pz_Kpfw_VI_Tiger_I, - Armor.HT_Pz_Kpfw_VI_Ausf__B__Tiger_II, - Armor.APC_Sd_Kfz_251, - Armor.IFV_Sd_Kfz_234_2_Puma, - Armor.Sd_Kfz_184_Elefant, - Armor.TD_Jagdpanther_G1, - Armor.TD_Jagdpanzer_IV, - - Artillery.Sturmpanzer_IV_Brummbär, - - Unarmed.Sd_Kfz_2, - Unarmed.Sd_Kfz_7, - Unarmed.Kübelwagen_82, - - Infantry.Infantry_Mauser_98, - AirDefence.AAA_8_8cm_Flak_36, - ],"requirements":{ - "WW2 Asset Pack": "https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/", - }, - "shorad": [ - AirDefence.AAA_8_8cm_Flak_36, - ], - "objects": WW2_GERMANY_BUILDINGS, - "doctrine": WWII_DOCTRINE, - "boat": ["UBoatGroupGenerator", "SchnellbootGroupGenerator"], - "boat_count": 2, - "missiles": ["V1GroupGenerator"], - "missiles_count": 1 -} \ No newline at end of file diff --git a/game/factions/germany_1944_easy.py b/game/factions/germany_1944_easy.py deleted file mode 100644 index 8be93457..00000000 --- a/game/factions/germany_1944_easy.py +++ /dev/null @@ -1,51 +0,0 @@ -from dcs.planes import ( - Bf_109K_4, - FW_190A8, - FW_190D9, - Ju_88A4, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Artillery, - Infantry, - Unarmed, -) - -from game.data.building_data import WW2_GERMANY_BUILDINGS -from game.data.doctrine import WWII_DOCTRINE - -Germany_1944_Easy = { - "country": "Third Reich", - "side": "red", - "units": [ - - FW_190A8, - FW_190D9, - Bf_109K_4, - Ju_88A4, - - Armor.MT_Pz_Kpfw_IV_Ausf_H, - Armor.APC_Sd_Kfz_251, - Armor.IFV_Sd_Kfz_234_2_Puma, - Artillery.Sturmpanzer_IV_Brummbär, - - Unarmed.Sd_Kfz_2, - Unarmed.Sd_Kfz_7, - Unarmed.Kübelwagen_82, - - Infantry.Infantry_Mauser_98, - AirDefence.AAA_8_8cm_Flak_36, - ],"requirements":{ - "WW2 Asset Pack": "https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/", - }, - "shorad":[ - AirDefence.AAA_8_8cm_Flak_36, - ], - "objects": WW2_GERMANY_BUILDINGS, - "doctrine": WWII_DOCTRINE, - "boat": ["UBoatGroupGenerator", "SchnellbootGroupGenerator"], - "boat_count": 1, - "missiles": ["V1GroupGenerator"], - "missiles_count": 1 -} \ No newline at end of file diff --git a/game/factions/germany_1990.py b/game/factions/germany_1990.py deleted file mode 100644 index 54432ef4..00000000 --- a/game/factions/germany_1990.py +++ /dev/null @@ -1,66 +0,0 @@ -from dcs.helicopters import ( - SA342L, - SA342M, - UH_1H, -) -from dcs.planes import ( - C_130, - E_3A, - F_4E, - KC130, - KC_135, - MiG_29G, - Tornado_IDS, -) -from dcs.ships import ( - Armed_speedboat, - CVN_74_John_C__Stennis, - LHA_1_Tarawa, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Infantry, - Unarmed, -) - -Germany_1990 = { - "country": "Germany", - "side": "blue", - "units":[ - MiG_29G, - Tornado_IDS, - F_4E, - - KC_135, - KC130, - C_130, - E_3A, - - UH_1H, - SA342M, - SA342L, - - Armor.TPz_Fuchs, - Armor.MBT_Leopard_1A3, - Armor.MBT_Leopard_2, - Armor.IFV_Marder, - - Unarmed.Transport_M818, - Infantry.Infantry_M4, - Infantry.Soldier_M249, - - AirDefence.SAM_Roland_ADS, - AirDefence.SAM_Hawk_PCP, - - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Armed_speedboat, - ], - "shorad":[ - AirDefence.SPAAA_Gepard, - AirDefence.SAM_Roland_ADS, - ], "boat":[ - "OliverHazardPerryGroupGenerator" - ] -} \ No newline at end of file diff --git a/game/factions/india_2010.py b/game/factions/india_2010.py deleted file mode 100644 index 756faa23..00000000 --- a/game/factions/india_2010.py +++ /dev/null @@ -1,80 +0,0 @@ -from dcs.helicopters import ( - AH_64A, - Mi_8MT, -) -from dcs.planes import ( - C_130, - E_3A, - KC130, - KC_135, - M_2000C, - MiG_21Bis, - MiG_27K, - MiG_29S, - Mirage_2000_5, - Su_30, -) -from dcs.ships import ( - Armed_speedboat, - CVN_74_John_C__Stennis, - CV_1143_5_Admiral_Kuznetsov, - FSG_1241_1MP_Molniya, - LHA_1_Tarawa, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Infantry, - Unarmed, -) - -India_2010 = { - "country": "India", - "side": "blue", - "units": [ - Mirage_2000_5, - M_2000C, - MiG_27K, - MiG_21Bis, - MiG_29S, - Su_30, - - KC_135, - KC130, - C_130, - E_3A, - - AH_64A, - Mi_8MT, - - Armor.MBT_T_90, - Armor.MBT_T_72B, - Armor.IFV_BMP_2, - - Unarmed.Transport_M818, - Infantry.Infantry_M4, - - AirDefence.SAM_SA_6_Kub_LN_2P25, - AirDefence.SAM_SA_3_S_125_LN_5P73, - - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Armed_speedboat, - ], - "shorad":[ - AirDefence.SAM_SA_8_Osa_9A33, - AirDefence.AAA_ZU_23_Emplacement, - AirDefence.SPAAA_ZSU_23_4_Shilka, - AirDefence.SAM_SA_13_Strela_10M3_9A35M3, - AirDefence.SAM_SA_8_Osa_9A33, - AirDefence.SAM_SA_19_Tunguska_2S6 - ], "aircraft_carrier": [ - CV_1143_5_Admiral_Kuznetsov, - ], "destroyer": [ - FSG_1241_1MP_Molniya, - ], "carrier_names": [ - "INS Vikramaditya" - ], "boat":[ - "ArleighBurkeGroupGenerator", "OliverHazardPerryGroupGenerator", "MolniyaGroupGenerator" - ], "has_jtac": True -} \ No newline at end of file diff --git a/game/factions/insurgent.py b/game/factions/insurgent.py deleted file mode 100644 index 66ae3459..00000000 --- a/game/factions/insurgent.py +++ /dev/null @@ -1,34 +0,0 @@ -from dcs.ships import ( - Bulk_cargo_ship_Yakushev, - Dry_cargo_ship_Ivanov, - Tanker_Elnya_160, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Infantry, - Unarmed, -) - -Insurgent = { - "country": "Insurgents", - "side": "red", - "units": [ - - AirDefence.AAA_ZU_23_Insurgent_Closed, - AirDefence.AAA_ZU_23_Insurgent_on_Ural_375, - - Armor.APC_Cobra, - Armor.APC_MTLB, - Armor.ARV_BRDM_2, - - Unarmed.Transport_Ural_375, - Unarmed.Transport_UAZ_469, - Infantry.Infantry_Soldier_Insurgents, - Infantry.Soldier_RPG, - - Bulk_cargo_ship_Yakushev, - Dry_cargo_ship_Ivanov, - Tanker_Elnya_160 - ] -} \ No newline at end of file diff --git a/game/factions/insurgent_modded.py b/game/factions/insurgent_modded.py deleted file mode 100644 index b19b4344..00000000 --- a/game/factions/insurgent_modded.py +++ /dev/null @@ -1,46 +0,0 @@ -from dcs.ships import ( - Bulk_cargo_ship_Yakushev, - Dry_cargo_ship_Ivanov, - Tanker_Elnya_160, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Infantry, - Unarmed, -) - -from pydcs_extensions.frenchpack.frenchpack import ( - DIM__KAMIKAZE, - DIM__TOYOTA_BLUE, - DIM__TOYOTA_DESERT, - DIM__TOYOTA_GREEN, -) - -Insurgent_modded = { - "country": "Insurgents", - "side": "red", - "units": [ - - AirDefence.AAA_ZU_23_Insurgent_Closed, - AirDefence.AAA_ZU_23_Insurgent_on_Ural_375, - - DIM__TOYOTA_BLUE, - DIM__TOYOTA_DESERT, - DIM__TOYOTA_GREEN, - DIM__KAMIKAZE, - Armor.ARV_BRDM_2, - Armor.APC_Cobra, - - Unarmed.Transport_Ural_375, - Unarmed.Transport_UAZ_469, - Infantry.Soldier_AK, - Infantry.Infantry_Soldier_Insurgents, - - Bulk_cargo_ship_Yakushev, - Dry_cargo_ship_Ivanov, - Tanker_Elnya_160 - ], "requirements": { - "frenchpack V3.5": "https://forums.eagle.ru/showthread.php?t=279974", - } -} \ No newline at end of file diff --git a/game/factions/iran_2015.py b/game/factions/iran_2015.py deleted file mode 100644 index 53f20dd6..00000000 --- a/game/factions/iran_2015.py +++ /dev/null @@ -1,86 +0,0 @@ -from dcs.helicopters import ( - Mi_24V, - Mi_28N, -) -from dcs.planes import ( - A_50, - An_26B, - An_30M, - F_14B, - F_4E, - F_5E_3, - IL_76MD, - IL_78M, - MiG_21Bis, - MiG_29A, - Su_17M4, - Su_24M, - Su_25, - Yak_40, -) -from dcs.ships import ( - Bulk_cargo_ship_Yakushev, - CV_1143_5_Admiral_Kuznetsov, - Dry_cargo_ship_Ivanov, - Tanker_Elnya_160, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Infantry, - Unarmed, -) - -Iran_2015 = { - "country": "Iran", - "side": "red", - "units": [ - - MiG_29A, - F_4E, - F_14B, - F_5E_3, - - MiG_21Bis, - Su_24M, - Su_25, - Su_17M4, - - IL_76MD, - IL_78M, - An_26B, - An_30M, - Yak_40, - - A_50, - - Mi_28N, - Mi_24V, - - AirDefence.SAM_Hawk_PCP, - AirDefence.SAM_SA_2_LN_SM_90, - AirDefence.SAM_SA_6_Kub_LN_2P25, - AirDefence.HQ_7_Self_Propelled_LN, - AirDefence.SAM_SA_11_Buk_LN_9A310M1, - - Armor.APC_M113, - Armor.APC_BTR_80, - Armor.MBT_M60A3_Patton, - Armor.MBT_T_72B, - - Unarmed.Transport_Ural_375, - Unarmed.Transport_UAZ_469, - Infantry.Soldier_AK, - - CV_1143_5_Admiral_Kuznetsov, - Bulk_cargo_ship_Yakushev, - Dry_cargo_ship_Ivanov, - Tanker_Elnya_160 - ], - "shorad":[ - AirDefence.HQ_7_Self_Propelled_LN, - AirDefence.AAA_ZU_23_Insurgent_Closed - ], "boat":[ - "GrishaGroupGenerator", "MolniyaGroupGenerator", "KiloSubGroupGenerator" - ] -} \ No newline at end of file diff --git a/game/factions/israel_1948.py b/game/factions/israel_1948.py deleted file mode 100644 index bc3b615c..00000000 --- a/game/factions/israel_1948.py +++ /dev/null @@ -1,47 +0,0 @@ -from dcs.planes import ( - B_17G, - Bf_109K_4, - P_51D, - P_51D_30_NA, - SpitfireLFMkIX, - SpitfireLFMkIXCW, -) -from dcs.ships import ( - Armed_speedboat, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Infantry, - Unarmed, -) - -Israel_1948 = { - "country": "Israel", - "side": "blue", - "units":[ - SpitfireLFMkIXCW, - SpitfireLFMkIX, - P_51D, - P_51D_30_NA, - Bf_109K_4, # Standing as Avia S-199 - B_17G, - - Armor.MT_M4A4_Sherman_Firefly, - Armor.APC_M2A1, - Armor.MT_M4_Sherman, - Armor.LAC_M8_Greyhound, - - Unarmed.Transport_M818, - Infantry.Infantry_SMLE_No_4_Mk_1, - - AirDefence.AAA_Bofors_40mm, - Armed_speedboat, - ],"requirements":{ - "WW2 Asset Pack": "https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/", - }, - "shorad": [ - AirDefence.AAA_Bofors_40mm - ], "boat": [ - ], "has_jtac": False -} \ No newline at end of file diff --git a/game/factions/israel_1973.py b/game/factions/israel_1973.py deleted file mode 100644 index 3bfa5d15..00000000 --- a/game/factions/israel_1973.py +++ /dev/null @@ -1,131 +0,0 @@ -from dcs.helicopters import ( - AH_1W, - UH_1H, -) -from dcs.planes import ( - C_130, - E_3A, - F_15C, - F_16A, - F_16C_50, - F_4E, - KC130, - KC_135, -) -from dcs.ships import ( - Armed_speedboat, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Infantry, - Unarmed, -) - -from pydcs_extensions.a4ec.a4ec import A_4E_C - -Israel_1973 = { - "country": "Israel", - "side": "blue", - "units":[ - F_4E, - A_4E_C, - - KC_135, - KC130, - C_130, - E_3A, - - UH_1H, - - Armor.MT_M4A4_Sherman_Firefly, - Armor.APC_M2A1, - Armor.MBT_M60A3_Patton, - Armor.APC_M113, - - Unarmed.Transport_M818, - Infantry.Infantry_M4, - - AirDefence.SAM_Hawk_PCP, - AirDefence.AAA_Bofors_40mm, - AirDefence.SAM_Chaparral_M48, - - Armed_speedboat, - ], "requirements": { - "Community A-4E": "https://heclak.github.io/community-a4e-c/", - "WW2 Asset Pack": "https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/", - }, "shorad": [ - AirDefence.SAM_Chaparral_M48, - AirDefence.AAA_Bofors_40mm - ], "boat": [ - ], "has_jtac": True -} - -Israel_1973_NO_WW2_UNITS = { - "country": "Israel", - "side": "blue", - "units":[ - F_4E, - A_4E_C, - - KC_135, - KC130, - C_130, - E_3A, - - UH_1H, - - Armor.MBT_M60A3_Patton, - Armor.APC_M113, - - Unarmed.Transport_M818, - Infantry.Infantry_M4, - - AirDefence.SAM_Hawk_PCP, - AirDefence.SAM_Chaparral_M48, - - Armed_speedboat, - ], "requirements": { - "Community A-4E": "https://heclak.github.io/community-a4e-c/", - }, "shorad": [ - AirDefence.SAM_Chaparral_M48, - ], "boat": [ - ], "has_jtac": True -} - -Israel_1982 = { - "country": "Israel", - "side": "blue", - "units":[ - F_4E, - A_4E_C, - F_15C, - F_16A, - F_16C_50, - - KC_135, - KC130, - C_130, - E_3A, - - UH_1H, - AH_1W, - - Armor.APC_M113, - Armor.MBT_M60A3_Patton, - Armor.MBT_Merkava_Mk__4, - - Unarmed.Transport_M818, - Infantry.Infantry_M4, - - AirDefence.SAM_Hawk_PCP, - AirDefence.SAM_Chaparral_M48, - - Armed_speedboat, - ], "requirements": { - "Community A-4E": "https://heclak.github.io/community-a4e-c/", - }, "shorad": [ - AirDefence.SAM_Chaparral_M48, - ], "boat": [ - ], "has_jtac": True -} \ No newline at end of file diff --git a/game/factions/israel_2000.py b/game/factions/israel_2000.py deleted file mode 100644 index d87460f9..00000000 --- a/game/factions/israel_2000.py +++ /dev/null @@ -1,66 +0,0 @@ -from dcs.helicopters import ( - AH_1W, - AH_64D, -) -from dcs.planes import ( - C_130, - E_3A, - F_15C, - F_15E, - F_16C_50, - F_4E, - KC130, - KC_135, -) -from dcs.ships import ( - Armed_speedboat, - CVN_74_John_C__Stennis, - LHA_1_Tarawa, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Artillery, - Infantry, - Unarmed, -) - -Israel_2000 = { - "country": "Israel", - "side": "blue", - "units":[ - F_16C_50, - F_15C, - F_15E, - F_4E, - - KC_135, - KC130, - C_130, - E_3A, - - AH_1W, - AH_64D, - - Armor.MBT_Merkava_Mk__4, - Armor.APC_M113, - Armor.APC_M1043_HMMWV_Armament, - Armor.ATGM_M1045_HMMWV_TOW, - Artillery.SPH_M109_Paladin, - Artillery.MLRS_M270, - - Unarmed.Transport_M818, - Infantry.Infantry_M4, - - AirDefence.SAM_Patriot_EPP_III, - - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Armed_speedboat, - ], - "shorad": [ - AirDefence.SAM_Avenger_M1097 - ], "boat": [ - "ArleighBurkeGroupGenerator" - ], "has_jtac": True -} \ No newline at end of file diff --git a/game/factions/italy_1990.py b/game/factions/italy_1990.py deleted file mode 100644 index 267cd611..00000000 --- a/game/factions/italy_1990.py +++ /dev/null @@ -1,69 +0,0 @@ -from dcs.helicopters import ( - AH_1W, - UH_1H, -) -from dcs.planes import ( - AV8BNA, - C_130, - E_3A, - KC_135, - S_3B_Tanker, - Tornado_IDS, -) -from dcs.ships import ( - Armed_speedboat, - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Oliver_Hazzard_Perry_class, - Ticonderoga_class, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Infantry, - Unarmed, -) - -Italy_1990 = { - "country": "Italy", - "side": "blue", - "units": [ - Tornado_IDS, - AV8BNA, - # MB339, - - KC_135, - S_3B_Tanker, - C_130, - E_3A, - - AH_1W, - UH_1H, - - Armor.MBT_Leopard_1A3, # OF-40 MBT - Armor.APC_M113, - - Unarmed.Transport_M818, - Infantry.Infantry_M4, - - AirDefence.SAM_Hawk_PCP, - AirDefence.SAM_Avenger_M1097, - - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Armed_speedboat, - ], "shorad":[ - AirDefence.SAM_Avenger_M1097, - ], "helicopter_carrier": [ - LHA_1_Tarawa, - ], "destroyer": [ - Oliver_Hazzard_Perry_class, - ], "cruiser": [ - Ticonderoga_class, - ], "lhanames": [ - "Giuseppe Garibaldi", - "Cavour", - ], "boat":[ - "OliverHazardPerryGroupGenerator" - ], "has_jtac": True -} diff --git a/game/factions/italy_1990_mb339.py b/game/factions/italy_1990_mb339.py deleted file mode 100644 index 9d594817..00000000 --- a/game/factions/italy_1990_mb339.py +++ /dev/null @@ -1,73 +0,0 @@ -from dcs.helicopters import ( - AH_1W, - UH_1H, -) -from dcs.planes import ( - AV8BNA, - C_130, - E_3A, - KC_135, - S_3B_Tanker, - Tornado_IDS, -) -from dcs.ships import ( - Armed_speedboat, - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Oliver_Hazzard_Perry_class, - Ticonderoga_class, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Infantry, - Unarmed, -) - -from pydcs_extensions.mb339.mb339 import MB_339PAN - -Italy_1990_MB339 = { - "country": "Italy", - "side": "blue", - "units": [ - Tornado_IDS, - AV8BNA, - MB_339PAN, - - KC_135, - S_3B_Tanker, - C_130, - E_3A, - - AH_1W, - UH_1H, - - Armor.MBT_Leopard_1A3, # OF-40 MBT - Armor.APC_M113, - - Unarmed.Transport_M818, - Infantry.Infantry_M4, - - AirDefence.SAM_Hawk_PCP, - AirDefence.SAM_Avenger_M1097, - - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Armed_speedboat, - ], "shorad":[ - AirDefence.SAM_Avenger_M1097, - ], "helicopter_carrier": [ - LHA_1_Tarawa, - ], "destroyer": [ - Oliver_Hazzard_Perry_class, - ], "cruiser": [ - Ticonderoga_class, - ], "lhanames": [ - "Giuseppe Garibaldi", - "Cavour", - ], "boat": [ - "OliverHazardPerryGroupGenerator" - ], "requirements": { - "MB-339A": "http://www.freccetricolorivirtuali.net/", - }, "has_jtac": True -} diff --git a/game/factions/japan_2005.py b/game/factions/japan_2005.py deleted file mode 100644 index d33800ff..00000000 --- a/game/factions/japan_2005.py +++ /dev/null @@ -1,71 +0,0 @@ -from dcs.helicopters import ( - AH_1W, - AH_64D, -) -from dcs.planes import ( - C_130, - E_3A, - F_15C, - F_16C_50, - F_4E, - KC130, - KC_135, -) -from dcs.ships import LHA_1_Tarawa, Ticonderoga_class, USS_Arleigh_Burke_IIa -from dcs.vehicles import ( - AirDefence, - Armor, - Artillery, - Infantry, - Unarmed, -) - -Japan_2005 = { - "country": "Japan", - "side": "blue", - "units": [ - F_15C, # F-15J/DJ - F_16C_50, # F-2A/B - F_4E, # F-4EJ - - KC_135, - KC130, - C_130, - E_3A, - - AH_1W, - AH_64D, - - Armor.MBT_Merkava_Mk__4, # Standing as Type 10 MBT - Armor.MBT_M1A2_Abrams, # Standing as Type 90 MBT - Armor.IFV_Marder, # Standing as Type 89 IFV - Armor.TPz_Fuchs, # Standing as Type 96 APC - Armor.IFV_LAV_25, # Standing as Type 16 or Type 87 - Armor.APC_M1043_HMMWV_Armament, - - Artillery.MLRS_M270, - Artillery.SPH_M109_Paladin, # Standing as Type 99 SPH - - Unarmed.Transport_M818, - Infantry.Infantry_M4, - Infantry.Soldier_M249, - - AirDefence.SAM_Hawk_PCP, - AirDefence.SAM_Patriot_EPP_III, - - LHA_1_Tarawa, - ], "shorad": [ - AirDefence.SPAAA_Gepard, # Type 87 SPAG - ], "helicopter_carrier": [ - LHA_1_Tarawa, # Standing as Hyuga-class helicopter carrier - ], "destroyer": [ - USS_Arleigh_Burke_IIa, - ], "cruiser": [ - Ticonderoga_class, - ], "lhanames": [ - "Hyuga", - "Ise", - ], "boat":[ - "ArleighBurkeGroupGenerator" - ], "has_jtac": True -} diff --git a/game/factions/libya_2011.py b/game/factions/libya_2011.py deleted file mode 100644 index 4de5b42c..00000000 --- a/game/factions/libya_2011.py +++ /dev/null @@ -1,68 +0,0 @@ -from dcs.helicopters import ( - Mi_24V, -) -from dcs.planes import ( - A_50, - An_26B, - An_30M, - IL_76MD, - IL_78M, - MiG_21Bis, - MiG_23MLD, - Su_17M4, - Su_24M, - Yak_40, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Artillery, - Infantry, - Unarmed, -) - -Libya_2011 = { - "country": "Libya", - "side": "red", - "units": [ - - MiG_21Bis, - MiG_23MLD, - Su_24M, - Su_17M4, - Mi_24V, - - IL_76MD, - IL_78M, - An_26B, - An_30M, - Yak_40, - A_50, - - AirDefence.SAM_SA_8_Osa_9A33, - AirDefence.SAM_SA_2_LN_SM_90, - AirDefence.SAM_SA_3_S_125_LN_5P73, - AirDefence.SAM_SA_6_Kub_LN_2P25, - AirDefence.HQ_7_Self_Propelled_LN, - - Armor.IFV_BMP_1, - Armor.FDDM_Grad, - Armor.ARV_BRDM_2, - Armor.MBT_T_55, - Armor.MBT_T_72B, - Artillery.MLRS_BM_21_Grad, - - Unarmed.Transport_Ural_375, - Unarmed.Transport_UAZ_469, - - Infantry.Paratrooper_RPG_16, - Infantry.Infantry_Soldier_Insurgents - - ], - "shorad":[ - AirDefence.HQ_7_Self_Propelled_LN, - AirDefence.SAM_SA_8_Osa_9A33, - ], "boat": [ - "GrishaGroupGenerator", "MolniyaGroupGenerator" - ] -} \ No newline at end of file diff --git a/game/factions/netherlands_1990.py b/game/factions/netherlands_1990.py deleted file mode 100644 index 48c916bd..00000000 --- a/game/factions/netherlands_1990.py +++ /dev/null @@ -1,56 +0,0 @@ -from dcs.helicopters import ( - AH_64A, -) -from dcs.planes import ( - C_130, - E_3A, - F_16C_50, - F_5E_3, - KC130, - KC_135, -) -from dcs.ships import ( - Armed_speedboat, - CVN_74_John_C__Stennis, - LHA_1_Tarawa, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Infantry, - Unarmed, -) - -Netherlands_1990 = { - "country": "The Netherlands", - "side": "blue", - "units": [ - F_16C_50, - F_5E_3, - - KC_135, - KC130, - C_130, - E_3A, - - AH_64A, - - Armor.APC_M113, - Armor.MBT_Leopard_1A3, - - Unarmed.Transport_M818, - Infantry.Infantry_M4, - - AirDefence.SAM_Hawk_PCP, - AirDefence.SAM_Avenger_M1097, - - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Armed_speedboat, - ], - "shorad": [ - AirDefence.SAM_Avenger_M1097 - ], "boat": [ - "OliverHazardPerryGroupGenerator" - ], "has_jtac": True -} diff --git a/game/factions/north_korea_2000.py b/game/factions/north_korea_2000.py deleted file mode 100644 index 15d1f587..00000000 --- a/game/factions/north_korea_2000.py +++ /dev/null @@ -1,79 +0,0 @@ -from dcs.helicopters import ( - Mi_24V, - Mi_8MT, -) -from dcs.planes import ( - A_50, - An_26B, - An_30M, - IL_76MD, - IL_78M, - MiG_15bis, - MiG_19P, - MiG_21Bis, - MiG_23MLD, - MiG_29A, - Su_25, - Yak_40, -) -from dcs.ships import ( - Bulk_cargo_ship_Yakushev, - CV_1143_5_Admiral_Kuznetsov, - Dry_cargo_ship_Ivanov, - Tanker_Elnya_160, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Infantry, - Unarmed, -) - -NorthKorea_2000 = { - "country": "North Korea", - "side": "red", - "units":[ - MiG_29A, - Su_25, - MiG_15bis, - MiG_21Bis, - MiG_23MLD, - MiG_19P, - - IL_76MD, - IL_78M, - An_26B, - An_30M, - Yak_40, - - A_50, - - Mi_8MT, - Mi_24V, - - Armor.MBT_T_55, - Armor.MBT_T_72B, - Armor.MBT_T_80U, - Armor.IFV_BMP_1, - Armor.APC_BTR_80, - Armor.ARV_BRDM_2, - - Unarmed.Transport_M818, - Infantry.Soldier_AK, - - AirDefence.SAM_SA_2_LN_SM_90, - AirDefence.SAM_SA_3_S_125_LN_5P73, - - CV_1143_5_Admiral_Kuznetsov, - Bulk_cargo_ship_Yakushev, - Dry_cargo_ship_Ivanov, - Tanker_Elnya_160 - ], - "shorad":[ - AirDefence.AAA_ZU_23_Emplacement, - AirDefence.SPAAA_ZSU_23_4_Shilka - ], - "boat": [ - "GrishaGroupGenerator", "MolniyaGroupGenerator" - ] -} \ No newline at end of file diff --git a/game/factions/pakistan_2015.py b/game/factions/pakistan_2015.py deleted file mode 100644 index 8d7c6190..00000000 --- a/game/factions/pakistan_2015.py +++ /dev/null @@ -1,60 +0,0 @@ -from dcs.helicopters import ( - AH_1W, - UH_1H, -) -from dcs.planes import ( - E_3A, - F_16C_50, - IL_78M, - JF_17, - MiG_19P, - MiG_21Bis, - WingLoong_I, -) -from dcs.ships import ( - Armed_speedboat, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Infantry, - Unarmed, -) - -Pakistan_2015 = { - "country": "Pakistan", - "side": "blue", - "units": [ - JF_17, - F_16C_50, - MiG_21Bis, # Standing as J-7 - MiG_19P, # Standing as J-6 - IL_78M, - E_3A, - - UH_1H, - AH_1W, - - Armor.MBT_T_80U, - Armor.MBT_T_55, # Standing as Al-Zarrar / Type 59 MBT - Armor.ZBD_04A, - Armor.APC_BTR_80, - Armor.APC_M113, - - Unarmed.Transport_M818, - Infantry.Infantry_M4, - - AirDefence.SAM_SA_2_LN_SM_90, # Standing as HQ-2 - AirDefence.SAM_SA_10_S_300PS_LN_5P85C, # Standing as HQ-9 - - Armed_speedboat, - ], "shorad": [ - AirDefence.HQ_7_Self_Propelled_LN, - AirDefence.AAA_ZU_23_Insurgent_on_Ural_375, - AirDefence.AAA_ZU_23_Closed - ], "boat": [ - "Type54GroupGenerator", "OliverHazardPerryGroupGenerator" - ], - "has_jtac": True, - "jtac_unit": WingLoong_I -} diff --git a/game/factions/private_miltary_companies.py b/game/factions/private_miltary_companies.py deleted file mode 100644 index 23fbcbdd..00000000 --- a/game/factions/private_miltary_companies.py +++ /dev/null @@ -1,109 +0,0 @@ -from dcs.helicopters import ( - Ka_50, - Mi_24V, - Mi_8MT, - OH_58D, - SA342M, - UH_1H, -) -from dcs.planes import ( - C_101CC, - L_39C, - L_39ZA, -) -from dcs.ships import ( - Armed_speedboat, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Infantry, - Unarmed, -) - -from pydcs_extensions.mb339.mb339 import MB_339PAN - -PMC_WESTERN_A = { - "country": "USA", - "side": "blue", - "units": [ - C_101CC, - - UH_1H, - Mi_8MT, - OH_58D, - SA342M, - - Armor.APC_M1043_HMMWV_Armament, - Armor.IFV_MCV_80, - - Unarmed.Transport_M818, - Infantry.Infantry_M4, - Infantry.Soldier_M249, - - AirDefence.SAM_Avenger_M1097, - - Armed_speedboat, - ], "shorad":[ - AirDefence.SAM_Avenger_M1097, - ], "has_jtac": True -} - -PMC_WESTERN_B = { - "country": "USA", - "side": "blue", - "units": [ - MB_339PAN, - C_101CC, - - UH_1H, - Mi_8MT, - OH_58D, - SA342M, - - Armor.APC_M1043_HMMWV_Armament, - Armor.IFV_MCV_80, - - Unarmed.Transport_M818, - Infantry.Infantry_M4, - Infantry.Soldier_M249, - - AirDefence.SAM_Avenger_M1097, - - Armed_speedboat, - ], "shorad":[ - AirDefence.SAM_Avenger_M1097, - ], "has_jtac": True, - "requirements": { - "MB-339A": "http://www.freccetricolorivirtuali.net/", - } -} - -PMC_RUSSIAN = { - "country": "Russia", - "side": "blue", - "units": [ - L_39C, - L_39ZA, - - Mi_8MT, - Mi_24V, - Ka_50, - - Armor.APC_Cobra, - Armor.APC_BTR_80, - Armor.ARV_BRDM_2, - - Unarmed.Transport_Ural_375, - Infantry.Paratrooper_AKS, - Infantry.Paratrooper_RPG_16, - - AirDefence.AAA_ZU_23_on_Ural_375, - - Armed_speedboat, - ], "shorad":[ - AirDefence.AAA_ZU_23_on_Ural_375, - AirDefence.AAA_ZU_23_Closed, - ], "has_jtac": True -} - diff --git a/game/factions/russia_1955.py b/game/factions/russia_1955.py deleted file mode 100644 index 98624ad5..00000000 --- a/game/factions/russia_1955.py +++ /dev/null @@ -1,53 +0,0 @@ -from dcs.planes import ( - An_26B, - An_30M, - IL_76MD, - IL_78M, - MiG_15bis, - Tu_95MS, - Yak_40, -) -from dcs.ships import ( - Bulk_cargo_ship_Yakushev, - CV_1143_5_Admiral_Kuznetsov, - Dry_cargo_ship_Ivanov, - Tanker_Elnya_160, -) -from dcs.vehicles import AirDefence, Armor, Artillery, Infantry, Unarmed - -Russia_1955 = { - "country": "Russia", - "side": "red", - "units": [ - MiG_15bis, - - IL_76MD, - IL_78M, - An_26B, - An_30M, - Yak_40, - - Tu_95MS, - - AirDefence.AAA_ZU_23_Closed, - AirDefence.AAA_ZU_23_on_Ural_375, - Armor.ARV_BRDM_2, - Armor.FDDM_Grad, - Armor.APC_MTLB, - Armor.MBT_T_55, - Artillery.MLRS_BM_21_Grad, - - Unarmed.Transport_Ural_375, - Unarmed.Transport_UAZ_469, - - CV_1143_5_Admiral_Kuznetsov, - Bulk_cargo_ship_Yakushev, - Dry_cargo_ship_Ivanov, - Tanker_Elnya_160, - - # Infantry squad - Infantry.Paratrooper_AKS, - Infantry.Infantry_Soldier_Rus, - Infantry.Paratrooper_RPG_16, - ] -} \ No newline at end of file diff --git a/game/factions/russia_1965.py b/game/factions/russia_1965.py deleted file mode 100644 index cb5d298a..00000000 --- a/game/factions/russia_1965.py +++ /dev/null @@ -1,73 +0,0 @@ -from dcs.helicopters import Mi_8MT -from dcs.planes import ( - A_50, - An_26B, - An_30M, - IL_76MD, - IL_78M, - MiG_15bis, - MiG_19P, - MiG_21Bis, - Tu_95MS, - Yak_40, -) -from dcs.ships import ( - Bulk_cargo_ship_Yakushev, - CV_1143_5_Admiral_Kuznetsov, - Dry_cargo_ship_Ivanov, - Tanker_Elnya_160, -) -from dcs.vehicles import AirDefence, Armor, Artillery, Infantry, Unarmed - -Russia_1965 = { - "country": "Russia", - "side": "red", - "units": [ - MiG_15bis, - MiG_19P, - MiG_21Bis, - - IL_76MD, - IL_78M, - An_26B, - An_30M, - Yak_40, - - Tu_95MS, - - A_50, - - Mi_8MT, - - AirDefence.SAM_SA_6_Kub_LN_2P25, - AirDefence.SAM_SA_2_LN_SM_90, - AirDefence.SAM_SA_3_S_125_LN_5P73, - - Armor.ARV_BRDM_2, - Armor.APC_BTR_80, - Armor.ARV_BTR_RD, - Armor.IFV_BMD_1, - Armor.IFV_BMP_1, - Armor.MBT_T_55, - Artillery.MLRS_BM_21_Grad, - - Unarmed.Transport_Ural_375, - Unarmed.Transport_UAZ_469, - - CV_1143_5_Admiral_Kuznetsov, - Bulk_cargo_ship_Yakushev, - Dry_cargo_ship_Ivanov, - Tanker_Elnya_160, - - # Infantry squad - Infantry.Paratrooper_AKS, - Infantry.Infantry_Soldier_Rus, - Infantry.Paratrooper_RPG_16, - - ], - "shorad":[ - AirDefence.AAA_ZU_23_Closed - ], "boat": [ - "GrishaGroupGenerator" - ] -} \ No newline at end of file diff --git a/game/factions/russia_1975.py b/game/factions/russia_1975.py deleted file mode 100644 index b12d28d4..00000000 --- a/game/factions/russia_1975.py +++ /dev/null @@ -1,99 +0,0 @@ -from dcs.helicopters import ( - Mi_24V, - Mi_8MT, -) -from dcs.planes import ( - A_50, - An_26B, - An_30M, - IL_76MD, - IL_78M, - MiG_21Bis, - MiG_23MLD, - MiG_25PD, - MiG_29A, - Su_17M4, - Su_24M, - Su_25, - Tu_22M3, - Tu_95MS, - Yak_40, -) -from dcs.ships import ( - Bulk_cargo_ship_Yakushev, - CGN_1144_2_Pyotr_Velikiy, - CV_1143_5_Admiral_Kuznetsov, - Dry_cargo_ship_Ivanov, - FF_1135M_Rezky, - Tanker_Elnya_160, -) -from dcs.vehicles import AirDefence, Armor, Artillery, Infantry, Unarmed - -Russia_1975 = { - "country": "Russia", - "side": "red", - "units": [ - - MiG_21Bis, - MiG_23MLD, - MiG_25PD, - MiG_29A, - - Su_17M4, - Su_24M, - Su_25, - - Tu_22M3, - Tu_95MS, - - IL_76MD, - IL_78M, - An_26B, - An_30M, - Yak_40, - - A_50, - - Mi_8MT, - Mi_24V, - - AirDefence.AAA_ZU_23_Closed, - AirDefence.SAM_SA_6_Kub_LN_2P25, - AirDefence.SAM_SA_3_S_125_LN_5P73, - - Armor.ARV_BRDM_2, - Armor.APC_BTR_80, - Armor.IFV_BMD_1, - Armor.IFV_BMP_1, - Armor.MBT_T_55, - - Artillery.SPH_2S9_Nona, - Artillery.SPH_2S1_Gvozdika, - - Unarmed.Transport_Ural_375, - Unarmed.Transport_UAZ_469, - - CV_1143_5_Admiral_Kuznetsov, - Bulk_cargo_ship_Yakushev, - Dry_cargo_ship_Ivanov, - Tanker_Elnya_160, - - # Infantry squad - Infantry.Paratrooper_AKS, - Infantry.Infantry_Soldier_Rus, - Infantry.Paratrooper_RPG_16, - - ], - "shorad": [ - AirDefence.AAA_ZU_23_Emplacement, - AirDefence.SPAAA_ZSU_23_4_Shilka - ], "aircraft_carrier": [ - CV_1143_5_Admiral_Kuznetsov, - ], "destroyer": [ - FF_1135M_Rezky, - ], "cruiser": [ - CGN_1144_2_Pyotr_Velikiy, - ], "boat": [ - "RussianNavyGroupGenerator", "KiloSubGroupGenerator", "MolniyaGroupGenerator" - ] -} \ No newline at end of file diff --git a/game/factions/russia_1990.py b/game/factions/russia_1990.py deleted file mode 100644 index 71e4c494..00000000 --- a/game/factions/russia_1990.py +++ /dev/null @@ -1,113 +0,0 @@ -from dcs.helicopters import ( - Ka_50, - Mi_24V, - Mi_8MT, -) -from dcs.planes import ( - A_50, - An_26B, - An_30M, - IL_76MD, - IL_78M, - MiG_23MLD, - MiG_25PD, - MiG_29A, - MiG_29S, - MiG_31, - Su_24M, - Su_25, - Su_27, - Tu_160, - Tu_22M3, - Tu_95MS, - Yak_40, -) -from dcs.ships import ( - Bulk_cargo_ship_Yakushev, - CV_1143_5_Admiral_Kuznetsov, - Dry_cargo_ship_Ivanov, - FF_1135M_Rezky, - FSG_1241_1MP_Molniya, - Tanker_Elnya_160, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Artillery, - Infantry, - Unarmed, -) - -Russia_1990 = { - "country": "Russia", - "side": "red", - "units": [ - - MiG_23MLD, - MiG_25PD, - MiG_29A, - MiG_29S, - MiG_31, - Su_27, - - Su_24M, - Su_25, - Ka_50, - - Tu_160, - Tu_22M3, - Tu_95MS, - - IL_76MD, - IL_78M, - An_26B, - An_30M, - Yak_40, - - A_50, - - Mi_8MT, - Mi_24V, - - AirDefence.AAA_ZU_23_Closed, - AirDefence.SAM_SA_6_Kub_LN_2P25, - AirDefence.SAM_SA_3_S_125_LN_5P73, - - Armor.ARV_BRDM_2, - Armor.APC_BTR_80, - Armor.IFV_BMD_1, - Armor.IFV_BMP_1, - Armor.MBT_T_55, - Artillery.MLRS_9K57_Uragan_BM_27, - Artillery.SPH_2S19_Msta, - - Unarmed.Transport_Ural_375, - Unarmed.Transport_UAZ_469, - - CV_1143_5_Admiral_Kuznetsov, - Bulk_cargo_ship_Yakushev, - Dry_cargo_ship_Ivanov, - Tanker_Elnya_160, - - # Infantry squad - Infantry.Paratrooper_AKS, - Infantry.Infantry_Soldier_Rus, - Infantry.Paratrooper_RPG_16, - ], - "shorad":[ - AirDefence.SAM_SA_9_Strela_1_9P31, - AirDefence.SAM_SA_13_Strela_10M3_9A35M3, - AirDefence.SPAAA_ZSU_23_4_Shilka - ], "carrier_names": [ - "Admiral Kuznetov", - "Admiral Gorshkov" - ], "aircraft_carrier": [ - CV_1143_5_Admiral_Kuznetsov, - ], "destroyer": [ - FF_1135M_Rezky, - ], "cruiser": [ - FSG_1241_1MP_Molniya, - ], "boat":[ - "RussianNavyGroupGenerator", "KiloSubGroupGenerator" - ] -} \ No newline at end of file diff --git a/game/factions/russia_2010.py b/game/factions/russia_2010.py deleted file mode 100644 index 852871f6..00000000 --- a/game/factions/russia_2010.py +++ /dev/null @@ -1,119 +0,0 @@ -from dcs.helicopters import ( - Ka_50, - Mi_24V, - Mi_28N, - Mi_8MT, -) -from dcs.planes import ( - A_50, - An_26B, - An_30M, - IL_76MD, - IL_78M, - L_39ZA, - MiG_29S, - MiG_31, - Su_24M, - Su_25, - Su_25T, - Su_27, - Su_30, - Su_33, - Su_34, - Tu_160, - Tu_22M3, - Tu_95MS, - Yak_40, -) -from dcs.ships import ( - Bulk_cargo_ship_Yakushev, - CV_1143_5_Admiral_Kuznetsov, - Dry_cargo_ship_Ivanov, - FF_1135M_Rezky, - FSG_1241_1MP_Molniya, - Tanker_Elnya_160, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Artillery, - Infantry, - Unarmed, -) - -Russia_2010 = { - "country": "Russia", - "side": "red", - "units": [ - - Su_27, - Su_30, - Su_33, - MiG_29S, - MiG_31, - - Su_25, - Su_25T, - Su_34, - Su_24M, - L_39ZA, - - Tu_160, - Tu_22M3, - Tu_95MS, - - IL_76MD, - IL_78M, - An_26B, - An_30M, - Yak_40, - A_50, - - Ka_50, - Mi_8MT, - Mi_24V, - Mi_28N, - - AirDefence.SAM_SA_19_Tunguska_2S6, - AirDefence.SAM_SA_11_Buk_LN_9A310M1, - AirDefence.SAM_SA_10_S_300PS_LN_5P85C, - - Armor.APC_BTR_80, - Armor.MBT_T_90, - Armor.MBT_T_80U, - Armor.MBT_T_72B, - Armor.IFV_BMP_1, - Armor.IFV_BMP_2, - Armor.IFV_BMP_3, - - Artillery.MLRS_9K57_Uragan_BM_27, - Artillery.SPH_2S19_Msta, - - Unarmed.Transport_Ural_375, - Unarmed.Transport_UAZ_469, - - CV_1143_5_Admiral_Kuznetsov, - Bulk_cargo_ship_Yakushev, - Dry_cargo_ship_Ivanov, - Tanker_Elnya_160, - - # Infantry squad - Infantry.Paratrooper_AKS, - Infantry.Infantry_Soldier_Rus, - Infantry.Paratrooper_RPG_16, - ], - "shorad":[ - AirDefence.SAM_SA_19_Tunguska_2S6, - AirDefence.SAM_SA_13_Strela_10M3_9A35M3 - ], "aircraft_carrier": [ - CV_1143_5_Admiral_Kuznetsov, - ], "carrier_names": [ - "Admiral Kuznetov" - ], "destroyer": [ - FF_1135M_Rezky, - ], "cruiser": [ - FSG_1241_1MP_Molniya, - ], "boat": [ - "RussianNavyGroupGenerator", "KiloSubGroupGenerator" - ] -} diff --git a/game/factions/russia_2020.py b/game/factions/russia_2020.py deleted file mode 100644 index 5df17da7..00000000 --- a/game/factions/russia_2020.py +++ /dev/null @@ -1,124 +0,0 @@ -from dcs.helicopters import ( - Ka_50, - Mi_24V, - Mi_28N, - Mi_8MT, -) -from dcs.planes import ( - A_50, - An_26B, - An_30M, - IL_76MD, - IL_78M, - L_39ZA, - MiG_29S, - MiG_31, - Su_24M, - Su_25, - Su_25T, - Su_27, - Su_30, - Su_33, - Su_34, - Tu_160, - Tu_22M3, - Tu_95MS, - Yak_40, -) -from dcs.ships import ( - Bulk_cargo_ship_Yakushev, - CV_1143_5_Admiral_Kuznetsov, - Dry_cargo_ship_Ivanov, - FF_1135M_Rezky, - FSG_1241_1MP_Molniya, - Tanker_Elnya_160, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Artillery, - Infantry, - Unarmed, -) - -from pydcs_extensions.su57.su57 import Su_57 - -Russia_2020 = { - "country": "Russia", - "side": "red", - "units": [ - - Su_27, - Su_30, - Su_33, - MiG_29S, - MiG_31, - Su_57, - - Su_25, - Su_25T, - Su_34, - Su_24M, - L_39ZA, - - Tu_160, - Tu_22M3, - Tu_95MS, - - IL_76MD, - IL_78M, - An_26B, - An_30M, - Yak_40, - A_50, - - Ka_50, - Mi_8MT, - Mi_24V, - Mi_28N, - - AirDefence.SAM_SA_19_Tunguska_2S6, - AirDefence.SAM_SA_11_Buk_LN_9A310M1, - AirDefence.SAM_SA_10_S_300PS_LN_5P85C, - - Armor.APC_BTR_80, - Armor.MBT_T_90, - Armor.MBT_T_80U, - Armor.MBT_T_72B, - Armor.IFV_BMP_1, - Armor.IFV_BMP_2, - Armor.IFV_BMP_3, - - Artillery.MLRS_9K57_Uragan_BM_27, - Artillery.SPH_2S19_Msta, - - Unarmed.Transport_Ural_375, - Unarmed.Transport_UAZ_469, - - CV_1143_5_Admiral_Kuznetsov, - Bulk_cargo_ship_Yakushev, - Dry_cargo_ship_Ivanov, - Tanker_Elnya_160, - - # Infantry squad - Infantry.Paratrooper_AKS, - Infantry.Infantry_Soldier_Rus, - Infantry.Paratrooper_RPG_16, - ], - "shorad":[ - AirDefence.SAM_SA_19_Tunguska_2S6, - AirDefence.SAM_SA_13_Strela_10M3_9A35M3 - ], "aircraft_carrier": [ - CV_1143_5_Admiral_Kuznetsov, - ], "carrier_names": [ - "Admiral Kuznetov" - ], "destroyer": [ - FF_1135M_Rezky, - ], "cruiser": [ - FSG_1241_1MP_Molniya, - ], "boat": [ - "RussianNavyGroupGenerator", "KiloSubGroupGenerator" - ], "requirements": { - "SU-57 Felon By CubanAce Simulations": "https://www.digitalcombatsimulator.com/fr/files/2539621/" - } -} diff --git a/game/factions/spain_1990.py b/game/factions/spain_1990.py deleted file mode 100644 index 246484a4..00000000 --- a/game/factions/spain_1990.py +++ /dev/null @@ -1,70 +0,0 @@ -from dcs.planes import ( - AV8BNA, - C_101CC, - C_130, - E_3A, - FA_18C_hornet, - F_5E_3, - KC130, - KC_135, -) -from dcs.ships import ( - Armed_speedboat, - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Oliver_Hazzard_Perry_class, - Ticonderoga_class, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Infantry, - Unarmed, -) - -Spain_1990 = { - "country": "Spain", - "side": "blue", - "units": [ - FA_18C_hornet, - AV8BNA, - F_5E_3, - C_101CC, - - KC_135, - KC130, - C_130, - E_3A, - - Armor.MBT_M60A3_Patton, - Armor.MBT_Leopard_2, - Armor.APC_M113, - - Unarmed.Transport_M818, - Infantry.Infantry_M4, - Infantry.Soldier_M249, - - AirDefence.SAM_Hawk_PCP, - AirDefence.SAM_Avenger_M1097, - - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Armed_speedboat, - ], "shorad":[ - AirDefence.SAM_Avenger_M1097, - ], "aircraft_carrier": [ - CVN_74_John_C__Stennis, # Standing as Principe de Asturias - ], "helicopter_carrier": [ - LHA_1_Tarawa, # Standing as Juan Carlos - ], "destroyer": [ - Oliver_Hazzard_Perry_class, - ], "cruiser": [ - Ticonderoga_class, - ], "carrier_names": [ - "Principe de Asturias", - ], "lhanames": [ - "Juan Carlos I", - ], "boat":[ - "OliverHazardPerryGroupGenerator" - ], "has_jtac": True -} \ No newline at end of file diff --git a/game/factions/sweden_1990.py b/game/factions/sweden_1990.py deleted file mode 100644 index 058f1478..00000000 --- a/game/factions/sweden_1990.py +++ /dev/null @@ -1,45 +0,0 @@ -from dcs.helicopters import ( - UH_1H, -) -from dcs.planes import ( - AJS37, -) -from dcs.ships import ( - Bulk_cargo_ship_Yakushev, - CV_1143_5_Admiral_Kuznetsov, - Dry_cargo_ship_Ivanov, - Tanker_Elnya_160, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Infantry, - Unarmed, -) - -Sweden_1990 = { - "country": "Sweden", - "side": "blue", - "units": [ - AJS37, - - UH_1H, - - AirDefence.SAM_Hawk_PCP, - - Armor.IFV_MCV_80, # Standing as Strf 90 - Armor.MBT_Leopard_2, - Armor.APC_M1126_Stryker_ICV, # Closest thing available - - Unarmed.Transport_Ural_375, - Unarmed.Transport_UAZ_469, - Infantry.Soldier_AK, - CV_1143_5_Admiral_Kuznetsov, - Bulk_cargo_ship_Yakushev, - Dry_cargo_ship_Ivanov, - Tanker_Elnya_160, - ], - "shorad": [ - AirDefence.SAM_Avenger_M1097 - ], "has_jtac": True -} \ No newline at end of file diff --git a/game/factions/syria.py b/game/factions/syria.py deleted file mode 100644 index f7017e86..00000000 --- a/game/factions/syria.py +++ /dev/null @@ -1,296 +0,0 @@ -from dcs.helicopters import ( - Mi_24V, - Mi_8MT, - SA342L, - SA342M, -) -from dcs.planes import ( - A_50, - An_26B, - An_30M, - IL_76MD, - IL_78M, - L_39ZA, - MiG_15bis, - MiG_19P, - MiG_21Bis, - MiG_23MLD, - MiG_25PD, - MiG_29S, - SpitfireLFMkIX, - SpitfireLFMkIXCW, - Su_17M4, - Su_24M, - Yak_40, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Artillery, - Infantry, - Unarmed, -) - -Syria_2011 = { - "country": "Syria", - "side": "red", - "units": [ - - MiG_21Bis, - MiG_23MLD, - MiG_25PD, - MiG_29S, - - Su_17M4, - Su_24M, - - L_39ZA, - - Mi_24V, - Mi_8MT, - SA342M, - SA342L, - - IL_76MD, - IL_78M, - An_26B, - An_30M, - Yak_40, - A_50, - - AirDefence.SAM_SA_6_Kub_LN_2P25, - AirDefence.SAM_SA_3_S_125_LN_5P73, - AirDefence.SAM_SA_2_LN_SM_90, - AirDefence.SAM_SA_8_Osa_9A33, - AirDefence.SAM_SA_11_Buk_LN_9A310M1, - AirDefence.SAM_SA_10_S_300PS_LN_5P85C, - - Armor.IFV_BMP_1, - Armor.IFV_BMP_2, - Armor.APC_BTR_80, - Armor.ARV_BRDM_2, - Armor.APC_MTLB, - Armor.APC_Cobra, - Armor.MBT_T_55, - Armor.MBT_T_72B, - Armor.MBT_T_90, - Artillery.MLRS_BM_21_Grad, - Artillery.MLRS_9K57_Uragan_BM_27, - Artillery.SPH_2S1_Gvozdika, - Artillery.SPH_2S9_Nona, - - Unarmed.Transport_Ural_375, - Unarmed.Transport_UAZ_469, - - Infantry.Paratrooper_RPG_16, - Infantry.Soldier_AK - - ], - "shorad": [ - AirDefence.SAM_SA_8_Osa_9A33, - AirDefence.SAM_SA_13_Strela_10M3_9A35M3, - AirDefence.SAM_SA_9_Strela_1_9P31, - AirDefence.SAM_SA_19_Tunguska_2S6, - AirDefence.AAA_ZU_23_on_Ural_375, - ], "boat": [ - "GrishaGroupGenerator", "MolniyaGroupGenerator" - ] -} - -Syria_1973 = { - "country": "Syria", - "side": "red", - "units": [ - - MiG_21Bis, - MiG_19P, - MiG_15bis, # Standing as Mig-17 - - Su_17M4, # Standing as Su-7 - Mi_8MT, - - IL_76MD, - IL_78M, - An_26B, - An_30M, - Yak_40, - - AirDefence.SAM_SA_6_Kub_LN_2P25, - AirDefence.SAM_SA_3_S_125_LN_5P73, - AirDefence.SAM_SA_2_LN_SM_90, - - Armor.IFV_BMP_1, - Armor.APC_MTLB, - Armor.MBT_T_55, - Artillery.MLRS_BM_21_Grad, - - Unarmed.Transport_Ural_375, - Unarmed.Transport_UAZ_469, - - Infantry.Paratrooper_RPG_16, - Infantry.Soldier_AK - - ], - "shorad": [ - AirDefence.AAA_ZU_23_on_Ural_375, - ], "boat": [ - "GrishaGroupGenerator" - ] -} - - -Syria_1982 = { - "country": "Syria", - "side": "red", - "units": [ - - MiG_21Bis, - MiG_23MLD, - MiG_25PD, - MiG_19P, - - Su_17M4, # Standing as Su-7 - Mi_8MT, - - IL_76MD, - IL_78M, - An_26B, - An_30M, - Yak_40, - - AirDefence.SAM_SA_6_Kub_LN_2P25, - AirDefence.SAM_SA_3_S_125_LN_5P73, - AirDefence.SAM_SA_2_LN_SM_90, - - Armor.IFV_BMP_1, - Armor.APC_MTLB, - Armor.MBT_T_55, - Armor.MBT_T_72B, - Artillery.MLRS_BM_21_Grad, - - Unarmed.Transport_Ural_375, - Unarmed.Transport_UAZ_469, - - Infantry.Paratrooper_RPG_16, - Infantry.Soldier_AK - - ], - "shorad": [ - AirDefence.AAA_ZU_23_on_Ural_375, - ], "boat": [ - "GrishaGroupGenerator" - ] -} - - -Syria_1967 = { - "country": "Syria", - "side": "red", - "units": [ - - MiG_21Bis, - MiG_19P, - MiG_15bis, # Standing as Mig-17 - - Su_17M4, # Standing as Su-7 - Mi_8MT, - - IL_76MD, - IL_78M, - An_26B, - An_30M, - Yak_40, - - AirDefence.SAM_SA_2_LN_SM_90, - - Armor.ARV_BRDM_2, - Armor.MBT_T_55, - Artillery.MLRS_BM_21_Grad, - - Unarmed.Transport_Ural_375, - Unarmed.Transport_UAZ_469, - - Infantry.Paratrooper_RPG_16, - Infantry.Soldier_AK - - ], - "shorad": [ - AirDefence.AAA_ZU_23_on_Ural_375, - ], "boat": [ - "GrishaGroupGenerator" - ] -} - -Syria_1967_WW2_Weapons = { - "country": "Syria", - "side": "red", - "units": [ - - MiG_21Bis, - MiG_19P, - MiG_15bis, # Standing as Mig-17 - - Su_17M4, # Standing as Su-7 - Mi_8MT, - - IL_76MD, - IL_78M, - An_26B, - An_30M, - Yak_40, - - AirDefence.SAM_SA_2_LN_SM_90, - - Armor.ARV_BRDM_2, - Armor.MBT_T_55, - Armor.MT_Pz_Kpfw_IV_Ausf_H, - Armor.StuG_III_Ausf__G, - Armor.TD_Jagdpanzer_IV, - Artillery.MLRS_BM_21_Grad, - - Unarmed.Transport_Ural_375, - Unarmed.Transport_UAZ_469, - - Infantry.Soldier_RPG, - Infantry.Soldier_AK - - ], "requirements": { - "WW2 Asset Pack": "https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/", - }, - "shorad": [ - AirDefence.AAA_ZU_23_on_Ural_375, - ], "boat": [ - "GrishaGroupGenerator" - ] -} - -Arab_Armies_1948 = { - "country": "Syria", - "side": "red", - "units": [ - SpitfireLFMkIX, - SpitfireLFMkIXCW, - - AirDefence.SAM_SA_2_LN_SM_90, - - Armor.MT_M4_Sherman, - Armor.MT_Pz_Kpfw_IV_Ausf_H, - Armor.APC_Sd_Kfz_251, - Armor.IFV_Sd_Kfz_234_2_Puma, - - Unarmed.Transport_Ural_375, - Unarmed.Transport_UAZ_469, - - Infantry.Infantry_SMLE_No_4_Mk_1, - - AirDefence.AAA_8_8cm_Flak_36, - - ], "requirements": { - "WW2 Asset Pack": "https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/", - }, - "shorad": [ - AirDefence.AAA_8_8cm_Flak_36, - ], "boat": [ - "GrishaGroupGenerator" - ] -} diff --git a/game/factions/turkey_2005.py b/game/factions/turkey_2005.py deleted file mode 100644 index be68334c..00000000 --- a/game/factions/turkey_2005.py +++ /dev/null @@ -1,60 +0,0 @@ -from dcs.helicopters import ( - AH_1W, - UH_1H, -) -from dcs.planes import ( - C_130, - E_3A, - F_16C_50, - F_4E, - KC130, - KC_135, -) -from dcs.ships import ( - Armed_speedboat, - CVN_74_John_C__Stennis, - LHA_1_Tarawa, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Infantry, - Unarmed, -) - -Turkey_2005 = { - "country": "Turkey", - "side": "blue", - "units":[ - F_16C_50, - F_4E, - - KC_135, - KC130, - C_130, - E_3A, - - UH_1H, - AH_1W, - - Armor.MBT_Leopard_2, - Armor.MBT_Leopard_1A3, - Armor.MBT_M60A3_Patton, - Armor.APC_Cobra, - Armor.APC_BTR_80, - - Unarmed.Transport_M818, - Infantry.Infantry_M4, - - AirDefence.SAM_Avenger_M1097, - - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Armed_speedboat, - ], "shorad":[ - AirDefence.AAA_ZU_23_Emplacement, - AirDefence.SPAAA_ZSU_23_4_Shilka - ], "boat":[ - "OliverHazardPerryGroupGenerator" - ], "has_jtac": True -} \ No newline at end of file diff --git a/game/factions/uae_2005.py b/game/factions/uae_2005.py deleted file mode 100644 index d6240332..00000000 --- a/game/factions/uae_2005.py +++ /dev/null @@ -1,58 +0,0 @@ -from dcs.helicopters import ( - AH_64D, -) -from dcs.planes import ( - C_130, - E_3A, - F_16C_50, - KC130, - KC_135, - M_2000C, - Mirage_2000_5, - WingLoong_I, -) -from dcs.ships import ( - Armed_speedboat, - CVN_74_John_C__Stennis, - LHA_1_Tarawa, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Infantry, - Unarmed, -) - -UAE_2005 = { - "country": "United Arab Emirates", - "side": "blue", - "units":[ - M_2000C, - Mirage_2000_5, - F_16C_50, - - KC_135, - KC130, - C_130, - E_3A, - - AH_64D, - - Armor.MBT_Leclerc, - Armor.IFV_BMP_3, - Armor.TPz_Fuchs, - - Unarmed.Transport_M818, - Infantry.Infantry_M4, - - AirDefence.Rapier_FSA_Launcher, - - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Armed_speedboat, - ], "boat":[ - "OliverHazardPerryGroupGenerator" - ], - "has_jtac": True, - "jtac_unit": WingLoong_I -} \ No newline at end of file diff --git a/game/factions/uk_1944.py b/game/factions/uk_1944.py deleted file mode 100644 index 2620fc86..00000000 --- a/game/factions/uk_1944.py +++ /dev/null @@ -1,57 +0,0 @@ -from dcs.planes import ( - A_20G, - B_17G, - P_47D_30, - P_51D, - P_51D_30_NA, - SpitfireLFMkIX, - SpitfireLFMkIXCW, -) -from dcs.ships import LCVP__Higgins_boat, LST_Mk_II, LS_Samuel_Chase -from dcs.vehicles import ( - AirDefence, - Armor, - Infantry, - Unarmed, -) - -from game.data.building_data import WW2_ALLIES_BUILDINGS -from game.data.doctrine import WWII_DOCTRINE - -UK_1944 = { - "country": "UK", - "side": "blue", - "units": [ - P_51D, - P_51D_30_NA, - P_47D_30, - SpitfireLFMkIX, - SpitfireLFMkIXCW, - A_20G, - B_17G, - - Armor.MT_M4A4_Sherman_Firefly, - Armor.MT_M4_Sherman, - Armor.APC_M2A1, - Armor.CT_Cromwell_IV, - Armor.ST_Centaur_IV, - Armor.HIT_Churchill_VII, - - Infantry.Infantry_SMLE_No_4_Mk_1, - - LS_Samuel_Chase, - LST_Mk_II, - LCVP__Higgins_boat, - - Unarmed.CCKW_353, - AirDefence.AAA_Bofors_40mm, - ], "shorad":[ - AirDefence.AAA_Bofors_40mm, - ],"requirements":{ - "WW2 Asset Pack": "https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/", - }, - "objects": WW2_ALLIES_BUILDINGS, - "doctrine": WWII_DOCTRINE, - "boat": ["WW2LSTGroupGenerator"], - "boat_count": 1 -} \ No newline at end of file diff --git a/game/factions/uk_1990.py b/game/factions/uk_1990.py deleted file mode 100644 index 4855059d..00000000 --- a/game/factions/uk_1990.py +++ /dev/null @@ -1,74 +0,0 @@ -from dcs.helicopters import ( - AH_64A, - SA342M, -) -from dcs.planes import ( - AV8BNA, - C_130, - E_3A, - F_4E, - KC130, - KC_135, - Tornado_GR4, -) -from dcs.ships import ( - Armed_speedboat, - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Oliver_Hazzard_Perry_class, - Ticonderoga_class, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Infantry, - Unarmed, -) - -UnitedKingdom_1990 = { - "country": "UK", - "side": "blue", - "units":[ - AV8BNA, # Standing as BAE Harrier 2 - Tornado_GR4, - F_4E, - - KC_135, - KC130, - C_130, - E_3A, - - SA342M, - AH_64A, - - Armor.MBT_Challenger_II, - Armor.IFV_MCV_80, - Armor.APC_M1043_HMMWV_Armament, - Armor.ATGM_M1045_HMMWV_TOW, - - Unarmed.Transport_M818, - Infantry.Infantry_M4, - Infantry.Soldier_M249, - - AirDefence.Rapier_FSA_Launcher, - AirDefence.SAM_Avenger_M1097, # Standing as Starstreak - - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Armed_speedboat, - ], "shorad":[ - AirDefence.SAM_Avenger_M1097, - ], "helicopter_carrier": [ - LHA_1_Tarawa, - ], "destroyer": [ - Oliver_Hazzard_Perry_class, - ], "cruiser": [ - Ticonderoga_class, - ], "lhanames": [ - "HMS Invincible", - "HMS Illustrious", - "HMS Ark Royal", - ], "boat":[ - "ArleighBurkeGroupGenerator", "OliverHazardPerryGroupGenerator" - ], "has_jtac": True -} \ No newline at end of file diff --git a/game/factions/ukraine_2010.py b/game/factions/ukraine_2010.py deleted file mode 100644 index de030137..00000000 --- a/game/factions/ukraine_2010.py +++ /dev/null @@ -1,79 +0,0 @@ -from dcs.helicopters import ( - Mi_24V, - Mi_8MT, -) -from dcs.planes import ( - A_50, - An_26B, - An_30M, - IL_76MD, - IL_78M, - L_39ZA, - MiG_29S, - Su_24M, - Su_25, - Su_25T, - Su_27, - Yak_40, -) -from dcs.ships import ( - Bulk_cargo_ship_Yakushev, - CV_1143_5_Admiral_Kuznetsov, - Dry_cargo_ship_Ivanov, - Tanker_Elnya_160, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Infantry, - Unarmed, -) - -Ukraine_2010 = { - "country": "Ukraine", - "side": "blue", - "units": [ - Su_25, - Su_25T, - Su_24M, - Su_27, - MiG_29S, - L_39ZA, - - IL_76MD, - IL_78M, - An_26B, - An_30M, - Yak_40, - A_50, - - Mi_8MT, - Mi_24V, - - AirDefence.SAM_SA_3_S_125_LN_5P73, - AirDefence.SAM_SA_11_Buk_LN_9A310M1, - AirDefence.SAM_SA_10_S_300PS_LN_5P85C, - - Armor.APC_M1043_HMMWV_Armament, - Armor.IFV_BMP_3, - Armor.IFV_BMP_2, - Armor.APC_BTR_80, - Armor.MBT_T_80U, - Armor.MBT_T_72B, - - Unarmed.Transport_Ural_375, - Unarmed.Transport_UAZ_469, - Infantry.Soldier_AK, - CV_1143_5_Admiral_Kuznetsov, - Bulk_cargo_ship_Yakushev, - Dry_cargo_ship_Ivanov, - Tanker_Elnya_160, - ], - "shorad":[ - AirDefence.SAM_SA_19_Tunguska_2S6, - AirDefence.SAM_SA_13_Strela_10M3_9A35M3, - AirDefence.AAA_ZU_23_on_Ural_375 - ], "boat":[ - "GrishaGroupGenerator" - ] -} \ No newline at end of file diff --git a/game/factions/us_aggressors.py b/game/factions/us_aggressors.py deleted file mode 100644 index 4e2cce11..00000000 --- a/game/factions/us_aggressors.py +++ /dev/null @@ -1,108 +0,0 @@ -from dcs.helicopters import ( - AH_64D, - Ka_50, - SA342L, - SA342M, - UH_1H, -) -from dcs.planes import ( - B_1B, - B_52H, - C_130, - E_3A, - FA_18C_hornet, - F_15C, - F_15E, - F_16C_50, - F_5E_3, - KC130, - KC_135, - Su_27, -) -from dcs.ships import ( - Armed_speedboat, - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Oliver_Hazzard_Perry_class, - Ticonderoga_class, - USS_Arleigh_Burke_IIa, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Artillery, - Infantry, - Unarmed, -) - -US_Aggressors = { - "country": "USAF Aggressors", - "side": "red", - "units": [ - - F_15C, - F_15E, - F_5E_3, - FA_18C_hornet, - F_16C_50, - Su_27, - - B_1B, - B_52H, - - KC_135, - KC130, - C_130, - E_3A, - - UH_1H, - AH_64D, - Ka_50, - SA342M, - SA342L, - - Armor.MBT_M1A2_Abrams, - Armor.MBT_Leopard_2, - Armor.ATGM_M1134_Stryker, - Armor.IFV_M2A2_Bradley, - Armor.APC_M1043_HMMWV_Armament, - - Artillery.MLRS_M270, - Artillery.SPH_M109_Paladin, - - Unarmed.Transport_M818, - Infantry.Infantry_M4, - Infantry.Soldier_M249, - - AirDefence.SAM_Hawk_PCP, - AirDefence.SAM_Patriot_EPP_III, - - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Armed_speedboat, - ], "shorad": [ - AirDefence.SAM_Avenger_M1097, - ], "aircraft_carrier": [ - CVN_74_John_C__Stennis, - ], "helicopter_carrier": [ - LHA_1_Tarawa, - ], "destroyer": [ - Oliver_Hazzard_Perry_class, - USS_Arleigh_Burke_IIa, - ], "cruiser": [ - Ticonderoga_class, - ], "carrier_names": [ - "CVN-71 Theodore Roosevelt", - "CVN-72 Abraham Lincoln", - "CVN-73 George Washington", - "CVN-74 John C. Stennis", - ], "lhanames": [ - "LHA-1 Tarawa", - "LHA-2 Saipan", - "LHA-3 Belleau Wood", - "LHA-4 Nassau", - "LHA-5 Peleliu" - ], "boat":[ - "ArleighBurkeGroupGenerator", "OliverHazardPerryGroupGenerator" - ] -} diff --git a/game/factions/usa_1944.py b/game/factions/usa_1944.py deleted file mode 100644 index 7b99bd42..00000000 --- a/game/factions/usa_1944.py +++ /dev/null @@ -1,100 +0,0 @@ -from dcs.planes import ( - A_20G, - B_17G, - P_47D_30, - P_51D, - P_51D_30_NA, - SpitfireLFMkIX, - SpitfireLFMkIXCW, -) -from dcs.ships import LCVP__Higgins_boat, LST_Mk_II, LS_Samuel_Chase -from dcs.vehicles import ( - AirDefence, - Armor, - Artillery, - Infantry, - Unarmed, -) - -from game.data.building_data import WW2_ALLIES_BUILDINGS -from game.data.doctrine import WWII_DOCTRINE - -USA_1944 = { - "country": "USA", - "side": "blue", - "units": [ - P_51D, - P_51D_30_NA, - P_47D_30, - A_20G, - B_17G, - - Armor.MT_M4_Sherman, - Armor.M30_Cargo_Carrier, - Armor.APC_M2A1, - Armor.LAC_M8_Greyhound, - Armor.TD_M10_GMC, - Artillery.M12_GMC, - - Infantry.Infantry_M1_Garand, - - LS_Samuel_Chase, - LST_Mk_II, - LCVP__Higgins_boat, - - Unarmed.CCKW_353, - AirDefence.AAA_Bofors_40mm, - ], "shorad":[ - AirDefence.AAA_Bofors_40mm, - ],"requirements":{ - "WW2 Asset Pack": "https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/", - }, - "objects": WW2_ALLIES_BUILDINGS, - "doctrine": WWII_DOCTRINE, - "boat": ["WW2LSTGroupGenerator"], - "boat_count": 2 -} - -ALLIES_1944 = { - "country": "USA", - "side": "blue", - "units": [ - P_51D, - P_51D_30_NA, - P_47D_30, - SpitfireLFMkIX, - SpitfireLFMkIXCW, - A_20G, - B_17G, - - Armor.MT_M4_Sherman, - Armor.MT_M4A4_Sherman_Firefly, - Armor.CT_Cromwell_IV, - Armor.M30_Cargo_Carrier, - Armor.APC_M2A1, - Armor.CT_Cromwell_IV, - Armor.ST_Centaur_IV, - Armor.HIT_Churchill_VII, - Armor.LAC_M8_Greyhound, - Armor.TD_M10_GMC, - Artillery.M12_GMC, - - Infantry.Infantry_M1_Garand, - Infantry.Infantry_SMLE_No_4_Mk_1, - - LS_Samuel_Chase, - LST_Mk_II, - LCVP__Higgins_boat, - - Unarmed.CCKW_353, - AirDefence.AAA_Bofors_40mm, - ], "shorad":[ - AirDefence.AAA_Bofors_40mm, - ],"requirements":{ - "WW2 Asset Pack": "https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/", - }, - "objects": WW2_ALLIES_BUILDINGS, - "doctrine": WWII_DOCTRINE, - "boat": ["WW2LSTGroupGenerator"], - "boat_count": 2 -} \ No newline at end of file diff --git a/game/factions/usa_1955.py b/game/factions/usa_1955.py deleted file mode 100644 index bbafc9c4..00000000 --- a/game/factions/usa_1955.py +++ /dev/null @@ -1,51 +0,0 @@ -from dcs.planes import ( - B_52H, - C_130, - E_3A, - F_86F_Sabre, - KC130, - KC_135, - P_51D, -) -from dcs.ships import ( - Armed_speedboat, - CVN_74_John_C__Stennis, - LHA_1_Tarawa, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Infantry, - Unarmed, -) - -USA_1955 = { - "country": "USA", - "side": "blue", - "units": [ - F_86F_Sabre, - P_51D, - - B_52H, - - KC_135, - KC130, - C_130, - E_3A, - - Armor.MT_M4A4_Sherman_Firefly, - Armor.MT_M4_Sherman, - Armor.MBT_M60A3_Patton, - Armor.APC_M2A1, - Armor.M30_Cargo_Carrier, - - Unarmed.Transport_M818, - Infantry.Infantry_M4, - - AirDefence.AAA_Bofors_40mm, - - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Armed_speedboat, - ] -} \ No newline at end of file diff --git a/game/factions/usa_1960.py b/game/factions/usa_1960.py deleted file mode 100644 index b87ec470..00000000 --- a/game/factions/usa_1960.py +++ /dev/null @@ -1,57 +0,0 @@ -from dcs.helicopters import ( - UH_1H, -) -from dcs.planes import ( - B_52H, - C_130, - E_3A, - F_86F_Sabre, - KC130, - KC_135, - P_51D, -) -from dcs.ships import ( - Armed_speedboat, - CVN_74_John_C__Stennis, - LHA_1_Tarawa, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Infantry, - Unarmed, -) - -USA_1960 = { - "country": "USA", - "side": "blue", - "units": [ - F_86F_Sabre, - P_51D, - - B_52H, - - KC_135, - KC130, - C_130, - E_3A, - - UH_1H, - - Armor.MBT_M60A3_Patton, - Armor.APC_M113, - - Unarmed.Transport_M818, - Infantry.Infantry_M4, - Infantry.Soldier_M249, - - AirDefence.AAA_Vulcan_M163, - - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Armed_speedboat, - ], - "shorad":[ - AirDefence.AAA_Vulcan_M163 - ] -} \ No newline at end of file diff --git a/game/factions/usa_1965.py b/game/factions/usa_1965.py deleted file mode 100644 index cba61391..00000000 --- a/game/factions/usa_1965.py +++ /dev/null @@ -1,60 +0,0 @@ -from dcs.helicopters import ( - UH_1H, -) -from dcs.planes import ( - B_52H, - C_130, - E_3A, - F_4E, - F_5E_3, - KC130, - KC_135, -) -from dcs.ships import ( - Armed_speedboat, - CVN_74_John_C__Stennis, - LHA_1_Tarawa, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Infantry, - Unarmed, -) - -USA_1965 = { - "country": "USA", - "side": "blue", - "units": [ - - F_5E_3, - F_4E, - - KC_135, - KC130, - C_130, - E_3A, - - B_52H, - - UH_1H, - - Armor.MBT_M60A3_Patton, - Armor.APC_M113, - Unarmed.Transport_M818, - Infantry.Infantry_M4, - Infantry.Soldier_M249, - - AirDefence.SAM_Chaparral_M48, - AirDefence.SAM_Hawk_PCP, - - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Armed_speedboat, - ], - "shorad":[ - AirDefence.AAA_Vulcan_M163, - AirDefence.SAM_Chaparral_M48 - ], "boat":[ - ] -} \ No newline at end of file diff --git a/game/factions/usa_1990.py b/game/factions/usa_1990.py deleted file mode 100644 index 7413d956..00000000 --- a/game/factions/usa_1990.py +++ /dev/null @@ -1,99 +0,0 @@ -from dcs.helicopters import ( - AH_64A, - UH_1H, -) -from dcs.planes import ( - AV8BNA, - A_10A, - B_1B, - B_52H, - C_130, - E_3A, - FA_18C_hornet, - F_117A, - F_14B, - F_15C, - F_15E, - F_16C_50, - KC130, - KC_135, -) -from dcs.ships import ( - Armed_speedboat, - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Oliver_Hazzard_Perry_class, - Ticonderoga_class, - USS_Arleigh_Burke_IIa, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Infantry, - Unarmed, -) - -USA_1990 = { - "country": "USA", - "side": "blue", - "units": [ - F_15C, - F_15E, - F_14B, - FA_18C_hornet, - F_16C_50, - - A_10A, - AV8BNA, - - B_1B, - B_52H, - F_117A, - - KC_135, - KC130, - C_130, - E_3A, - - UH_1H, - AH_64A, - - Armor.MBT_M1A2_Abrams, - Armor.IFV_LAV_25, - Armor.APC_M1043_HMMWV_Armament, - Armor.ATGM_M1045_HMMWV_TOW, - Armor.ATGM_M1134_Stryker, - Unarmed.Transport_M818, - Infantry.Infantry_M4, - Infantry.Soldier_M249, - - AirDefence.SAM_Hawk_PCP, - - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Armed_speedboat, - ], "shorad":[ - AirDefence.SAM_Avenger_M1097, - ], "aircraft_carrier": [ - CVN_74_John_C__Stennis, - ], "helicopter_carrier": [ - LHA_1_Tarawa, - ], "destroyer": [ - Oliver_Hazzard_Perry_class, - USS_Arleigh_Burke_IIa, - ], "cruiser": [ - Ticonderoga_class, - ], "carrier_names": [ - "CVN-72 Abraham Lincoln", - "CVN-73 Georges Washington", - "CVN-74 John C. Stennis", - ], "lhanames": [ - "LHA-1 Tarawa", - "LHA-2 Saipan", - "LHA-3 Belleau Wood", - "LHA-4 Nassau", - "LHA-5 Peleliu" - ], "boat":[ - "ArleighBurkeGroupGenerator", "OliverHazardPerryGroupGenerator" - ], "has_jtac": True -} \ No newline at end of file diff --git a/game/factions/usa_2005.py b/game/factions/usa_2005.py deleted file mode 100644 index 5a40aa8b..00000000 --- a/game/factions/usa_2005.py +++ /dev/null @@ -1,109 +0,0 @@ -from dcs.helicopters import ( - AH_64D, - UH_1H, -) -from dcs.planes import ( - AV8BNA, - A_10C, - A_10C_2, - B_1B, - B_52H, - C_130, - E_3A, - FA_18C_hornet, - F_117A, - F_14B, - F_15C, - F_15E, - F_16C_50, - KC130, - KC_135, - MQ_9_Reaper, -) -from dcs.ships import ( - Armed_speedboat, - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Ticonderoga_class, - USS_Arleigh_Burke_IIa, -) -from dcs.vehicles import ( - AirDefence, - Armor, - Artillery, - Infantry, - Unarmed, -) - -USA_2005 = { - "country": "USA", - "side": "blue", - "units": [ - F_15C, - F_15E, - F_14B, - FA_18C_hornet, - F_16C_50, - A_10C, - A_10C_2, - AV8BNA, - MQ_9_Reaper, - - B_1B, - B_52H, - F_117A, - - KC_135, - KC130, - C_130, - E_3A, - - UH_1H, - AH_64D, - - Armor.MBT_M1A2_Abrams, - Armor.ATGM_M1134_Stryker, - Armor.APC_M1126_Stryker_ICV, - Armor.IFV_M2A2_Bradley, - Armor.IFV_LAV_25, - Armor.APC_M1043_HMMWV_Armament, - Armor.ATGM_M1045_HMMWV_TOW, - - Artillery.MLRS_M270, - Artillery.SPH_M109_Paladin, - - Unarmed.Transport_M818, - Infantry.Infantry_M4, - Infantry.Soldier_M249, - - AirDefence.SAM_Hawk_PCP, - AirDefence.SAM_Patriot_EPP_III, - - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - Armed_speedboat, - ], "shorad": [ - AirDefence.SAM_Avenger_M1097, - ], "aircraft_carrier": [ - CVN_74_John_C__Stennis, - ], "helicopter_carrier": [ - LHA_1_Tarawa, - ], "destroyer": [ - USS_Arleigh_Burke_IIa, - ], "cruiser": [ - Ticonderoga_class, - ], "carrier_names": [ - "CVN-71 Theodore Roosevelt", - "CVN-72 Abraham Lincoln", - "CVN-73 George Washington", - "CVN-74 John C. Stennis", - ], "lhanames": [ - "LHA-1 Tarawa", - "LHA-2 Saipan", - "LHA-3 Belleau Wood", - "LHA-4 Nassau", - "LHA-5 Peleliu" - ], "boat":[ - "ArleighBurkeGroupGenerator" - ], "has_jtac": True -} diff --git a/game/game.py b/game/game.py index bd0f68f7..0d7b1f28 100644 --- a/game/game.py +++ b/game/game.py @@ -26,6 +26,7 @@ from . import persistency from .debriefing import Debriefing from .event.event import Event, UnitsDeliveryEvent from .event.frontlineattack import FrontlineAttackEvent +from .factions.faction import Faction from .infos.information import Information from .settings import Settings from plugin import LuaPluginManager @@ -76,9 +77,9 @@ class Game: self.events: List[Event] = [] self.theater = theater self.player_name = player_name - self.player_country = db.FACTIONS[player_name]["country"] + self.player_country = db.FACTIONS[player_name].country self.enemy_name = enemy_name - self.enemy_country = db.FACTIONS[enemy_name]["country"] + self.enemy_country = db.FACTIONS[enemy_name].country self.turn = 0 self.date = date(start_date.year, start_date.month, start_date.day) self.game_stats = GameStats() @@ -123,11 +124,11 @@ class Game: self.enemy_country = "Russia" @property - def player_faction(self) -> Dict[str, Any]: + def player_faction(self) -> Faction: return db.FACTIONS[self.player_name] @property - def enemy_faction(self) -> Dict[str, Any]: + def enemy_faction(self) -> Faction: return db.FACTIONS[self.enemy_name] def _roll(self, prob, mult): @@ -141,8 +142,10 @@ class Game: self.events.append(event_class(self, player_cp, enemy_cp, enemy_cp.position, self.player_name, self.enemy_name)) def _generate_events(self): - for player_cp, enemy_cp in self.theater.conflicts(True): - self._generate_player_event(FrontlineAttackEvent, player_cp, enemy_cp) + for front_line in self.theater.conflicts(True): + self._generate_player_event(FrontlineAttackEvent, + front_line.control_point_a, + front_line.control_point_b) def commision_unit_types(self, cp: ControlPoint, for_task: Task) -> List[UnitType]: importance_factor = (cp.importance - IMPORTANCE_LOW) / (IMPORTANCE_HIGH - IMPORTANCE_LOW) @@ -235,10 +238,10 @@ class Game: if not hasattr(self, "conditions"): self.conditions = self.generate_conditions() - def pass_turn(self, no_action=False): + def pass_turn(self, no_action: bool = False) -> None: logging.info("Pass turn") self.informations.append(Information("End of turn #" + str(self.turn), "-" * 40, 0)) - self.turn = self.turn + 1 + self.turn += 1 for event in self.events: if self.settings.version == "dev": @@ -259,6 +262,14 @@ class Game: if not cp.is_carrier and not cp.is_lha: cp.base.affect_strength(-PLAYER_BASE_STRENGTH_RECOVERY) + self.conditions = self.generate_conditions() + + self.initialize_turn() + + # Autosave progress + persistency.autosave(self) + + def initialize_turn(self) -> None: self.events = [] self._generate_events() @@ -269,8 +280,6 @@ class Game: for cp in self.theater.controlpoints: self.aircraft_inventory.set_from_control_point(cp) - self.conditions = self.generate_conditions() - # Plan flights & combat for next turn self.__culling_points = self.compute_conflicts_position() self.ground_planners = {} @@ -284,9 +293,6 @@ class Game: gplanner.plan_groundwar() self.ground_planners[cp.id] = gplanner - # Autosave progress - persistency.autosave(self) - def _enemy_reinforcement(self): """ Compute and commision reinforcement for enemy bases @@ -314,7 +320,7 @@ class Game: potential_cp_armor = self.theater.enemy_points() i = 0 - potential_units = [u for u in db.FACTIONS[self.enemy_name]["units"] if u in db.UNIT_BY_TASK[PinpointStrike]] + potential_units = db.FACTIONS[self.enemy_name].frontline_units print("Enemy Recruiting") print(potential_cp_armor) @@ -340,8 +346,9 @@ class Game: if budget_for_armored_units > 0: budget_for_aircraft += budget_for_armored_units - potential_units = [u for u in db.FACTIONS[self.enemy_name]["units"] if - u in db.UNIT_BY_TASK[CAS] or u in db.UNIT_BY_TASK[CAP]] + potential_units = [u for u in db.FACTIONS[self.enemy_name].aircrafts + if u in db.UNIT_BY_TASK[CAS] or u in db.UNIT_BY_TASK[CAP]] + if len(potential_units) > 0 and len(potential_cp_armor) > 0: while budget_for_aircraft > 0: i = i + 1 @@ -388,10 +395,13 @@ class Game: points = [] # By default, use the existing frontline conflict position - for conflict in self.theater.conflicts(): - points.append(Conflict.frontline_position(self.theater, conflict[0], conflict[1])[0]) - points.append(conflict[0].position) - points.append(conflict[1].position) + for front_line in self.theater.conflicts(): + position = Conflict.frontline_position(self.theater, + front_line.control_point_a, + front_line.control_point_b) + points.append(position[0]) + points.append(front_line.control_point_a.position) + points.append(front_line.control_point_b.position) # If there is no conflict take the center point between the two nearest opposing bases if len(points) == 0: diff --git a/game/operation/operation.py b/game/operation/operation.py index 3c32210c..43883625 100644 --- a/game/operation/operation.py +++ b/game/operation/operation.py @@ -67,9 +67,9 @@ class Operation: to_cp: ControlPoint): self.game = game self.attacker_name = attacker_name - self.attacker_country = db.FACTIONS[attacker_name]["country"] + self.attacker_country = db.FACTIONS[attacker_name].country self.defender_name = defender_name - self.defender_country = db.FACTIONS[defender_name]["country"] + self.defender_country = db.FACTIONS[defender_name].country print(self.defender_country, self.attacker_country) self.from_cp = from_cp self.departure_cp = departure_cp @@ -253,7 +253,9 @@ class Operation: # Generate ground units on frontline everywhere jtacs: List[JtacInfo] = [] - for player_cp, enemy_cp in self.game.theater.conflicts(True): + for front_line in self.game.theater.conflicts(True): + player_cp = front_line.control_point_a + enemy_cp = front_line.control_point_b conflict = Conflict.frontline_cas_conflict(self.attacker_name, self.defender_name, self.current_mission.country(self.attacker_country), self.current_mission.country(self.defender_country), diff --git a/gen/armor.py b/gen/armor.py index 2873002a..a717b677 100644 --- a/gen/armor.py +++ b/gen/armor.py @@ -142,13 +142,13 @@ class GroundConflictGenerator: # Add JTAC jtacPlugin = LuaPluginManager().getPlugin("jtacautolase") useJTAC = jtacPlugin and jtacPlugin.isEnabled() - if "has_jtac" in self.game.player_faction and self.game.player_faction["has_jtac"] and useJTAC: + if self.game.player_faction.has_jtac and useJTAC: n = "JTAC" + str(self.conflict.from_cp.id) + str(self.conflict.to_cp.id) code = 1688 - len(self.jtacs) utype = MQ_9_Reaper - if "jtac_unit" in self.game.player_faction: - utype = self.game.player_faction["jtac_unit"] + if self.game.player_faction.jtac_unit is not None: + utype = self.game.player_faction.jtac_unit jtac = self.mission.flight_group(country=self.mission.country(self.game.player_country), name=n, diff --git a/gen/briefinggen.py b/gen/briefinggen.py index 63f29396..bd9acf0d 100644 --- a/gen/briefinggen.py +++ b/gen/briefinggen.py @@ -194,14 +194,10 @@ class BriefingGenerator(MissionInfoGenerator): conflict_number = 0 - for c in self.game.theater.conflicts(): + for front_line in self.game.theater.conflicts(from_player=True): conflict_number = conflict_number + 1 - if c[0].captured: - player_base = c[0] - enemy_base = c[1] - else: - player_base = c[1] - enemy_base = c[0] + player_base = front_line.control_point_a + enemy_base = front_line.control_point_b has_numerical_superiority = player_base.base.total_armor > enemy_base.base.total_armor self.description += self.__random_frontline_sentence(player_base.name, enemy_base.name) diff --git a/gen/defenses/armor_group_generator.py b/gen/defenses/armor_group_generator.py index 7b772e31..c8bc472c 100644 --- a/gen/defenses/armor_group_generator.py +++ b/gen/defenses/armor_group_generator.py @@ -11,8 +11,7 @@ def generate_armor_group(faction:str, game, ground_object): This generate a group of ground units :return: Generated group """ - - possible_unit = [u for u in db.FACTIONS[faction]["units"] if u in Armor.__dict__.values()] + possible_unit = [u for u in db.FACTIONS[faction].frontline_units if u in Armor.__dict__.values()] if len(possible_unit) > 0: unit_type = random.choice(possible_unit) return generate_armor_group_of_type(game, ground_object, unit_type) diff --git a/gen/fleet/carrier_group.py b/gen/fleet/carrier_group.py index 119bc2d4..bc7a712c 100644 --- a/gen/fleet/carrier_group.py +++ b/gen/fleet/carrier_group.py @@ -12,19 +12,15 @@ class CarrierGroupGenerator(GroupGenerator): def generate(self): # Add carrier - if "aircraft_carrier" in self.faction.keys(): - - if "supercarrier" in self.faction.keys() and self.game.settings.supercarrier: - carrier_type = random.choice(self.faction["supercarrier"]) - else: - carrier_type = random.choice(self.faction["aircraft_carrier"]) + if len(self.faction.aircraft_carrier) > 0: + carrier_type = random.choice(self.faction.aircraft_carrier) self.add_unit(carrier_type, "Carrier", self.position.x, self.position.y, self.heading) else: return # Add destroyers escort - if "destroyer" in self.faction.keys(): - dd_type = random.choice(self.faction["destroyer"]) + if len(self.faction.destroyers) > 0: + dd_type = random.choice(self.faction.destroyers) self.add_unit(dd_type, "DD1", self.position.x + 2500, self.position.y + 4500, self.heading) self.add_unit(dd_type, "DD2", self.position.x + 2500, self.position.y - 4500, self.heading) diff --git a/gen/fleet/lha_group.py b/gen/fleet/lha_group.py index 8945c8f2..6fbf23ac 100644 --- a/gen/fleet/lha_group.py +++ b/gen/fleet/lha_group.py @@ -12,13 +12,13 @@ class LHAGroupGenerator(GroupGenerator): def generate(self): # Add carrier - if "helicopter_carrier" in self.faction.keys(): - carrier_type = random.choice(self.faction["helicopter_carrier"]) + if len(self.faction.helicopter_carrier) > 0: + carrier_type = random.choice(self.faction.helicopter_carrier) self.add_unit(carrier_type, "LHA", self.position.x, self.position.y, self.heading) # Add destroyers escort - if "destroyer" in self.faction.keys(): - dd_type = random.choice(self.faction["destroyer"]) + if len(self.faction.destroyers) > 0: + dd_type = random.choice(self.faction.destroyers) self.add_unit(dd_type, "DD1", self.position.x + 1250, self.position.y + 1450, self.heading) self.add_unit(dd_type, "DD2", self.position.x + 1250, self.position.y - 1450, self.heading) diff --git a/gen/fleet/ship_group_generator.py b/gen/fleet/ship_group_generator.py index db974d6c..b60746b7 100644 --- a/gen/fleet/ship_group_generator.py +++ b/gen/fleet/ship_group_generator.py @@ -34,16 +34,14 @@ def generate_ship_group(game, ground_object, faction_name: str): :return: Nothing, but put the group reference inside the ground object """ faction = db.FACTIONS[faction_name] - if "boat" in faction: - generators = faction["boat"] - if len(generators) > 0: - gen = random.choice(generators) - if gen in SHIP_MAP.keys(): - generator = SHIP_MAP[gen](game, ground_object, faction) - generator.generate() - return generator.get_generated_group() - else: - logging.info("Unable to generate ship group, generator : " + str(gen) + "does not exists") + if len(faction.navy_generators) > 0: + gen = random.choice(faction.navy_generators) + if gen in SHIP_MAP.keys(): + generator = SHIP_MAP[gen](game, ground_object, faction) + generator.generate() + return generator.get_generated_group() + else: + logging.info("Unable to generate ship group, generator : " + str(gen) + "does not exists") return None diff --git a/gen/flights/flightplan.py b/gen/flights/flightplan.py index e7bbb1b0..3141707b 100644 --- a/gen/flights/flightplan.py +++ b/gen/flights/flightplan.py @@ -46,7 +46,7 @@ class FlightPlanBuilder: faction = self.game.player_faction else: faction = self.game.enemy_faction - self.doctrine: Doctrine = faction.get("doctrine", MODERN_DOCTRINE) + self.doctrine: Doctrine = faction.doctrine def populate_flight_plan( self, flight: Flight, diff --git a/gen/missiles/missiles_group_generator.py b/gen/missiles/missiles_group_generator.py index 4e2e9d73..9a89669b 100644 --- a/gen/missiles/missiles_group_generator.py +++ b/gen/missiles/missiles_group_generator.py @@ -14,8 +14,8 @@ def generate_missile_group(game, ground_object, faction_name: str): :return: Nothing, but put the group reference inside the ground object """ faction = db.FACTIONS[faction_name] - if "missiles" in faction: - generators = faction["missiles"] + if len(faction.missiles) > 0: + generators = faction.missiles if len(generators) > 0: gen = random.choice(generators) if gen in MISSILES_MAP.keys(): diff --git a/gen/sam/genericsam_group_generator.py b/gen/sam/genericsam_group_generator.py index e8a1bcb2..90933e3a 100644 --- a/gen/sam/genericsam_group_generator.py +++ b/gen/sam/genericsam_group_generator.py @@ -10,12 +10,10 @@ class GenericSamGroupGenerator(GroupGenerator): This is the base for all SAM group generators """ - def getGroupNamePrefix(self, faction): - if not faction: - return "" - + @property + def groupNamePrefix(self): # prefix the SAM site for use with the Skynet IADS plugin - prefix = "BLUE SAM " - if db.FACTIONS[faction]["side"] == "red": - prefix = "RED SAM " - return prefix + if self.faction == self.game.player_name: # this is the player faction + return "BLUE SAM " + else: + return "RED SAM " diff --git a/gen/sam/group_generator.py b/gen/sam/group_generator.py index 74e1d681..010650cc 100644 --- a/gen/sam/group_generator.py +++ b/gen/sam/group_generator.py @@ -8,21 +8,18 @@ from dcs.unit import Vehicle class GroupGenerator(): - def __init__(self, game, ground_object, faction = None): + def __init__(self, game, ground_object, faction = None): # faction is not mandatory because some subclasses do not use it self.game = game self.go = ground_object self.position = ground_object.position self.heading = random.randint(0, 359) - groupNamePrefix = self.getGroupNamePrefix(faction) - self.vg = unitgroup.VehicleGroup(self.game.next_group_id(), groupNamePrefix + self.go.group_identifier) - + self.faction = faction + self.vg = unitgroup.VehicleGroup(self.game.next_group_id(), self.groupNamePrefix + self.go.group_identifier) wp = self.vg.add_waypoint(self.position, PointAction.OffRoad, 0) wp.ETA_locked = True - def getGroupNamePrefix(self, faction): - if not faction: - return "" - + @property + def groupNamePrefix(self): return "" def generate(self): diff --git a/gen/sam/sam_group_generator.py b/gen/sam/sam_group_generator.py index 968e6866..7a127830 100644 --- a/gen/sam/sam_group_generator.py +++ b/gen/sam/sam_group_generator.py @@ -1,5 +1,5 @@ import random -from typing import List +from typing import List, Type from dcs.unittype import UnitType from dcs.vehicles import AirDefence @@ -8,6 +8,7 @@ from game import db from gen.sam.aaa_bofors import BoforsGenerator from gen.sam.aaa_flak import FlakGenerator from gen.sam.aaa_zu23_insurgent import ZU23InsurgentGenerator +from gen.sam.group_generator import GroupGenerator from gen.sam.sam_avenger import AvengerGenerator from gen.sam.sam_chaparral import ChaparralGenerator from gen.sam.sam_gepard import GepardGenerator @@ -34,37 +35,33 @@ from gen.sam.sam_zu23_ural import ZU23UralGenerator from gen.sam.sam_zu23_ural_insurgent import ZU23UralInsurgentGenerator SAM_MAP = { - AirDefence.SAM_Hawk_PCP: HawkGenerator, - AirDefence.AAA_ZU_23_Emplacement: ZU23Generator, - AirDefence.AAA_ZU_23_Closed: ZU23Generator, - AirDefence.AAA_ZU_23_on_Ural_375: ZU23UralGenerator, - AirDefence.AAA_ZU_23_Insurgent_on_Ural_375: ZU23UralInsurgentGenerator, - AirDefence.AAA_ZU_23_Insurgent_Closed: ZU23InsurgentGenerator, - AirDefence.AAA_ZU_23_Insurgent: ZU23InsurgentGenerator, - AirDefence.SPAAA_ZSU_23_4_Shilka: ZSU23Generator, - AirDefence.AAA_Vulcan_M163: VulcanGenerator, - AirDefence.SAM_Linebacker_M6: LinebackerGenerator, - AirDefence.Rapier_FSA_Launcher: RapierGenerator, - AirDefence.SAM_Avenger_M1097: AvengerGenerator, - AirDefence.SPAAA_Gepard: GepardGenerator, - AirDefence.SAM_Roland_ADS: RolandGenerator, - AirDefence.SAM_Patriot_LN_M901: PatriotGenerator, - AirDefence.SAM_Patriot_EPP_III: PatriotGenerator, - AirDefence.SAM_Chaparral_M48: ChaparralGenerator, - AirDefence.AAA_Bofors_40mm: BoforsGenerator, - AirDefence.AAA_8_8cm_Flak_36: FlakGenerator, - AirDefence.SAM_SA_2_LN_SM_90: SA2Generator, - AirDefence.SAM_SA_3_S_125_LN_5P73: SA3Generator, - AirDefence.SAM_SA_6_Kub_LN_2P25: SA6Generator, - AirDefence.SAM_SA_8_Osa_9A33: SA8Generator, - AirDefence.SAM_SA_9_Strela_1_9P31: SA9Generator, - AirDefence.SAM_SA_10_S_300PS_LN_5P85C: SA10Generator, - AirDefence.SAM_SA_10_S_300PS_CP_54K6: SA10Generator, - AirDefence.SAM_SA_11_Buk_LN_9A310M1: SA11Generator, - AirDefence.SAM_SA_13_Strela_10M3_9A35M3: SA13Generator, - AirDefence.SAM_SA_15_Tor_9A331: SA15Generator, - AirDefence.SAM_SA_19_Tunguska_2S6: SA19Generator, - AirDefence.HQ_7_Self_Propelled_LN: HQ7Generator + "HawkGenerator": HawkGenerator, + "ZU23Generator": ZU23Generator, + "ZU23UralGenerator": ZU23UralGenerator, + "ZU23UralInsurgentGenerator": ZU23UralInsurgentGenerator, + "ZU23InsurgentGenerator": ZU23InsurgentGenerator, + "ZSU23Generator": ZSU23Generator, + "VulcanGenerator": VulcanGenerator, + "LinebackerGenerator": LinebackerGenerator, + "RapierGenerator": RapierGenerator, + "AvengerGenerator": AvengerGenerator, + "GepardGenerator": GepardGenerator, + "RolandGenerator": RolandGenerator, + "PatriotGenerator": PatriotGenerator, + "ChaparralGenerator": ChaparralGenerator, + "BoforsGenerator": BoforsGenerator, + "FlakGenerator": FlakGenerator, + "SA2Generator": SA2Generator, + "SA3Generator": SA3Generator, + "SA6Generator": SA6Generator, + "SA8Generator": SA8Generator, + "SA9Generator": SA9Generator, + "SA10Generator": SA10Generator, + "SA11Generator": SA11Generator, + "SA13Generator": SA13Generator, + "SA15Generator": SA15Generator, + "SA19Generator": SA19Generator, + "HQ7Generator": HQ7Generator } SAM_PRICES = { @@ -102,20 +99,12 @@ SAM_PRICES = { } -def get_faction_possible_sams_units(faction: str) -> List[UnitType]: - """ - Return the list - :param faction: Faction to search units for - """ - return [u for u in db.FACTIONS[faction]["units"] if u in AirDefence.__dict__.values()] - - -def get_faction_possible_sams_generator(faction: str) -> List[UnitType]: +def get_faction_possible_sams_generator(faction: str) -> List[Type[GroupGenerator]]: """ Return the list of possible SAM generator for the given faction - :param faction: Faction to search units for + :param faction: Faction name to search units for """ - return [SAM_MAP[u] for u in get_faction_possible_sams_units(faction)] + return [SAM_MAP[s] for s in db.FACTIONS[faction].sams if s in SAM_MAP.keys()] def generate_anti_air_group(game, parent_cp, ground_object, faction:str): """ @@ -125,24 +114,25 @@ def generate_anti_air_group(game, parent_cp, ground_object, faction:str): :param country: Owner country :return: Nothing, but put the group reference inside the ground object """ - possible_sams = get_faction_possible_sams_units(faction) - if len(possible_sams) > 0: - sam = random.choice(possible_sams) - generator = SAM_MAP[sam](game, ground_object, faction) + possible_sams_generators = get_faction_possible_sams_generator(faction) + if len(possible_sams_generators) > 0: + sam_generator_class = random.choice(possible_sams_generators) + generator = sam_generator_class(game, ground_object, faction) generator.generate() return generator.get_generated_group() return None -def generate_shorad_group(game, parent_cp, ground_object, faction:str): - if("shorad") in db.FACTIONS[faction].keys(): - shorad = db.FACTIONS[faction]["shorad"] - sam = random.choice(shorad) +def generate_shorad_group(game, parent_cp, ground_object, faction_name: str): + faction = db.FACTIONS[faction_name] + + if len(faction.shorads) > 0: + sam = random.choice(faction.shorads) generator = SAM_MAP[sam](game, ground_object) generator.generate() return generator.get_generated_group() else: - return generate_anti_air_group(game, parent_cp, ground_object, faction) + return generate_anti_air_group(game, parent_cp, ground_object, faction_name) diff --git a/gen/visualgen.py b/gen/visualgen.py index 5bc315e5..efd0c1f9 100644 --- a/gen/visualgen.py +++ b/gen/visualgen.py @@ -98,7 +98,9 @@ class VisualGenerator: self.game = game def _generate_frontline_smokes(self): - for from_cp, to_cp in self.game.theater.conflicts(): + for front_line in self.game.theater.conflicts(): + from_cp = front_line.control_point_a + to_cp = front_line.control_point_b if from_cp.is_global or to_cp.is_global: continue diff --git a/pydcs_extensions/mod_units.py b/pydcs_extensions/mod_units.py new file mode 100644 index 00000000..132a7b2f --- /dev/null +++ b/pydcs_extensions/mod_units.py @@ -0,0 +1,43 @@ +from pydcs_extensions.a4ec.a4ec import A_4E_C +from pydcs_extensions.mb339.mb339 import MB_339PAN +from pydcs_extensions.rafale.rafale import Rafale_M, Rafale_A_S +from pydcs_extensions.su57.su57 import Su_57 +import pydcs_extensions.frenchpack.frenchpack as frenchpack + +MODDED_AIRPLANES = [A_4E_C, MB_339PAN, Rafale_A_S, Rafale_M, Su_57] +MODDED_VEHICLES = [ + frenchpack._FIELD_HIDE, + frenchpack._FIELD_HIDE_SMALL, + frenchpack.SMOKE_SAM_IR, + frenchpack.SmokeD1, + frenchpack.SmokeD3, + frenchpack.AMX_10RCR, + frenchpack.AMX_10RCR_SEPAR, + frenchpack.ERC_90, + frenchpack.MO_120_RT, + frenchpack._53T2, + frenchpack.TRM_2000, + frenchpack.TRM_2000_Fuel, + frenchpack.TRM_2000_53T2, + frenchpack.TRM_2000_PAMELA, + frenchpack.VAB_MEDICAL, + frenchpack.VAB, + frenchpack.VAB__50, + frenchpack.VAB_T20_13, + frenchpack.VAB_MEPHISTO, + frenchpack.VAB_MORTIER, + frenchpack.VBL__50, + frenchpack.VBL_AANF1, + frenchpack.VBL, + frenchpack.VBAE_CRAB, + frenchpack.VBAE_CRAB_MMP, + frenchpack.AMX_30B2, + frenchpack.Tracma_TD_1500, + frenchpack.Infantry_Soldier_JTAC, + frenchpack.Char_M551_Sheridan, + frenchpack.Leclerc_Serie_XXI, + frenchpack.DIM__TOYOTA_BLUE, + frenchpack.DIM__TOYOTA_GREEN, + frenchpack.DIM__TOYOTA_DESERT, + frenchpack.DIM__KAMIKAZE +] \ No newline at end of file diff --git a/qt_ui/widgets/QTopPanel.py b/qt_ui/widgets/QTopPanel.py index dadbee0d..7b7f3ec6 100644 --- a/qt_ui/widgets/QTopPanel.py +++ b/qt_ui/widgets/QTopPanel.py @@ -156,7 +156,9 @@ class QTopPanel(QFrame): "Packages panel on the left of the main window, and then a flight " "from the Flights panel below the Packages panel. The edit button " "below the Flights panel will allow you to edit the number of " - "client slots in the flight. Each client slot allows one player."), + "client slots in the flight. Each client slot allows one player.
" + "
Click 'Yes' to continue with an AI only mission" + "
Click 'No' if you'd like to make more changes."), QMessageBox.No, QMessageBox.Yes ) diff --git a/qt_ui/widgets/map/QMapControlPoint.py b/qt_ui/widgets/map/QMapControlPoint.py index ef9bf5c9..d90a973d 100644 --- a/qt_ui/widgets/map/QMapControlPoint.py +++ b/qt_ui/widgets/map/QMapControlPoint.py @@ -1,6 +1,7 @@ from typing import Optional from PySide2.QtGui import QColor, QPainter +from PySide2.QtWidgets import QAction, QMenu import qt_ui.uiconstants as const from qt_ui.models import GameModel @@ -8,6 +9,7 @@ from qt_ui.windows.basemenu.QBaseMenu2 import QBaseMenu2 from theater import ControlPoint from .QMapObject import QMapObject from ...displayoptions import DisplayOptions +from ...windows.GameUpdateSignal import GameUpdateSignal class QMapControlPoint(QMapObject): @@ -20,6 +22,9 @@ class QMapControlPoint(QMapObject): self.setZValue(1) self.setToolTip(self.control_point.name) self.base_details_dialog: Optional[QBaseMenu2] = None + self.capture_action = QAction( + f"CHEAT: Capture {self.control_point.name}") + self.capture_action.triggered.connect(self.cheat_capture) def paint(self, painter, option, widget=None) -> None: if DisplayOptions.control_points: @@ -64,3 +69,24 @@ class QMapControlPoint(QMapObject): self.game_model ) self.base_details_dialog.show() + + def add_context_menu_actions(self, menu: QMenu) -> None: + if self.control_point.is_fleet: + return + + if self.control_point.captured: + return + + for connected in self.control_point.connected_points: + if connected.captured: + break + else: + return + + menu.addAction(self.capture_action) + + def cheat_capture(self) -> None: + self.control_point.capture(self.game_model.game, for_player=True) + # Reinitialized ground planners and the like. + self.game_model.game.initialize_turn() + GameUpdateSignal.get_instance().updateGame(self.game_model.game) diff --git a/qt_ui/widgets/map/QMapObject.py b/qt_ui/widgets/map/QMapObject.py index c98cce5e..fa28c333 100644 --- a/qt_ui/widgets/map/QMapObject.py +++ b/qt_ui/widgets/map/QMapObject.py @@ -37,6 +37,9 @@ class QMapObject(QGraphicsRectItem): if event.button() == Qt.LeftButton: self.on_click() + def add_context_menu_actions(self, menu: QMenu) -> None: + pass + def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None: menu = QMenu("Menu", self.parent) @@ -48,6 +51,8 @@ class QMapObject(QGraphicsRectItem): new_package_action.triggered.connect(self.open_new_package_dialog) menu.addAction(new_package_action) + self.add_context_menu_actions(menu) + menu.exec_(event.screenPos()) @property diff --git a/qt_ui/windows/newgame/QCampaignList.py b/qt_ui/windows/newgame/QCampaignList.py index 5d3de21c..1c3ccf7d 100644 --- a/qt_ui/windows/newgame/QCampaignList.py +++ b/qt_ui/windows/newgame/QCampaignList.py @@ -19,6 +19,7 @@ from theater import ConflictTheater class Campaign: name: str icon_name: str + authors: str theater: ConflictTheater @classmethod @@ -27,7 +28,7 @@ class Campaign: data = json.load(campaign_file) sanitized_theater = data["theater"].replace(" ", "") - return cls(data["name"], f"Terrain_{sanitized_theater}", + return cls(data["name"], f"Terrain_{sanitized_theater}", data.get("authors", "???"), ConflictTheater.from_json(data)) diff --git a/qt_ui/windows/newgame/QNewGameWizard.py b/qt_ui/windows/newgame/QNewGameWizard.py index 9f49e8d1..5fa06451 100644 --- a/qt_ui/windows/newgame/QNewGameWizard.py +++ b/qt_ui/windows/newgame/QNewGameWizard.py @@ -150,11 +150,15 @@ class FactionSelection(QtWidgets.QWizardPage): redFaction = QtWidgets.QLabel("Enemy Faction :") self.redFactionSelect = QtWidgets.QComboBox() + redFaction.setBuddy(self.redFactionSelect) + + # Setup default selected factions for i, r in enumerate(db.FACTIONS): self.redFactionSelect.addItem(r) - if r == "Russia 1990": # Default ennemy + if r == "Russia 1990": self.redFactionSelect.setCurrentIndex(i) - redFaction.setBuddy(self.redFactionSelect) + if r == "USA 2005": + self.blueFactionSelect.setCurrentIndex(i) self.blueSideRecap = QtWidgets.QLabel("") self.blueSideRecap.setFont(CONST.FONT_PRIMARY_I) @@ -200,8 +204,8 @@ class FactionSelection(QtWidgets.QWizardPage): red_faction = db.FACTIONS[self.redFactionSelect.currentText()] blue_faction = db.FACTIONS[self.blueFactionSelect.currentText()] - red_units = red_faction["units"] - blue_units = blue_faction["units"] + red_units = red_faction.aircrafts + blue_units = blue_faction.aircrafts blue_txt = "" for u in blue_units: @@ -218,16 +222,16 @@ class FactionSelection(QtWidgets.QWizardPage): self.redSideRecap.setText(red_txt) has_mod = False - if "requirements" in red_faction.keys(): + if len(red_faction.requirements.keys()) > 0: has_mod = True - for mod in red_faction["requirements"].keys(): - self.requiredMods.setText(self.requiredMods.text() + "\n
  • " + mod + ": " + red_faction["requirements"][mod] + "
  • ") + for mod in red_faction.requirements.keys(): + self.requiredMods.setText(self.requiredMods.text() + "\n
  • " + mod + ": " + red_faction.requirements[mod] + "
  • ") - if "requirements" in blue_faction.keys(): + if len(blue_faction.requirements.keys()) > 0: has_mod = True - for mod in blue_faction["requirements"].keys(): - if not "requirements" in red_faction.keys() or mod not in red_faction["requirements"].keys(): - self.requiredMods.setText(self.requiredMods.text() + "\n
  • " + mod + ": " + blue_faction["requirements"][mod] + "
  • ") + for mod in blue_faction.requirements.keys(): + if not "requirements" in red_faction.keys() or mod not in red_faction.requirements.keys(): + self.requiredMods.setText(self.requiredMods.text() + "\n
  • " + mod + ": " + blue_faction.requirements[mod] + "
  • ") if has_mod: self.requiredMods.setText(self.requiredMods.text() + "\n\n") diff --git a/resources/campaigns/battle_of_britain.json b/resources/campaigns/battle_of_britain.json index 38f5ab0f..281e2db3 100644 --- a/resources/campaigns/battle_of_britain.json +++ b/resources/campaigns/battle_of_britain.json @@ -1,86 +1,88 @@ { - "name": "The Channel - Battle of Britain", - "theater": "The Channel", - "player_points": [ - { - "type": "airbase", - "id": "Hawkinge", - "size": 600, - "importance": 1 - }, - { - "type": "airbase", - "id": "Lympne", - "size": 600, - "importance": 1 - }, - { - "type": "airbase", - "id": "Manston", - "size": 600, - "importance": 1 - }, - { - "type": "airbase", - "id": "High Halden", - "size": 600, - "importance": 1 - } + "name": "The Channel - Battle of Britain", + "theater": "The Channel", + "authors": "Khopa", + "description": "", + "player_points": [ + { + "type": "airbase", + "id": "Hawkinge", + "size": 600, + "importance": 1 + }, + { + "type": "airbase", + "id": "Lympne", + "size": 600, + "importance": 1 + }, + { + "type": "airbase", + "id": "Manston", + "size": 600, + "importance": 1 + }, + { + "type": "airbase", + "id": "High Halden", + "size": 600, + "importance": 1 + } + ], + "enemy_points": [ + { + "type": "airbase", + "id": "Dunkirk Mardyck", + "size": 600, + "importance": 1, + "captured_invert": true + }, + { + "type": "airbase", + "id": "Saint Omer Longuenesse", + "size": 600, + "importance": 1, + "captured_invert": true + }, + { + "type": "airbase", + "id": "Merville Calonne", + "size": 600, + "importance": 1, + "captured_invert": true + }, + { + "type": "airbase", + "id": "Abbeville Drucat", + "size": 600, + "importance": 1, + "captured_invert": true + } + ], + "links": [ + [ + "Hawkinge", + "Lympne" ], - "enemy_points": [ - { - "type": "airbase", - "id": "Dunkirk Mardyck", - "size": 600, - "importance": 1, - "captured_invert": true - }, - { - "type": "airbase", - "id": "Saint Omer Longuenesse", - "size": 600, - "importance": 1, - "captured_invert": true - }, - { - "type": "airbase", - "id": "Merville Calonne", - "size": 600, - "importance": 1, - "captured_invert": true - }, - { - "type": "airbase", - "id": "Abbeville Drucat", - "size": 600, - "importance": 1, - "captured_invert": true - } + [ + "Hawkinge", + "Manston" ], - "links": [ - [ - "Hawkinge", - "Lympne" - ], - [ - "Hawkinge", - "Manston" - ], - [ - "High Halden", - "Lympne" - ], - [ - "Dunkirk Mardyck", - "Saint Omer Longuenesse" - ], - [ - "Merville Calonne", - "Saint Omer Longuenesse" - ], - [ - "Abbeville Drucat", - "Saint Omer Longuenesse" - ] + [ + "High Halden", + "Lympne" + ], + [ + "Dunkirk Mardyck", + "Saint Omer Longuenesse" + ], + [ + "Merville Calonne", + "Saint Omer Longuenesse" + ], + [ + "Abbeville Drucat", + "Saint Omer Longuenesse" ] + ] } \ No newline at end of file diff --git a/resources/campaigns/desert_war.json b/resources/campaigns/desert_war.json index e49f7081..088ba7b9 100644 --- a/resources/campaigns/desert_war.json +++ b/resources/campaigns/desert_war.json @@ -1,61 +1,63 @@ { - "name": "Persian Gulf - Desert War", - "theater": "Persian Gulf", - "player_points": [ - { - "type": "airbase", - "id": "Liwa Airbase", - "size": 2000, - "importance": 1.2 - }, - { - "type": "lha", - "id": 1002, - "x": -164000, - "y": -257000, - "captured_invert": true - }, - { - "type": "carrier", - "id": 1001, - "x": -124000, - "y": -303000, - "captured_invert": true - } + "name": "Persian Gulf - Desert War", + "theater": "Persian Gulf", + "authors": "Khopa", + "description": "", + "player_points": [ + { + "type": "airbase", + "id": "Liwa Airbase", + "size": 2000, + "importance": 1.2 + }, + { + "type": "lha", + "id": 1002, + "x": -164000, + "y": -257000, + "captured_invert": true + }, + { + "type": "carrier", + "id": 1001, + "x": -124000, + "y": -303000, + "captured_invert": true + } + ], + "enemy_points": [ + { + "type": "airbase", + "id": "Al Ain International Airport", + "size": 2000, + "importance": 1, + "captured_invert": true + }, + { + "type": "airbase", + "id": "Al Maktoum Intl", + "size": 2000, + "importance": 1 + }, + { + "type": "airbase", + "id": "Al Minhad AB", + "size": 1000, + "importance": 1 + } + ], + "links": [ + [ + "Al Ain International Airport", + "Liwa Airbase" ], - "enemy_points": [ - { - "type": "airbase", - "id": "Al Ain International Airport", - "size": 2000, - "importance": 1, - "captured_invert": true - }, - { - "type": "airbase", - "id": "Al Maktoum Intl", - "size": 2000, - "importance": 1 - }, - { - "type": "airbase", - "id": "Al Minhad AB", - "size": 1000, - "importance": 1 - } + [ + "Al Ain International Airport", + "Al Maktoum Intl" ], - "links": [ - [ - "Al Ain International Airport", - "Liwa Airbase" - ], - [ - "Al Ain International Airport", - "Al Maktoum Intl" - ], - [ - "Al Maktoum Intl", - "Al Minhad AB" - ] + [ + "Al Maktoum Intl", + "Al Minhad AB" ] + ] } \ No newline at end of file diff --git a/resources/campaigns/dunkirk.json b/resources/campaigns/dunkirk.json index d3cf11b0..a8b6c1ba 100644 --- a/resources/campaigns/dunkirk.json +++ b/resources/campaigns/dunkirk.json @@ -1,77 +1,79 @@ { - "name": "The Channel - Dunkirk", - "theater": "The Channel", - "player_points": [ - { - "type": "airbase", - "id": "Hawkinge", - "size": 600, - "importance": 1 - }, - { - "type": "airbase", - "id": "Lympne", - "size": 600, - "importance": 1 - }, - { - "type": "airbase", - "id": "Manston", - "size": 600, - "importance": 1, - "captured_invert": true - }, - { - "type": "airbase", - "id": "Dunkirk Mardyck", - "size": 600, - "importance": 1, - "captured_invert": true - } + "name": "The Channel - Dunkirk", + "theater": "The Channel", + "authors": "Khopa", + "description": "", + "player_points": [ + { + "type": "airbase", + "id": "Hawkinge", + "size": 600, + "importance": 1 + }, + { + "type": "airbase", + "id": "Lympne", + "size": 600, + "importance": 1 + }, + { + "type": "airbase", + "id": "Manston", + "size": 600, + "importance": 1, + "captured_invert": true + }, + { + "type": "airbase", + "id": "Dunkirk Mardyck", + "size": 600, + "importance": 1, + "captured_invert": true + } + ], + "enemy_points": [ + { + "type": "airbase", + "id": "Saint Omer Longuenesse", + "size": 600, + "importance": 1, + "captured_invert": true + }, + { + "type": "airbase", + "id": "Merville Calonne", + "size": 600, + "importance": 1, + "captured_invert": true + }, + { + "type": "airbase", + "id": "Abbeville Drucat", + "size": 600, + "importance": 1, + "captured_invert": true + } + ], + "links": [ + [ + "Hawkinge", + "Lympne" ], - "enemy_points": [ - { - "type": "airbase", - "id": "Saint Omer Longuenesse", - "size": 600, - "importance": 1, - "captured_invert": true - }, - { - "type": "airbase", - "id": "Merville Calonne", - "size": 600, - "importance": 1, - "captured_invert": true - }, - { - "type": "airbase", - "id": "Abbeville Drucat", - "size": 600, - "importance": 1, - "captured_invert": true - } + [ + "Hawkinge", + "Manston" ], - "links": [ - [ - "Hawkinge", - "Lympne" - ], - [ - "Hawkinge", - "Manston" - ], - [ - "Dunkirk Mardyck", - "Saint Omer Longuenesse" - ], - [ - "Merville Calonne", - "Saint Omer Longuenesse" - ], - [ - "Abbeville Drucat", - "Saint Omer Longuenesse" - ] + [ + "Dunkirk Mardyck", + "Saint Omer Longuenesse" + ], + [ + "Merville Calonne", + "Saint Omer Longuenesse" + ], + [ + "Abbeville Drucat", + "Saint Omer Longuenesse" ] + ] } \ No newline at end of file diff --git a/resources/campaigns/emirates.json b/resources/campaigns/emirates.json index 1e032e63..acab6325 100644 --- a/resources/campaigns/emirates.json +++ b/resources/campaigns/emirates.json @@ -1,106 +1,108 @@ { - "name": "Persian Gulf - Emirates", - "theater": "Persian Gulf", - "player_points": [ - { - "type": "airbase", - "id": "Fujairah Intl", - "radials": [ - 180, - 225, - 270, - 315, - 0 - ], - "size": 1000, - "importance": 1, - "captured_invert": true - }, - { - "type": "lha", - "id": 1002, - "x": -79770, - "y": 49430, - "captured_invert": true - }, - { - "type": "carrier", - "id": 1001, - "x": -61770, - "y": 69039, - "captured_invert": true - } + "name": "Persian Gulf - Emirates", + "theater": "Persian Gulf", + "authors": "Khopa", + "description": "", + "player_points": [ + { + "type": "airbase", + "id": "Fujairah Intl", + "radials": [ + 180, + 225, + 270, + 315, + 0 + ], + "size": 1000, + "importance": 1, + "captured_invert": true + }, + { + "type": "lha", + "id": 1002, + "x": -79770, + "y": 49430, + "captured_invert": true + }, + { + "type": "carrier", + "id": 1001, + "x": -61770, + "y": 69039, + "captured_invert": true + } + ], + "enemy_points": [ + { + "type": "airbase", + "id": "Al Dhafra AB", + "size": 2000, + "importance": 1.2 + }, + { + "type": "airbase", + "id": "Al Ain International Airport", + "size": 2000, + "importance": 1 + }, + { + "type": "airbase", + "id": "Al Maktoum Intl", + "size": 2000, + "importance": 1 + }, + { + "type": "airbase", + "id": "Al Minhad AB", + "size": 1000, + "importance": 1 + }, + { + "type": "airbase", + "id": "Sharjah Intl", + "size": 2000, + "importance": 1 + }, + { + "type": "airbase", + "id": "Ras Al Khaimah", + "size": 1000, + "importance": 1 + } + ], + "links": [ + [ + "Al Ain International Airport", + "Al Dhafra AB" ], - "enemy_points": [ - { - "type": "airbase", - "id": "Al Dhafra AB", - "size": 2000, - "importance": 1.2 - }, - { - "type": "airbase", - "id": "Al Ain International Airport", - "size": 2000, - "importance": 1 - }, - { - "type": "airbase", - "id": "Al Maktoum Intl", - "size": 2000, - "importance": 1 - }, - { - "type": "airbase", - "id": "Al Minhad AB", - "size": 1000, - "importance": 1 - }, - { - "type": "airbase", - "id": "Sharjah Intl", - "size": 2000, - "importance": 1 - }, - { - "type": "airbase", - "id": "Ras Al Khaimah", - "size": 1000, - "importance": 1 - } + [ + "Al Dhafra AB", + "Al Maktoum Intl" ], - "links": [ - [ - "Al Ain International Airport", - "Al Dhafra AB" - ], - [ - "Al Dhafra AB", - "Al Maktoum Intl" - ], - [ - "Al Ain International Airport", - "Fujairah Intl" - ], - [ - "Al Ain International Airport", - "Al Maktoum Intl" - ], - [ - "Al Maktoum Intl", - "Al Minhad AB" - ], - [ - "Al Minhad AB", - "Sharjah Intl" - ], - [ - "Ras Al Khaimah", - "Sharjah Intl" - ], - [ - "Fujairah Intl", - "Sharjah Intl" - ] + [ + "Al Ain International Airport", + "Fujairah Intl" + ], + [ + "Al Ain International Airport", + "Al Maktoum Intl" + ], + [ + "Al Maktoum Intl", + "Al Minhad AB" + ], + [ + "Al Minhad AB", + "Sharjah Intl" + ], + [ + "Ras Al Khaimah", + "Sharjah Intl" + ], + [ + "Fujairah Intl", + "Sharjah Intl" ] + ] } \ No newline at end of file diff --git a/resources/campaigns/full_caucasus.json b/resources/campaigns/full_caucasus.json new file mode 100644 index 00000000..a312a999 --- /dev/null +++ b/resources/campaigns/full_caucasus.json @@ -0,0 +1,168 @@ +{ + "name": "Caucasus - Full Map", + "theater": "Caucasus", + "authors": "george", + "description": "Full Caucasus Map", + "player_points": [ + { + "type": "airbase", + "id": "Kobuleti", + "size": 600, + "importance": 1 + }, + { + "type": "airbase", + "id": "Senaki-Kolkhi", + "size": 600, + "importance": 1 + }, + { + "type": "airbase", + "id": "Kutaisi", + "size": 600, + "importance": 1 + }, + { + "type": "carrier", + "id": 1001, + "x": -304708, + "y": 552839, + "captured_invert": true + }, + { + "type": "lha", + "id": 1002, + "x": -326050.6875, + "y": 519452.1875, + "captured_invert": true + } + ], + "enemy_points": [ + { + "type": "airbase", + "id": "Beslan", + "size": 1000, + "importance": 1.3 + }, + { + "type": "airbase", + "id": "Nalchik", + "size": 1000, + "importance": 1.1 + }, + { + "type": "airbase", + "id": "Mozdok", + "size": 2000, + "importance": 1.1 + }, + { + "type": "airbase", + "id": "Mineralnye Vody", + "size": 2000, + "importance": 1.2 + }, + { + "type": "airbase", + "id": "Maykop-Khanskaya", + "size": 3000, + "importance": 1.2 + }, + { + "type": "airbase", + "id": "Sukhumi-Babushara", + "size": 2000, + "importance": 1.3 + }, + { + "type": "airbase", + "id": "Gudauta", + "size": 2000, + "importance": 1.3 + }, + { + "type": "airbase", + "id": "Sochi-Adler", + "size": 2000, + "importance": 1.1 + }, + { + "type": "airbase", + "id": "Gelendzhik", + "size": 2000, + "importance": 1.3 + }, + { + "type": "airbase", + "id": "Vaziani", + "size": 2000, + "importance": 1.3 + } + ], + "links": [ + [ + "Kutaisi", + "Vaziani" + ], + [ + "Beslan", + "Vaziani" + ], + [ + "Beslan", + "Mozdok" + ], + [ + "Beslan", + "Nalchik" + ], + [ + "Mozdok", + "Nalchik" + ], + [ + "Mineralnye Vody", + "Nalchik" + ], + [ + "Mineralnye Vody", + "Mozdok" + ], + [ + "Maykop-Khanskaya", + "Mineralnye Vody" + ], + [ + "Maykop-Khanskaya", + "Gelendzhik" + ], + [ + "Gelendzhik", + "Sochi-Adler" + ], + [ + "Gudauta", + "Sochi-Adler" + ], + [ + "Gudauta", + "Sukhumi-Babushara" + ], + [ + "Senaki-Kolkhi", + "Sukhumi-Babushara" + ], + [ + "Kutaisi", + "Senaki-Kolkhi" + ], + [ + "Senaki-Kolkhi", + "Kobuleti" + ], + [ + "Kobuleti", + "Kutaisi" + ] + ] +} \ No newline at end of file diff --git a/resources/campaigns/full_map.json b/resources/campaigns/full_map.json index f88963ea..935fb0f0 100644 --- a/resources/campaigns/full_map.json +++ b/resources/campaigns/full_map.json @@ -1,183 +1,185 @@ { - "name": "Syria - Full Map", - "theater": "Syria", - "player_points": [ - { - "type": "airbase", - "id": "Ramat David", - "size": 1000, - "importance": 1.4 - }, - { - "type": "carrier", - "id": 1001, - "x": -151000, - "y": -106000, - "captured_invert": true - }, - { - "type": "lha", - "id": 1002, - "x": -131000, - "y": -161000, - "captured_invert": true - } + "name": "Syria - Full Map", + "theater": "Syria", + "authors": "Khopa", + "description": "", + "player_points": [ + { + "type": "airbase", + "id": "Ramat David", + "size": 1000, + "importance": 1.4 + }, + { + "type": "carrier", + "id": 1001, + "x": -151000, + "y": -106000, + "captured_invert": true + }, + { + "type": "lha", + "id": 1002, + "x": -131000, + "y": -161000, + "captured_invert": true + } + ], + "enemy_points": [ + { + "type": "airbase", + "id": "King Hussein Air College", + "size": 1000, + "importance": 1.4 + }, + { + "type": "airbase", + "id": "Khalkhalah", + "size": 1000, + "importance": 1.2 + }, + { + "type": "airbase", + "id": "Al-Dumayr", + "size": 1000, + "importance": 1.2 + }, + { + "type": "airbase", + "id": "Al Qusayr", + "size": 1000, + "importance": 1 + }, + { + "type": "airbase", + "id": "Rene Mouawad", + "size": 1000, + "importance": 1.4 + }, + { + "type": "airbase", + "id": "Hama", + "size": 1000, + "importance": 1 + }, + { + "type": "airbase", + "id": "Bassel Al-Assad", + "size": 1000, + "importance": 1.4 + }, + { + "type": "airbase", + "id": "Palmyra", + "size": 1000, + "importance": 1 + }, + { + "type": "airbase", + "id": "Tabqa", + "size": 1000, + "importance": 1 + }, + { + "type": "airbase", + "id": "Jirah", + "size": 1000, + "importance": 1 + }, + { + "type": "airbase", + "id": "Aleppo", + "size": 1000, + "importance": 1.2 + }, + { + "type": "airbase", + "id": "Minakh", + "size": 1000, + "importance": 1 + }, + { + "type": "airbase", + "id": "Hatay", + "size": 1000, + "importance": 1.4 + }, + { + "type": "airbase", + "id": "Incirlik", + "size": 1000, + "importance": 1.4, + "captured_invert": true + } + ], + "links": [ + [ + "King Hussein Air College", + "Ramat David" ], - "enemy_points": [ - { - "type": "airbase", - "id": "King Hussein Air College", - "size": 1000, - "importance": 1.4 - }, - { - "type": "airbase", - "id": "Khalkhalah", - "size": 1000, - "importance": 1.2 - }, - { - "type": "airbase", - "id": "Al-Dumayr", - "size": 1000, - "importance": 1.2 - }, - { - "type": "airbase", - "id": "Al Qusayr", - "size": 1000, - "importance": 1 - }, - { - "type": "airbase", - "id": "Rene Mouawad", - "size": 1000, - "importance": 1.4 - }, - { - "type": "airbase", - "id": "Hama", - "size": 1000, - "importance": 1 - }, - { - "type": "airbase", - "id": "Bassel Al-Assad", - "size": 1000, - "importance": 1.4 - }, - { - "type": "airbase", - "id": "Palmyra", - "size": 1000, - "importance": 1 - }, - { - "type": "airbase", - "id": "Tabqa", - "size": 1000, - "importance": 1 - }, - { - "type": "airbase", - "id": "Jirah", - "size": 1000, - "importance": 1 - }, - { - "type": "airbase", - "id": "Aleppo", - "size": 1000, - "importance": 1.2 - }, - { - "type": "airbase", - "id": "Minakh", - "size": 1000, - "importance": 1 - }, - { - "type": "airbase", - "id": "Hatay", - "size": 1000, - "importance": 1.4 - }, - { - "type": "airbase", - "id": "Incirlik", - "size": 1000, - "importance": 1.4, - "captured_invert": true - } + [ + "Khalkhalah", + "King Hussein Air College" ], - "links": [ - [ - "King Hussein Air College", - "Ramat David" - ], - [ - "Khalkhalah", - "King Hussein Air College" - ], - [ - "Al-Dumayr", - "Khalkhalah" - ], - [ - "Al Qusayr", - "Al-Dumayr" - ], - [ - "Al Qusayr", - "Hama" - ], - [ - "Al Qusayr", - "Palmyra" - ], - [ - "Al Qusayr", - "Rene Mouawad" - ], - [ - "Bassel Al-Assad", - "Rene Mouawad" - ], - [ - "Aleppo", - "Hama" - ], - [ - "Bassel Al-Assad", - "Hama" - ], - [ - "Bassel Al-Assad", - "Hatay" - ], - [ - "Palmyra", - "Tabqa" - ], - [ - "Jirah", - "Tabqa" - ], - [ - "Aleppo", - "Jirah" - ], - [ - "Aleppo", - "Minakh" - ], - [ - "Hatay", - "Minakh" - ], - [ - "Incirlik", - "Minakh" - ] + [ + "Al-Dumayr", + "Khalkhalah" + ], + [ + "Al Qusayr", + "Al-Dumayr" + ], + [ + "Al Qusayr", + "Hama" + ], + [ + "Al Qusayr", + "Palmyra" + ], + [ + "Al Qusayr", + "Rene Mouawad" + ], + [ + "Bassel Al-Assad", + "Rene Mouawad" + ], + [ + "Aleppo", + "Hama" + ], + [ + "Bassel Al-Assad", + "Hama" + ], + [ + "Bassel Al-Assad", + "Hatay" + ], + [ + "Palmyra", + "Tabqa" + ], + [ + "Jirah", + "Tabqa" + ], + [ + "Aleppo", + "Jirah" + ], + [ + "Aleppo", + "Minakh" + ], + [ + "Hatay", + "Minakh" + ], + [ + "Incirlik", + "Minakh" ] + ] } \ No newline at end of file diff --git a/resources/campaigns/golan_heights_battle.json b/resources/campaigns/golan_heights_battle.json index f498b3bb..978e38eb 100644 --- a/resources/campaigns/golan_heights_battle.json +++ b/resources/campaigns/golan_heights_battle.json @@ -1,81 +1,83 @@ { - "name": "Syria - Golan heights battle", - "theater": "Syria", - "player_points": [ - { - "type": "airbase", - "id": "Ramat David", - "size": 1000, - "importance": 1.4 - }, - { - "type": "carrier", - "id": 1001, - "x": -280000, - "y": -238000, - "captured_invert": true - }, - { - "type": "lha", - "id": 1002, - "x": -237000, - "y": -89800, - "captured_invert": true - } + "name": "Syria - Golan heights battle", + "theater": "Syria", + "authors": "Khopa", + "description": "", + "player_points": [ + { + "type": "airbase", + "id": "Ramat David", + "size": 1000, + "importance": 1.4 + }, + { + "type": "carrier", + "id": 1001, + "x": -280000, + "y": -238000, + "captured_invert": true + }, + { + "type": "lha", + "id": 1002, + "x": -237000, + "y": -89800, + "captured_invert": true + } + ], + "enemy_points": [ + { + "type": "airbase", + "id": "Khalkhalah", + "size": 1000, + "importance": 1.2 + }, + { + "type": "airbase", + "id": "King Hussein Air College", + "size": 1000, + "importance": 1.4 + }, + { + "type": "airbase", + "id": "Marj Ruhayyil", + "size": 1000, + "importance": 1 + }, + { + "type": "airbase", + "id": "Mezzeh", + "size": 1000, + "importance": 1.2 + }, + { + "type": "airbase", + "id": "Al-Dumayr", + "size": 1000, + "importance": 1.2, + "captured_invert": true + } + ], + "links": [ + [ + "Khalkhalah", + "Ramat David" ], - "enemy_points": [ - { - "type": "airbase", - "id": "Khalkhalah", - "size": 1000, - "importance": 1.2 - }, - { - "type": "airbase", - "id": "King Hussein Air College", - "size": 1000, - "importance": 1.4 - }, - { - "type": "airbase", - "id": "Marj Ruhayyil", - "size": 1000, - "importance": 1 - }, - { - "type": "airbase", - "id": "Mezzeh", - "size": 1000, - "importance": 1.2 - }, - { - "type": "airbase", - "id": "Al-Dumayr", - "size": 1000, - "importance": 1.2, - "captured_invert": true - } + [ + "Khalkhalah", + "King Hussein Air College" ], - "links": [ - [ - "Khalkhalah", - "Ramat David" - ], - [ - "Khalkhalah", - "King Hussein Air College" - ], - [ - "Khalkhalah", - "Marj Ruhayyil" - ], - [ - "Marj Ruhayyil", - "Mezzeh" - ], - [ - "Al-Dumayr", - "Marj Ruhayyil" - ] + [ + "Khalkhalah", + "Marj Ruhayyil" + ], + [ + "Marj Ruhayyil", + "Mezzeh" + ], + [ + "Al-Dumayr", + "Marj Ruhayyil" ] + ] } \ No newline at end of file diff --git a/resources/campaigns/inherent_resolve.json b/resources/campaigns/inherent_resolve.json index b968bc77..7c178920 100644 --- a/resources/campaigns/inherent_resolve.json +++ b/resources/campaigns/inherent_resolve.json @@ -1,82 +1,84 @@ { - "name": "Syria - Inherent Resolve", - "theater": "Syria", - "player_points": [ - { - "type": "airbase", - "id": "King Hussein Air College", - "size": 1000, - "importance": 1.4 - }, - { - "type": "airbase", - "id": "Incirlik", - "size": 1000, - "importance": 1.4, - "captured_invert": true - }, - { - "type": "carrier", - "id": 1001, - "x": -210000, - "y": -200000, - "captured_invert": true - }, - { - "type": "lha", - "id": 1002, - "x": -131000, - "y": -161000, - "captured_invert": true - } + "name": "Syria - Inherent Resolve", + "theater": "Syria", + "authors": "Khopa", + "description": "", + "player_points": [ + { + "type": "airbase", + "id": "King Hussein Air College", + "size": 1000, + "importance": 1.4 + }, + { + "type": "airbase", + "id": "Incirlik", + "size": 1000, + "importance": 1.4, + "captured_invert": true + }, + { + "type": "carrier", + "id": 1001, + "x": -210000, + "y": -200000, + "captured_invert": true + }, + { + "type": "lha", + "id": 1002, + "x": -131000, + "y": -161000, + "captured_invert": true + } + ], + "enemy_points": [ + { + "type": "airbase", + "id": "Khalkhalah", + "size": 1000, + "importance": 1.2 + }, + { + "type": "airbase", + "id": "Palmyra", + "size": 1000, + "importance": 1 + }, + { + "type": "airbase", + "id": "Tabqa", + "size": 1000, + "importance": 1 + }, + { + "type": "airbase", + "id": "Jirah", + "size": 1000, + "importance": 1, + "captured_invert": true + } + ], + "links": [ + [ + "Khalkhalah", + "King Hussein Air College" ], - "enemy_points": [ - { - "type": "airbase", - "id": "Khalkhalah", - "size": 1000, - "importance": 1.2 - }, - { - "type": "airbase", - "id": "Palmyra", - "size": 1000, - "importance": 1 - }, - { - "type": "airbase", - "id": "Tabqa", - "size": 1000, - "importance": 1 - }, - { - "type": "airbase", - "id": "Jirah", - "size": 1000, - "importance": 1, - "captured_invert": true - } + [ + "Incirlik", + "Incirlik" ], - "links": [ - [ - "Khalkhalah", - "King Hussein Air College" - ], - [ - "Incirlik", - "Incirlik" - ], - [ - "Khalkhalah", - "Palmyra" - ], - [ - "Palmyra", - "Tabqa" - ], - [ - "Jirah", - "Tabqa" - ] + [ + "Khalkhalah", + "Palmyra" + ], + [ + "Palmyra", + "Tabqa" + ], + [ + "Jirah", + "Tabqa" ] + ] } \ No newline at end of file diff --git a/resources/campaigns/invasion_from_turkey.json b/resources/campaigns/invasion_from_turkey.json index de0f6189..7473d5d4 100644 --- a/resources/campaigns/invasion_from_turkey.json +++ b/resources/campaigns/invasion_from_turkey.json @@ -1,85 +1,87 @@ { - "name": "Syria - Invasion from Turkey", - "theater": "Syria", - "player_points": [ - { - "type": "airbase", - "id": "Incirlik", - "size": 1000, - "importance": 1.4 - }, - { - "type": "airbase", - "id": "Hatay", - "size": 1000, - "importance": 1.4 - }, - { - "type": "carrier", - "id": 1001, - "x": 133000, - "y": -54000 - }, - { - "type": "lha", - "id": 1002, - "x": 155000, - "y": -19000 - } + "name": "Syria - Invasion from Turkey", + "theater": "Syria", + "authors": "Khopa", + "description": "", + "player_points": [ + { + "type": "airbase", + "id": "Incirlik", + "size": 1000, + "importance": 1.4 + }, + { + "type": "airbase", + "id": "Hatay", + "size": 1000, + "importance": 1.4 + }, + { + "type": "carrier", + "id": 1001, + "x": 133000, + "y": -54000 + }, + { + "type": "lha", + "id": 1002, + "x": 155000, + "y": -19000 + } + ], + "enemy_points": [ + { + "type": "airbase", + "id": "Minakh", + "size": 1000, + "importance": 1 + }, + { + "type": "airbase", + "id": "Aleppo", + "size": 1000, + "importance": 1.2 + }, + { + "type": "airbase", + "id": "Kuweires", + "size": 1000, + "importance": 1 + }, + { + "type": "airbase", + "id": "Jirah", + "size": 1000, + "importance": 1 + }, + { + "type": "airbase", + "id": "Tabqa", + "size": 1000, + "importance": 1, + "captured_invert": true + } + ], + "links": [ + [ + "Hatay", + "Minakh" ], - "enemy_points": [ - { - "type": "airbase", - "id": "Minakh", - "size": 1000, - "importance": 1 - }, - { - "type": "airbase", - "id": "Aleppo", - "size": 1000, - "importance": 1.2 - }, - { - "type": "airbase", - "id": "Kuweires", - "size": 1000, - "importance": 1 - }, - { - "type": "airbase", - "id": "Jirah", - "size": 1000, - "importance": 1 - }, - { - "type": "airbase", - "id": "Tabqa", - "size": 1000, - "importance": 1, - "captured_invert": true - } + [ + "Aleppo", + "Minakh" ], - "links": [ - [ - "Hatay", - "Minakh" - ], - [ - "Aleppo", - "Minakh" - ], - [ - "Aleppo", - "Kuweires" - ], - [ - "Jirah", - "Kuweires" - ], - [ - "Jirah", - "Tabqa" - ] + [ + "Aleppo", + "Kuweires" + ], + [ + "Jirah", + "Kuweires" + ], + [ + "Jirah", + "Tabqa" ] + ] } \ No newline at end of file diff --git a/resources/campaigns/invasion_of_iran.json b/resources/campaigns/invasion_of_iran.json index 888d29f5..727e2543 100644 --- a/resources/campaigns/invasion_of_iran.json +++ b/resources/campaigns/invasion_of_iran.json @@ -1,141 +1,143 @@ { - "name": "Persian Gulf - Invasion of Iran", - "theater": "Persian Gulf", - "player_points": [ - { - "type": "airbase", - "id": "Ras Al Khaimah", - "size": 1000, - "importance": 1.2 - }, - { - "type": "airbase", - "id": "Khasab", - "size": 600, - "importance": 1.2 - }, - { - "type": "airbase", - "id": "Qeshm Island", - "radials": [ - 270, - 315, - 0, - 45, - 90, - 135, - 180 - ], - "size": 600, - "importance": 1.1 - }, - { - "type": "airbase", - "id": "Havadarya", - "radials": [ - 225, - 270, - 315, - 0, - 45 - ], - "size": 1000, - "importance": 1.4 - }, - { - "type": "airbase", - "id": "Bandar Abbas Intl", - "size": 2000, - "importance": 1.4 - }, - { - "type": "carrier", - "id": 1001, - "x": 59514.324335475, - "y": 28165.517980635 - }, - { - "type": "lha", - "id": 1002, - "x": -27500.813952358, - "y": -147000.65947136 - } + "name": "Persian Gulf - Invasion of Iran", + "theater": "Persian Gulf", + "authors": "Khopa", + "description": "", + "player_points": [ + { + "type": "airbase", + "id": "Ras Al Khaimah", + "size": 1000, + "importance": 1.2 + }, + { + "type": "airbase", + "id": "Khasab", + "size": 600, + "importance": 1.2 + }, + { + "type": "airbase", + "id": "Qeshm Island", + "radials": [ + 270, + 315, + 0, + 45, + 90, + 135, + 180 + ], + "size": 600, + "importance": 1.1 + }, + { + "type": "airbase", + "id": "Havadarya", + "radials": [ + 225, + 270, + 315, + 0, + 45 + ], + "size": 1000, + "importance": 1.4 + }, + { + "type": "airbase", + "id": "Bandar Abbas Intl", + "size": 2000, + "importance": 1.4 + }, + { + "type": "carrier", + "id": 1001, + "x": 59514.324335475, + "y": 28165.517980635 + }, + { + "type": "lha", + "id": 1002, + "x": -27500.813952358, + "y": -147000.65947136 + } + ], + "enemy_points": [ + { + "type": "airbase", + "id": "Bandar Lengeh", + "radials": [ + 270, + 315, + 0, + 45 + ], + "size": 600, + "importance": 1.4 + }, + { + "type": "airbase", + "id": "Shiraz International Airport", + "size": 2000, + "importance": 1.4, + "captured_invert": true + }, + { + "type": "airbase", + "id": "Jiroft Airport", + "size": 2000, + "importance": 1.4 + }, + { + "type": "airbase", + "id": "Kerman Airport", + "size": 2000, + "importance": 1.4 + }, + { + "type": "airbase", + "id": "Lar Airbase", + "size": 1000, + "importance": 1.4 + } + ], + "links": [ + [ + "Khasab", + "Ras Al Khaimah" ], - "enemy_points": [ - { - "type": "airbase", - "id": "Bandar Lengeh", - "radials": [ - 270, - 315, - 0, - 45 - ], - "size": 600, - "importance": 1.4 - }, - { - "type": "airbase", - "id": "Shiraz International Airport", - "size": 2000, - "importance": 1.4, - "captured_invert": true - }, - { - "type": "airbase", - "id": "Jiroft Airport", - "size": 2000, - "importance": 1.4 - }, - { - "type": "airbase", - "id": "Kerman Airport", - "size": 2000, - "importance": 1.4 - }, - { - "type": "airbase", - "id": "Lar Airbase", - "size": 1000, - "importance": 1.4 - } + [ + "Bandar Lengeh", + "Lar Airbase" ], - "links": [ - [ - "Khasab", - "Ras Al Khaimah" - ], - [ - "Bandar Lengeh", - "Lar Airbase" - ], - [ - "Havadarya", - "Lar Airbase" - ], - [ - "Bandar Abbas Intl", - "Havadarya" - ], - [ - "Bandar Abbas Intl", - "Jiroft Airport" - ], - [ - "Lar Airbase", - "Shiraz International Airport" - ], - [ - "Kerman Airport", - "Shiraz International Airport" - ], - [ - "Jiroft Airport", - "Kerman Airport" - ], - [ - "Kerman Airport", - "Lar Airbase" - ] + [ + "Havadarya", + "Lar Airbase" + ], + [ + "Bandar Abbas Intl", + "Havadarya" + ], + [ + "Bandar Abbas Intl", + "Jiroft Airport" + ], + [ + "Lar Airbase", + "Shiraz International Airport" + ], + [ + "Kerman Airport", + "Shiraz International Airport" + ], + [ + "Jiroft Airport", + "Kerman Airport" + ], + [ + "Kerman Airport", + "Lar Airbase" ] + ] } \ No newline at end of file diff --git a/resources/campaigns/invasion_of_iran_[lite].json b/resources/campaigns/invasion_of_iran_[lite].json index aa31a7fa..2ac59c3b 100644 --- a/resources/campaigns/invasion_of_iran_[lite].json +++ b/resources/campaigns/invasion_of_iran_[lite].json @@ -1,75 +1,77 @@ { - "name": "Persian Gulf - Invasion of Iran [Lite]", - "theater": "Persian Gulf", - "player_points": [ - { - "type": "airbase", - "id": "Bandar Lengeh", - "radials": [ - 270, - 315, - 0, - 45 - ], - "size": 600, - "importance": 1.4 - }, - { - "type": "carrier", - "id": 1001, - "x": 72000.324335475, - "y": -376000 - }, - { - "type": "lha", - "id": 1002, - "x": -27500.813952358, - "y": -147000.65947136 - } + "name": "Persian Gulf - Invasion of Iran [Lite]", + "theater": "Persian Gulf", + "authors": "Khopa", + "description": "", + "player_points": [ + { + "type": "airbase", + "id": "Bandar Lengeh", + "radials": [ + 270, + 315, + 0, + 45 + ], + "size": 600, + "importance": 1.4 + }, + { + "type": "carrier", + "id": 1001, + "x": 72000.324335475, + "y": -376000 + }, + { + "type": "lha", + "id": 1002, + "x": -27500.813952358, + "y": -147000.65947136 + } + ], + "enemy_points": [ + { + "type": "airbase", + "id": "Shiraz International Airport", + "size": 2000, + "importance": 1.4, + "captured_invert": true + }, + { + "type": "airbase", + "id": "Jiroft Airport", + "size": 2000, + "importance": 1.4 + }, + { + "type": "airbase", + "id": "Kerman Airport", + "size": 2000, + "importance": 1.4 + }, + { + "type": "airbase", + "id": "Lar Airbase", + "size": 1000, + "importance": 1.4 + } + ], + "links": [ + [ + "Bandar Lengeh", + "Lar Airbase" ], - "enemy_points": [ - { - "type": "airbase", - "id": "Shiraz International Airport", - "size": 2000, - "importance": 1.4, - "captured_invert": true - }, - { - "type": "airbase", - "id": "Jiroft Airport", - "size": 2000, - "importance": 1.4 - }, - { - "type": "airbase", - "id": "Kerman Airport", - "size": 2000, - "importance": 1.4 - }, - { - "type": "airbase", - "id": "Lar Airbase", - "size": 1000, - "importance": 1.4 - } + [ + "Lar Airbase", + "Shiraz International Airport" ], - "links": [ - [ - "Bandar Lengeh", - "Lar Airbase" - ], - [ - "Lar Airbase", - "Shiraz International Airport" - ], - [ - "Kerman Airport", - "Shiraz International Airport" - ], - [ - "Jiroft Airport", - "Kerman Airport" - ] + [ + "Kerman Airport", + "Shiraz International Airport" + ], + [ + "Jiroft Airport", + "Kerman Airport" ] + ] } \ No newline at end of file diff --git a/resources/campaigns/normandy.json b/resources/campaigns/normandy.json index 8c71716e..f74554bc 100644 --- a/resources/campaigns/normandy.json +++ b/resources/campaigns/normandy.json @@ -1,83 +1,85 @@ { - "name": "Normandy - Normandy", - "theater": "Normandy", - "player_points": [ - { - "type": "airbase", - "id": "Chailey", - "size": 600, - "importance": 1 - }, - { - "type": "airbase", - "id": "Needs Oar Point", - "size": 600, - "importance": 1 - }, - { - "type": "airbase", - "id": "Deux Jumeaux", - "size": 600, - "importance": 1 - } + "name": "Normandy - Normandy", + "theater": "Normandy", + "authors": "Khopa", + "description": "", + "player_points": [ + { + "type": "airbase", + "id": "Chailey", + "size": 600, + "importance": 1 + }, + { + "type": "airbase", + "id": "Needs Oar Point", + "size": 600, + "importance": 1 + }, + { + "type": "airbase", + "id": "Deux Jumeaux", + "size": 600, + "importance": 1 + } + ], + "enemy_points": [ + { + "type": "airbase", + "id": "Lignerolles", + "size": 600, + "importance": 1 + }, + { + "type": "airbase", + "id": "Lessay", + "size": 600, + "importance": 1 + }, + { + "type": "airbase", + "id": "Carpiquet", + "size": 600, + "importance": 1 + }, + { + "type": "airbase", + "id": "Maupertus", + "size": 600, + "importance": 1 + }, + { + "type": "airbase", + "id": "Evreux", + "size": 600, + "importance": 1, + "captured_invert": true + } + ], + "links": [ + [ + "Chailey", + "Needs Oar Point" ], - "enemy_points": [ - { - "type": "airbase", - "id": "Lignerolles", - "size": 600, - "importance": 1 - }, - { - "type": "airbase", - "id": "Lessay", - "size": 600, - "importance": 1 - }, - { - "type": "airbase", - "id": "Carpiquet", - "size": 600, - "importance": 1 - }, - { - "type": "airbase", - "id": "Maupertus", - "size": 600, - "importance": 1 - }, - { - "type": "airbase", - "id": "Evreux", - "size": 600, - "importance": 1, - "captured_invert": true - } + [ + "Deux Jumeaux", + "Lignerolles" ], - "links": [ - [ - "Chailey", - "Needs Oar Point" - ], - [ - "Deux Jumeaux", - "Lignerolles" - ], - [ - "Lessay", - "Lignerolles" - ], - [ - "Carpiquet", - "Lignerolles" - ], - [ - "Lessay", - "Maupertus" - ], - [ - "Carpiquet", - "Evreux" - ] + [ + "Lessay", + "Lignerolles" + ], + [ + "Carpiquet", + "Lignerolles" + ], + [ + "Lessay", + "Maupertus" + ], + [ + "Carpiquet", + "Evreux" ] + ] } \ No newline at end of file diff --git a/resources/campaigns/normandy_small.json b/resources/campaigns/normandy_small.json index a0bff78e..a38529a5 100644 --- a/resources/campaigns/normandy_small.json +++ b/resources/campaigns/normandy_small.json @@ -1,53 +1,55 @@ { - "name": "Normandy - Normandy Small", - "theater": "Normandy", - "player_points": [ - { - "type": "airbase", - "id": "Needs Oar Point", - "size": 600, - "importance": 1 - }, - { - "type": "airbase", - "id": "Deux Jumeaux", - "size": 600, - "importance": 1 - } + "name": "Normandy - Normandy Small", + "theater": "Normandy", + "authors": "Khopa", + "description": "", + "player_points": [ + { + "type": "airbase", + "id": "Needs Oar Point", + "size": 600, + "importance": 1 + }, + { + "type": "airbase", + "id": "Deux Jumeaux", + "size": 600, + "importance": 1 + } + ], + "enemy_points": [ + { + "type": "airbase", + "id": "Lignerolles", + "size": 600, + "importance": 1 + }, + { + "type": "airbase", + "id": "Carpiquet", + "size": 600, + "importance": 1 + }, + { + "type": "airbase", + "id": "Evreux", + "size": 600, + "importance": 1, + "captured_invert": true + } + ], + "links": [ + [ + "Deux Jumeaux", + "Lignerolles" ], - "enemy_points": [ - { - "type": "airbase", - "id": "Lignerolles", - "size": 600, - "importance": 1 - }, - { - "type": "airbase", - "id": "Carpiquet", - "size": 600, - "importance": 1 - }, - { - "type": "airbase", - "id": "Evreux", - "size": 600, - "importance": 1, - "captured_invert": true - } + [ + "Carpiquet", + "Lignerolles" ], - "links": [ - [ - "Deux Jumeaux", - "Lignerolles" - ], - [ - "Carpiquet", - "Lignerolles" - ], - [ - "Carpiquet", - "Evreux" - ] + [ + "Carpiquet", + "Evreux" ] + ] } \ No newline at end of file diff --git a/resources/campaigns/north_caucasus.json b/resources/campaigns/north_caucasus.json index 0da83e72..bebb5efb 100644 --- a/resources/campaigns/north_caucasus.json +++ b/resources/campaigns/north_caucasus.json @@ -1,99 +1,101 @@ { - "name": "Caucasus - North Caucasus", - "theater": "Caucasus", - "player_points": [ - { - "type": "airbase", - "id": "Kutaisi", - "size": 600, - "importance": 1 - }, - { - "type": "airbase", - "id": "Vaziani", - "size": 600, - "importance": 1 - }, - { - "type": "carrier", - "id": 1001, - "x": -285810.6875, - "y": 496399.1875, - "captured_invert": true - }, - { - "type": "lha", - "id": 1002, - "x": -326050.6875, - "y": 519452.1875, - "captured_invert": true - } + "name": "Caucasus - North Caucasus", + "theater": "Caucasus", + "authors": "Khopa", + "description": "", + "player_points": [ + { + "type": "airbase", + "id": "Kutaisi", + "size": 600, + "importance": 1 + }, + { + "type": "airbase", + "id": "Vaziani", + "size": 600, + "importance": 1 + }, + { + "type": "carrier", + "id": 1001, + "x": -285810.6875, + "y": 496399.1875, + "captured_invert": true + }, + { + "type": "lha", + "id": 1002, + "x": -326050.6875, + "y": 519452.1875, + "captured_invert": true + } + ], + "enemy_points": [ + { + "type": "airbase", + "id": "Beslan", + "size": 1000, + "importance": 1 + }, + { + "type": "airbase", + "id": "Nalchik", + "size": 1000, + "importance": 1.1 + }, + { + "type": "airbase", + "id": "Mozdok", + "size": 2000, + "importance": 1.1 + }, + { + "type": "airbase", + "id": "Mineralnye Vody", + "size": 2000, + "importance": 1.3 + }, + { + "type": "airbase", + "id": "Maykop-Khanskaya", + "size": 3000, + "importance": 1.4, + "captured_invert": true + } + ], + "links": [ + [ + "Kutaisi", + "Vaziani" ], - "enemy_points": [ - { - "type": "airbase", - "id": "Beslan", - "size": 1000, - "importance": 1 - }, - { - "type": "airbase", - "id": "Nalchik", - "size": 1000, - "importance": 1.1 - }, - { - "type": "airbase", - "id": "Mozdok", - "size": 2000, - "importance": 1.1 - }, - { - "type": "airbase", - "id": "Mineralnye Vody", - "size": 2000, - "importance": 1.3 - }, - { - "type": "airbase", - "id": "Maykop-Khanskaya", - "size": 3000, - "importance": 1.4, - "captured_invert": true - } + [ + "Beslan", + "Vaziani" ], - "links": [ - [ - "Kutaisi", - "Vaziani" - ], - [ - "Beslan", - "Vaziani" - ], - [ - "Beslan", - "Mozdok" - ], - [ - "Beslan", - "Nalchik" - ], - [ - "Mozdok", - "Nalchik" - ], - [ - "Mineralnye Vody", - "Nalchik" - ], - [ - "Mineralnye Vody", - "Mozdok" - ], - [ - "Maykop-Khanskaya", - "Mineralnye Vody" - ] + [ + "Beslan", + "Mozdok" + ], + [ + "Beslan", + "Nalchik" + ], + [ + "Mozdok", + "Nalchik" + ], + [ + "Mineralnye Vody", + "Nalchik" + ], + [ + "Mineralnye Vody", + "Mozdok" + ], + [ + "Maykop-Khanskaya", + "Mineralnye Vody" ] + ] } \ No newline at end of file diff --git a/resources/campaigns/north_nevada.json b/resources/campaigns/north_nevada.json index d1687a7c..dc0a2a71 100644 --- a/resources/campaigns/north_nevada.json +++ b/resources/campaigns/north_nevada.json @@ -1,71 +1,73 @@ { - "name": "Nevada - North Nevada", - "theater": "Nevada", - "player_points": [ - { - "type": "airbase", - "id": "Nellis AFB", - "size": 2000, - "importance": 1.4 - } + "name": "Nevada - North Nevada", + "theater": "Nevada", + "authors": "Khopa", + "description": "", + "player_points": [ + { + "type": "airbase", + "id": "Nellis AFB", + "size": 2000, + "importance": 1.4 + } + ], + "enemy_points": [ + { + "type": "airbase", + "id": "Tonopah Test Range Airfield", + "size": 600, + "importance": 1, + "captured_invert": true + }, + { + "type": "airbase", + "id": "Lincoln County", + "size": 600, + "importance": 1.2 + }, + { + "type": "airbase", + "id": "Groom Lake AFB", + "size": 1000, + "importance": 1.4 + }, + { + "type": "airbase", + "id": "Creech AFB", + "size": 2000, + "importance": 1.4 + }, + { + "type": "airbase", + "id": "Mesquite", + "size": 1000, + "importance": 1.3 + } + ], + "links": [ + [ + "Lincoln County", + "Tonopah Test Range Airfield" ], - "enemy_points": [ - { - "type": "airbase", - "id": "Tonopah Test Range Airfield", - "size": 600, - "importance": 1, - "captured_invert": true - }, - { - "type": "airbase", - "id": "Lincoln County", - "size": 600, - "importance": 1.2 - }, - { - "type": "airbase", - "id": "Groom Lake AFB", - "size": 1000, - "importance": 1.4 - }, - { - "type": "airbase", - "id": "Creech AFB", - "size": 2000, - "importance": 1.4 - }, - { - "type": "airbase", - "id": "Mesquite", - "size": 1000, - "importance": 1.3 - } + [ + "Groom Lake AFB", + "Tonopah Test Range Airfield" ], - "links": [ - [ - "Lincoln County", - "Tonopah Test Range Airfield" - ], - [ - "Groom Lake AFB", - "Tonopah Test Range Airfield" - ], - [ - "Lincoln County", - "Mesquite" - ], - [ - "Groom Lake AFB", - "Mesquite" - ], - [ - "Creech AFB", - "Groom Lake AFB" - ], - [ - "Creech AFB", - "Nellis AFB" - ] + [ + "Lincoln County", + "Mesquite" + ], + [ + "Groom Lake AFB", + "Mesquite" + ], + [ + "Creech AFB", + "Groom Lake AFB" + ], + [ + "Creech AFB", + "Nellis AFB" ] + ] } \ No newline at end of file diff --git a/resources/campaigns/russia_small.json b/resources/campaigns/russia_small.json index 341d90d0..022aa893 100644 --- a/resources/campaigns/russia_small.json +++ b/resources/campaigns/russia_small.json @@ -1,37 +1,39 @@ { - "name": "Caucasus - Russia Small", - "theater": "Caucasus", - "player_points": [ - { - "type": "airbase", - "id": "Mozdok", - "size": 2000, - "importance": 1.1 - } + "name": "Caucasus - Russia Small", + "theater": "Caucasus", + "authors": "Khopa", + "description": "", + "player_points": [ + { + "type": "airbase", + "id": "Mozdok", + "size": 2000, + "importance": 1.1 + } + ], + "enemy_points": [ + { + "type": "airbase", + "id": "Mineralnye Vody", + "size": 2000, + "importance": 1.3 + }, + { + "type": "airbase", + "id": "Maykop-Khanskaya", + "size": 3000, + "importance": 1.4, + "captured_invert": true + } + ], + "links": [ + [ + "Mineralnye Vody", + "Mozdok" ], - "enemy_points": [ - { - "type": "airbase", - "id": "Mineralnye Vody", - "size": 2000, - "importance": 1.3 - }, - { - "type": "airbase", - "id": "Maykop-Khanskaya", - "size": 3000, - "importance": 1.4, - "captured_invert": true - } - ], - "links": [ - [ - "Mineralnye Vody", - "Mozdok" - ], - [ - "Maykop-Khanskaya", - "Mineralnye Vody" - ] + [ + "Maykop-Khanskaya", + "Mineralnye Vody" ] + ] } \ No newline at end of file diff --git a/resources/campaigns/syrian_civil_war.json b/resources/campaigns/syrian_civil_war.json index 0f041d39..233e1734 100644 --- a/resources/campaigns/syrian_civil_war.json +++ b/resources/campaigns/syrian_civil_war.json @@ -1,91 +1,93 @@ { - "name": "Syria - Syrian Civil War", - "theater": "Syria", - "player_points": [ - { - "type": "airbase", - "id": "Bassel Al-Assad", - "size": 1000, - "importance": 1.4 - }, - { - "type": "airbase", - "id": "Marj Ruhayyil", - "size": 1000, - "importance": 1 - }, - { - "type": "carrier", - "id": 1001, - "x": 18537, - "y": -52000, - "captured_invert": true - }, - { - "type": "lha", - "id": 1002, - "x": 116000, - "y": -30000, - "captured_invert": true - } + "name": "Syria - Syrian Civil War", + "theater": "Syria", + "authors": "Khopa", + "description": "", + "player_points": [ + { + "type": "airbase", + "id": "Bassel Al-Assad", + "size": 1000, + "importance": 1.4 + }, + { + "type": "airbase", + "id": "Marj Ruhayyil", + "size": 1000, + "importance": 1 + }, + { + "type": "carrier", + "id": 1001, + "x": 18537, + "y": -52000, + "captured_invert": true + }, + { + "type": "lha", + "id": 1002, + "x": 116000, + "y": -30000, + "captured_invert": true + } + ], + "enemy_points": [ + { + "type": "airbase", + "id": "Hama", + "size": 1000, + "importance": 1 + }, + { + "type": "airbase", + "id": "Aleppo", + "size": 1000, + "importance": 1.2, + "captured_invert": true + }, + { + "type": "airbase", + "id": "Al Qusayr", + "size": 1000, + "importance": 1 + }, + { + "type": "airbase", + "id": "Palmyra", + "size": 1000, + "importance": 1 + }, + { + "type": "airbase", + "id": "Al-Dumayr", + "size": 1000, + "importance": 1.2 + } + ], + "links": [ + [ + "Bassel Al-Assad", + "Hama" ], - "enemy_points": [ - { - "type": "airbase", - "id": "Hama", - "size": 1000, - "importance": 1 - }, - { - "type": "airbase", - "id": "Aleppo", - "size": 1000, - "importance": 1.2, - "captured_invert": true - }, - { - "type": "airbase", - "id": "Al Qusayr", - "size": 1000, - "importance": 1 - }, - { - "type": "airbase", - "id": "Palmyra", - "size": 1000, - "importance": 1 - }, - { - "type": "airbase", - "id": "Al-Dumayr", - "size": 1000, - "importance": 1.2 - } + [ + "Al-Dumayr", + "Marj Ruhayyil" ], - "links": [ - [ - "Bassel Al-Assad", - "Hama" - ], - [ - "Al-Dumayr", - "Marj Ruhayyil" - ], - [ - "Aleppo", - "Hama" - ], - [ - "Al Qusayr", - "Hama" - ], - [ - "Al Qusayr", - "Al-Dumayr" - ], - [ - "Al Qusayr", - "Palmyra" - ] + [ + "Aleppo", + "Hama" + ], + [ + "Al Qusayr", + "Hama" + ], + [ + "Al Qusayr", + "Al-Dumayr" + ], + [ + "Al Qusayr", + "Palmyra" ] + ] } \ No newline at end of file diff --git a/resources/campaigns/western_georgia.json b/resources/campaigns/western_georgia.json index b0e64464..e111d236 100644 --- a/resources/campaigns/western_georgia.json +++ b/resources/campaigns/western_georgia.json @@ -1,111 +1,113 @@ { - "name": "Caucasus - Western Georgia", - "theater": "Caucasus", - "player_points": [ - { - "type": "airbase", - "id": "Kobuleti", - "radials": [ - 0, - 45, - 90, - 135, - 180, - 225, - 315 - ], - "size": 600, - "importance": 1.1 - }, - { - "type": "carrier", - "id": 1001, - "x": -285810.6875, - "y": 496399.1875, - "captured_invert": true - }, - { - "type": "lha", - "id": 1002, - "x": -326050.6875, - "y": 519452.1875, - "captured_invert": true - } + "name": "Caucasus - Western Georgia", + "theater": "Caucasus", + "authors": "Khopa", + "description": "", + "player_points": [ + { + "type": "airbase", + "id": "Kobuleti", + "radials": [ + 0, + 45, + 90, + 135, + 180, + 225, + 315 + ], + "size": 600, + "importance": 1.1 + }, + { + "type": "carrier", + "id": 1001, + "x": -285810.6875, + "y": 496399.1875, + "captured_invert": true + }, + { + "type": "lha", + "id": 1002, + "x": -326050.6875, + "y": 519452.1875, + "captured_invert": true + } + ], + "enemy_points": [ + { + "type": "airbase", + "id": "Kutaisi", + "size": 600, + "importance": 1 + }, + { + "type": "airbase", + "id": "Senaki-Kolkhi", + "size": 1000, + "importance": 1 + }, + { + "type": "airbase", + "id": "Sukhumi-Babushara", + "radials": [ + 315, + 0, + 45, + 90, + 135 + ], + "size": 1000, + "importance": 1.2 + }, + { + "type": "airbase", + "id": "Gudauta", + "radials": [ + 315, + 0, + 45, + 90, + 135 + ], + "size": 1000, + "importance": 1.2 + }, + { + "type": "airbase", + "id": "Sochi-Adler", + "radials": [ + 315, + 0, + 45, + 90, + 135 + ], + "size": 2000, + "importance": 1.4, + "captured_invert": true + } + ], + "links": [ + [ + "Kutaisi", + "Senaki-Kolkhi" ], - "enemy_points": [ - { - "type": "airbase", - "id": "Kutaisi", - "size": 600, - "importance": 1 - }, - { - "type": "airbase", - "id": "Senaki-Kolkhi", - "size": 1000, - "importance": 1 - }, - { - "type": "airbase", - "id": "Sukhumi-Babushara", - "radials": [ - 315, - 0, - 45, - 90, - 135 - ], - "size": 1000, - "importance": 1.2 - }, - { - "type": "airbase", - "id": "Gudauta", - "radials": [ - 315, - 0, - 45, - 90, - 135 - ], - "size": 1000, - "importance": 1.2 - }, - { - "type": "airbase", - "id": "Sochi-Adler", - "radials": [ - 315, - 0, - 45, - 90, - 135 - ], - "size": 2000, - "importance": 1.4, - "captured_invert": true - } + [ + "Kobuleti", + "Senaki-Kolkhi" ], - "links": [ - [ - "Kutaisi", - "Senaki-Kolkhi" - ], - [ - "Kobuleti", - "Senaki-Kolkhi" - ], - [ - "Senaki-Kolkhi", - "Sukhumi-Babushara" - ], - [ - "Gudauta", - "Sukhumi-Babushara" - ], - [ - "Gudauta", - "Sochi-Adler" - ] + [ + "Senaki-Kolkhi", + "Sukhumi-Babushara" + ], + [ + "Gudauta", + "Sukhumi-Babushara" + ], + [ + "Gudauta", + "Sochi-Adler" ] + ] } \ No newline at end of file diff --git a/resources/factions/allies_1944.json b/resources/factions/allies_1944.json new file mode 100644 index 00000000..cc93ec03 --- /dev/null +++ b/resources/factions/allies_1944.json @@ -0,0 +1,65 @@ +{ + "country": "USA", + "name": "Allies 1944", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "P_51D", + "P_51D_30_NA", + "P_47D_30", + "SpitfireLFMkIX", + "SpitfireLFMkIXCW", + "A_20G", + "B_17G" + ], + "frontline_units": [ + "MT_M4A4_Sherman_Firefly", + "MT_M4_Sherman", + "APC_M2A1", + "CT_Cromwell_IV", + "ST_Centaur_IV", + "HIT_Churchill_VII", + "M30_Cargo_Carrier", + "LAC_M8_Greyhound", + "TD_M10_GMC" + ], + "artillery_units": [ + "M12_GMC" + ], + "logistics_units": [ + "Bedford_MWD", + "CCKW_353" + ], + "infantry_units": [ + "Infantry_SMLE_No_4_Mk_1", + "Infantry_M1_Garand" + ], + "shorads": [ + "BoforsGenerator" + ], + "sams": [ + "BoforsGenerator" + ], + "aircraft_carrier": [ + ], + "helicopter_carrier": [ + ], + "destroyers": [ + ], + "cruisers": [ + ], + "requirements": { + "WW2 Asset Pack": "https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/" + }, + "carrier_names": [ + ], + "helicopter_carrier_names": [ + ], + "navy_generators": [ + "WW2LSTGroupGenerator" + ], + "navy_group_count": 1, + "has_jtac": false, + "doctrine": "ww2", + "building_set": "ww2ally" +} diff --git a/resources/factions/australia_2005.json b/resources/factions/australia_2005.json new file mode 100644 index 00000000..5c7b859c --- /dev/null +++ b/resources/factions/australia_2005.json @@ -0,0 +1,63 @@ +{ + "country": "Australia", + "name": "Australia 2005", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "FA_18C_hornet", + "UH_1H", + "AH_1W" + ], + "awacs": [ + "E_3A" + ], + "tankers": [ + "KC_135", + "KC130" + ], + "frontline_units": [ + "MBT_M1A2_Abrams", + "MBT_Leopard_1A3", + "APC_M113", + "IFV_LAV_25", + "IFV_MCV_80" + ], + "artillery_units": [ + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4", + "Soldier_M249" + ], + "shorads": [ + "RapierGenerator" + ], + "sams": [ + "HawkGenerator", + "RapierGenerator" + ], + "aircraft_carrier": [ + ], + "helicopter_carrier": [ + "LHA_1_Tarawa" + ], + "destroyers": [ + "USS_Arleigh_Burke_IIa" + ], + "cruisers": [ + ], + "requirements": {}, + "carrier_names": [ + ], + "helicopter_carrier_names": [ + "HMAS Canberra", + "HMAS Adelaide" + ], + "navy_generators": [ + "ArleighBurkeGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper" +} diff --git a/resources/factions/bluefor_coldwar.json b/resources/factions/bluefor_coldwar.json new file mode 100644 index 00000000..73265ae3 --- /dev/null +++ b/resources/factions/bluefor_coldwar.json @@ -0,0 +1,78 @@ +{ + "country": "Combined Joint Task Forces Blue", + "name": "Bluefor Coldwar", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "F_14B", + "F_4E", + "F_5E_3", + "A_10A", + "AJS37", + "UH_1H", + "SA342M", + "SA342L", + "B_52H" + ], + "awacs": [ + "C_130", + "E_3A" + ], + "tankers": [ + "KC_135", + "KC130" + ], + "frontline_units": [ + "MBT_M60A3_Patton", + "APC_M113" + ], + "artillery_units": [ + "SPH_M109_Paladin" + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4", + "Soldier_M249" + ], + "shorads": [ + "VulcanGenerator" + ], + "sams": [ + "HawkGenerator", + "ChaparralGenerator" + ], + "aircraft_carrier": [ + "CVN_74_John_C__Stennis" + ], + "helicopter_carrier": [ + "LHA_1_Tarawa" + ], + "destroyers": [ + "USS_Arleigh_Burke_IIa" + ], + "cruisers": [ + "Ticonderoga_class" + ], + "requirements": {}, + "carrier_names": [ + "CVN-71 Theodore Roosevelt", + "CVN-72 Abraham Lincoln", + "CVN-73 George Washington", + "CVN-74 John C. Stennis" + ], + "helicopter_carrier_names": [ + "LHA-1 Tarawa", + "LHA-2 Saipan", + "LHA-3 Belleau Wood", + "LHA-4 Nassau", + "LHA-5 Peleliu" + ], + "navy_generators": [ + "ArleighBurkeGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper", + "doctrine": "coldwar" +} diff --git a/resources/factions/bluefor_coldwar_a4.json b/resources/factions/bluefor_coldwar_a4.json new file mode 100644 index 00000000..7a2fd90d --- /dev/null +++ b/resources/factions/bluefor_coldwar_a4.json @@ -0,0 +1,81 @@ +{ + "country": "Combined Joint Task Forces Blue", + "name": "Bluefor Coldwar (With A4)", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "F_14B", + "F_4E", + "F_5E_3", + "A_10A", + "AJS37", + "UH_1H", + "SA342M", + "SA342L", + "A_4E_C", + "B_52H" + ], + "awacs": [ + "C_130", + "E_3A" + ], + "tankers": [ + "KC_135", + "KC130" + ], + "frontline_units": [ + "MBT_M60A3_Patton", + "APC_M113" + ], + "artillery_units": [ + "SPH_M109_Paladin" + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4", + "Soldier_M249" + ], + "shorads": [ + "VulcanGenerator" + ], + "sams": [ + "HawkGenerator", + "ChaparralGenerator" + ], + "aircraft_carrier": [ + "CVN_74_John_C__Stennis" + ], + "helicopter_carrier": [ + "LHA_1_Tarawa" + ], + "destroyers": [ + "USS_Arleigh_Burke_IIa" + ], + "cruisers": [ + "Ticonderoga_class" + ], + "requirements": { + "Community A-4E": "https://heclak.github.io/community-a4e-c/" + }, + "carrier_names": [ + "CVN-71 Theodore Roosevelt", + "CVN-72 Abraham Lincoln", + "CVN-73 George Washington", + "CVN-74 John C. Stennis" + ], + "helicopter_carrier_names": [ + "LHA-1 Tarawa", + "LHA-2 Saipan", + "LHA-3 Belleau Wood", + "LHA-4 Nassau", + "LHA-5 Peleliu" + ], + "navy_generators": [ + "ArleighBurkeGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper", + "doctrine": "coldwar" +} diff --git a/resources/factions/bluefor_coldwar_a4_mb339.json b/resources/factions/bluefor_coldwar_a4_mb339.json new file mode 100644 index 00000000..880f505f --- /dev/null +++ b/resources/factions/bluefor_coldwar_a4_mb339.json @@ -0,0 +1,82 @@ +{ + "country": "Combined Joint Task Forces Blue", + "name": "Bluefor Coldwar (With A4 & MB339)", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "F_14B", + "F_4E", + "F_5E_3", + "A_10A", + "AJS37", + "UH_1H", + "SA342M", + "SA342L", + "A_4E_C", + "MB_339PAN", + "B_52H" + ], + "awacs": [ + "C_130", + "E_3A" + ], + "tankers": [ + "KC_135", + "KC130" + ], + "frontline_units": [ + "MBT_M60A3_Patton", + "APC_M113" + ], + "artillery_units": [ + "SPH_M109_Paladin" + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4", + "Soldier_M249" + ], + "shorads": [ + "VulcanGenerator" + ], + "sams": [ + "HawkGenerator", + "ChaparralGenerator" + ], + "aircraft_carrier": [ + "CVN_74_John_C__Stennis" + ], + "helicopter_carrier": [ + "LHA_1_Tarawa" + ], + "destroyers": [ + "USS_Arleigh_Burke_IIa" + ], + "cruisers": [ + "Ticonderoga_class" + ], + "requirements": { + "Community A-4E": "https://heclak.github.io/community-a4e-c/" + }, + "carrier_names": [ + "CVN-71 Theodore Roosevelt", + "CVN-72 Abraham Lincoln", + "CVN-73 George Washington", + "CVN-74 John C. Stennis" + ], + "helicopter_carrier_names": [ + "LHA-1 Tarawa", + "LHA-2 Saipan", + "LHA-3 Belleau Wood", + "LHA-4 Nassau", + "LHA-5 Peleliu" + ], + "navy_generators": [ + "ArleighBurkeGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper", + "doctrine": "coldwar" +} diff --git a/resources/factions/bluefor_modern.json b/resources/factions/bluefor_modern.json new file mode 100644 index 00000000..fa931951 --- /dev/null +++ b/resources/factions/bluefor_modern.json @@ -0,0 +1,96 @@ +{ + "country": "Combined Joint Task Forces Blue", + "name": "Bluefor Modern", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "F_14B", + "F_15C", + "F_16C_50", + "FA_18C_hornet", + "JF_17", + "M_2000C", + "F_5E_3", + "Su_27", + "Su_25T", + "A_10A", + "A_10C", + "A_10C_2", + "AV8BNA", + "AJS37", + "UH_1H", + "AH_64D", + "Ka_50", + "SA342M", + "SA342L", + "B_52H", + "B_1B" + ], + "awacs": [ + "E_3A" + ], + "tankers": [ + "KC_135", + "KC130" + ], + "frontline_units": [ + "MBT_M1A2_Abrams", + "MBT_Leopard_2", + "MBT_Merkava_Mk__4", + "ATGM_M1134_Stryker", + "IFV_M2A2_Bradley", + "IFV_Marder", + "IFV_LAV_25", + "APC_M1043_HMMWV_Armament", + "ATGM_M1045_HMMWV_TOW" + ], + "artillery_units": [ + "MLRS_M270", + "SPH_M109_Paladin" + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4", + "Soldier_M249" + ], + "shorads": [ + "AvengerGenerator" + ], + "sams": [ + "HawkGenerator", + "PatriotGenerator" + ], + "aircraft_carrier": [ + "CVN_74_John_C__Stennis" + ], + "helicopter_carrier": [ + "LHA_1_Tarawa" + ], + "destroyers": [ + "USS_Arleigh_Burke_IIa" + ], + "cruisers": [ + "Ticonderoga_class" + ], + "requirements": {}, + "carrier_names": [ + "CVN-71 Theodore Roosevelt", + "CVN-72 Abraham Lincoln", + "CVN-73 George Washington", + "CVN-74 John C. Stennis" + ], + "helicopter_carrier_names": [ + "LHA-1 Tarawa", + "LHA-2 Saipan", + "LHA-3 Belleau Wood", + "LHA-4 Nassau", + "LHA-5 Peleliu" + ], + "navy_generators": [ + "ArleighBurkeGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper" +} diff --git a/resources/factions/canada_2005.json b/resources/factions/canada_2005.json new file mode 100644 index 00000000..9866f107 --- /dev/null +++ b/resources/factions/canada_2005.json @@ -0,0 +1,61 @@ +{ + "country": "Canada", + "name": "Canada 2005", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "FA_18C_hornet", + "UH_1H", + "AH_1W" + ], + "awacs": [ + "E_3A" + ], + "tankers": [ + "KC_135", + "KC130" + ], + "frontline_units": [ + "MBT_Leopard_1A3", + "MBT_Leopard_2", + "IFV_LAV_25", + "APC_M113", + "IFV_MCV_80" + ], + "artillery_units": [ + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4", + "Soldier_M249" + ], + "shorads": [ + "AvengerGenerator" + ], + "sams": [ + "HawkGenerator", + "AvengerGenerator" + ], + "aircraft_carrier": [ + ], + "helicopter_carrier": [ + ], + "destroyers": [ + "USS_Arleigh_Burke_IIa" + ], + "cruisers": [ + "Ticonderoga_class" + ], + "requirements": {}, + "carrier_names": [ + ], + "helicopter_carrier_names": [ + ], + "navy_generators": [ + "ArleighBurkeGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper" +} diff --git a/resources/factions/china_2010.json b/resources/factions/china_2010.json new file mode 100644 index 00000000..1b3c7a34 --- /dev/null +++ b/resources/factions/china_2010.json @@ -0,0 +1,83 @@ +{ + "country": "China", + "name": "China 2010", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "MiG_21Bis", + "Su_30", + "Su_33", + "J_11A", + "JF_17", + "Mi_8MT", + "Mi_28N" + ], + "awacs": [ + "KJ_2000" + ], + "tankers": [ + "IL_78M" + ], + "frontline_units": [ + "ZTZ_96B", + "MBT_T_55", + "ZBD_04A", + "IFV_BMP_1" + ], + "artillery_units": [ + "MLRS_9A52_Smerch", + "SPH_2S9_Nona" + ], + "logistics_units": [ + "Transport_Ural_375", + "Transport_UAZ_469" + ], + "infantry_units": [ + "Paratrooper_AKS", + "Infantry_Soldier_Rus", + "Paratrooper_RPG_16" + ], + "shorads": [ + "SA9Generator", + "SA13Generator", + "ZSU23Generator", + "ZU23Generator" + ], + "sams": [ + "HQ7Generator", + "SA10Generator", + "SA6Generator" + ], + "aircraft_carrier": [ + "CV_1143_5_Admiral_Kuznetsov" + ], + "carrier_names": [ + "001 Liaoning", + "002 Shandong" + ], + "helicopter_carrier": [ + "Type_071_Amphibious_Transport_Dock" + ], + "helicopter_carrier_names": [ + "Kunlun Shan", + "Jinggang Shan", + "Changbai Shan", + "Yimeng Shan", + "Longhu Shan", + "Wuzhi Shan", + "Wudang Shan" + ], + "destroyers": [ + "Type_052B_Destroyer", + "Type_052C_Destroyer" + ], + "cruiser": [ + "Type_054A_Frigate" + ], + "requirements": {}, + "navy_generators": [ + "Type54GroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "WingLoong_I" +} diff --git a/resources/factions/france_1995.json b/resources/factions/france_1995.json new file mode 100644 index 00000000..3af4d91c --- /dev/null +++ b/resources/factions/france_1995.json @@ -0,0 +1,72 @@ +{ + "country": "France", + "name": "France 1995", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "M_2000C", + "Mirage_2000_5", + "SA342M", + "SA342L", + "SA342Mistral" + ], + "awacs": [ + "E_3A" + ], + "tankers": [ + "KC_135", + "KC130" + ], + "frontline_units": [ + "MBT_Leclerc", + "TPz_Fuchs", + "APC_Cobra", + "ATGM_M1134_Stryker", + "IFV_LAV_25", + "APC_M1043_HMMWV_Armament", + "ATGM_M1045_HMMWV_TOW" + ], + "artillery_units": [ + "MLRS_M270", + "SPH_M109_Paladin" + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4", + "Soldier_M249" + ], + "shorads": [ + "HQ7Generator", + "RolandGenerator" + ], + "sams": [ + "RolandGenerator", + "HawkGenerator" + ], + "aircraft_carrier": [ + ], + "helicopter_carrier": [ + "LHA_1_Tarawa" + ], + "destroyers": [ + "USS_Arleigh_Burke_IIa", + "Oliver_Hazzard_Perry_class" + ], + "cruisers": [ + "Ticonderoga_class" + ], + "requirements": {}, + "carrier_names": [ + ], + "helicopter_carrier_names": [ + "Jeanne d'Arc" + ], + "navy_generators": [ + "ArleighBurkeGroupGenerator", + "OliverHazardPerryGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper" +} diff --git a/resources/factions/france_2005_modded.json b/resources/factions/france_2005_modded.json new file mode 100644 index 00000000..0ad6d3e1 --- /dev/null +++ b/resources/factions/france_2005_modded.json @@ -0,0 +1,86 @@ +{ + "country": "France", + "name": "France 2005 (Modded)", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "M_2000C", + "Mirage_2000_5", + "Rafale_M", + "Rafale_A_S", + "SA342M", + "SA342L", + "SA342Mistral" + ], + "awacs": [ + "E_3A" + ], + "tankers": [ + "KC_135", + "KC130" + ], + "frontline_units": [ + "AMX_10RCR", + "AMX_10RCR_SEPAR", + "ERC_90", + "TRM_2000_PAMELA", + "VAB__50", + "VAB_MEPHISTO", + "VAB_T20_13", + "VAB_T20_13", + "VBL__50", + "VBL_AANF1", + "VBAE_CRAB", + "VBAE_CRAB_MMP", + "AMX_30B2", + "Leclerc_Serie_XXI" + ], + "artillery_units": [ + "MLRS_M270", + "SPH_M109_Paladin" + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4", + "Soldier_M249" + ], + "shorads": [ + "HQ7Generator", + "RolandGenerator" + ], + "sams": [ + "RolandGenerator", + "HawkGenerator" + ], + "aircraft_carrier": [ + "CVN_74_John_C__Stennis" + ], + "helicopter_carrier": [ + "LHA_1_Tarawa" + ], + "destroyers": [ + "USS_Arleigh_Burke_IIa" + ], + "cruisers": [ + "Ticonderoga_class" + ], + "requirements": { + "frenchpack V3.5": "https://forums.eagle.ru/showthread.php?t=279974", + "RAFALE 2.5.5": "https://www.digitalcombatsimulator.com/fr/files/3307478/" + }, + "carrier_names": [ + "L9013 Mistral", + "L9014 Tonerre", + "L9015 Dixmude" + ], + "helicopter_carrier_names": [ + "Jeanne d'Arc" + ], + "navy_generators": [ + "ArleighBurkeGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper" +} diff --git a/resources/factions/germany_1942.json b/resources/factions/germany_1942.json new file mode 100644 index 00000000..9a641bc2 --- /dev/null +++ b/resources/factions/germany_1942.json @@ -0,0 +1,59 @@ +{ + "country": "Third Reich", + "name": "Germany 1944", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "FW_190A8", + "FW_190D9", + "Bf_109K_4", + "Ju_88A4" + ], + "frontline_units": [ + "MT_Pz_Kpfw_IV_Ausf_H", + "APC_Sd_Kfz_251", + "IFV_Sd_Kfz_234_2_Puma", + "TD_Jagdpanzer_IV" + ], + "artillery_units": [ + "Sturmpanzer_IV_Brummbär" + ], + "logistics_units": [ + "Blitz_3_6_6700A", + "Kübelwagen_82", + "Sd_Kfz_7", + "Sd_Kfz_2" + ], + "infantry_units": [ + "Infantry_Mauser_98" + ], + "shorads": [ + "FlakGenerator" + ], + "sams": [ + "FlakGenerator" + ], + "aircraft_carrier": [ + ], + "helicopter_carrier": [ + ], + "destroyers": [ + ], + "cruisers": [ + ], + "requirements": { + "WW2 Asset Pack": "https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/" + }, + "carrier_names": [ + ], + "helicopter_carrier_names": [ + ], + "navy_generators": [ + "UBoatGroupGenerator", + "SchnellbootGroupGenerator" + ], + "navy_group_count": 2, + "has_jtac": false, + "doctrine": "ww2", + "building_set": "ww2germany" +} diff --git a/resources/factions/germany_1944.json b/resources/factions/germany_1944.json new file mode 100644 index 00000000..7ff13b99 --- /dev/null +++ b/resources/factions/germany_1944.json @@ -0,0 +1,68 @@ +{ + "country": "Third Reich", + "name": "Germany 1944", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "FW_190A8", + "FW_190D9", + "Bf_109K_4", + "Ju_88A4" + ], + "frontline_units": [ + "MT_Pz_Kpfw_V_Panther_Ausf_G", + "MT_Pz_Kpfw_IV_Ausf_H", + "HT_Pz_Kpfw_VI_Tiger_I", + "HT_Pz_Kpfw_VI_Ausf__B__Tiger_II", + "APC_Sd_Kfz_251", + "IFV_Sd_Kfz_234_2_Puma", + "Sd_Kfz_184_Elefant", + "TD_Jagdpanther_G1", + "TD_Jagdpanzer_IV" + ], + "artillery_units": [ + "Sturmpanzer_IV_Brummbär" + ], + "logistics_units": [ + "Blitz_3_6_6700A", + "Kübelwagen_82", + "Sd_Kfz_7", + "Sd_Kfz_2" + ], + "infantry_units": [ + "Infantry_Mauser_98" + ], + "shorads": [ + "FlakGenerator" + ], + "sams": [ + "FlakGenerator" + ], + "aircraft_carrier": [ + ], + "helicopter_carrier": [ + ], + "destroyers": [ + ], + "cruisers": [ + ], + "requirements": { + "WW2 Asset Pack": "https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/" + }, + "carrier_names": [ + ], + "helicopter_carrier_names": [ + ], + "navy_generators": [ + "UBoatGroupGenerator", + "SchnellbootGroupGenerator" + ], + "navy_group_count": 2, + "missiles": [ + "V1GroupGenerator" + ], + "missiles_group_count": 1, + "has_jtac": false, + "doctrine": "ww2", + "building_set": "ww2germany" +} diff --git a/resources/factions/germany_1990.json b/resources/factions/germany_1990.json new file mode 100644 index 00000000..9376387c --- /dev/null +++ b/resources/factions/germany_1990.json @@ -0,0 +1,63 @@ +{ + "country": "Germany", + "name": "Germany 1990", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "MiG_29G", + "Tornado_IDS", + "F_4E", + "UH_1H", + "SA342M", + "SA342L" + ], + "awacs": [ + "E_3A" + ], + "tankers": [ + "KC_135", + "KC130" + ], + "frontline_units": [ + "TPz_Fuchs", + "MBT_Leopard_1A3", + "MBT_Leopard_2", + "IFV_Marder" + ], + "artillery_units": [ + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4", + "Soldier_M249" + ], + "shorads": [ + "RolandGenerator" + ], + "sams": [ + "HawkGenerator", + "RolandGenerator" + ], + "aircraft_carrier": [ + ], + "helicopter_carrier": [ + ], + "destroyers": [ + "Oliver_Hazzard_Perry_class" + ], + "cruisers": [ + "Ticonderoga_class" + ], + "requirements": {}, + "carrier_names": [ + ], + "helicopter_carrier_names": [ + ], + "navy_generators": [ + "OliverHazardPerryGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper" +} diff --git a/resources/factions/india_2010.json b/resources/factions/india_2010.json new file mode 100644 index 00000000..9572c669 --- /dev/null +++ b/resources/factions/india_2010.json @@ -0,0 +1,71 @@ +{ + "country": "India", + "name": "India 2010", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "Mirage_2000_5", + "M_2000C", + "MiG_27K", + "MiG_21Bis", + "MiG_29S", + "Su_30", + "AH_64A", + "Mi_8MT" + ], + "awacs": [ + "A_50" + ], + "tankers": [ + "IL_78M" + ], + "frontline_units": [ + "MBT_T_90", + "MBT_T_72B", + "IFV_BMP_2" + ], + "artillery_units": [ + "MLRS_9K57_Uragan_BM_27", + "SPH_2S19_Msta" + ], + "logistics_units": [ + "Transport_Ural_375", + "Transport_UAZ_469" + ], + "infantry_units": [ + "Infantry_M4", + "Paratrooper_RPG_16" + ], + "shorads": [ + "SA8Generator", + "SA13Generator", + "SA19Generator", + "ZSU23Generator" + ], + "sams": [ + "SA6Generator", + "SA3Generator" + ], + "aircraft_carrier": [ + "CV_1143_5_Admiral_Kuznetsov" + ], + "helicopter_carrier": [ + ], + "helicopter_carrier_names": [ + ], + "destroyers": [ + "FF_1135M_Rezky" + ], + "cruisers": [ + ], + "requirements": {}, + "carrier_names": [ + "INS Vikramaditya" + ], + "navy_generators": [ + "OliverHazardPerryGroupGenerator", + "MolniyaGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper" +} diff --git a/resources/factions/insurgents.json b/resources/factions/insurgents.json new file mode 100644 index 00000000..a4dd0e24 --- /dev/null +++ b/resources/factions/insurgents.json @@ -0,0 +1,35 @@ +{ + "country": "Insurgents", + "name": "Insurgents", + "authors": "Khopa", + "description": "", + "aircrafts": [ + ], + "frontline_units": [ + "APC_Cobra", + "APC_MTLB", + "ARV_BRDM_2", + "AAA_ZU_23_Insurgent_on_Ural_375" + ], + "artillery_units": [ + "MLRS_9K57_Uragan_BM_27", + "SPH_2S19_Msta" + ], + "logistics_units": [ + "Transport_Ural_375", + "Transport_UAZ_469" + ], + "infantry_units": [ + "Infantry_Soldier_Insurgents", + "Soldier_RPG" + ], + "shorads": [ + "SA9Generator", + "ZU23Generator", + "ZSU23Generator" + ], + "sams": [ + "ZU23Generator", + "ZSU23Generator" + ] +} diff --git a/resources/factions/insurgents_modded.json b/resources/factions/insurgents_modded.json new file mode 100644 index 00000000..9c4603e6 --- /dev/null +++ b/resources/factions/insurgents_modded.json @@ -0,0 +1,39 @@ +{ + "country": "Insurgents", + "name": "Insurgents (Modded)", + "authors": "Khopa", + "description": "", + "aircrafts": [ + ], + "frontline_units": [ + "DIM__TOYOTA_BLUE", + "DIM__TOYOTA_DESERT", + "DIM__TOYOTA_GREEN", + "DIM__KAMIKAZE", + "AAA_ZU_23_Insurgent_on_Ural_375" + ], + "artillery_units": [ + "MLRS_9K57_Uragan_BM_27", + "SPH_2S19_Msta" + ], + "logistics_units": [ + "Transport_Ural_375", + "Transport_UAZ_469" + ], + "infantry_units": [ + "Infantry_Soldier_Insurgents", + "Soldier_RPG" + ], + "shorads": [ + "SA9Generator", + "ZU23Generator", + "ZSU23Generator" + ], + "sams": [ + "ZU23Generator", + "ZSU23Generator" + ], + "requirements": { + "frenchpack V3.5": "https://forums.eagle.ru/showthread.php?t=279974" + } +} diff --git a/resources/factions/iran_2015.json b/resources/factions/iran_2015.json new file mode 100644 index 00000000..5b6d8757 --- /dev/null +++ b/resources/factions/iran_2015.json @@ -0,0 +1,78 @@ +{ + "country": "Iran", + "name": "Iran 2015", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "MiG_21Bis", + "MiG_29A", + "F_4E", + "F_5E_3", + "F_14B", + "Su_17M4", + "Su_24M", + "Su_25", + "Su_25T", + "Mi_28N", + "Mi_24V" + ], + "awacs": [ + "A_50" + ], + "tankers": [ + "IL_78M" + ], + "frontline_units": [ + "APC_M113", + "APC_BTR_80", + "MBT_M60A3_Patton", + "IFV_BMP_1", + "MBT_T_72B" + ], + "artillery_units": [ + "MLRS_BM_21_Grad", + "SPH_2S19_Msta" + ], + "logistics_units": [ + "Transport_Ural_375", + "Transport_UAZ_469" + ], + "infantry_units": [ + "Paratrooper_AKS", + "Infantry_Soldier_Insurgents", + "Paratrooper_RPG_16" + ], + "shorads": [ + "HQ7Generator", + "ZSU23Generator" + ], + "sams": [ + "SA2Generator", + "SA6Generator", + "SA11Generator", + "HawkGenerator", + "HQ7Generator" + ], + "aircraft_carrier": [ + ], + "helicopter_carrier": [ + ], + "helicopter_carrier_names": [ + ], + "destroyers": [ + "FF_1135M_Rezky" + ], + "cruisers": [ + "FSG_1241_1MP_Molniya" + ], + "requirements": {}, + "carrier_names": [ + ], + "navy_generators": [ + "GrishaGroupGenerator", + "MolniyaGroupGenerator", + "KiloSubGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper" +} diff --git a/resources/factions/israel_1948.json b/resources/factions/israel_1948.json new file mode 100644 index 00000000..cd723e9f --- /dev/null +++ b/resources/factions/israel_1948.json @@ -0,0 +1,53 @@ +{ + "country": "Israel", + "name": "Israel 1948", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "SpitfireLFMkIXCW", + "SpitfireLFMkIX", + "P_51D", + "P_51D_30_NA", + "Bf_109K_4", + "B_17G" + ], + "frontline_units": [ + "MT_M4A4_Sherman_Firefly", + "APC_M2A1", + "MT_M4_Sherman", + "LAC_M8_Greyhound" + ], + "artillery_units": [ + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_SMLE_No_4_Mk_1" + ], + "shorads": [ + "BoforsGenerator" + ], + "sams": [ + "BoforsGenerator" + ], + "aircraft_carrier": [ + ], + "helicopter_carrier": [ + ], + "destroyers": [ + ], + "cruisers": [ + ], + "requirements": { + "WW2 Asset Pack": "https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/" + }, + "carrier_names": [ + ], + "helicopter_carrier_names": [ + ], + "navy_generators": [ + ], + "has_jtac": false, + "doctrine": "ww2" +} diff --git a/resources/factions/israel_1973.json b/resources/factions/israel_1973.json new file mode 100644 index 00000000..071833c3 --- /dev/null +++ b/resources/factions/israel_1973.json @@ -0,0 +1,60 @@ +{ + "country": "Israel", + "name": "Israel 1973", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "F_4E", + "A_4E_C", + "UH_1H" + ], + "awacs": [ + "E_3A" + ], + "tankers": [ + "KC_135", + "KC130" + ], + "frontline_units": [ + "MT_M4A4_Sherman_Firefly", + "APC_M2A1", + "MBT_M60A3_Patton", + "APC_M113" + ], + "artillery_units": [ + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4", + "Soldier_M249" + ], + "shorads": [ + "BoforsGenerator" + ], + "sams": [ + "HawkGenerator", + "ChaparralGenerator" + ], + "aircraft_carrier": [ + ], + "helicopter_carrier": [ + ], + "destroyers": [ + ], + "cruisers": [ + ], + "requirements": { + "Community A-4E": "https://heclak.github.io/community-a4e-c/", + "WW2 Asset Pack": "https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/" + }, + "carrier_names": [ + ], + "helicopter_carrier_names": [ + ], + "navy_generators": [ + ], + "has_jtac": false, + "doctrine": "coldwar" +} diff --git a/resources/factions/israel_1982.json b/resources/factions/israel_1982.json new file mode 100644 index 00000000..fe80cf27 --- /dev/null +++ b/resources/factions/israel_1982.json @@ -0,0 +1,62 @@ +{ + "country": "Israel", + "name": "Israel 1982", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "F_4E", + "A_4E_C", + "F_15C", + "F_16A", + "F_16C_50", + "UH_1H", + "AH_1W" + ], + "awacs": [ + "E_3A" + ], + "tankers": [ + "KC_135", + "KC130" + ], + "frontline_units": [ + "APC_M113", + "MBT_M60A3_Patton", + "MBT_Merkava_Mk__4" + ], + "artillery_units": [ + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4", + "Soldier_M249" + ], + "shorads": [ + "ChaparralGenerator" + ], + "sams": [ + "HawkGenerator", + "ChaparralGenerator" + ], + "aircraft_carrier": [ + ], + "helicopter_carrier": [ + ], + "destroyers": [ + ], + "cruisers": [ + ], + "requirements": { + "Community A-4E": "https://heclak.github.io/community-a4e-c/" + }, + "carrier_names": [ + ], + "helicopter_carrier_names": [ + ], + "navy_generators": [ + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper" +} diff --git a/resources/factions/israel_2000.json b/resources/factions/israel_2000.json new file mode 100644 index 00000000..97aeed68 --- /dev/null +++ b/resources/factions/israel_2000.json @@ -0,0 +1,65 @@ +{ + "country": "Israel", + "name": "Israel 2000", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "F_4E", + "F_15C", + "F_15E", + "F_16C_50", + "UH_1H", + "AH_1W", + "AH_64D" + ], + "awacs": [ + "E_3A" + ], + "tankers": [ + "KC_135", + "KC130" + ], + "frontline_units": [ + "APC_M113", + "APC_M1043_HMMWV_Armament", + "ATGM_M1045_HMMWV_TOW", + "MBT_Merkava_Mk__4" + ], + "artillery_units": [ + "SPH_M109_Paladin", + "MLRS_M270" + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4", + "Soldier_M249" + ], + "shorads": [ + "ChaparralGenerator" + ], + "sams": [ + "HawkGenerator", + "ChaparralGenerator" + ], + "aircraft_carrier": [ + ], + "helicopter_carrier": [ + ], + "destroyers": [ + ], + "cruisers": [ + ], + "requirements": { + }, + "carrier_names": [ + ], + "helicopter_carrier_names": [ + ], + "navy_generators": [ + "ArleighBurkeGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper" +} diff --git a/resources/factions/italy_1990.json b/resources/factions/italy_1990.json new file mode 100644 index 00000000..a1e863da --- /dev/null +++ b/resources/factions/italy_1990.json @@ -0,0 +1,64 @@ +{ + "country": "Italy", + "name": "Italy 1990", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "Tornado_IDS", + "AV8BNA", + "UH_1H", + "AH_1W" + ], + "awacs": [ + "E_3A" + ], + "tankers": [ + "KC_135", + "KC130" + ], + "frontline_units": [ + "MBT_Leopard_1A3", + "APC_M113" + ], + "artillery_units": [ + "SPH_M109_Paladin" + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4", + "Soldier_M249" + ], + "shorads": [ + "AvengerGenerator" + ], + "sams": [ + "HawkGenerator", + "AvengerGenerator" + ], + "aircraft_carrier": [ + ], + "helicopter_carrier": [ + "LHA_1_Tarawa" + ], + "destroyers": [ + "Oliver_Hazzard_Perry_class" + ], + "cruisers": [ + "Ticonderoga_class" + ], + "requirements": { + }, + "carrier_names": [ + ], + "helicopter_carrier_names": [ + "Giuseppe Garibaldi", + "Cavour" + ], + "navy_generators": [ + "OliverHazardPerryGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper" +} diff --git a/resources/factions/italy_1990_mb339.json b/resources/factions/italy_1990_mb339.json new file mode 100644 index 00000000..fb8d6eff --- /dev/null +++ b/resources/factions/italy_1990_mb339.json @@ -0,0 +1,65 @@ +{ + "country": "Italy", + "name": "Italy 1990 (With MB339)", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "Tornado_IDS", + "AV8BNA", + "MB_339PAN", + "UH_1H", + "AH_1W" + ], + "awacs": [ + "E_3A" + ], + "tankers": [ + "KC_135", + "KC130" + ], + "frontline_units": [ + "MBT_Leopard_1A3", + "APC_M113" + ], + "artillery_units": [ + "SPH_M109_Paladin" + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4", + "Soldier_M249" + ], + "shorads": [ + "AvengerGenerator" + ], + "sams": [ + "HawkGenerator", + "AvengerGenerator" + ], + "aircraft_carrier": [ + ], + "helicopter_carrier": [ + "LHA_1_Tarawa" + ], + "destroyers": [ + "Oliver_Hazzard_Perry_class" + ], + "cruisers": [ + "Ticonderoga_class" + ], + "requirements": { + }, + "carrier_names": [ + ], + "helicopter_carrier_names": [ + "Giuseppe Garibaldi", + "Cavour" + ], + "navy_generators": [ + "OliverHazardPerryGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper" +} diff --git a/resources/factions/japan_2005.json b/resources/factions/japan_2005.json new file mode 100644 index 00000000..a9a3e6ff --- /dev/null +++ b/resources/factions/japan_2005.json @@ -0,0 +1,70 @@ +{ + "country": "Japan", + "name": "Japan 2005", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "F_15C", + "F_16C_50", + "F_4E", + "AH_1W", + "AH_64D" + ], + "awacs": [ + "E_3A" + ], + "tankers": [ + "KC_135", + "KC130" + ], + "frontline_units": [ + "MBT_Merkava_Mk__4", + "MBT_M1A2_Abrams", + "IFV_Marder", + "TPz_Fuchs", + "IFV_LAV_25", + "APC_M1043_HMMWV_Armament" + ], + "artillery_units": [ + "SPH_M109_Paladin", + "MLRS_M270" + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4", + "Soldier_M249" + ], + "shorads": [ + "GepardGenerator" + ], + "sams": [ + "HawkGenerator", + "PatriotGenerator" + ], + "aircraft_carrier": [ + ], + "helicopter_carrier": [ + "LHA_1_Tarawa" + ], + "destroyers": [ + "USS_Arleigh_Burke_IIa" + ], + "cruisers": [ + "Ticonderoga_class" + ], + "requirements": { + }, + "carrier_names": [ + ], + "helicopter_carrier_names": [ + "Hyuga", + "Ise" + ], + "navy_generators": [ + "ArleighBurkeGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper" +} diff --git a/resources/factions/libya_2011.json b/resources/factions/libya_2011.json new file mode 100644 index 00000000..6758167d --- /dev/null +++ b/resources/factions/libya_2011.json @@ -0,0 +1,64 @@ +{ + "country": "Libya", + "name": "Libya 2011", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "MiG_21Bis", + "MiG_23MLD", + "Su_17M4", + "Su_24M", + "Mi_24V" + ], + "awacs": [ + "A_50" + ], + "tankers": [ + "IL_78M" + ], + "frontline_units": [ + "IFV_BMP_1", + "ARV_BRDM_2", + "MBT_T_72B", + "MBT_T_55" + ], + "artillery_units": [ + "MLRS_BM_21_Grad" + ], + "logistics_units": [ + "Transport_Ural_375", + "Transport_UAZ_469" + ], + "infantry_units": [ + "Infantry_Soldier_Insurgents", + "Paratrooper_RPG_16" + ], + "shorads": [ + "HQ7Generator", + "SA8Generator", + "ZSU23Generator" + ], + "sams": [ + "SA2Generator", + "SA3Generator", + "SA6Generator" + ], + "aircraft_carrier": [ + ], + "helicopter_carrier": [ + ], + "helicopter_carrier_names": [ + ], + "destroyers": [ + "FF_1135M_Rezky" + ], + "cruisers": [ + "FSG_1241_1MP_Molniya" + ], + "requirements": {}, + "carrier_names": [ + ], + "navy_generators": [ + "GrishaGroupGenerator", "MolniyaGroupGenerator" + ] +} diff --git a/resources/factions/netherlands_1990.json b/resources/factions/netherlands_1990.json new file mode 100644 index 00000000..3b915643 --- /dev/null +++ b/resources/factions/netherlands_1990.json @@ -0,0 +1,55 @@ +{ + "country": "The Netherlands", + "name": "Netherlands 1990", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "F_16C_50", + "F_5E_3", + "AH_64A" + ], + "awacs": [ + "E_3A" + ], + "tankers": [ + "KC_135", + "KC130" + ], + "frontline_units": [ + "APC_M113", + "MBT_Leopard_1A3" + ], + "artillery_units": [ + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4", + "Soldier_M249" + ], + "shorads": [ + "AvengerGenerator" + ], + "sams": [ + "HawkGenerator" + ], + "aircraft_carrier": [ + ], + "helicopter_carrier": [ + ], + "destroyers": [ + ], + "cruisers": [ + ], + "requirements": {}, + "carrier_names": [ + ], + "helicopter_carrier_names": [ + ], + "navy_generators": [ + "OliverHazardPerryGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper" +} diff --git a/resources/factions/north_korea_2000.json b/resources/factions/north_korea_2000.json new file mode 100644 index 00000000..db8881f3 --- /dev/null +++ b/resources/factions/north_korea_2000.json @@ -0,0 +1,72 @@ +{ + "country": "North Korea", + "name": "North Korea 2000", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "MiG_15bis", + "MiG_19P", + "MiG_21Bis", + "MiG_23MLD", + "MiG_29A", + "Mi_8MT", + "Mi_24V" + ], + "awacs": [ + "A_50" + ], + "tankers": [ + "IL_78M" + ], + "frontline_units": [ + "ARV_BRDM_2", + "APC_BTR_80", + "IFV_BMP_1", + "MBT_T_55", + "MBT_T_72B", + "MBT_T_80U" + ], + "artillery_units": [ + "MLRS_BM_21_Grad", + "MLRS_9K57_Uragan_BM_27", + "SPH_2S19_Msta" + ], + "logistics_units": [ + "Transport_Ural_375", + "Transport_UAZ_469" + ], + "infantry_units": [ + "Paratrooper_AKS", + "Infantry_Soldier_Rus", + "Paratrooper_RPG_16" + ], + "shorads": [ + "SA9Generator", + "SA13Generator", + "ZSU23Generator", + "ZU23Generator" + ], + "sams": [ + "SA2Generator", + "SA3Generator", + "SA6Generator" + ], + "aircraft_carrier": [ + ], + "helicopter_carrier": [ + ], + "helicopter_carrier_names": [ + ], + "destroyers": [ + ], + "cruisers": [ + ], + "requirements": {}, + "carrier_names": [ + ], + "navy_generators": [ + "GrishaGroupGenerator", + "MolniyaGroupGenerator" + ], + "has_jtac": false +} diff --git a/resources/factions/pakistan_2015.json b/resources/factions/pakistan_2015.json new file mode 100644 index 00000000..7020e6be --- /dev/null +++ b/resources/factions/pakistan_2015.json @@ -0,0 +1,69 @@ +{ + "country": "Pakistan", + "name": "Pakistan 2015", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "JF_17", + "F_16C_50", + "MiG_21Bis", + "MiG_19P", + "Mi_8MT", + "UH_1H", + "AH_1W" + ], + "awacs": [ + "KJ_2000" + ], + "tankers": [ + "IL_78M" + ], + "frontline_units": [ + "MBT_T_80U", + "MBT_T_55", + "ZBD_04A", + "APC_BTR_80", + "APC_M113" + ], + "artillery_units": [ + "MLRS_9A52_Smerch", + "SPH_2S9_Nona" + ], + "logistics_units": [ + "Transport_Ural_375", + "Transport_UAZ_469" + ], + "infantry_units": [ + "Paratrooper_AKS", + "Infantry_Soldier_Rus", + "Paratrooper_RPG_16" + ], + "shorads": [ + "HQ7Generator", + "ZU23UralGenerator", + "ZU23Generator" + ], + "sams": [ + "SA10Generator", + "SA2Generator" + ], + "aircraft_carrier": [ + ], + "carrier_names": [ + ], + "helicopter_carrier": [ + ], + "helicopter_carrier_names": [ + ], + "destroyers": [ + ], + "cruiser": [ + ], + "requirements": {}, + "navy_generators": [ + "Type54GroupGenerator", + "OliverHazardPerryGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "WingLoong_I" +} diff --git a/resources/factions/pmc_russian.json b/resources/factions/pmc_russian.json new file mode 100644 index 00000000..560e891a --- /dev/null +++ b/resources/factions/pmc_russian.json @@ -0,0 +1,36 @@ +{ + "country": "Russia", + "name": "Private Military Company - Russian", + "authors": "Khopa", + "description": "A private military company using Russian units.", + "aircrafts": [ + "L_39C", + "L_39ZA", + "Mi_8MT", + "Mi_24V", + "Ka_50" + ], + "frontline_units": [ + "APC_Cobra", + "APC_BTR_80", + "ARV_BRDM_2" + ], + "artillery_units": [ + "SPH_2S19_Msta" + ], + "logistics_units": [ + "Transport_Ural_375", + "Transport_UAZ_469" + ], + "infantry_units": [ + "Paratrooper_AKS", + "Paratrooper_RPG_16" + ], + "shorads": [ + "SA13Generator", + "SA9Generator" + ], + "sams": [ + "SA13Generator" + ] +} diff --git a/resources/factions/pmc_us.json b/resources/factions/pmc_us.json new file mode 100644 index 00000000..58902aab --- /dev/null +++ b/resources/factions/pmc_us.json @@ -0,0 +1,33 @@ +{ + "country": "USA", + "name": "Private Military Company - USA", + "authors": "Khopa", + "description": "A private military company using western units.", + "aircrafts": [ + "C_101CC", + "UH_1H", + "Mi_8MT", + "SA342M" + ], + "frontline_units": [ + "APC_M1043_HMMWV_Armament", + "IFV_MCV_80" + ], + "artillery_units": [ + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4", + "Soldier_M249" + ], + "shorads": [ + "AvengerGenerator" + ], + "sams": [ + "AvengerGenerator" + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper" +} diff --git a/resources/factions/pmc_us_with_mb339.json b/resources/factions/pmc_us_with_mb339.json new file mode 100644 index 00000000..2180b0fe --- /dev/null +++ b/resources/factions/pmc_us_with_mb339.json @@ -0,0 +1,37 @@ +{ + "country": "USA", + "name": "Private Military Company - USA", + "authors": "Khopa", + "description": "A private military company using western units (And using the MB339 mod).", + "aircrafts": [ + "MB_339PAN", + "C_101CC", + "UH_1H", + "Mi_8MT", + "SA342M" + ], + "frontline_units": [ + "APC_M1043_HMMWV_Armament", + "IFV_MCV_80" + ], + "artillery_units": [ + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4", + "Soldier_M249" + ], + "shorads": [ + "AvengerGenerator" + ], + "sams": [ + "AvengerGenerator" + ], + "requirements": { + "MB-339A": "http://www.freccetricolorivirtuali.net/" + }, + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper" +} diff --git a/resources/factions/russia_1955.json b/resources/factions/russia_1955.json new file mode 100644 index 00000000..f621238f --- /dev/null +++ b/resources/factions/russia_1955.json @@ -0,0 +1,60 @@ +{ + "country": "Russia", + "name": "Russia 1955", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "MiG_15bis" + ], + "awacs": [ + "A_50" + ], + "tankers": [ + "IL_78M" + ], + "frontline_units": [ + "ARV_BRDM_2", + "FDDM_Grad", + "APC_MTLB", + "MBT_T_55", + "AAA_ZU_23_on_Ural_375" + ], + "artillery_units": [ + "MLRS_BM_21_Grad" + ], + "logistics_units": [ + "Transport_Ural_375", + "Transport_UAZ_469" + ], + "infantry_units": [ + "Infantry_Soldier_Rus", + "Soldier_RPG" + ], + "shorads": [ + "ZSU23Generator", + "ZU23Generator", + "ZU23UralGenerator" + ], + "sams": [ + "ZSU23Generator", + "ZU23Generator", + "ZU23UralGenerator" + ], + "aircraft_carrier": [ + ], + "helicopter_carrier": [ + ], + "helicopter_carrier_names": [ + ], + "destroyers": [ + ], + "cruisers": [ + ], + "requirements": {}, + "carrier_names": [ + ], + "navy_generators": [ + ], + "has_jtac": false, + "doctrine": "coldwar" +} diff --git a/resources/factions/russia_1965.json b/resources/factions/russia_1965.json new file mode 100644 index 00000000..7f5a9811 --- /dev/null +++ b/resources/factions/russia_1965.json @@ -0,0 +1,64 @@ +{ + "country": "Russia", + "name": "Russia 1955", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "MiG_15bis", + "MiG_19P", + "MiG_21Bis", + "Mi_8MT" + ], + "awacs": [ + "A_50" + ], + "tankers": [ + "IL_78M" + ], + "frontline_units": [ + "ARV_BRDM_2", + "APC_BTR_80", + "ARV_BTR_RD", + "IFV_BMD_1", + "IFV_BMP_1", + "MBT_T_55" + ], + "artillery_units": [ + "MLRS_BM_21_Grad" + ], + "logistics_units": [ + "Transport_Ural_375", + "Transport_UAZ_469" + ], + "infantry_units": [ + "Infantry_Soldier_Rus", + "Soldier_RPG" + ], + "shorads": [ + "ZSU23Generator", + "ZU23Generator", + "ZU23UralGenerator" + ], + "sams": [ + "SA2Generator", + "SA3Generator", + "SA6Generator" + ], + "aircraft_carrier": [ + ], + "helicopter_carrier": [ + ], + "helicopter_carrier_names": [ + ], + "destroyers": [ + ], + "cruisers": [ + ], + "requirements": {}, + "carrier_names": [ + ], + "navy_generators": [ + ], + "has_jtac": false, + "doctrine": "coldwar" +} diff --git a/resources/factions/russia_1975.json b/resources/factions/russia_1975.json new file mode 100644 index 00000000..d3c99900 --- /dev/null +++ b/resources/factions/russia_1975.json @@ -0,0 +1,70 @@ +{ + "country": "Russia", + "name": "Russia 1955", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "MiG_21Bis", + "MiG_23MLD", + "MiG_25PD", + "MiG_29A", + "Su_17M4", + "Su_24M", + "Su_25", + "Mi_8MT", + "Mi_24V" + ], + "awacs": [ + "A_50" + ], + "tankers": [ + "IL_78M" + ], + "frontline_units": [ + "ARV_BRDM_2", + "APC_BTR_80", + "IFV_BMD_1", + "IFV_BMP_1", + "MBT_T_55" + ], + "artillery_units": [ + "MLRS_BM_21_Grad", + "SPH_2S9_Nona", + "SPH_2S1_Gvozdika" + ], + "logistics_units": [ + "Transport_Ural_375", + "Transport_UAZ_469" + ], + "infantry_units": [ + "Infantry_Soldier_Rus", + "Soldier_RPG" + ], + "shorads": [ + "ZSU23Generator", + "ZU23Generator", + "ZU23UralGenerator" + ], + "sams": [ + "SA3Generator", + "SA6Generator" + ], + "aircraft_carrier": [ + ], + "helicopter_carrier": [ + ], + "helicopter_carrier_names": [ + ], + "destroyers": [ + ], + "cruisers": [ + ], + "requirements": {}, + "carrier_names": [ + ], + "navy_generators": [ + "KiloSubGroupGenerator", "MolniyaGroupGenerator" + ], + "has_jtac": false, + "doctrine": "coldwar" +} diff --git a/resources/factions/russia_1990.json b/resources/factions/russia_1990.json new file mode 100644 index 00000000..697167b8 --- /dev/null +++ b/resources/factions/russia_1990.json @@ -0,0 +1,77 @@ +{ + "country": "Russia", + "name": "Russia 1990", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "MiG_23MLD", + "MiG_25PD", + "MiG_29A", + "MiG_29S", + "MiG_31", + "Su_27", + "Su_24M", + "Su_25", + "Ka_50" + ], + "awacs": [ + "A_50" + ], + "tankers": [ + "IL_78M" + ], + "frontline_units": [ + "ARV_BRDM_2", + "APC_BTR_80", + "IFV_BMD_1", + "IFV_BMP_1", + "MBT_T_55" + ], + "artillery_units": [ + "MLRS_9K57_Uragan_BM_27", + "SPH_2S19_Msta" + ], + "logistics_units": [ + "Transport_Ural_375", + "Transport_UAZ_469" + ], + "infantry_units": [ + "Paratrooper_AKS", + "Infantry_Soldier_Rus", + "Paratrooper_RPG_16" + ], + "shorads": [ + "SA9Generator", + "SA13Generator", + "ZSU23Generator", + "ZU23Generator" + ], + "sams": [ + "SA6Generator", + "SA3Generator" + ], + "aircraft_carrier": [ + "CV_1143_5_Admiral_Kuznetsov" + ], + "helicopter_carrier": [ + ], + "helicopter_carrier_names": [ + ], + "destroyers": [ + "FF_1135M_Rezky" + ], + "cruisers": [ + "FSG_1241_1MP_Molniya" + ], + "requirements": {}, + "carrier_names": [ + "Admiral Kuznetov", + "Admiral Gorshkov" + ], + "navy_generators": [ + "RussianNavyGroupGenerator", + "KiloSubGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper" +} diff --git a/resources/factions/russia_2010.json b/resources/factions/russia_2010.json new file mode 100644 index 00000000..6a4c5a47 --- /dev/null +++ b/resources/factions/russia_2010.json @@ -0,0 +1,83 @@ +{ + "country": "Russia", + "name": "Russia 2010", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "MiG_29S", + "MiG_31", + "Su_24M", + "Su_25", + "Su_25T", + "Su_27", + "Su_30", + "Su_33", + "Su_34", + "L_39ZA", + "Mi_8MT", + "Mi_24V", + "Mi_28N", + "Ka_50" + ], + "awacs": [ + "A_50" + ], + "tankers": [ + "IL_78M" + ], + "frontline_units": [ + "IFV_BMP_1", + "IFV_BMP_2", + "IFV_BMP_3", + "APC_BTR_80", + "MBT_T_90", + "MBT_T_80U", + "MBT_T_72B" + ], + "artillery_units": [ + "MLRS_9K57_Uragan_BM_27", + "SPH_2S19_Msta" + ], + "logistics_units": [ + "Transport_Ural_375", + "Transport_UAZ_469" + ], + "infantry_units": [ + "Paratrooper_AKS", + "Infantry_Soldier_Rus", + "Paratrooper_RPG_16" + ], + "shorads": [ + "SA19Generator", + "SA13Generator" + ], + "sams": [ + "SA11Generator", + "SA10Generator", + "SA6Generator", + "SA19Generator" + ], + "aircraft_carrier": [ + "CV_1143_5_Admiral_Kuznetsov" + ], + "helicopter_carrier": [ + ], + "helicopter_carrier_names": [ + ], + "destroyers": [ + "FF_1135M_Rezky" + ], + "cruisers": [ + "FSG_1241_1MP_Molniya" + ], + "requirements": {}, + "carrier_names": [ + "Admiral Kuznetov" + ], + "navy_generators": [ + "RussianNavyGroupGenerator", + "KiloSubGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper" +} diff --git a/resources/factions/russia_2020.json b/resources/factions/russia_2020.json new file mode 100644 index 00000000..68e95ae7 --- /dev/null +++ b/resources/factions/russia_2020.json @@ -0,0 +1,80 @@ +{ + "country": "Russia", + "name": "Russia 2020 (Modded)", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "MiG_29S", + "MiG_31", + "Su_24M", + "Su_25", + "Su_25T", + "Su_27", + "Su_30", + "Su_33", + "Su_34", + "Su_57", + "L_39ZA", + "Mi_8MT", + "Mi_24V", + "Mi_28N", + "Ka_50" + ], + "awacs": [ + "A_50" + ], + "tankers": [ + "IL_78M" + ], + "frontline_units": [ + "IFV_BMP_2", + "IFV_BMP_3", + "MBT_T_90", + "MBT_T_80U", + "MBT_T_72B" + ], + "artillery_units": [ + "MLRS_9K57_Uragan_BM_27", + "SPH_2S19_Msta" + ], + "logistics_units": [ + "Transport_Ural_375", + "Transport_UAZ_469" + ], + "infantry_units": [ + "Paratrooper_AKS", + "Infantry_Soldier_Rus", + "Paratrooper_RPG_16" + ], + "shorads": [ + "SA19Generator" + ], + "sams": [ + "SA11Generator", + "SA10Generator", + "SA19Generator" + ], + "aircraft_carrier": [ + "CV_1143_5_Admiral_Kuznetsov" + ], + "helicopter_carrier": [ + ], + "helicopter_carrier_names": [ + ], + "destroyers": [ + "FF_1135M_Rezky" + ], + "cruisers": [ + "FSG_1241_1MP_Molniya" + ], + "requirements": {}, + "carrier_names": [ + "Admiral Kuznetov" + ], + "navy_generators": [ + "RussianNavyGroupGenerator", + "KiloSubGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper" +} diff --git a/resources/factions/spain_1990.json b/resources/factions/spain_1990.json new file mode 100644 index 00000000..7555c396 --- /dev/null +++ b/resources/factions/spain_1990.json @@ -0,0 +1,64 @@ +{ + "country": "Spain", + "name": "Spain 1990", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "FA_18C_hornet", + "AV8BNA", + "F_5E_3", + "C_101CC", + "UH_1H" + ], + "awacs": [ + "E_3A" + ], + "tankers": [ + "KC_135", + "KC130" + ], + "frontline_units": [ + "MBT_M60A3_Patton", + "MBT_Leopard_2", + "APC_M113" + ], + "artillery_units": [ + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4", + "Soldier_M249" + ], + "shorads": [ + "AvengerGenerator" + ], + "sams": [ + "HawkGenerator" + ], + "aircraft_carrier": [ + "CVN_74_John_C__Stennis" + ], + "helicopter_carrier": [ + "LHA_1_Tarawa" + ], + "destroyers": [ + "Oliver_Hazzard_Perry_class" + ], + "cruisers": [ + "Ticonderoga_class" + ], + "requirements": {}, + "carrier_names": [ + "Principe de Asturias" + ], + "helicopter_carrier_names": [ + "Juan Carlos I" + ], + "navy_generators": [ + "OliverHazardPerryGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper" +} diff --git a/resources/factions/sweden_1990.json b/resources/factions/sweden_1990.json new file mode 100644 index 00000000..3d57c5fe --- /dev/null +++ b/resources/factions/sweden_1990.json @@ -0,0 +1,42 @@ +{ + "country": "Sweden", + "name": "Sweden 1990", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "AJS37", + "UH_1H" + ], + "awacs": [ + "E_3A" + ], + "tankers": [ + "KC_135", + "KC130" + ], + "frontline_units": [ + "IFV_MCV_80", + "MBT_Leopard_2", + "APC_M1126_Stryker_ICV" + ], + "artillery_units": [ + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4", + "Soldier_M249" + ], + "shorads": [ + "AvengerGenerator" + ], + "sams": [ + "HawkGenerator" + ], + "navy_generators": [ + "OliverHazardPerryGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper" +} diff --git a/resources/factions/syria_1948.json b/resources/factions/syria_1948.json new file mode 100644 index 00000000..d9693bf1 --- /dev/null +++ b/resources/factions/syria_1948.json @@ -0,0 +1,49 @@ +{ + "country": "Syria", + "name": "Syria 1948", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "SpitfireLFMkIX", + "SpitfireLFMkIXCW" + ], + "frontline_units": [ + "IFV_Sd_Kfz_234_2_Puma", + "APC_Sd_Kfz_251", + "MT_Pz_Kpfw_IV_Ausf_H", + "MT_M4_Sherman" + ], + "artillery_units": [ + ], + "logistics_units": [ + "Transport_Ural_375", + "Transport_UAZ_469" + ], + "infantry_units": [ + "Infantry_SMLE_No_4_Mk_1" + ], + "shorads": [ + "FlakGenerator" + ], + "sams": [ + "FlakGenerator" + ], + "aircraft_carrier": [ + ], + "helicopter_carrier": [ + ], + "helicopter_carrier_names": [ + ], + "destroyers": [ + ], + "cruisers": [ + ], + "carrier_names": [ + ], + "navy_generators": [ + "SchnellbootGroupGenerator" + ], "requirements": { + "WW2 Asset Pack": "https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/" + }, + "doctrine": "ww2" +} diff --git a/resources/factions/syria_1967.json b/resources/factions/syria_1967.json new file mode 100644 index 00000000..0a2eda52 --- /dev/null +++ b/resources/factions/syria_1967.json @@ -0,0 +1,59 @@ +{ + "country": "Syria", + "name": "Syria 1967", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "MiG_15bis", + "MiG_19P", + "MiG_21Bis", + "Su_17M4", + "Mi_8MT" + ], + "awacs": [ + "A_50" + ], + "tankers": [ + "IL_78M" + ], + "frontline_units": [ + "ARV_BRDM_2", + "MBT_T_55" + ], + "artillery_units": [ + "MLRS_BM_21_Grad" + ], + "logistics_units": [ + "Transport_Ural_375", + "Transport_UAZ_469" + ], + "infantry_units": [ + "Paratrooper_AKS", + "Infantry_Soldier_Rus", + "Soldier_RPG" + ], + "shorads": [ + "ZU23Generator", + "ZU23UralGenerator" + ], + "sams": [ + "SA2Generator" + ], + "aircraft_carrier": [ + ], + "helicopter_carrier": [ + ], + "helicopter_carrier_names": [ + ], + "destroyers": [ + ], + "cruisers": [ + ], + "requirements": {}, + "carrier_names": [ + ], + "navy_generators": [ + "GrishaGroupGenerator" + ], + "doctrine": "coldwar" +} diff --git a/resources/factions/syria_1967_with_ww2_weapons.json b/resources/factions/syria_1967_with_ww2_weapons.json new file mode 100644 index 00000000..1a37b8c0 --- /dev/null +++ b/resources/factions/syria_1967_with_ww2_weapons.json @@ -0,0 +1,64 @@ +{ + "country": "Syria", + "name": "Syria 1967 (With WW2 Weapons)", + "authors": "Khopa", + "description": "(Yes Syria was still using a few Panzer IV and Stug in Yom Kippur War)", + "aircrafts": [ + "MiG_15bis", + "MiG_19P", + "MiG_21Bis", + + "Su_17M4", + "Mi_8MT" + ], + "awacs": [ + "A_50" + ], + "tankers": [ + "IL_78M" + ], + "frontline_units": [ + "ARV_BRDM_2", + "MBT_T_55", + "MT_Pz_Kpfw_IV_Ausf_H", + "StuG_III_Ausf__G", + "TD_Jagdpanzer_IV" + ], + "artillery_units": [ + "MLRS_BM_21_Grad" + ], + "logistics_units": [ + "Transport_Ural_375", + "Transport_UAZ_469" + ], + "infantry_units": [ + "Paratrooper_AKS", + "Infantry_Soldier_Rus", + "Soldier_RPG" + ], + "shorads": [ + "ZU23Generator", + "ZU23UralGenerator" + ], + "sams": [ + "SA2Generator" + ], + "aircraft_carrier": [ + ], + "helicopter_carrier": [ + ], + "helicopter_carrier_names": [ + ], + "destroyers": [ + ], + "cruisers": [ + ], + "carrier_names": [ + ], + "navy_generators": [ + "GrishaGroupGenerator" + ], "requirements": { + "WW2 Asset Pack": "https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/" + }, + "doctrine": "coldwar" +} diff --git a/resources/factions/syria_1973.json b/resources/factions/syria_1973.json new file mode 100644 index 00000000..7d04ab52 --- /dev/null +++ b/resources/factions/syria_1973.json @@ -0,0 +1,63 @@ +{ + "country": "Syria", + "name": "Syria 1973", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "MiG_21Bis", + "MiG_19P", + "MiG_15bis", + "Su_17M4", + "Mi_8MT" + ], + "awacs": [ + "A_50" + ], + "tankers": [ + "IL_78M" + ], + "frontline_units": [ + "IFV_BMP_1", + "APC_MTLB", + "MBT_T_55" + ], + "artillery_units": [ + "MLRS_BM_21_Grad" + ], + "logistics_units": [ + "Transport_Ural_375", + "Transport_UAZ_469" + ], + "infantry_units": [ + "Paratrooper_AKS", + "Infantry_Soldier_Rus", + "Soldier_RPG" + ], + "shorads": [ + "ZSU23Generator", + "ZU23Generator", + "ZU23UralGenerator" + ], + "sams": [ + "SA2Generator", + "SA3Generator", + "SA6Generator" + ], + "aircraft_carrier": [ + ], + "helicopter_carrier": [ + ], + "helicopter_carrier_names": [ + ], + "destroyers": [ + ], + "cruisers": [ + ], + "requirements": {}, + "carrier_names": [ + ], + "navy_generators": [ + "GrishaGroupGenerator" + ], + "doctrine": "coldwar" +} diff --git a/resources/factions/syria_1982.json b/resources/factions/syria_1982.json new file mode 100644 index 00000000..30f53661 --- /dev/null +++ b/resources/factions/syria_1982.json @@ -0,0 +1,65 @@ +{ + "country": "Syria", + "name": "Syria 1982", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "MiG_19P", + "MiG_21Bis", + "MiG_23MLD", + "MiG_25PD", + "Su_17M4", + "Mi_8MT" + ], + "awacs": [ + "A_50" + ], + "tankers": [ + "IL_78M" + ], + "frontline_units": [ + "IFV_BMP_1", + "APC_MTLB", + "MBT_T_55", + "MBT_T_72B" + ], + "artillery_units": [ + "MLRS_BM_21_Grad" + ], + "logistics_units": [ + "Transport_Ural_375", + "Transport_UAZ_469" + ], + "infantry_units": [ + "Paratrooper_AKS", + "Infantry_Soldier_Rus", + "Paratrooper_RPG_16" + ], + "shorads": [ + "ZSU23Generator", + "ZU23Generator", + "ZU23Generator", + "ZU23UralGenerator" + ], + "sams": [ + "SA2Generator", + "SA3Generator", + "SA6Generator" + ], + "aircraft_carrier": [ + ], + "helicopter_carrier": [ + ], + "helicopter_carrier_names": [ + ], + "destroyers": [ + ], + "cruisers": [ + ], + "requirements": {}, + "carrier_names": [ + ], + "navy_generators": [ + "GrishaGroupGenerator" + ] +} diff --git a/resources/factions/syria_2011.json b/resources/factions/syria_2011.json new file mode 100644 index 00000000..a96f843c --- /dev/null +++ b/resources/factions/syria_2011.json @@ -0,0 +1,81 @@ +{ + "country": "Syria", + "name": "Syria 2011", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "MiG_21Bis", + "MiG_23MLD", + "MiG_25PD", + "MiG_29S", + "Su_17M4", + "Su_24M", + "L_39ZA", + "Mi_24V", + "Mi_8MT", + "SA342M", + "SA342L" + ], + "awacs": [ + "A_50" + ], + "tankers": [ + "IL_78M" + ], + "frontline_units": [ + "IFV_BMP_1", + "IFV_BMP_2", + "APC_BTR_80", + "ARV_BRDM_2", + "APC_MTLB", + "APC_Cobra", + "MBT_T_55", + "MBT_T_72B", + "MBT_T_90" + ], + "artillery_units": [ + "MLRS_9K57_Uragan_BM_27", + "SPH_2S9_Nona", + "MLRS_BM_21_Grad", + "SPH_2S1_Gvozdika" + ], + "logistics_units": [ + "Transport_Ural_375", + "Transport_UAZ_469" + ], + "infantry_units": [ + "Paratrooper_AKS", + "Infantry_Soldier_Rus", + "Paratrooper_RPG_16" + ], + "shorads": [ + "SA8Generator", + "SA9Generator", + "SA13Generator", + "SA19Generator", + "ZSU23Generator" + ], + "sams": [ + "SA2Generator", + "SA3Generator", + "SA6Generator", + "SA10Generator", + "SA11Generator" + ], + "aircraft_carrier": [ + ], + "helicopter_carrier": [ + ], + "helicopter_carrier_names": [ + ], + "destroyers": [ + ], + "cruisers": [ + ], + "requirements": {}, + "carrier_names": [ + ], + "navy_generators": [ + "GrishaGroupGenerator", "MolniyaGroupGenerator" + ] +} diff --git a/resources/factions/turkey_2005.json b/resources/factions/turkey_2005.json new file mode 100644 index 00000000..54762875 --- /dev/null +++ b/resources/factions/turkey_2005.json @@ -0,0 +1,49 @@ +{ + "country": "Turkey", + "name": "Turkey 2005", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "F_16C_50", + "F_4E", + "UH_1H", + "AH_1W" + ], + "awacs": [ + "E_3A" + ], + "tankers": [ + "KC_135", + "KC130" + ], + "frontline_units": [ + "MBT_Leopard_2", + "MBT_Leopard_1A3", + "MBT_M60A3_Patton", + "APC_Cobra", + "APC_BTR_80" + ], + "artillery_units": [ + "SPH_M109_Paladin" + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4", + "Soldier_M249", + "Paratrooper_AKS" + ], + "shorads": [ + "AvengerGenerator", + "ZSU23Generator" + ], + "sams": [ + "HawkGenerator" + ], + "navy_generators": [ + "OliverHazardPerryGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper" +} diff --git a/resources/factions/uae_2005.json b/resources/factions/uae_2005.json new file mode 100644 index 00000000..125bbce7 --- /dev/null +++ b/resources/factions/uae_2005.json @@ -0,0 +1,47 @@ +{ + "country": "United Arab Emirates", + "name": "United Arab Emirates 2005", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "M_2000C", + "Mirage_2000_5", + "F_16C_50", + "AH_64D" + ], + "awacs": [ + "E_3A" + ], + "tankers": [ + "KC_135", + "KC130" + ], + "frontline_units": [ + "MBT_Leclerc", + "TPz_Fuchs", + "IFV_BMP_3" + ], + "artillery_units": [ + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4", + "Soldier_M249" + ], + "shorads": [ + "RapierGenerator" + ], + "sams": [ + "HawkGenerator" + ], + "requirements": {}, + "carrier_names": [ + ], + "navy_generators": [ + "OliverHazardPerryGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "WingLoong_I" +} diff --git a/resources/factions/uk_1944.json b/resources/factions/uk_1944.json new file mode 100644 index 00000000..92c32f8c --- /dev/null +++ b/resources/factions/uk_1944.json @@ -0,0 +1,60 @@ +{ + "country": "UK", + "name": "United Kingdom 1944", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "P_51D", + "P_51D_30_NA", + "P_47D_30", + "SpitfireLFMkIX", + "SpitfireLFMkIXCW", + "A_20G", + "B_17G" + ], + "frontline_units": [ + "MT_M4A4_Sherman_Firefly", + "MT_M4_Sherman", + "APC_M2A1", + "CT_Cromwell_IV", + "ST_Centaur_IV", + "HIT_Churchill_VII" + ], + "artillery_units": [ + ], + "logistics_units": [ + "Bedford_MWD", + "CCKW_353" + ], + "infantry_units": [ + "Infantry_SMLE_No_4_Mk_1" + ], + "shorads": [ + "BoforsGenerator" + ], + "sams": [ + "BoforsGenerator" + ], + "aircraft_carrier": [ + ], + "helicopter_carrier": [ + ], + "destroyers": [ + ], + "cruisers": [ + ], + "requirements": { + "WW2 Asset Pack": "https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/" + }, + "carrier_names": [ + ], + "helicopter_carrier_names": [ + ], + "navy_generators": [ + "WW2LSTGroupGenerator" + ], + "navy_group_count": 1, + "has_jtac": false, + "doctrine": "ww2", + "building_set": "ww2ally" +} diff --git a/resources/factions/uk_1990.json b/resources/factions/uk_1990.json new file mode 100644 index 00000000..9b274eeb --- /dev/null +++ b/resources/factions/uk_1990.json @@ -0,0 +1,70 @@ +{ + "country": "UK", + "name": "United Kingdom 1990", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "Tornado_GR4", + "AV8BNA", + "F_4E", + "SA342M", + "AH_64A" + ], + "awacs": [ + "E_3A" + ], + "tankers": [ + "KC_135", + "KC130" + ], + "frontline_units": [ + "MBT_Challenger_II", + "IFV_MCV_80", + "APC_M1043_HMMWV_Armament", + "ATGM_M1045_HMMWV_TOW" + ], + "artillery_units": [ + "MLRS_M270", + "SPH_M109_Paladin" + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4", + "Soldier_M249" + ], + "shorads": [ + "AvengerGenerator", + "RapierGenerator" + ], + "sams": [ + "HawkGenerator" + ], + "aircraft_carrier": [ + "CVN_74_John_C__Stennis" + ], + "helicopter_carrier": [ + "LHA_1_Tarawa" + ], + "destroyers": [ + "Oliver_Hazzard_Perry_class" + ], + "cruisers": [ + "Ticonderoga_class" + ], + "requirements": {}, + "carrier_names": [ + ], + "helicopter_carrier_names": [ + "HMS Invincible", + "HMS Illustrious", + "HMS Ark Royal" + ], + "navy_generators": [ + "OliverHazardPerryGroupGenerator", + "ArleighBurkeGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper" +} \ No newline at end of file diff --git a/resources/factions/ukraine_2010.json b/resources/factions/ukraine_2010.json new file mode 100644 index 00000000..872d3a7e --- /dev/null +++ b/resources/factions/ukraine_2010.json @@ -0,0 +1,59 @@ +{ + "country": "Ukraine", + "name": "Ukraine 2010", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "Su_25", + "Su_25T", + "Su_24M", + "Su_27", + "MiG_29S", + "L_39ZA", + "Mi_8MT", + "Mi_24V" + ], + "awacs": [ + "A_50" + ], + "tankers": [ + "IL_78M" + ], + "frontline_units": [ + "APC_M1043_HMMWV_Armament", + "IFV_BMP_3", + "IFV_BMP_2", + "APC_BTR_80", + "MBT_T_80U", + "MBT_T_72B" + ], + "artillery_units": [ + ], + "logistics_units": [ + "Transport_Ural_375", + "Transport_UAZ_469" + ], + "infantry_units": [ + "Paratrooper_AKS", + "Infantry_Soldier_Rus", + "Paratrooper_RPG_16" + ], + "shorads": [ + "SA9Generator", + "SA13Generator", + "SA19Generator" + ], + "sams": [ + "SA3Generator", + "SA10Generator", + "SA11Generator" + ], + "requirements": {}, + "carrier_names": [ + "Admiral Kuznetov", + "Admiral Gorshkov" + ], + "navy_generators": [ + "GrishaGroupGenerator" + ] +} diff --git a/resources/factions/us_aggressors.json b/resources/factions/us_aggressors.json new file mode 100644 index 00000000..340d6b19 --- /dev/null +++ b/resources/factions/us_aggressors.json @@ -0,0 +1,62 @@ +{ + "country": "USAF Aggressors", + "name": "USAF Aggressors", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "F_15C", + "F_15E", + "F_14B", + "FA_18C_hornet", + "F_16C_50", + "A_10A", + "A_10C", + "AV8BNA", + "UH_1H", + "AH_64D", + "Ka_50", + "B_52H", + "B_1B", + "F_117A", + "Su_27" + ], + "awacs": [ + "E_3A" + ], + "tankers": [ + "KC_135", + "KC130" + ], + "frontline_units": [ + "MBT_M1A2_Abrams", + "MBT_Leopard_2", + "ATGM_M1134_Stryker", + "IFV_M2A2_Bradley", + "IFV_LAV_25", + "APC_M1043_HMMWV_Armament" + ], + "artillery_units": [ + "MLRS_M270", + "SPH_M109_Paladin" + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4", + "Soldier_M249" + ], + "shorads": [ + "AvengerGenerator" + ], + "sams": [ + "HawkGenerator", + "PatriotGenerator" + ], + "requirements": {}, + "navy_generators": [ + "OliverHazardPerryGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper" +} \ No newline at end of file diff --git a/resources/factions/usa_1944.json b/resources/factions/usa_1944.json new file mode 100644 index 00000000..02bfdefa --- /dev/null +++ b/resources/factions/usa_1944.json @@ -0,0 +1,59 @@ +{ + "country": "USA", + "name": "USA 1944", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "P_51D", + "P_51D_30_NA", + "P_47D_30", + "SpitfireLFMkIX", + "A_20G", + "B_17G" + ], + "frontline_units": [ + "MT_M4A4_Sherman_Firefly", + "MT_M4_Sherman", + "APC_M2A1", + "M30_Cargo_Carrier", + "LAC_M8_Greyhound", + "TD_M10_GMC" + ], + "artillery_units": [ + "M12_GMC" + ], + "logistics_units": [ + "CCKW_353" + ], + "infantry_units": [ + "Infantry_M1_Garand" + ], + "shorads": [ + "BoforsGenerator" + ], + "sams": [ + "BoforsGenerator" + ], + "aircraft_carrier": [ + ], + "helicopter_carrier": [ + ], + "destroyers": [ + ], + "cruisers": [ + ], + "requirements": { + "WW2 Asset Pack": "https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/" + }, + "carrier_names": [ + ], + "helicopter_carrier_names": [ + ], + "navy_generators": [ + "WW2LSTGroupGenerator" + ], + "navy_group_count": 1, + "has_jtac": false, + "doctrine": "ww2", + "building_set": "ww2ally" +} diff --git a/resources/factions/usa_1955.json b/resources/factions/usa_1955.json new file mode 100644 index 00000000..3c5de408 --- /dev/null +++ b/resources/factions/usa_1955.json @@ -0,0 +1,38 @@ +{ + "country": "USA", + "name": "USA 1955", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "F_86F_Sabre", + "P_51D", + "P_51D_30_NA", + "B_52H" + ], + "frontline_units": [ + "MT_M4A4_Sherman_Firefly", + "MT_M4_Sherman", + "MBT_M60A3_Patton", + "APC_M2A1" + ], + "artillery_units": [ + "M12_GMC" + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M1_Garand" + ], + "shorads": [ + "BoforsGenerator" + ], + "sams": [ + "BoforsGenerator" + ], + "doctrine": "ww2", + "building_set": "ww2ally", + "requirements": { + "WW2 Asset Pack": "https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/" + } +} \ No newline at end of file diff --git a/resources/factions/usa_1960.json b/resources/factions/usa_1960.json new file mode 100644 index 00000000..7c2d762d --- /dev/null +++ b/resources/factions/usa_1960.json @@ -0,0 +1,33 @@ +{ + "country": "USA", + "name": "USA 1960", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "F_86F_Sabre", + "P_51D", + "P_51D_30_NA", + "B_52H", + "UH_1H" + ], + "frontline_units": [ + "MBT_M60A3_Patton", + "APC_M113" + ], + "artillery_units": [ + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4" + ], + "shorads": [ + "VulcanGenerator" + ], + "sams": [ + "VulcanGenerator" + ], + "requirements": {}, + "doctrine": "coldwar" +} \ No newline at end of file diff --git a/resources/factions/usa_1965.json b/resources/factions/usa_1965.json new file mode 100644 index 00000000..74171172 --- /dev/null +++ b/resources/factions/usa_1965.json @@ -0,0 +1,36 @@ +{ + "country": "USA", + "name": "USA 1965", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "F_5E_3", + "F_4E", + "B_52H", + "UH_1H" + ], + "frontline_units": [ + "MBT_M60A3_Patton", + "APC_M113" + ], + "artillery_units": [ + "SPH_M109_Paladin" + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4", + "Soldier_M249" + ], + "shorads": [ + "VulcanGenerator", + "ChaparralGenerator" + ], + "sams": [ + "HawkGenerator", + "ChaparralGenerator" + ], + "requirements": {}, + "doctrine": "coldwar" +} \ No newline at end of file diff --git a/resources/factions/usa_1975.json b/resources/factions/usa_1975.json new file mode 100644 index 00000000..5c999a9e --- /dev/null +++ b/resources/factions/usa_1975.json @@ -0,0 +1,40 @@ +{ + "country": "USA", + "name": "USA 1975", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "F_5E_3", + "F_4E", + "F_14B", + "B_52H", + "UH_1H" + ], + "frontline_units": [ + "MBT_M60A3_Patton", + "APC_M113" + ], + "artillery_units": [ + "SPH_M109_Paladin" + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4", + "Soldier_M249" + ], + "shorads": [ + "VulcanGenerator", + "ChaparralGenerator" + ], + "sams": [ + "HawkGenerator", + "ChaparralGenerator" + ], + "navy_generators": [ + "OliverHazardPerryGroupGenerator" + ], + "requirements": {}, + "doctrine": "coldwar" +} \ No newline at end of file diff --git a/resources/factions/usa_1990.json b/resources/factions/usa_1990.json new file mode 100644 index 00000000..c2ff5f98 --- /dev/null +++ b/resources/factions/usa_1990.json @@ -0,0 +1,86 @@ +{ + "country": "USA", + "name": "USA 1990", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "F_15C", + "F_15E", + "F_14B", + "FA_18C_hornet", + "F_16C_50", + "A_10A", + "AV8BNA", + "UH_1H", + "AH_64A", + "B_52H", + "B_1B", + "F_117A" + ], + "awacs": [ + "E_3A" + ], + "tankers": [ + "KC_135", + "KC130" + ], + "frontline_units": [ + "MBT_M1A2_Abrams", + "ATGM_M1134_Stryker", + "APC_M1126_Stryker_ICV", + "IFV_M2A2_Bradley", + "IFV_LAV_25", + "APC_M1043_HMMWV_Armament", + "ATGM_M1045_HMMWV_TOW" + ], + "artillery_units": [ + "MLRS_M270", + "SPH_M109_Paladin" + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4", + "Soldier_M249" + ], + "shorads": [ + "AvengerGenerator" + ], + "sams": [ + "HawkGenerator" + ], + "aircraft_carrier": [ + "CVN_74_John_C__Stennis" + ], + "helicopter_carrier": [ + "LHA_1_Tarawa" + ], + "destroyers": [ + "Oliver_Hazzard_Perry_class", + "USS_Arleigh_Burke_IIa" + ], + "cruisers": [ + "Ticonderoga_class" + ], + "requirements": {}, + "carrier_names": [ + "CVN-71 Theodore Roosevelt", + "CVN-72 Abraham Lincoln", + "CVN-73 George Washington", + "CVN-74 John C. Stennis" + ], + "helicopter_carrier_names": [ + "LHA-1 Tarawa", + "LHA-2 Saipan", + "LHA-3 Belleau Wood", + "LHA-4 Nassau", + "LHA-5 Peleliu" + ], + "navy_generators": [ + "OliverHazardPerryGroupGenerator", + "ArleighBurkeGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper" +} \ No newline at end of file diff --git a/resources/factions/usa_2005.json b/resources/factions/usa_2005.json new file mode 100644 index 00000000..8f8f4a60 --- /dev/null +++ b/resources/factions/usa_2005.json @@ -0,0 +1,87 @@ +{ + "country": "USA", + "name": "USA 2005", + "authors": "Khopa", + "description": "", + "aircrafts": [ + "F_15C", + "F_15E", + "F_14B", + "FA_18C_hornet", + "F_16C_50", + "A_10C", + "A_10C_2", + "AV8BNA", + "UH_1H", + "AH_64D", + "B_52H", + "B_1B", + "F_117A" + ], + "awacs": [ + "E_3A" + ], + "tankers": [ + "KC_135", + "KC130" + ], + "frontline_units": [ + "MBT_M1A2_Abrams", + "ATGM_M1134_Stryker", + "APC_M1126_Stryker_ICV", + "IFV_M2A2_Bradley", + "IFV_LAV_25", + "APC_M1043_HMMWV_Armament", + "ATGM_M1045_HMMWV_TOW" + ], + "artillery_units": [ + "MLRS_M270", + "SPH_M109_Paladin" + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4", + "Soldier_M249" + ], + "shorads": [ + "AvengerGenerator" + ], + "sams": [ + "HawkGenerator", + "PatriotGenerator" + ], + "aircraft_carrier": [ + "CVN_74_John_C__Stennis" + ], + "helicopter_carrier": [ + "LHA_1_Tarawa" + ], + "destroyers": [ + "USS_Arleigh_Burke_IIa" + ], + "cruisers": [ + "Ticonderoga_class" + ], + "requirements": {}, + "carrier_names": [ + "CVN-71 Theodore Roosevelt", + "CVN-72 Abraham Lincoln", + "CVN-73 George Washington", + "CVN-74 John C. Stennis" + ], + "helicopter_carrier_names": [ + "LHA-1 Tarawa", + "LHA-2 Saipan", + "LHA-3 Belleau Wood", + "LHA-4 Nassau", + "LHA-5 Peleliu" + ], + "navy_generators": [ + "ArleighBurkeGroupGenerator", + "OliverHazardPerryGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper" +} \ No newline at end of file diff --git a/resources/tools/generate_loadout_check.py b/resources/tools/generate_loadout_check.py index 09835265..21a6e997 100644 --- a/resources/tools/generate_loadout_check.py +++ b/resources/tools/generate_loadout_check.py @@ -20,7 +20,7 @@ for t, uts in db.UNIT_BY_TASK.items(): pos.x += 10000 for ut in uts: pos.y += 5000 - ctr = mis.country([v["country"] for k, v in db.FACTIONS.items() if ut in v["units"]][0]) + ctr = mis.country([v["country"] for k, v in db.FACTIONS.items() if ut in v.units][0]) g = mis.flight_group_inflight( country=ctr, diff --git a/tests/resources/invalid_faction_country.json b/tests/resources/invalid_faction_country.json new file mode 100644 index 00000000..84579ab0 --- /dev/null +++ b/tests/resources/invalid_faction_country.json @@ -0,0 +1,86 @@ +{ + "country": "C", + "name": "USA 2005", + "authors": "Khopa", + "description": "This is a test description", + "aircrafts": [ + "F_15C", + "F_15E", + "F_14B", + "FA_18C_hornet", + "F_16C_50", + "A_10A", + "AV8BNA", + "UH_1H", + "AH_64A", + "B_52H", + "B_1B", + "F_117A" + ], + "awacs": [ + "E_3A" + ], + "tankers": [ + "KC_135", + "KC130" + ], + "frontline_units": [ + "MBT_M1A2_Abrams", + "ATGM_M1134_Stryker", + "APC_M1126_Stryker_ICV", + "IFV_M2A2_Bradley", + "IFV_LAV_25", + "APC_M1043_HMMWV_Armament", + "ATGM_M1045_HMMWV_TOW" + ], + "artillery_units": [ + "MLRS_M270", + "SPH_M109_Paladin" + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4", + "Soldier_M249" + ], + "shorads": [ + "AvengerGenerator" + ], + "sams": [ + "HawkGenerator" + ], + "aircraft_carrier": [ + "CVN_74_John_C__Stennis" + ], + "helicopter_carrier": [ + "LHA_1_Tarawa" + ], + "destroyers": [ + "Oliver_Hazzard_Perry_class", + "USS_Arleigh_Burke_IIa" + ], + "cruisers": [ + "Ticonderoga_class" + ], + "requirements": {"mod": "Some mod is required"}, + "carrier_names": [ + "CVN-71 Theodore Roosevelt", + "CVN-72 Abraham Lincoln", + "CVN-73 George Washington", + "CVN-74 John C. Stennis" + ], + "helicopter_carrier_names": [ + "LHA-1 Tarawa", + "LHA-2 Saipan", + "LHA-3 Belleau Wood", + "LHA-4 Nassau", + "LHA-5 Peleliu" + ], + "navy_generators": [ + "OliverHazardPerryGroupGenerator", + "ArleighBurkeGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper" +} \ No newline at end of file diff --git a/tests/resources/valid_faction.json b/tests/resources/valid_faction.json new file mode 100644 index 00000000..b540cc55 --- /dev/null +++ b/tests/resources/valid_faction.json @@ -0,0 +1,88 @@ +{ + "country": "USA", + "name": "USA 2005", + "authors": "Khopa", + "description": "This is a test description", + "aircrafts": [ + "F_15C", + "F_15E", + "F_14B", + "FA_18C_hornet", + "F_16C_50", + "A_10A", + "AV8BNA", + "UH_1H", + "AH_64A", + "B_52H", + "B_1B", + "F_117A", + "A_10C", + "A_10C_2" + ], + "awacs": [ + "E_3A" + ], + "tankers": [ + "KC_135", + "KC130" + ], + "frontline_units": [ + "MBT_M1A2_Abrams", + "ATGM_M1134_Stryker", + "APC_M1126_Stryker_ICV", + "IFV_M2A2_Bradley", + "IFV_LAV_25", + "APC_M1043_HMMWV_Armament", + "ATGM_M1045_HMMWV_TOW" + ], + "artillery_units": [ + "MLRS_M270", + "SPH_M109_Paladin" + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4", + "Soldier_M249" + ], + "shorads": [ + "AvengerGenerator" + ], + "sams": [ + "HawkGenerator" + ], + "aircraft_carrier": [ + "CVN_74_John_C__Stennis" + ], + "helicopter_carrier": [ + "LHA_1_Tarawa" + ], + "destroyers": [ + "Oliver_Hazzard_Perry_class", + "USS_Arleigh_Burke_IIa" + ], + "cruisers": [ + "Ticonderoga_class" + ], + "requirements": {"mod": "Some mod is required"}, + "carrier_names": [ + "CVN-71 Theodore Roosevelt", + "CVN-72 Abraham Lincoln", + "CVN-73 George Washington", + "CVN-74 John C. Stennis" + ], + "helicopter_carrier_names": [ + "LHA-1 Tarawa", + "LHA-2 Saipan", + "LHA-3 Belleau Wood", + "LHA-4 Nassau", + "LHA-5 Peleliu" + ], + "navy_generators": [ + "OliverHazardPerryGroupGenerator", + "ArleighBurkeGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper" +} \ No newline at end of file diff --git a/tests/test_factions.py b/tests/test_factions.py new file mode 100644 index 00000000..ecd32533 --- /dev/null +++ b/tests/test_factions.py @@ -0,0 +1,95 @@ +import json +import unittest + +from dcs.helicopters import UH_1H, AH_64A +from dcs.planes import F_15C, F_15E, F_14B, FA_18C_hornet, F_16C_50, A_10A, AV8BNA, B_52H, B_1B, F_117A, MQ_9_Reaper, \ + E_3A, KC130, KC_135, A_10C, A_10C_2 +from dcs.ships import CVN_74_John_C__Stennis, LHA_1_Tarawa, Oliver_Hazzard_Perry_class, USS_Arleigh_Burke_IIa, \ + Ticonderoga_class +from dcs.vehicles import Armor, Unarmed, Infantry, Artillery + +from game.factions.faction import Faction + + +class TestFactionLoader(unittest.TestCase): + + def setUp(self): + pass + + def test_load_valid_faction(self): + with open("./resources/valid_faction.json", 'r') as data: + faction = Faction.from_json(json.load(data)) + + self.assertEqual(faction.country, "USA") + self.assertEqual(faction.name, "USA 2005") + self.assertEqual(faction.authors, "Khopa") + self.assertEqual(faction.description, "This is a test description") + + self.assertIn(F_15C, faction.aircrafts) + self.assertIn(F_15E, faction.aircrafts) + self.assertIn(F_14B, faction.aircrafts) + self.assertIn(FA_18C_hornet, faction.aircrafts) + self.assertIn(F_16C_50, faction.aircrafts) + self.assertIn(A_10A, faction.aircrafts) + self.assertIn(AV8BNA, faction.aircrafts) + self.assertIn(UH_1H, faction.aircrafts) + self.assertIn(AH_64A, faction.aircrafts) + self.assertIn(B_52H, faction.aircrafts) + self.assertIn(B_1B, faction.aircrafts) + self.assertIn(F_117A, faction.aircrafts) + self.assertIn(A_10C, faction.aircrafts) + self.assertIn(A_10C_2, faction.aircrafts) + + self.assertEqual(len(faction.awacs), 1) + self.assertIn(E_3A, faction.awacs) + + self.assertEqual(len(faction.tankers), 2) + self.assertIn(KC_135, faction.tankers) + self.assertIn(KC130, faction.tankers) + + self.assertTrue(faction.has_jtac) + self.assertEqual(faction.jtac_unit, MQ_9_Reaper) + + self.assertIn(Armor.MBT_M1A2_Abrams, faction.frontline_units) + self.assertIn(Armor.ATGM_M1134_Stryker, faction.frontline_units) + self.assertIn(Armor.APC_M1126_Stryker_ICV, faction.frontline_units) + self.assertIn(Armor.IFV_M2A2_Bradley, faction.frontline_units) + self.assertIn(Armor.IFV_LAV_25, faction.frontline_units) + self.assertIn(Armor.APC_M1043_HMMWV_Armament, faction.frontline_units) + self.assertIn(Armor.ATGM_M1045_HMMWV_TOW, faction.frontline_units) + + self.assertIn(Artillery.MLRS_M270, faction.artillery_units) + self.assertIn(Artillery.SPH_M109_Paladin, faction.artillery_units) + + self.assertIn(Unarmed.Transport_M818, faction.logistics_units) + + self.assertIn(Infantry.Infantry_M4, faction.infantry_units) + self.assertIn(Infantry.Soldier_M249, faction.infantry_units) + + self.assertIn("AvengerGenerator", faction.shorads) + + self.assertIn("HawkGenerator", faction.sams) + + self.assertIn(CVN_74_John_C__Stennis, faction.aircraft_carrier) + self.assertIn(LHA_1_Tarawa, faction.helicopter_carrier) + self.assertIn(Oliver_Hazzard_Perry_class, faction.destroyers) + self.assertIn(USS_Arleigh_Burke_IIa, faction.destroyers) + self.assertIn(Ticonderoga_class, faction.cruisers) + + self.assertIn("mod", faction.requirements.keys()) + self.assertIn("Some mod is required", faction.requirements.values()) + + self.assertEqual(4, len(faction.carrier_names)) + self.assertEqual(5, len(faction.helicopter_carrier_names)) + + self.assertIn("OliverHazardPerryGroupGenerator", faction.navy_generators) + self.assertIn("ArleighBurkeGroupGenerator", faction.navy_generators) + + def test_load_valid_faction_with_invalid_country(self): + + with open("./resources/invalid_faction_country.json", 'r') as data: + try: + Faction.from_json(json.load(data)) + self.fail("Should have thrown assertion error") + except AssertionError as e: + pass diff --git a/theater/base.py b/theater/base.py index 4ca5dec7..47b3580e 100644 --- a/theater/base.py +++ b/theater/base.py @@ -148,6 +148,9 @@ class Base: elif self.strength <= 0: self.strength = BASE_MIN_STRENGTH + def set_strength_to_minimum(self) -> None: + self.strength = BASE_MIN_STRENGTH + def scramble_count(self, multiplier: float, task: Task = None) -> int: if task: count = sum([v for k, v in self.aircraft.items() if db.unit_task(k) == task]) diff --git a/theater/conflicttheater.py b/theater/conflicttheater.py index 4339236b..4095f2aa 100644 --- a/theater/conflicttheater.py +++ b/theater/conflicttheater.py @@ -1,7 +1,6 @@ from __future__ import annotations -import json -from typing import Any, Dict, Iterator, List, Optional, Tuple +from typing import Any, Dict, Iterator, List, Optional, Tuple, TYPE_CHECKING from dcs.mapping import Point from dcs.terrain import ( @@ -17,6 +16,9 @@ from dcs.terrain.terrain import Terrain from .controlpoint import ControlPoint from .landmap import Landmap, load_landmap, poly_contains +if TYPE_CHECKING: + from . import FrontLine + SIZE_TINY = 150 SIZE_SMALL = 600 SIZE_REGULAR = 1000 @@ -125,10 +127,11 @@ class ConflictTheater: def player_points(self) -> List[ControlPoint]: return [point for point in self.controlpoints if point.captured] - def conflicts(self, from_player=True) -> Iterator[Tuple[ControlPoint, ControlPoint]]: + def conflicts(self, from_player=True) -> Iterator[FrontLine]: + from . import FrontLine # Circular import that needs to be resolved. for cp in [x for x in self.controlpoints if x.captured == from_player]: for connected_point in [x for x in cp.connected_points if x.captured != from_player]: - yield cp, connected_point + yield FrontLine(cp, connected_point) def enemy_points(self) -> List[ControlPoint]: return [point for point in self.controlpoints if not point.captured] diff --git a/theater/controlpoint.py b/theater/controlpoint.py index 6f520bd1..96f2c060 100644 --- a/theater/controlpoint.py +++ b/theater/controlpoint.py @@ -1,5 +1,7 @@ +from __future__ import annotations + import re -from typing import Dict, List +from typing import Dict, List, TYPE_CHECKING from enum import Enum from dcs.mapping import Point @@ -17,6 +19,9 @@ from .base import Base from .missiontarget import MissionTarget from .theatergroundobject import TheaterGroundObject +if TYPE_CHECKING: + from game import Game + class ControlPointType(Enum): AIRBASE = 0 # An airbase with slots for everything @@ -207,3 +212,26 @@ class ControlPoint(MissionTarget): def is_friendly(self, to_player: bool) -> bool: return self.captured == to_player + + def capture(self, game: Game, for_player: bool) -> None: + if for_player: + self.captured = True + faction_name = game.player_name + else: + self.captured = False + faction_name = game.enemy_name + + self.base.set_strength_to_minimum() + + self.base.aircraft = {} + self.base.armor = {} + + # Handle cyclic dependency. + from .start_generator import generate_airbase_defense_group + airbase_def_id = 0 + for ground_object in self.ground_objects: + ground_object.groups = [] + if ground_object.airbase_group and faction_name != "": + generate_airbase_defense_group(airbase_def_id, ground_object, + faction_name, game, self) + airbase_def_id = airbase_def_id + 1 diff --git a/theater/start_generator.py b/theater/start_generator.py index b14f1b99..135d50fc 100644 --- a/theater/start_generator.py +++ b/theater/start_generator.py @@ -31,7 +31,7 @@ from theater import ( ) from theater.conflicttheater import IMPORTANCE_HIGH, IMPORTANCE_LOW -UNIT_VARIETY = 3 +UNIT_VARIETY = 6 UNIT_AMOUNT_FACTOR = 16 UNIT_COUNT_IMPORTANCE_LOG = 1.3 @@ -90,6 +90,7 @@ def generate_groundobjects(theater: ConflictTheater, game): faction_name = game.player_name else: faction_name = game.enemy_name + faction = db.FACTIONS[faction_name] if cp.cptype == ControlPointType.AIRCRAFT_CARRIER_GROUP: # Create ground object group @@ -110,8 +111,8 @@ def generate_groundobjects(theater: ConflictTheater, game): g.groups.append(group) cp.ground_objects.append(g) # Set new name : - if "carrier_names" in db.FACTIONS[faction_name]: - cp.name = random.choice(db.FACTIONS[faction_name]["carrier_names"]) + if len(faction.carrier_names) > 0: + cp.name = random.choice(faction.carrier_names) else: cp_to_remove.append(cp) elif cp.cptype == ControlPointType.LHA_GROUP: @@ -133,8 +134,8 @@ def generate_groundobjects(theater: ConflictTheater, game): g.groups.append(group) cp.ground_objects.append(g) # Set new name : - if "lhanames" in db.FACTIONS[faction_name]: - cp.name = random.choice(db.FACTIONS[faction_name]["lhanames"]) + if len(faction.helicopter_carrier_names) > 0: + cp.name = random.choice(faction.helicopter_carrier_names) else: cp_to_remove.append(cp) else: @@ -171,19 +172,14 @@ def generate_groundobjects(theater: ConflictTheater, game): logging.info(ground_object.groups) # Generate navy groups - if "boat" in db.FACTIONS[faction_name].keys() and cp.allow_sea_units: + if len(faction.navy_generators) > 0 and cp.allow_sea_units: if cp.captured and game.settings.do_not_generate_player_navy: continue - if not cp.captured and game.settings.do_not_generate_enemy_navy: continue - boat_count = 1 - if "boat_count" in db.FACTIONS[faction_name].keys(): - boat_count = int(db.FACTIONS[faction_name]["boat_count"]) - - for i in range(boat_count): + for i in range(faction.navy_group_count): point = find_location(False, cp.position, theater, 5000, 40000, [], False) @@ -210,15 +206,9 @@ def generate_groundobjects(theater: ConflictTheater, game): g.groups.append(group) cp.ground_objects.append(g) + if len(faction.missiles) > 0: - - if "missiles" in db.FACTIONS[faction_name].keys(): - - missiles_count = 1 - if "missiles_count" in db.FACTIONS[faction_name].keys(): - missiles_count = int(db.FACTIONS[faction_name]["missiles_count"]) - - for i in range(missiles_count): + for i in range(faction.missiles_group_count): point = find_location(True, cp.position, theater, 2500, 40000, [], False) @@ -347,9 +337,7 @@ def generate_cp_ground_points(cp: ControlPoint, theater, game, group_id, templat faction = game.enemy_name faction_data = db.FACTIONS[faction] - available_categories = DEFAULT_AVAILABLE_BUILDINGS - if "objects" in faction_data.keys(): - available_categories = faction_data["objects"] + available_categories = faction_data.building_set if len(available_categories) == 0: return False From 0477247cf23de1b4a9ae42a92b1bffe59c2eb586 Mon Sep 17 00:00:00 2001 From: David Pierron Date: Mon, 26 Oct 2020 21:05:34 +0100 Subject: [PATCH 6/8] correction : skipping plugin work orders did not work --- game/operation/operation.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/game/operation/operation.py b/game/operation/operation.py index 43883625..69c152ba 100644 --- a/game/operation/operation.py +++ b/game/operation/operation.py @@ -148,23 +148,23 @@ class Operation: logging.debug( f"Skipping already loaded {script} for {plugin_mnemonic}" ) + else: + self.plugin_scripts.append(script_mnemonic) - self.plugin_scripts.append(script_mnemonic) + plugin_path = Path("./resources/plugins", plugin_mnemonic) - plugin_path = Path("./resources/plugins", plugin_mnemonic) + script_path = Path(plugin_path, script) + if not script_path.exists(): + logging.error( + f"Cannot find {script_path} for plugin {plugin_mnemonic}" + ) + return - script_path = Path(plugin_path, script) - if not script_path.exists(): - logging.error( - f"Cannot find {script_path} for plugin {plugin_mnemonic}" - ) - return - - trigger = TriggerStart(comment=f"Load {script_mnemonic}") - filename = script_path.resolve() - fileref = self.current_mission.map_resource.add_resource_file(filename) - trigger.add_action(DoScriptFile(fileref)) - self.current_mission.triggerrules.triggers.append(trigger) + trigger = TriggerStart(comment=f"Load {script_mnemonic}") + filename = script_path.resolve() + fileref = self.current_mission.map_resource.add_resource_file(filename) + trigger.add_action(DoScriptFile(fileref)) + self.current_mission.triggerrules.triggers.append(trigger) def generate(self): radio_registry = RadioRegistry() From de95cfc98162414448076353ccd15420d58aaca3 Mon Sep 17 00:00:00 2001 From: David Pierron Date: Mon, 26 Oct 2020 21:06:08 +0100 Subject: [PATCH 7/8] base plugin needs to be loaded first, before skynetiads --- resources/plugins/plugins.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/plugins/plugins.json b/resources/plugins/plugins.json index 6d4fb155..6dcac3ae 100644 --- a/resources/plugins/plugins.json +++ b/resources/plugins/plugins.json @@ -1,5 +1,5 @@ [ - "skynetiads", + "base", "jtacautolase", - "base" + "skynetiads" ] From 2cb37b5bd8961cae03f5177f9dab8d505ed2809d Mon Sep 17 00:00:00 2001 From: David Pierron Date: Mon, 26 Oct 2020 21:06:25 +0100 Subject: [PATCH 8/8] added typing --- gen/sam/genericsam_group_generator.py | 2 +- gen/sam/group_generator.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gen/sam/genericsam_group_generator.py b/gen/sam/genericsam_group_generator.py index 90933e3a..00d5ed6e 100644 --- a/gen/sam/genericsam_group_generator.py +++ b/gen/sam/genericsam_group_generator.py @@ -11,7 +11,7 @@ class GenericSamGroupGenerator(GroupGenerator): """ @property - def groupNamePrefix(self): + def groupNamePrefix(self) -> str: # prefix the SAM site for use with the Skynet IADS plugin if self.faction == self.game.player_name: # this is the player faction return "BLUE SAM " diff --git a/gen/sam/group_generator.py b/gen/sam/group_generator.py index 010650cc..9f150ef4 100644 --- a/gen/sam/group_generator.py +++ b/gen/sam/group_generator.py @@ -19,7 +19,7 @@ class GroupGenerator(): wp.ETA_locked = True @property - def groupNamePrefix(self): + def groupNamePrefix(self) -> str: return "" def generate(self):