diff --git a/changelog.md b/changelog.md index a1d9239f..096caf61 100644 --- a/changelog.md +++ b/changelog.md @@ -19,6 +19,9 @@ * **[Mission Generator]** The briefing will now contain the carrier ATC frequency * **[Mission Generator]** The briefing contains a small situation update. * **[Mission Generator]** Previously destroyed units are visible in the mission. (And added a performance settings to disable this behaviour) +* **[Campaign Generator]** Added Tarawa in caucasus campaigns +* **[Campaign Generator]** Tuned the various existing campaign parameters +* **[Campaign Generator]** Added small campaign : "Russia" on Caucasus Theater ## Fixed issues : * **[Mission Generator]** Carrier will sail into the wind, not in the same direction diff --git a/game/factions/australia_2005.py b/game/factions/australia_2005.py index 28148c9d..df4972e6 100644 --- a/game/factions/australia_2005.py +++ b/game/factions/australia_2005.py @@ -46,5 +46,5 @@ Australia_2005 = { "HMAS Adelaide" ], "boat":[ "ArleighBurkeGroupGenerator" - ] + ], "has_jtac": True } diff --git a/game/factions/bluefor_coldwar.py b/game/factions/bluefor_coldwar.py index cb811d98..70bf9467 100644 --- a/game/factions/bluefor_coldwar.py +++ b/game/factions/bluefor_coldwar.py @@ -54,5 +54,5 @@ BLUEFOR_COLDWAR = { "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 index a7f50ab0..5bf366f5 100644 --- a/game/factions/bluefor_coldwar_a4.py +++ b/game/factions/bluefor_coldwar_a4.py @@ -61,5 +61,5 @@ BLUEFOR_COLDWAR_A4 = { ], "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 index 8e60eaf5..aece4e46 100644 --- a/game/factions/bluefor_coldwar_mods.py +++ b/game/factions/bluefor_coldwar_mods.py @@ -64,5 +64,5 @@ BLUEFOR_COLDWAR_MODS = { ], "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 index 3d04bb3b..ca64e2f5 100644 --- a/game/factions/bluefor_modern.py +++ b/game/factions/bluefor_modern.py @@ -78,5 +78,5 @@ BLUEFOR_MODERN = { "LHA-5 Peleliu" ], "boat":[ "ArleighBurkeGroupGenerator", "OliverHazardPerryGroupGenerator" - ] + ], "has_jtac": True } diff --git a/game/factions/canada_2005.py b/game/factions/canada_2005.py index 2fdeb786..ea4497ca 100644 --- a/game/factions/canada_2005.py +++ b/game/factions/canada_2005.py @@ -39,5 +39,5 @@ Canada_2005 = { Ticonderoga_class, ], "boat":[ "ArleighBurkeGroupGenerator" - ] + ], "has_jtac": True } diff --git a/game/factions/china_2010.py b/game/factions/china_2010.py index 88763281..b13faec4 100644 --- a/game/factions/china_2010.py +++ b/game/factions/china_2010.py @@ -73,5 +73,5 @@ China_2010 = { "002 Shandong", ], "boat":[ "Type54GroupGenerator" - ] + ], "has_jtac": True } \ No newline at end of file diff --git a/game/factions/france_1995.py b/game/factions/france_1995.py index 8ba1e71b..5b7fe1b2 100644 --- a/game/factions/france_1995.py +++ b/game/factions/france_1995.py @@ -42,5 +42,5 @@ France_1995 = { 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 index abc8b882..d5e25b49 100644 --- a/game/factions/france_2005.py +++ b/game/factions/france_2005.py @@ -57,5 +57,5 @@ France_2005 = { "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 index 94ee5743..ea557f30 100644 --- a/game/factions/france_modded.py +++ b/game/factions/france_modded.py @@ -75,5 +75,5 @@ France_2005_Modded = { ], "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/india_2010.py b/game/factions/india_2010.py index 47a1d5b2..2dc756ec 100644 --- a/game/factions/india_2010.py +++ b/game/factions/india_2010.py @@ -51,5 +51,5 @@ India_2010 = { "INS Vikramaditya" ], "boat":[ "ArleighBurkeGroupGenerator", "OliverHazardPerryGroupGenerator", "MolniyaGroupGenerator" - ] + ], "has_jtac": True } \ No newline at end of file diff --git a/game/factions/israel_2000.py b/game/factions/israel_2000.py index f135432e..163bbfe3 100644 --- a/game/factions/israel_2000.py +++ b/game/factions/israel_2000.py @@ -35,5 +35,5 @@ Israel_2000 = { 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 index 6f8d594d..2c2175a8 100644 --- a/game/factions/italy_1990.py +++ b/game/factions/italy_1990.py @@ -44,5 +44,5 @@ Italy_1990 = { "Cavour", ], "boat":[ "OliverHazardPerryGroupGenerator" - ] + ], "has_jtac": True } diff --git a/game/factions/italy_1990_mb339.py b/game/factions/italy_1990_mb339.py index 17abab43..92307749 100644 --- a/game/factions/italy_1990_mb339.py +++ b/game/factions/italy_1990_mb339.py @@ -48,5 +48,5 @@ Italy_1990_MB339 = { "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 index 43bd7b86..c9657348 100644 --- a/game/factions/japan_2005.py +++ b/game/factions/japan_2005.py @@ -50,5 +50,5 @@ Japan_2005 = { "Ise", ], "boat":[ "ArleighBurkeGroupGenerator" - ] + ], "has_jtac": True } diff --git a/game/factions/netherlands_1990.py b/game/factions/netherlands_1990.py index 2ef42ba0..b32fe7d0 100644 --- a/game/factions/netherlands_1990.py +++ b/game/factions/netherlands_1990.py @@ -34,5 +34,5 @@ Netherlands_1990 = { AirDefence.SAM_Avenger_M1097 ], "boat": [ "OliverHazardPerryGroupGenerator" - ] + ], "has_jtac": True } diff --git a/game/factions/pakistan_2015.py b/game/factions/pakistan_2015.py index 549deebd..b5fa5f29 100644 --- a/game/factions/pakistan_2015.py +++ b/game/factions/pakistan_2015.py @@ -36,5 +36,5 @@ Pakistan_2015 = { AirDefence.AAA_ZU_23_Closed ], "boat": [ "Type54GroupGenerator", "OliverHazardPerryGroupGenerator" - ] + ], "has_jtac": True } diff --git a/game/factions/spain_1990.py b/game/factions/spain_1990.py index b46e3dd1..f016cb8a 100644 --- a/game/factions/spain_1990.py +++ b/game/factions/spain_1990.py @@ -46,5 +46,5 @@ Spain_1990 = { "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 index 8df60107..5bafb20b 100644 --- a/game/factions/sweden_1990.py +++ b/game/factions/sweden_1990.py @@ -27,5 +27,5 @@ Sweden_1990 = { ], "shorad": [ AirDefence.SAM_Avenger_M1097 - ] + ], "has_jtac": True } \ No newline at end of file diff --git a/game/factions/turkey_2005.py b/game/factions/turkey_2005.py index 38176ea8..9a0504bf 100644 --- a/game/factions/turkey_2005.py +++ b/game/factions/turkey_2005.py @@ -38,5 +38,5 @@ Turkey_2005 = { 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 index a4152fa9..90990493 100644 --- a/game/factions/uae_2005.py +++ b/game/factions/uae_2005.py @@ -32,5 +32,5 @@ UAE_2005 = { Armed_speedboat, ], "boat":[ "OliverHazardPerryGroupGenerator" - ] + ], "has_jtac": True } \ No newline at end of file diff --git a/game/factions/uk_1990.py b/game/factions/uk_1990.py index 699eafb6..f2bcc57a 100644 --- a/game/factions/uk_1990.py +++ b/game/factions/uk_1990.py @@ -48,5 +48,5 @@ UnitedKingdom_1990 = { "HMS Ark Royal", ], "boat":[ "ArleighBurkeGroupGenerator", "OliverHazardPerryGroupGenerator" - ] + ], "has_jtac": True } \ No newline at end of file diff --git a/game/factions/usa_1990.py b/game/factions/usa_1990.py index b0383148..ae090e17 100644 --- a/game/factions/usa_1990.py +++ b/game/factions/usa_1990.py @@ -61,5 +61,5 @@ USA_1990 = { "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 index e321682f..5425aa92 100644 --- a/game/factions/usa_2005.py +++ b/game/factions/usa_2005.py @@ -66,5 +66,5 @@ USA_2005 = { "LHA-5 Peleliu" ], "boat":[ "ArleighBurkeGroupGenerator" - ] + ], "has_jtac": True } diff --git a/game/game.py b/game/game.py index c437d2b1..1dbb397d 100644 --- a/game/game.py +++ b/game/game.py @@ -75,6 +75,7 @@ class Game: self.__culling_points = self.compute_conflicts_position() self.__frontlineData = [] self.__destroyed_units = [] + self.jtacs = [] self.savepath = "" self.sanitize_sides() diff --git a/game/operation/operation.py b/game/operation/operation.py index 59dd3c11..d69c406d 100644 --- a/game/operation/operation.py +++ b/game/operation/operation.py @@ -157,6 +157,7 @@ class Operation: self.airgen.generate_flights(cp, country, self.game.planners[cp.id]) # Generate ground units on frontline everywhere + self.game.jtacs = [] for player_cp, enemy_cp in self.game.theater.conflicts(True): conflict = Conflict.frontline_cas_conflict(self.attacker_name, self.defender_name, self.current_mission.country(self.attacker_country), @@ -175,14 +176,14 @@ class Operation: else: self.current_mission.groundControl.red_tactical_commander = self.ca_slots - # triggers + # Triggers if self.game.is_player_attack(self.conflict.attackers_country): cp = self.conflict.from_cp else: cp = self.conflict.to_cp self.triggersgen.generate() - # options + # Options self.forcedoptionsgen.generate() # Generate Visuals Smoke Effects @@ -195,6 +196,18 @@ class Operation: load_mist.add_action(DoScript(String(f.read()))) self.current_mission.triggerrules.triggers.append(load_mist) + # Load Ciribob's JTACAutoLase script + load_autolase = TriggerStart(comment="Load JTAC script") + with open("./resources/scripts/JTACAutoLase.lua") as f: + + script = f.read() + script = script + "\n" + for jtac in self.game.jtacs: + script = script + "\n" + "JTACAutoLase('" + str(jtac[2]) + "', " + str(jtac[1]) + ", true, \"vehicle\")" + "\n" + + load_autolase.add_action(DoScript(String(script))) + self.current_mission.triggerrules.triggers.append(load_autolase) + load_dcs_libe = TriggerStart(comment="Load DCS Liberation Script") with open("./resources/scripts/dcs_liberation.lua") as f: script = f.read() diff --git a/game/settings.py b/game/settings.py index 263bfc06..c42e727b 100644 --- a/game/settings.py +++ b/game/settings.py @@ -24,6 +24,7 @@ class Settings: self.sams = True # Legacy parameter do not use self.cold_start = False # Legacy parameter do not use self.version = None + self.include_jtac_if_available = True # Performance oriented self.perf_red_alert_state = True diff --git a/gen/armor.py b/gen/armor.py index b53ae0d6..7c2e0f89 100644 --- a/gen/armor.py +++ b/gen/armor.py @@ -97,6 +97,20 @@ class GroundConflictGenerator: self.plan_action_for_groups(self.player_stance, player_groups, enemy_groups, self.conflict.heading + 90, self.conflict.from_cp, self.conflict.to_cp) self.plan_action_for_groups(self.enemy_stance, enemy_groups, player_groups, self.conflict.heading - 90, self.conflict.to_cp, self.conflict.from_cp) + # Add JTAC + if "has_jtac" in self.game.player_faction and self.game.player_faction["has_jtac"] and self.game.settings.include_jtac_if_available: + n = "JTAC" + str(self.conflict.from_cp.id) + str(self.conflict.to_cp.id) + code = 1688 + len(self.game.jtacs) + jtac = self.mission.flight_group(country=self.mission.country(self.game.player_country), + name=n, + aircraft_type=MQ_9_Reaper, + position=position[0], + airport=None, + altitude=5000) + jtac.points[0].tasks.append(OrbitAction(5000, 300, OrbitAction.OrbitPattern.Circle)) + jtac.points[0].tasks.append(SetInvisibleCommand(True)) + jtac.points[0].tasks.append(SetImmortalCommand(True)) + self.game.jtacs.append(("Frontline " + self.conflict.from_cp.name + "/" + self.conflict.to_cp.name, code, n)) def gen_infantry_group_for_group(self, group, is_player, side:Country, forward_heading): diff --git a/gen/briefinggen.py b/gen/briefinggen.py index 081d7915..9b4a87c5 100644 --- a/gen/briefinggen.py +++ b/gen/briefinggen.py @@ -24,6 +24,8 @@ class BriefingGenerator: self.targets = [] self.waypoints = [] + self.jtacs = [] + def append_frequency(self, name: str, frequency: str): self.freqs.append((name, frequency)) @@ -109,6 +111,12 @@ class BriefingGenerator: self.description += "ICLS Channel : " + str(cp.icls) + "\n" self.description += "-" * 50 + "\n" + + self.description += "JTACS [F-10 Menu] : \n" + self.description += "===================" + for jtac in self.game.jtacs: + self.description += str(jtac[0]) + " -- Code : " + str(jtac[1]) + "\n" + self.m.set_description_text(self.description) self.m.add_picture_blue(os.path.abspath("./resources/ui/splash_screen.png")) diff --git a/qt_ui/windows/mission/QMissionPlanning.py b/qt_ui/windows/mission/QMissionPlanning.py index b64ec969..431ce00b 100644 --- a/qt_ui/windows/mission/QMissionPlanning.py +++ b/qt_ui/windows/mission/QMissionPlanning.py @@ -16,7 +16,7 @@ class QMissionPlanning(QDialog): super(QMissionPlanning, self).__init__() self.game = game self.setWindowFlags(Qt.WindowStaysOnTopHint) - self.setMinimumSize(1000, 420) + self.setMinimumSize(1000, 440) self.setModal(True) self.setWindowTitle("Mission Preparation") self.setWindowIcon(EVENT_ICONS["strike"]) diff --git a/qt_ui/windows/settings/QSettingsWindow.py b/qt_ui/windows/settings/QSettingsWindow.py index 14f870c5..e6ba069a 100644 --- a/qt_ui/windows/settings/QSettingsWindow.py +++ b/qt_ui/windows/settings/QSettingsWindow.py @@ -166,13 +166,23 @@ class QSettingsWindow(QDialog): self.generate_marks.setChecked(self.game.settings.generate_marks) self.generate_marks.toggled.connect(self.applySettings) + + if not hasattr(self.game.settings, "include_jtac_if_available"): + self.game.settings.include_jtac_if_available = True + + self.include_jtac_if_available = QCheckBox() + self.include_jtac_if_available.setChecked(self.game.settings.include_jtac_if_available) + self.include_jtac_if_available.toggled.connect(self.applySettings) + self.gameplayLayout.addWidget(QLabel("Use Supercarrier Module"), 0, 0) self.gameplayLayout.addWidget(self.supercarrier, 0, 1, Qt.AlignRight) self.gameplayLayout.addWidget(QLabel("Put Objective Markers on Map"), 1, 0) self.gameplayLayout.addWidget(self.generate_marks, 1, 1, Qt.AlignRight) + self.gameplayLayout.addWidget(QLabel("Include JTAC (If available)"), 2, 0) + self.gameplayLayout.addWidget(self.include_jtac_if_available, 2, 1, Qt.AlignRight) self.performance = QGroupBox("Performance") - self.performanceLayout = QGridLayout(); + self.performanceLayout = QGridLayout() self.performanceLayout.setAlignment(Qt.AlignTop) self.performance.setLayout(self.performanceLayout) @@ -288,6 +298,7 @@ class QSettingsWindow(QDialog): self.game.settings.map_coalition_visibility = self.mapVisibiitySelection.currentData() self.game.settings.external_views_allowed = self.ext_views.isChecked() self.game.settings.generate_marks = self.generate_marks.isChecked() + self.game.settings.include_jtac_if_available = self.include_jtac_if_available.isChecked() print(self.game.settings.map_coalition_visibility) diff --git a/resources/caulandmap.p b/resources/caulandmap.p index 59a92f39..a9fe3cbe 100644 Binary files a/resources/caulandmap.p and b/resources/caulandmap.p differ diff --git a/resources/scripts/JTACAutoLase.lua b/resources/scripts/JTACAutoLase.lua new file mode 100644 index 00000000..f0af3ecb --- /dev/null +++ b/resources/scripts/JTACAutoLase.lua @@ -0,0 +1,729 @@ +--[[ +JTAC Automatic Targeting and Laser Script + +Allows a JTAC to mark and hold an IR and Laser point on a target allowing TGP's to lock onto the lase and ease +of target location using NV Goggles + +The JTAC will automatically switch targets when a target is destroyed or goes out of Line of Sight + +NOTE: LOS doesn't include buildings or tree's... Sorry! + +The script can also be useful in daylight by enabling the JTAC to mark enemy positions with Smoke. +The JTAC will only move the smoke to the target every 5 minutes (to stop a huge trail of smoke markers) unless the target +is destroyed, in which case the new target will be marked straight away with smoke. + +You can also enable an F10 menu option for coalition units allowing the JTAC(s) to report their current status. + +If a JTAC is down it won't report in. + +USAGE: + +Place JTAC units on the map with the mission editor putting each JTAC in it's own group containing only itself and no +other units. Name the group something easy to remember e.g. JTAC1 and make sure the JTAC units have a unique name which must +not be the same as the group name. The editor should do this for you but be careful if you copy and paste. + +Load the script at the start of the mission with a Trigger Once or as the init script of the mission + +Run the code below as a DO SCRIPT at the start of the mission, or after a delay if you prefer + +JTACAutoLase('JTAC1', 1688) + +OR + +JTACAutoLase('JTAC1', 1688, false,"all") - means no smoke marks for this jtac and it will target all ground troops + +OR + +JTACAutoLase('JTAC1', 1688, true,"vehicle") - means smoke marks for this jtac and it will target ONLY ground vehicles + +OR + +JTACAutoLase('JTAC1', 1688, true,"troop") - means smoke marks for this jtac and it will target ONLY ground troops + +OR + +JTACAutoLase('JTAC1', 1688, true,"all", 4) - means BLUE smoke marks for this jtac and it will target all ground troops + +Where JTAC1 is the Group name of the JTAC Group with one and only one JTAC unit. + +The script doesn't care if the unit isn't activated when run, as it'll automatically activate when the JTAC is activated in +the mission but there can be a delay of up to 30 seconds after activation for the JTAC to start searching for targets. + +You can also run the code at any time if a JTAC is dynamically added to the map as long as you know the Group name of the JTAC. + +Last Edit: 21/04/2015 + +Change log: + Added new config of JTAC smoke colour globally or per JTAC + Added new config of JTAC targetting to either target only troops or vehicles or all + Added new induvidual config of JTAC smoke and location which will override global setting + Added Lat / Lon + MGRS to JTAC Target position + Changed Lasing method to update laser marker rather than create and destroy + - This gives much better tracking behaviour! + Fixed JTAC lasing through terrain. + Fixed Lase staying on when JTAC Dies + Fixed Lase staying on when target dies and there are no other targets + Added Radio noise when message is shown + Stop multiple JTACS targeting the same target + + +Parts of MIST used. Source is https://github.com/mrSkortch/MissionScriptingTools/blob/master/mist.lua + +]] + +-- CONFIG +JTAC_maxDistance = 50000 -- How far a JTAC can "see" in meters (with LOS) + +JTAC_smokeOn = true -- enables marking of target with smoke, can be overriden by the JTACAutoLase in editor + +JTAC_smokeColour = 1 -- Green = 0 , Red = 1, White = 2, Orange = 3, Blue = 4 + +JTAC_jtacStatusF10 = true -- enables F10 JTAC Status menu + +JTAC_location = true -- shows location in JTAC message, can be overriden by the JTACAutoLase in editor + +JTAC_lock = "all" -- "vehicle" OR "troop" OR "all" forces JTAC to only lock vehicles or troops or all ground units + +-- END CONFIG + +-- BE CAREFUL MODIFYING BELOW HERE + +GLOBAL_JTAC_LASE = {} +GLOBAL_JTAC_IR = {} +GLOBAL_JTAC_SMOKE = {} +GLOBAL_JTAC_UNITS = {} -- list of JTAC units for f10 command +GLOBAL_JTAC_CURRENT_TARGETS = {} +GLOBAL_JTAC_RADIO_ADDED = {} --keeps track of who's had the radio command added +GLOBAL_JTAC_LASER_CODES = {} -- keeps track of laser codes for jtac + + +function JTACAutoLase(jtacGroupName, laserCode,smoke,lock,colour) + + if smoke == nil then + + smoke = JTAC_smokeOn + end + + if lock == nil then + + lock = JTAC_lock + end + + if colour == nil then + colour = JTAC_smokeColour + end + + GLOBAL_JTAC_LASER_CODES[jtacGroupName] = laserCode + + local jtacGroup = getGroup(jtacGroupName) + local jtacUnit + + if jtacGroup == nil or #jtacGroup == 0 then + + notify("JTAC Group " .. jtacGroupName .. " KIA!", 10) + + --remove from list + GLOBAL_JTAC_UNITS[jtacGroupName] = nil + + cleanupJTAC(jtacGroupName) + + return + else + + jtacUnit = jtacGroup[1] + --add to list + GLOBAL_JTAC_UNITS[jtacGroupName] = jtacUnit:getName() + + + end + + + -- search for current unit + + if jtacUnit:isActive() == false then + + cleanupJTAC(jtacGroupName) + + env.info(jtacGroupName .. ' Not Active - Waiting 30 seconds') + timer.scheduleFunction(timerJTACAutoLase, { jtacGroupName, laserCode,smoke,lock,colour}, timer.getTime() + 30) + + return + end + + local enemyUnit = getCurrentUnit(jtacUnit, jtacGroupName) + + if enemyUnit == nil and GLOBAL_JTAC_CURRENT_TARGETS[jtacGroupName] ~= nil then + + local tempUnitInfo = GLOBAL_JTAC_CURRENT_TARGETS[jtacGroupName] + + -- env.info("TEMP UNIT INFO: " .. tempUnitInfo.name .. " " .. tempUnitInfo.unitType) + + local tempUnit = Unit.getByName(tempUnitInfo.name) + + if tempUnit ~= nil and tempUnit:getLife() > 0 and tempUnit:isActive() == true then + notify(jtacGroupName .. " target " .. tempUnitInfo.unitType .. " lost. Scanning for Targets. ", 10) + else + notify(jtacGroupName .. " target " .. tempUnitInfo.unitType .. " KIA. Good Job! Scanning for Targets. ", 10) + end + + --remove from smoke list + GLOBAL_JTAC_SMOKE[tempUnitInfo.name] = nil + + -- remove from target list + GLOBAL_JTAC_CURRENT_TARGETS[jtacGroupName] = nil + + --stop lasing + cancelLase(jtacGroupName) + + end + + + if enemyUnit == nil then + enemyUnit = findNearestVisibleEnemy(jtacUnit,lock) + + if enemyUnit ~= nil then + + -- store current target for easy lookup + GLOBAL_JTAC_CURRENT_TARGETS[jtacGroupName] = { name = enemyUnit:getName(), unitType = enemyUnit:getTypeName(), unitId = enemyUnit:getID() } + + notify(jtacGroupName .. " lasing new target " .. enemyUnit:getTypeName() .. '. CODE: ' .. laserCode ..getPositionString(enemyUnit) , 10) + + -- create smoke + if smoke == true then + + --create first smoke + createSmokeMarker(enemyUnit,colour) + end + end + end + + if enemyUnit ~= nil then + + laseUnit(enemyUnit, jtacUnit, jtacGroupName, laserCode) + + -- env.info('Timer timerSparkleLase '..jtacGroupName.." "..laserCode.." "..enemyUnit:getName()) + timer.scheduleFunction(timerJTACAutoLase, { jtacGroupName, laserCode, smoke,lock,colour }, timer.getTime() + 1) + + + if smoke == true then + local nextSmokeTime = GLOBAL_JTAC_SMOKE[enemyUnit:getName()] + + --recreate smoke marker after 5 mins + if nextSmokeTime ~= nil and nextSmokeTime < timer.getTime() then + + createSmokeMarker(enemyUnit, colour) + end + end + + else + -- env.info('LASE: No Enemies Nearby') + + -- stop lazing the old spot + cancelLase(jtacGroupName) + -- env.info('Timer Slow timerSparkleLase '..jtacGroupName.." "..laserCode.." "..enemyUnit:getName()) + + timer.scheduleFunction(timerJTACAutoLase, { jtacGroupName, laserCode, smoke,lock,colour }, timer.getTime() + 5) + end +end + + +-- used by the timer function +function timerJTACAutoLase(args) + + JTACAutoLase(args[1], args[2], args[3],args[4]) +end + +function cleanupJTAC(jtacGroupName) + -- clear laser - just in case + cancelLase(jtacGroupName) + + -- Cleanup + GLOBAL_JTAC_UNITS[jtacGroupName] = nil + + GLOBAL_JTAC_CURRENT_TARGETS[jtacGroupName] = nil +end + + +function notify(message, displayFor) + + + trigger.action.outTextForCoalition(coalition.side.BLUE, message, displayFor) + trigger.action.outSoundForCoalition(coalition.side.BLUE, "radiobeep.ogg") +end + +function createSmokeMarker(enemyUnit,colour) + + --recreate in 5 mins + GLOBAL_JTAC_SMOKE[enemyUnit:getName()] = timer.getTime() + 300.0 + + -- move smoke 2 meters above target for ease + local enemyPoint = enemyUnit:getPoint() + trigger.action.smoke({ x = enemyPoint.x, y = enemyPoint.y + 2.0, z = enemyPoint.z }, colour) +end + +function cancelLase(jtacGroupName) + + --local index = "JTAC_"..jtacUnit:getID() + + local tempLase = GLOBAL_JTAC_LASE[jtacGroupName] + + if tempLase ~= nil then + Spot.destroy(tempLase) + GLOBAL_JTAC_LASE[jtacGroupName] = nil + + -- env.info('Destroy laze '..index) + + tempLase = nil + end + + local tempIR = GLOBAL_JTAC_IR[jtacGroupName] + + if tempIR ~= nil then + Spot.destroy(tempIR) + GLOBAL_JTAC_IR[jtacGroupName] = nil + + -- env.info('Destroy laze '..index) + + tempIR = nil + end +end + +function laseUnit(enemyUnit, jtacUnit, jtacGroupName, laserCode) + + --cancelLase(jtacGroupName) + + local spots = {} + + local enemyVector = enemyUnit:getPoint() + local enemyVectorUpdated = { x = enemyVector.x, y = enemyVector.y + 2.0, z = enemyVector.z } + + local oldLase = GLOBAL_JTAC_LASE[jtacGroupName] + local oldIR = GLOBAL_JTAC_IR[jtacGroupName] + + if oldLase == nil or oldIR == nil then + + -- create lase + + local status, result = pcall(function() + spots['irPoint'] = Spot.createInfraRed(jtacUnit, { x = 0, y = 2.0, z = 0 }, enemyVectorUpdated) + spots['laserPoint'] = Spot.createLaser(jtacUnit, { x = 0, y = 2.0, z = 0 }, enemyVectorUpdated, laserCode) + return spots + end) + + if not status then + env.error('ERROR: ' .. assert(result), false) + else + if result.irPoint then + + -- env.info(jtacUnit:getName() .. ' placed IR Pointer on '..enemyUnit:getName()) + + GLOBAL_JTAC_IR[jtacGroupName] = result.irPoint --store so we can remove after + + end + if result.laserPoint then + + -- env.info(jtacUnit:getName() .. ' is Lasing '..enemyUnit:getName()..'. CODE:'..laserCode) + + GLOBAL_JTAC_LASE[jtacGroupName] = result.laserPoint + end + end + + else + + -- update lase + + if oldLase~=nil then + oldLase:setPoint(enemyVectorUpdated) + end + + if oldIR ~= nil then + oldIR:setPoint(enemyVectorUpdated) + end + + end + +end + +-- get currently selected unit and check they're still in range +function getCurrentUnit(jtacUnit, jtacGroupName) + + + local unit = nil + + if GLOBAL_JTAC_CURRENT_TARGETS[jtacGroupName] ~= nil then + unit = Unit.getByName(GLOBAL_JTAC_CURRENT_TARGETS[jtacGroupName].name) + end + + local tempPoint = nil + local tempDist = nil + local tempPosition = nil + + local jtacPosition = jtacUnit:getPosition() + local jtacPoint = jtacUnit:getPoint() + + if unit ~= nil and unit:getLife() > 0 and unit:isActive() == true then + + -- calc distance + tempPoint = unit:getPoint() + -- tempPosition = unit:getPosition() + + tempDist = getDistance(tempPoint.x, tempPoint.z, jtacPoint.x, jtacPoint.z) + if tempDist < JTAC_maxDistance then + -- calc visible + + -- check slightly above the target as rounding errors can cause issues, plus the unit has some height anyways + local offsetEnemyPos = { x = tempPoint.x, y = tempPoint.y + 2.0, z = tempPoint.z } + local offsetJTACPos = { x = jtacPoint.x, y = jtacPoint.y + 2.0, z = jtacPoint.z } + + if land.isVisible(offsetEnemyPos, offsetJTACPos) then + return unit + end + end + end + return nil +end + + +-- Find nearest enemy to JTAC that isn't blocked by terrain +function findNearestVisibleEnemy(jtacUnit, targetType) + + local x = 1 + local i = 1 + + local units = nil + local groupName = nil + + local nearestUnit = nil + local nearestDistance = JTAC_maxDistance + + local redGroups = coalition.getGroups(1, Group.Category.GROUND) + + local jtacPoint = jtacUnit:getPoint() + local jtacPosition = jtacUnit:getPosition() + + local tempPoint = nil + local tempPosition = nil + + local tempDist = nil + + -- finish this function + for i = 1, #redGroups do + if redGroups[i] ~= nil then + groupName = redGroups[i]:getName() + units = getGroup(groupName) + if #units > 0 then + + for x = 1, #units do + + --check to see if a JTAC has already targeted this unit + local targeted = alreadyTarget(jtacUnit,units[x]) + local allowedTarget = true + + if targetType == "vehicle" then + + allowedTarget = isVehicle(units[x]) + + elseif targetType == "troop" then + + allowedTarget = isInfantry(units[x]) + + end + + + if units[x]:isActive() == true and targeted == false and allowedTarget == true then + + -- calc distance + tempPoint = units[x]:getPoint() + -- tempPosition = units[x]:getPosition() + + tempDist = getDistance(tempPoint.x, tempPoint.z, jtacPoint.x, jtacPoint.z) + + -- env.info("tempDist" ..tempDist) + -- env.info("JTAC_maxDistance" ..JTAC_maxDistance) + + if tempDist < JTAC_maxDistance and tempDist < nearestDistance then + + local offsetEnemyPos = { x = tempPoint.x, y = tempPoint.y + 2.0, z = tempPoint.z } + local offsetJTACPos = { x = jtacPoint.x, y = jtacPoint.y + 2.0, z = jtacPoint.z } + + + -- calc visible + if land.isVisible(offsetEnemyPos, offsetJTACPos) then + + nearestDistance = tempDist + nearestUnit = units[x] + end + + end + end + end + end + end + end + + + if nearestUnit == nil then + return nil + end + + + return nearestUnit +end +-- tests whether the unit is targeted by another JTAC +function alreadyTarget(jtacUnit, enemyUnit) + + for y , jtacTarget in pairs(GLOBAL_JTAC_CURRENT_TARGETS) do + + if jtacTarget.unitId == enemyUnit:getID() then + -- env.info("ALREADY TARGET") + return true + end + + end + + return false + +end + + +-- Returns only alive units from group but the group / unit may not be active + +function getGroup(groupName) + + local groupUnits = Group.getByName(groupName) + + local filteredUnits = {} --contains alive units + local x = 1 + + if groupUnits ~= nil then + + groupUnits = groupUnits:getUnits() + + if groupUnits ~= nil and #groupUnits > 0 then + for x = 1, #groupUnits do + if groupUnits[x]:getLife() > 0 then + table.insert(filteredUnits, groupUnits[x]) + end + end + end + end + + return filteredUnits +end + +-- Distance measurement between two positions, assume flat world + +function getDistance(xUnit, yUnit, xZone, yZone) + local xDiff = xUnit - xZone + local yDiff = yUnit - yZone + + return math.sqrt(xDiff * xDiff + yDiff * yDiff) +end + +-- gets the JTAC status and displays to coalition units +function getJTACStatus() + + --returns the status of all JTAC units + + local jtacGroupName = nil + local jtacUnit = nil + local jtacUnitName = nil + + local message = "JTAC STATUS: \n\n" + + for jtacGroupName, jtacUnitName in pairs(GLOBAL_JTAC_UNITS) do + + --look up units + jtacUnit = Unit.getByName(jtacUnitName) + + if jtacUnit ~= nil and jtacUnit:getLife() > 0 and jtacUnit:isActive() == true then + + local enemyUnit = getCurrentUnit(jtacUnit, jtacGroupName) + + local laserCode = GLOBAL_JTAC_LASER_CODES[jtacGroupName] + + if laserCode == nil then + laserCode = "UNKNOWN" + end + + if enemyUnit ~= nil and enemyUnit:getLife() > 0 and enemyUnit:isActive() == true then + message = message .. "" .. jtacGroupName .. " targeting " .. enemyUnit:getTypeName().. " CODE: ".. laserCode .. getPositionString(enemyUnit) .. "\n" + else + message = message .. "" .. jtacGroupName .. " searching for targets" .. getPositionString(jtacUnit) .."\n" + end + end + end + + notify(message, 10) +end + + +-- Radio command for players (F10 Menu) + +function addRadioCommands() + + --looop over all players and add command + -- missionCommands.addCommandForCoalition( coalition.side.BLUE, "JTAC Status" ,nil, getJTACStatus ,nil) + + timer.scheduleFunction(addRadioCommands, nil, timer.getTime() + 10) + + local blueGroups = coalition.getGroups(coalition.side.BLUE) + local x = 1 + + if blueGroups ~= nil then + for x, tmpGroup in pairs(blueGroups) do + + + local index = "GROUP_" .. Group.getID(tmpGroup) + -- env.info("adding command for "..index) + if GLOBAL_JTAC_RADIO_ADDED[index] == nil then + -- env.info("about command for "..index) + missionCommands.addCommandForGroup(Group.getID(tmpGroup), "JTAC Status", nil, getJTACStatus, nil) + GLOBAL_JTAC_RADIO_ADDED[index] = true + -- env.info("Added command for " .. index) + end + end + end +end + +function isInfantry(unit) + + local typeName = unit:getTypeName() + + --type coerce tostring + typeName = string.lower(typeName.."") + + local soldierType = { "infantry","paratrooper","stinger","manpad"} + + for key,value in pairs(soldierType) do + if string.match(typeName, value) then + return true + end + end + + return false + +end + +-- assume anything that isnt soldier is vehicle +function isVehicle(unit) + + if isInfantry(unit) then + return false + end + + return true + +end + + +function getPositionString(unit) + + if JTAC_location == false then + return "" + end + + local latLngStr = latLngString(unit,3) + + local mgrsString = MGRSString(coord.LLtoMGRS(coord.LOtoLL(unit:getPosition().p)),5) + + return " @ " .. latLngStr .. " - MGRS "..mgrsString + +end + +-- source of Function MIST - https://github.com/mrSkortch/MissionScriptingTools/blob/master/mist.lua +function latLngString(unit, acc) + + local lat, lon = coord.LOtoLL(unit:getPosition().p) + + 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 + + -- degrees, decimal minutes. + latMin = roundNumber(latMin, acc) + lonMin = roundNumber(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 + +-- source of Function MIST - https://github.com/mrSkortch/MissionScriptingTools/blob/master/mist.lua + function MGRSString(MGRS, acc) + if acc == 0 then + return MGRS.UTMZone .. ' ' .. MGRS.MGRSDigraph + else + return MGRS.UTMZone .. ' ' .. MGRS.MGRSDigraph .. ' ' .. string.format('%0' .. acc .. 'd', roundNumber(MGRS.Easting/(10^(5-acc)), 0)) + .. ' ' .. string.format('%0' .. acc .. 'd', roundNumber(MGRS.Northing/(10^(5-acc)), 0)) + end +end +-- From http://lua-users.org/wiki/SimpleRound + function roundNumber(num, idp) + local mult = 10^(idp or 0) + return math.floor(num * mult + 0.5) / mult +end + +-- add the radio commands +if JTAC_jtacStatusF10 == true then + timer.scheduleFunction(addRadioCommands, nil, timer.getTime() + 1) +end + + + +--DEBUG FUNCTIONS - IGNORE +--function print_functions(o) +-- +-- for key,value in pairs(getmetatable(o)) do +-- env.info(tostring(key) .." ".. tostring(value)) +-- end +--end +-- +--function dump_table(o) +-- if type(o) == 'table' then +-- local s = '{ ' +-- for k,v in pairs(o) do +-- if type(k) ~= 'number' then k = '"'..k..'"' end +-- s = s .. '['..k..'] = ' .. dump_table(v) .. ',' +-- end +-- return s .. '} ' +-- else +-- return tostring(o) +-- end +--end \ No newline at end of file diff --git a/resources/tools/cau_terrain.miz b/resources/tools/cau_terrain.miz index 9292ee14..e0542b89 100644 Binary files a/resources/tools/cau_terrain.miz and b/resources/tools/cau_terrain.miz differ diff --git a/resources/tools/generate_landmap.py b/resources/tools/generate_landmap.py index 6d8d98ce..816747c5 100644 --- a/resources/tools/generate_landmap.py +++ b/resources/tools/generate_landmap.py @@ -3,7 +3,7 @@ import pickle from dcs.mission import Mission from dcs.planes import A_10C -for terrain in ["gulf", "nev", "channel", "normandy"]: +for terrain in ["cau"]: print("Terrain " + terrain) m = Mission() m.load_file("./{}_terrain.miz".format(terrain))