From 2091fdbb2797baeb642ac9e92b54888c3f5c6b70 Mon Sep 17 00:00:00 2001 From: Cedric Menard Date: Sun, 3 Nov 2024 08:02:36 -0500 Subject: [PATCH] Add Mbot's Call Artillery Script plugin (#410) * Add Mbot's Call Artillery Script plugin * Applied PR comments * Fix for wrong indentation, remove unused client skill check, added changelog item --- README.md | 1 + changelog.md | 1 + game/missiongenerator/flotgenerator.py | 16 +- game/missiongenerator/luagenerator.py | 67 ++ game/missiongenerator/missiondata.py | 9 + game/plugins/luaplugin.py | 9 + .../plugins/artymbot/CallArtilleryScript.lua | 955 ++++++++++++++++++ .../plugins/artymbot/artymbot-config.lua | 27 + resources/plugins/artymbot/beep.wav | Bin 0 -> 57574 bytes resources/plugins/artymbot/plugin.json | 26 + resources/plugins/plugins.json | 1 + 11 files changed, 1111 insertions(+), 1 deletion(-) create mode 100644 resources/plugins/artymbot/CallArtilleryScript.lua create mode 100644 resources/plugins/artymbot/artymbot-config.lua create mode 100644 resources/plugins/artymbot/beep.wav create mode 100644 resources/plugins/artymbot/plugin.json diff --git a/README.md b/README.md index 1556d48d..bb13b2c2 100644 --- a/README.md +++ b/README.md @@ -91,5 +91,6 @@ Excellent lua scripts DCS Liberation/Retribution uses as plugins: * For the JTAC feature, DCS Retribution embeds Ciribob's JTAC Autolase [script](https://github.com/ciribob/DCS-JTACAutoLaze). * Walder's [Skynet-IADS](https://github.com/walder/Skynet-IADS) is used for Integrated Air Defense System. * Carstens Arty Spotter https://www.digitalcombatsimulator.com/en/files/3339128/ is an amazing force multiplyer to drop the hammer on enemies. +* MBot's [Call Artillery Script](https://forum.dcs.world/topic/310506-call-artillery-script/) uses in-map artillery and forward observers to enable artillery fire missions. Please also show some support to these projects ! diff --git a/changelog.md b/changelog.md index 49fd2d8b..46b4e756 100644 --- a/changelog.md +++ b/changelog.md @@ -25,6 +25,7 @@ * **[UI/UX]** Allow changing conditions such as Time, Date & Weather * **[Modding]** Added support for Su-15 Flagon mod (v1.0) * **[Plugins]** Support for Carsten's Arty Spotter script +* **[Plugins]** Support for MBot's Call Artillery script (using on-map artillery) * **[Modding]** Added support for SK-60 mod (v1.2.1) * **[Mission Generation]** Introducing the Armed Recon flight plan, i.e. CAS against any Theater Ground Object * **[Doctrine]** Ability to customize the startup time allocated to the player diff --git a/game/missiongenerator/flotgenerator.py b/game/missiongenerator/flotgenerator.py index 4dd0be52..89be57e7 100644 --- a/game/missiongenerator/flotgenerator.py +++ b/game/missiongenerator/flotgenerator.py @@ -45,7 +45,7 @@ from game.unitmap import UnitMap from game.utils import Heading from .frontlineconflictdescription import FrontLineConflictDescription from .groundforcepainter import GroundForcePainter -from .missiondata import JtacInfo, MissionData +from .missiondata import JtacInfo, MissionData, FrontlineUnitGroupsInfo from ..ato import FlightType if TYPE_CHECKING: @@ -198,6 +198,20 @@ class FlotGenerator: ) ) + for vehicle_group, combat_group in player_groups: + self.mission_data.player_frontline_groups.append( + FrontlineUnitGroupsInfo( + group_name=vehicle_group.name, unit_type=combat_group.unit_type + ) + ) + + for vehicle_group, combat_group in enemy_groups: + self.mission_data.enemy_frontline_groups.append( + FrontlineUnitGroupsInfo( + group_name=vehicle_group.name, unit_type=combat_group.unit_type + ) + ) + def gen_infantry_group_for_group( self, group: VehicleGroup, diff --git a/game/missiongenerator/luagenerator.py b/game/missiongenerator/luagenerator.py index a2b54f86..9c8cca52 100644 --- a/game/missiongenerator/luagenerator.py +++ b/game/missiongenerator/luagenerator.py @@ -10,6 +10,7 @@ from dcs import Mission from dcs.action import DoScript, DoScriptFile from dcs.translation import String from dcs.triggers import TriggerStart +from dcs.unit import Skill from game.ato import FlightType from game.dcs.aircrafttype import AircraftType @@ -17,6 +18,7 @@ from game.plugins import LuaPluginManager from game.theater import TheaterGroundObject from game.theater.iadsnetwork.iadsrole import IadsRole from game.utils import escape_string_for_lua +from game.data.units import UnitClass from .missiondata import MissionData if TYPE_CHECKING: @@ -211,6 +213,59 @@ class LuaGenerator: for role, connections in node.connections.items(): iads_element.add_data_array(role, connections) + # Add artillery and support units info + artillery_object = lua_data.add_item("artilleryGroups") + ground_artillery_group_collection = artillery_object.get_or_create_item( + "groundArtillery" + ) + ship_artillery_group_collection = artillery_object.get_or_create_item( + "shipArtillery" + ) + + # First add all artillery units that are theater objects (mostly ships) + for ground_object in self.game.theater.ground_objects: + for group in ground_object.groups: + # Check if first unit in group is ground-based or ship artillery + group_first_unit = group.units[0] + if group_first_unit.unit_type is None: + continue + if group_first_unit.unit_type.unit_class == UnitClass.ARTILLERY: + ground_artillery_group = ( + ground_artillery_group_collection.add_item() + ) + ground_artillery_group.add_key_value("groupName", group.group_name) + elif group_first_unit.unit_type.unit_class in ( + UnitClass.CRUISER, + UnitClass.DESTROYER, + UnitClass.FRIGATE, + ): + # TODO: we assume that these ship classes have guns... Which might not be the case. + ship_artillery_group = ship_artillery_group_collection.add_item() + ship_artillery_group.add_key_value("groupName", group.group_name) + + # Add artillery that are frontline groups + for frontline_group in ( + self.mission_data.player_frontline_groups + + self.mission_data.enemy_frontline_groups + ): + if frontline_group.unit_type.unit_class == UnitClass.ARTILLERY: + ground_artillery_group = ground_artillery_group_collection.add_item() + ground_artillery_group.add_key_value( + "groupName", frontline_group.group_name + ) + + # Add forward observer (FO) (TODO: maybe adding new flight type "Foward Observer"?) + forward_observer_object = lua_data.add_item("forwardObserverUnits") + for flight in self.mission_data.flights: + if len(flight.client_units) == 0: + continue + if flight.flight_type != FlightType.ARMED_RECON: + continue + + for client_unit in flight.client_units: + forward_observer = forward_observer_object.add_item() + forward_observer.add_key_value("unitName", client_unit.name) + trigger = TriggerStart(comment="Set DCS Retribution data") trigger.add_action(DoScript(String(lua_data.create_operations_lua()))) self.mission.triggerrules.triggers.append(trigger) @@ -245,11 +300,23 @@ class LuaGenerator: trigger.add_action(DoScriptFile(fileref)) self.mission.triggerrules.triggers.append(trigger) + def inject_other_plugin_resources(self, plugin_mnemonic: str, file: str) -> None: + plugin_path = Path("./resources/plugins", plugin_mnemonic) + + resource_path = Path(plugin_path, file) + if not resource_path.exists(): + logging.error(f"Cannot find {resource_path} for plugin {plugin_mnemonic}") + return + + filename = resource_path.resolve() + self.mission.map_resource.add_resource_file(filename) + def inject_plugins(self) -> None: for plugin in LuaPluginManager.plugins(): if plugin.enabled: plugin.inject_scripts(self) plugin.inject_configuration(self) + plugin.inject_other_resource_files(self) class LuaValue: diff --git a/game/missiongenerator/missiondata.py b/game/missiongenerator/missiondata.py index 062d6a56..5b7b1447 100644 --- a/game/missiongenerator/missiondata.py +++ b/game/missiongenerator/missiondata.py @@ -5,6 +5,7 @@ from datetime import datetime from typing import Optional, TYPE_CHECKING from game.dcs.aircrafttype import AircraftType +from game.dcs.groundunittype import GroundUnitType from game.missiongenerator.aircraft.flightdata import FlightData from game.runways import RunwayData @@ -89,6 +90,12 @@ class LogisticsInfo: preload: bool = field(default=False) +@dataclass +class FrontlineUnitGroupsInfo: + group_name: str + unit_type: GroundUnitType + + @dataclass class MissionData: awacs: list[AwacsInfo] = field(default_factory=list) @@ -99,3 +106,5 @@ class MissionData: jtacs: list[JtacInfo] = field(default_factory=list) logistics: list[LogisticsInfo] = field(default_factory=list) cp_stack: dict[UUID, Distance] = field(default_factory=dict) + player_frontline_groups: list[FrontlineUnitGroupsInfo] = field(default_factory=list) + enemy_frontline_groups: list[FrontlineUnitGroupsInfo] = field(default_factory=list) diff --git a/game/plugins/luaplugin.py b/game/plugins/luaplugin.py index 865996b9..2b0948a0 100644 --- a/game/plugins/luaplugin.py +++ b/game/plugins/luaplugin.py @@ -77,6 +77,7 @@ class LuaPluginDefinition: options: List[LuaPluginOption] work_orders: List[LuaPluginWorkOrder] config_work_orders: List[LuaPluginWorkOrder] + other_resource_files: List[str] @classmethod def from_json(cls, name: str, path: Path) -> LuaPluginDefinition: @@ -124,6 +125,7 @@ class LuaPluginDefinition: options=options, work_orders=work_orders, config_work_orders=config_work_orders, + other_resource_files=data.get("otherResourceFiles", []), ) @@ -199,3 +201,10 @@ class LuaPlugin(PluginSettings): for work_order in self.definition.config_work_orders: work_order.work(lua_generator) + + def inject_other_resource_files(self, lua_generator: LuaGenerator) -> None: + for resource_file in self.definition.other_resource_files: + # TODO: should probably deconflict names of resources + lua_generator.inject_other_plugin_resources( + self.definition.identifier, resource_file + ) diff --git a/resources/plugins/artymbot/CallArtilleryScript.lua b/resources/plugins/artymbot/CallArtilleryScript.lua new file mode 100644 index 00000000..638541b8 --- /dev/null +++ b/resources/plugins/artymbot/CallArtilleryScript.lua @@ -0,0 +1,955 @@ +----- Call Artillery Script ----- +--v02.10.2022 +--By Marc "MBot" Marbot +---------------------------------------------------------------- + +--Script control functions: +--AddFS(GroupName, SalvoSize) --define group that can provide fire support; SalvoSize = numer of rounds per fire mission or table with selectable amounts of rounds per fire mission +--AddFO(UnitName) --define unit that can call fire support + +---------------------------------------------------------------- + +local SoundFilename = "l10n/DEFAULT/beep.wav" --name of sound file to be played whenever a radio text message is displayed +env.info("DCSRetribution|Mbot's Call Artillery Script plugin - Imported") +--Fire support groups +FS = {} --Fire Support +function AddFS(GroupName, SalvoSize) --add new fire support group + if Group.getByName(GroupName) then + FS[GroupName] = { + callsign = Group.getByName(GroupName):getUnit(1):getCallsign(), + SalvoSize = SalvoSize or {10, 20, 30, 40}, + } + if FS[GroupName].callsign == "" then + FS[GroupName].callsign = GroupName + else + local a,b = string.find(FS[GroupName].callsign, "%d%d") + FS[GroupName].callsign = string.sub(FS[GroupName].callsign, 1, a - 1) .. " " .. string.sub(FS[GroupName].callsign, a, a) .. "-" .. string.sub(FS[GroupName].callsign, b, b) --put callsign "Enfield11" into format "Enfield 1-1" + end + end +end + +--Forward observer units +FO = {} --Forward Obeserver that can request fire support +function AddFO(UnitName) --add new forward observer + FO[UnitName] = {} +end + +local Mission = {} --table to store fire missions + +--collect added user marks +local UserMark = { --table to store added user marks + [1] = {}, --red coalition + [2] = {}, --blue coalition +} +MarkEventHandler = {} +function MarkEventHandler:onEvent(event) + if event.id == world.event.S_EVENT_MARK_ADDED then + if event.coalition == 1 or event.coalition == 2 then --mark is coalition specific + table.insert(UserMark[event.coalition], event) --add to coalition + else --mark is not coalition specific + table.insert(UserMark[1], event) --add to both coalitions + table.insert(UserMark[2], event) --add to both coalitions + end + elseif event.id == 27 then --mark remove + if event.coalition == 1 or event.coalition == 2 then --mark is coalition specific + for i = 1, #UserMark[event.coalition] do + if UserMark[event.coalition][i].idx == event.idx then + table.remove(UserMark[event.coalition], i) + break + end + end + else --mark is not coalition specific + for c = 1, 2 do + for i = 1, #UserMark[c] do + if UserMark[c][i].idx == event.idx then + table.remove(UserMark[c], i) + break + end + end + end + end + end +end +world.addEventHandler(MarkEventHandler) + +--return amount of artillery ammo rounds in a group +local function ReturnArtilleryAmmo(GroupName) + local group = Group.getByName(GroupName) + if group then + local units = group:getUnits() + + --find the primary artillery ammo type of group leader + local ammoType = "n/a" --variable to store the primary artillery ammo type + local ammoCat --variable to store ammo category (shell or rocket) + local ammo = units[1]:getAmmo() --get all ammo of group leader + if ammo then --ammo is not nil + for a = 1, #ammo do --iterate through different ammo types + if ammo[a].desc and ammo[a].desc.category and ammo[a].desc.category == 0 then --ammo is a shell + if ammo[a].desc.warhead and ammo[a].desc.warhead.caliber and ammo[a].desc.warhead.caliber > 70 then --caliber needs to be bigger than 70 mm to be considered as artillery ammo + ammoType = ammo[a].desc.displayName --store ammo type name + ammoCat = ammo[a].desc.category --store ammo category (shell or rocket) + break + end + elseif ammo[a].desc and ammo[a].desc.category and ammo[a].desc.category == 2 then --ammo is a rocket + ammoType = ammo[a].desc.displayName --store ammo type name + ammoCat = ammo[a].desc.category --store ammo category (shell or rocket) + break + end + end + end + --trigger.action.outText(ammoType, 99) + + --collect the amount of primary artillery ammo in group + local ammoNumber = 0 + for u = 1, #units do --iterate throug all units in group + if units[u]:getTypeName() ~= "MLRS FDDM" then --MLRS FDDM includes 1 M26 round as ammo (seems to be a bug). Exclude this unit type from ammo count to correctly count ammo of MLRS batteries + ammo = units[u]:getAmmo() --get unit ammo + if ammo then --ammo is not nil + for a = 1, #ammo do --iterate through different ammo types + if ammo[a].desc and ammo[a].desc.displayName and ammo[a].desc.displayName == ammoType then --ammo type is the groups primary artillery ammo type + ammoNumber = ammoNumber + ammo[a].count --add up the number of rounds of this type in group + end + end + end + end + end + --trigger.action.outText(ammoNumber, 99) + + local minRange = 0 + local maxRange = 20000 + local warhead = "HE" + + if ammoType == 'leFH18_105HE' then + maxRange = 10500 + elseif ammoType == 'HE M1 Shell' then + maxRange = 11500 + elseif ammoType == 'Pz.Gr. 39 (75mm APCBC-HE-T)' then + maxRange = 3000 + elseif ammoType == '9M55K (300mm CM-AP)' then + minRange = 20000 + maxRange = 70000 + warhead = "ICM" + elseif ammoType == '9M55F (300mm HE)' then + minRange = 20000 + maxRange = 70000 + elseif ammoType == '9M27F (220mm HE)' then + minRange = 11500 + maxRange = 35500 + elseif ammoType == '9M22U (122mm HE)' then + minRange = 5000 + maxRange = 19000 + elseif ammoType == 'S-8OFP2' then + maxRange = 5000 + elseif ammoType == 'M26 (270mm DPICM)' then + minRange = 10000 + maxRange = 32000 + warhead = "ICM" + elseif ammoType == '3OF49 (120mm HE)' then + maxRange = 7000 + elseif ammoType == '155mm HE' then + maxRange = 23500 + elseif ammoType == '3OF56 (122mm HE)' then + maxRange = 15000 + elseif ammoType == '3OF45 (152mm HE)' then + maxRange = 23500 + elseif ammoType == '53OF540 (152mm HE)' then + maxRange = 17000 + elseif ammoType == '152-EOF (152mm HE)' then + maxRange = 18500 + elseif ammoType == 'M795 (155mm HE)' then + maxRange = 22000 + elseif ammoType == 'M101 (155mm HE)' then + maxRange = 18300 + elseif ammoType == 'K307 (155mm HE)' then + maxRange = 41000 + elseif ammoType == '130mm HE' then + maxRange = 23000 + elseif ammoType == '127mm HE' then + maxRange = 23700 + elseif ammoType == '100mm HE' then + maxRange = 20000 + elseif ammoType == '76mm HE' then + maxRange = 18350 + elseif ammoType == 'Mk 12 HE' then + maxRange = 16000 + end + + return ammoNumber, ammoCat, minRange, maxRange, warhead, ammoType + end +end + + +----- End Active Fire Mission ----- +local function FO_EndFireMission(N) + if FS[Mission[N].FS_name].status == "request" then + trigger.action.outTextForCoalition(Mission[N].coalition, Mission[N].FO_callsign .. ": Cancel mission, over.", 10) + trigger.action.outSoundForCoalition(Mission[N].coalition, SoundFilename) + + local function AcknowlegdeReply() + trigger.action.outTextForCoalition(Mission[N].coalition, Mission[N].FS_callsign .. ": Cancel mission, out.", 10) + trigger.action.outSoundForCoalition(Mission[N].coalition, SoundFilename) + PopulateFreshRadioMenu(Mission[N].FO_name) + FS[Mission[N].FS_name].status = nil + Mission[N].idle = nil + end + timer.scheduleFunction(AcknowlegdeReply, nil, timer.getTime() + 3) + + elseif FS[Mission[N].FS_name].status == "prepare fire" then + trigger.action.outTextForCoalition(Mission[N].coalition, Mission[N].FO_callsign .. ": Check fire, end mission, over.", 10) + trigger.action.outSoundForCoalition(Mission[N].coalition, SoundFilename) + + local function AcknowlegdeReply() + trigger.action.outTextForCoalition(Mission[N].coalition, Mission[N].FS_callsign .. ": Check fire, end mission, out.", 10) + trigger.action.outSoundForCoalition(Mission[N].coalition, SoundFilename) + PopulateFreshRadioMenu(Mission[N].FO_name) + FS[Mission[N].FS_name].status = nil + FS[Mission[N].FS_name].mission = nil + Mission[N].idle = nil + if Group.getByName(Mission[N].FS_name) then + Group.getByName(Mission[N].FS_name):getController():resetTask() --reset task + end + + end + timer.scheduleFunction(AcknowlegdeReply, nil, timer.getTime() + 3) + + elseif FS[Mission[N].FS_name].status == "firing" then + trigger.action.outTextForCoalition(Mission[N].coalition, Mission[N].FO_callsign .. ": Check fire, over.", 10) + trigger.action.outSoundForCoalition(Mission[N].coalition, SoundFilename) + FS[Mission[N].FS_name].status = "cease fire" + + local function AcknowlegdeReply() + trigger.action.outTextForCoalition(Mission[N].coalition, Mission[N].FS_callsign .. ": Check fire, out.", 10) + trigger.action.outSoundForCoalition(Mission[N].coalition, SoundFilename) + if Group.getByName(Mission[N].FS_name) then + Group.getByName(Mission[N].FS_name):getController():resetTask() --reset task + end + end + timer.scheduleFunction(AcknowlegdeReply, nil, timer.getTime() + 3) + + elseif FS[Mission[N].FS_name].status == "splash" then + trigger.action.outTextForCoalition(Mission[N].coalition, Mission[N].FO_callsign .. ": End of mission, over.", 10) + trigger.action.outSoundForCoalition(Mission[N].coalition, SoundFilename) + + local function AcknowlegdeReply() + trigger.action.outTextForCoalition(Mission[N].coalition, Mission[N].FS_callsign .. ": End of mission, out.", 10) + trigger.action.outSoundForCoalition(Mission[N].coalition, SoundFilename) + PopulateFreshRadioMenu(Mission[N].FO_name) + FS[Mission[N].FS_name].status = nil + Mission[N].idle = nil + if Group.getByName(Mission[N].FS_name) then + Group.getByName(Mission[N].FS_name):getController():resetTask() --reset task + end + end + timer.scheduleFunction(AcknowlegdeReply, nil, timer.getTime() + 3) + end + + missionCommands.removeItemForGroup(Mission[N].FO_id, "Fire Support") + missionCommands.addSubMenuForGroup(Mission[N].FO_id, "Fire Support") +end + + +----- Artillery Perform Fire Mission ----- +local function FireMission(N, expendQty) + if FS[Mission[N].FS_name].status then + if Group.getByName(Mission[N].FS_name) then + local FSammoQty, ammoCat, minRange, maxRange, warheadType, ammoType = ReturnArtilleryAmmo(Mission[N].FS_name) + local FSpoint = Group.getByName(Mission[N].FS_name):getUnit(1):getPoint() + local DistToTarget = math.sqrt(math.pow(FSpoint.x - Mission[N].x, 2) + math.pow(FSpoint.z - Mission[N].z, 2)) + if FSammoQty == 0 then + trigger.action.outTextForCoalition(Mission[N].coalition, Mission[N].FS_callsign .. ": Cannot comply, out of ammo, end of mission, out.", 15) + trigger.action.outSoundForCoalition(Mission[N].coalition, SoundFilename) + PopulateFreshRadioMenu(Mission[N].FO_name) + FS[Mission[N].FS_name].status = nil + Mission[N].idle = nil + elseif DistToTarget < minRange then + trigger.action.outTextForCoalition(Mission[N].coalition, Mission[N].FS_callsign .. ": Cannot comply, target within minimum range, end of mission, out.", 15) + trigger.action.outSoundForCoalition(Mission[N].coalition, SoundFilename) + PopulateFreshRadioMenu(Mission[N].FO_name) + FS[Mission[N].FS_name].status = nil + Mission[N].idle = nil + elseif DistToTarget > maxRange then + trigger.action.outTextForCoalition(Mission[N].coalition, Mission[N].FS_callsign .. ": Cannot comply, target out of range, end of mission, out.", 15) + trigger.action.outSoundForCoalition(Mission[N].coalition, SoundFilename) + PopulateFreshRadioMenu(Mission[N].FO_name) + FS[Mission[N].FS_name].status = nil + Mission[N].idle = nil + else + FS[Mission[N].FS_name].status = "prepare fire" + missionCommands.removeItemForGroup(Mission[N].FO_id, "Fire Support") + missionCommands.addSubMenuForGroup(Mission[N].FO_id, "Fire Support") + missionCommands.addCommandForGroup(Mission[N].FO_id, "Check Fire", {"Fire Support"}, FO_EndFireMission, N) + + if FSammoQty < expendQty then + trigger.action.outTextForCoalition(Mission[N].coalition, Mission[N].FS_callsign .. ": Cannot comply with " .. expendQty .. " rounds, " .. FSammoQty .. " rounds in effect, out.", 15) + trigger.action.outSoundForCoalition(Mission[N].coalition, SoundFilename) + expendQty = FSammoQty + end + + local weaponType + if ammoCat == 0 then --ammo category is shell + weaponType = 536870912 --fire with cannon + elseif ammoCat == 2 then --ammo category is rocket + weaponType = 30720 --fire with rocket + end + + local radius = 1 + if expendQty > 1 then + radius = 100 + end + + local function CheckFireMissionOngoing() --after 4 minutes of issuing the task, check if FS has shot first round (might be unable to and is stuck for unknown reason) + Mission[N].funcID = nil + if FS[Mission[N].FS_name].mission == N then --FS has not fired first shot yet, reset everything + trigger.action.outTextForCoalition(Mission[N].coalition, Mission[N].FS_callsign .. ": Unable to fire, end of mission, out.", 15) + trigger.action.outSoundForCoalition(Mission[N].coalition, SoundFilename) + PopulateFreshRadioMenu(Mission[N].FO_name) + FS[Mission[N].FS_name].status = nil + FS[Mission[N].FS_name].mission = nil + Mission[N].idle = nil + if Group.getByName(Mission[N].FS_name) then + Group.getByName(Mission[N].FS_name):getController():resetTask() --reset task + end + end + end + if Mission[N].funcID then + timer.removeFunction(Mission[N].funcID) + end + Mission[N].funcID = timer.scheduleFunction(CheckFireMissionOngoing, nil, timer.getTime() + 240) + + local FireMissionTask = { + id = "ComboTask", + params = { + tasks = { + [1] = { --fire at target task + id = 'ControlledTask', + params = { + task = { + id = 'FireAtPoint', + params = { + x = Mission[N].x, + y = Mission[N].z, + zoneRadius = radius, + expendQty = expendQty, + expendQtyEnabled = true, + weaponType = weaponType, + } + }, + stopCondition = { + duration = 600, --stop the task if it cannot be completed in 10 minutes + }, + }, + }, + }, + }, + } + Group.getByName(Mission[N].FS_name):getController():setTask(FireMissionTask) + Mission[N].lastRoundN = expendQty + FS[Mission[N].FS_name].mission = N + Mission[N].ammoType = ammoType + end + else + trigger.action.outTextForCoalition(Mission[N].coalition, Mission[N].FS_callsign .. ": Cannot comply, unit out of action, end of mission, out.", 15) + trigger.action.outSoundForCoalition(Mission[N].coalition, SoundFilename) + PopulateFreshRadioMenu(Mission[N].FO_name) + Mission[N].idle = nil + end + end +end + + +----- FO Actions After Salvo Impact ----- +--repeat the last salvo +local function FO_RepeatFireMission(N) + Mission[N].idle = timer.getTime() + 300 + missionCommands.removeItemForGroup(Mission[N].FO_id, "Fire Support") + missionCommands.addSubMenuForGroup(Mission[N].FO_id, "Fire Support") + + trigger.action.outTextForCoalition(Mission[N].coalition, Mission[N].FO_callsign .. ": Repeat, over.", 10) + trigger.action.outSoundForCoalition(Mission[N].coalition, SoundFilename) + local function AcknowlegdeReply() + trigger.action.outTextForCoalition(Mission[N].coalition, Mission[N].FS_callsign .. ": Repeat, out.", 10) + trigger.action.outSoundForCoalition(Mission[N].coalition, SoundFilename) + local function delay() + FireMission(N, Mission[N].lastRoundN) + end + timer.scheduleFunction(delay, nil, timer.getTime() + 10) + end + timer.scheduleFunction(AcknowlegdeReply, nil, timer.getTime() + 3) +end + +--adjust the next salvo +local function FO_AdjustFire(arg) + Mission[arg[1]].idle = timer.getTime() + 300 + missionCommands.removeItemForGroup(Mission[arg[1]].FO_id, "Fire Support") + missionCommands.addSubMenuForGroup(Mission[arg[1]].FO_id, "Fire Support") + + if arg[2] == "NORTH" then + Mission[arg[1]].x = Mission[arg[1]].x + arg[3] + elseif arg[2] == "SOUTH" then + Mission[arg[1]].x = Mission[arg[1]].x - arg[3] + end + + if arg[4] == "WEST" then + Mission[arg[1]].z = Mission[arg[1]].z - arg[5] + elseif arg[4] == "EAST" then + Mission[arg[1]].z = Mission[arg[1]].z + arg[5] + end + + local adjust_text = "" + if arg[3] > 0 then + adjust_text = adjust_text .. arg[2] .. " " .. arg[3] .. ", " + end + if arg[5] > 0 then + adjust_text = adjust_text .. arg[4] .. " " .. arg[5] .. ", " + end + + trigger.action.outTextForCoalition(Mission[arg[1]].coalition, Mission[arg[1]].FO_callsign .. ": " .. adjust_text .. arg[6] .. ", over.", 10) + trigger.action.outSoundForCoalition(Mission[arg[1]].coalition, SoundFilename) + local function AcknowlegdeReply() + trigger.action.outTextForCoalition(Mission[arg[1]].coalition, Mission[arg[1]].FS_callsign .. ": " .. adjust_text .. arg[6] .. ", out.", 10) + trigger.action.outSoundForCoalition(Mission[arg[1]].coalition, SoundFilename) + + local function delay() + if arg[6] == "adjust fire" then + FireMission(arg[1], 1) + elseif arg[6] == "fire for effect" then + FireMission(arg[1], Mission[arg[1]].rounds) + end + end + timer.scheduleFunction(delay, nil, timer.getTime() + 10) + end + timer.scheduleFunction(AcknowlegdeReply, nil, timer.getTime() + 5) +end + +--radio menu after salvo impact listing options: adjust impact point, repeat mission, end mission +local function FO_AdjustFireRadioMenu(N) + missionCommands.removeItemForGroup(Mission[N].FO_id, "Fire Support") + missionCommands.addSubMenuForGroup(Mission[N].FO_id, "Fire Support") + if Mission[N].MissionType == "adjust fire" and Mission[N].lastRoundN == 1 then + missionCommands.addCommandForGroup(Mission[N].FO_id, "Fire for effect", {"Fire Support"}, FO_AdjustFire, {N, "NORTH", 0, "WEST", 0, "fire for effect"}) + end + missionCommands.addSubMenuForGroup(Mission[N].FO_id, "Adjust Impact Point", {"Fire Support"}) + + for Lat = 1, 2 do + if Lat == 1 then + Lat = "NORTH" + elseif Lat == 2 then + Lat = "SOUTH" + end + missionCommands.addSubMenuForGroup(Mission[N].FO_id, Lat, {"Fire Support", "Adjust Impact Point"}) + + for LatDist = 1, 10 do + if LatDist == 1 then + LatDist = 0 + elseif LatDist == 2 then + LatDist = 50 + elseif LatDist == 3 then + LatDist = 100 + elseif LatDist == 4 then + LatDist = 200 + elseif LatDist == 5 then + LatDist = 300 + elseif LatDist == 6 then + LatDist = 400 + elseif LatDist == 7 then + LatDist = 500 + elseif LatDist == 8 then + LatDist = 600 + elseif LatDist == 9 then + LatDist = 800 + elseif LatDist == 10 then + LatDist = 1000 + end + missionCommands.addSubMenuForGroup(Mission[N].FO_id, LatDist .. " M", {"Fire Support", "Adjust Impact Point", Lat}) + + for Lon = 1, 2 do + if Lon == 1 then + Lon = "WEST" + elseif Lon == 2 then + Lon = "EAST" + end + missionCommands.addSubMenuForGroup(Mission[N].FO_id, Lon, {"Fire Support", "Adjust Impact Point", Lat, LatDist .. " M"}) + + for LonDist = 1, 10 do + if LonDist == 1 then + LonDist = 0 + elseif LonDist == 2 then + LonDist = 50 + elseif LonDist == 3 then + LonDist = 100 + elseif LonDist == 4 then + LonDist = 200 + elseif LonDist == 5 then + LonDist = 300 + elseif LonDist == 6 then + LonDist = 400 + elseif LonDist == 7 then + LonDist = 500 + elseif LonDist == 8 then + LonDist = 600 + elseif LonDist == 9 then + LonDist = 800 + elseif LonDist == 10 then + LonDist = 1000 + end + missionCommands.addSubMenuForGroup(Mission[N].FO_id, LonDist .. " M", {"Fire Support", "Adjust Impact Point", Lat, LatDist .. " M", Lon}) + + missionCommands.addCommandForGroup(Mission[N].FO_id, "Adjust Fire", {"Fire Support", "Adjust Impact Point", Lat, LatDist .. " M", Lon, LonDist .. " M"}, FO_AdjustFire, {N, Lat, LatDist, Lon, LonDist, "adjust fire"}) + missionCommands.addCommandForGroup(Mission[N].FO_id, "Fire For Effect", {"Fire Support", "Adjust Impact Point", Lat, LatDist .. " M", Lon, LonDist .. " M"}, FO_AdjustFire, {N, Lat, LatDist, Lon, LonDist, "fire for effect"}) + end + end + end + end + + missionCommands.addCommandForGroup(Mission[N].FO_id, "Repeat Mission", {"Fire Support"}, FO_RepeatFireMission, N) + missionCommands.addCommandForGroup(Mission[N].FO_id, "End Mission", {"Fire Support"}, FO_EndFireMission, N) +end + + +----- First Round Impact Call ----- +--weapon tracking to make splash call 5 seconds from impact of first round +local function TrackWeapon(N) + if Mission[N].weapon and Mission[N].weapon:isExist() then + local WeaponPoint = Mission[N].weapon:getPoint() --position of the weapon + local WeaponVV = Mission[N].weapon:getVelocity() --velocity vector of the weapon + local WeaponSpeed = math.sqrt(math.pow(WeaponVV.x, 2) + math.pow(WeaponVV.y, 2) + math.pow(WeaponVV.z, 2)) --airspeed of weapon + local FreeFallSpeed = 9.81 * math.sqrt((WeaponPoint.y - Mission[N].y) / (0.5 * 9.81)) --the potential speed weapon could pick up when free falling down on target due to gravity (this ignores weapon drag) + --trigger.action.outText("Current speed: " .. WeaponSpeed, 2) + WeaponSpeed = WeaponSpeed + FreeFallSpeed --add potential free fall speed to current weapon speed. This is important for high angle trajectories where weapon speed gets very low on top and then picks up again. + --trigger.action.outText("Alt above target: " .. WeaponPoint.y - Mission[N].y, 2) + --trigger.action.outText("Free fall speed: " .. FreeFallSpeed, 2) + local DistanceToTarget = math.sqrt(math.pow(Mission[N].x - WeaponPoint.x, 2) + math.pow(Mission[N].y - WeaponPoint.y, 2) + math.pow(Mission[N].z - WeaponPoint.z, 2)) --distance from weapon to target + local TimeToImpact = DistanceToTarget / WeaponSpeed --time until weapon reaches target + + --trigger.action.outText("Speed: " .. WeaponSpeed, 2) + --trigger.action.outText("Distance: " .. DistanceToTarget, 2) + --trigger.action.outText("TTI: " .. TimeToImpact, 2) + + if TimeToImpact > 5 then --time to impact is bigger than 5 seconds + return timer.getTime() + TimeToImpact - 4 --track weapon again 4 seconds from impact + else --time to impact is 5 seconds or less + trigger.action.outTextForCoalition(Mission[N].coalition, Mission[N].FS_callsign .. ": Splash, over.", 10) + trigger.action.outSoundForCoalition(Mission[N].coalition, SoundFilename) + local function AcknowlegdeReply() + trigger.action.outTextForCoalition(Mission[N].coalition, Mission[N].FO_callsign .. ": Splash, out.", 10) + trigger.action.outSoundForCoalition(Mission[N].coalition, SoundFilename) + FS[Mission[N].FS_name].status = "splash" + FO_AdjustFireRadioMenu(N) + Mission[N].idle = timer.getTime() + 180 + end + timer.scheduleFunction(AcknowlegdeReply, nil, timer.getTime() + 4) + end + else + FS[Mission[N].FS_name].status = "splash" + FO_AdjustFireRadioMenu(N) + Mission[N].idle = timer.getTime() + 180 + end +end + +--capture the first shot of the firing group +ShotEventHandler = {} +function ShotEventHandler:onEvent(event) + if event.id == world.event.S_EVENT_SHOT then + local InitGroupName = event.initiator:getGroup():getName() + if FS[InitGroupName] and FS[InitGroupName].mission then --group is Fire Support and has not yet fired first round + local N = FS[InitGroupName].mission + if event.weapon and event.weapon:getDesc() and event.weapon:getDesc().displayName and event.weapon:getDesc().displayName == Mission[N].ammoType then --weapon is the expected ammo type for fire mission + FS[Mission[N].FS_name].status = "firing" + trigger.action.outTextForCoalition(Mission[N].coalition, Mission[N].FS_callsign .. ": Shot, over.", 10) + trigger.action.outSoundForCoalition(Mission[N].coalition, SoundFilename) + local function ShotReply() + if FS[Mission[N].FS_name].status ~= "cease fire" then + trigger.action.outTextForCoalition(Mission[N].coalition, Mission[N].FO_callsign .. ": Shot, out.", 10) + trigger.action.outSoundForCoalition(Mission[N].coalition, SoundFilename) + end + end + timer.scheduleFunction(ShotReply, nil, timer.getTime() + 4) + + Mission[N].weapon = event.weapon --store the first weapon fired in this fire mission for later tracking + timer.scheduleFunction(TrackWeapon, N, timer.getTime() + 4) --track flight of shell to get its time to impact + FS[InitGroupName].mission = nil --mark as nil to stop tracking of subsequent shells in fire mission + Mission[N].idle = timer.getTime() + 180 + end + end + end +end +world.addEventHandler(ShotEventHandler) + + +----- Call for Fire Part 3 (description of target, method of engagement, and method of fire and control) ----- +local function FS_AcknowledgeMethod(N) + if Group.getByName(Mission[N].FS_name) then + Mission[N].idle = timer.getTime() + 300 + local text + if Mission[N].MissionType == "adjust fire" then + text = ": " .. Mission[N].warheadType .. " in effect, " .. Mission[N].rounds + elseif Mission[N].MissionType == "fire for effect" then + text = ": " .. Mission[N].warheadType .. ", " .. Mission[N].rounds + end + if Mission[N].rounds == 1 then + text = text .. " round, out." + else + text = text .. " rounds, out." + end + trigger.action.outTextForCoalition(Mission[N].coalition, Mission[N].FS_callsign .. text, 15) + trigger.action.outSoundForCoalition(Mission[N].coalition, SoundFilename) + + FS[Mission[N].FS_name].status = "prepare fire" + missionCommands.addCommandForGroup(Mission[N].FO_id, "Check Fire", {"Fire Support"}, FO_EndFireMission, N) + + local function delay() + if Mission[N].MissionType == "adjust fire" then + FireMission(N, 1) + elseif Mission[N].MissionType == "fire for effect" then + FireMission(N, Mission[N].rounds) + end + end + timer.scheduleFunction(delay, nil, timer.getTime() + 10) + + else + Mission[N].idle = nil + PopulateFreshRadioMenu(Mission[N].FO_name) + trigger.action.outTextForCoalition(Mission[N].coalition, Mission[N].FS_callsign .. ": Cannot comply, unit out of action, out.", 15) + trigger.action.outSoundForCoalition(Mission[N].coalition, SoundFilename) + end +end + +local function FO_TransmitMethod(arg) + local N = arg[1] + Mission[N].idle = timer.getTime() + 180 + Mission[N].rounds = arg[2] + local _, _, _, _, warheadType = ReturnArtilleryAmmo(Mission[N].FS_name) + Mission[N].warheadType = warheadType + local text + if Mission[N].MissionType == "adjust fire" then + text = ": " .. Mission[N].warheadType .. " in effect, " .. Mission[N].rounds + elseif Mission[N].MissionType == "fire for effect" then + text = ": " .. Mission[N].warheadType .. ", " .. Mission[N].rounds + end + if Mission[N].rounds == 1 then + text = text .. " round, over." + else + text = text .. " rounds, over." + end + trigger.action.outTextForCoalition(Mission[N].coalition, Mission[N].FO_callsign .. text, 15) + trigger.action.outSoundForCoalition(Mission[N].coalition, SoundFilename) + timer.scheduleFunction(FS_AcknowledgeMethod, N, timer.getTime() + 5) + missionCommands.removeItemForGroup(Mission[N].FO_id, "Fire Support") + missionCommands.addSubMenuForGroup(Mission[N].FO_id, "Fire Support") +end + +local function InsertSalvoSize(N) + local roundsN = FS[Mission[N].FS_name].SalvoSize + if type(roundsN) == "table" then + for n = 1, #roundsN do + missionCommands.addCommandForGroup(Mission[N].FO_id, roundsN[n] .. " rounds", {"Fire Support"}, FO_TransmitMethod, {N, roundsN[n]}) + if n == 9 then + break + end + end + missionCommands.addCommandForGroup(Mission[N].FO_id, "Cancel Call For Fire", {"Fire Support"}, FO_EndFireMission, N) + else + missionCommands.addCommandForGroup(Mission[N].FO_id, roundsN .. " rounds", {"Fire Support"}, FO_TransmitMethod, {N, roundsN}) + end +end + + +----- Call for Fire Part 2 (target location) ----- +local function FS_AcknowledgeTargetLocation(N) + if Group.getByName(Mission[N].FS_name) then + trigger.action.outTextForCoalition(Mission[N].coalition, Mission[N].FS_callsign .. ": Grid " .. Mission[N].grid .. ", out.", 15) + trigger.action.outSoundForCoalition(Mission[N].coalition, SoundFilename) + InsertSalvoSize(N) + else + trigger.action.outTextForCoalition(Mission[N].coalition, Mission[N].FS_callsign .. ": Cannot comply, unit out of action, out.", 15) + trigger.action.outSoundForCoalition(Mission[N].coalition, SoundFilename) + PopulateFreshRadioMenu(Mission[N].FO_name) + Mission[N].idle = nil + end +end + +local function FO_TransmitTargetLocation(arg) + Mission[arg[1]].idle = timer.getTime() + 180 + local N = arg[1] + Mission[N].x = arg[2].x + Mission[N].y = land.getHeight({x = arg[2].x, y = arg[2].z}) --target elevation + Mission[N].z = arg[2].z + Mission[N].grid = arg[3] + + trigger.action.outTextForCoalition(Mission[N].coalition, Mission[N].FO_callsign .. ": Grid " .. Mission[N].grid .. ", over.", 15) + trigger.action.outSoundForCoalition(Mission[N].coalition, SoundFilename) + timer.scheduleFunction(FS_AcknowledgeTargetLocation, N, timer.getTime() + 5) + missionCommands.removeItemForGroup(Mission[N].FO_id, "Fire Support") + missionCommands.addSubMenuForGroup(Mission[N].FO_id, "Fire Support") +end + +local function FO_DetermineTargetLoction(N) + + ----- Target coordinates from map marker ----- + local function StartMarkEntry() + Mission[N].idle = timer.getTime() + 180 + missionCommands.removeItemForGroup(Mission[N].FO_id, {"Fire Support", "Transmit Target Location", "From Map Marker"}) + missionCommands.addSubMenuForGroup(Mission[N].FO_id, "From Map Marker", {"Fire Support", "Transmit Target Location"}) + local c = Unit.getByName(Mission[N].FO_name):getCoalition() --FO coalition + for n = #UserMark[c], #UserMark[c] - 8, -1 do --get the last 9 added map markers + if n == 0 then + break + end + local point = UserMark[c][n].pos + local lat, lon = coord.LOtoLL(point) --target coordinates + local MGRS = coord.LLtoMGRS(lat, lon) --target MGRS grid + local MGRS6 = MGRS.MGRSDigraph .. " " .. math.floor(MGRS.Easting / 100) .. " " .. math.floor(MGRS.Northing / 100) --simplified MGRS grid to 100m precision + missionCommands.addCommandForGroup(Mission[N].FO_id, MGRS6, {"Fire Support", "Transmit Target Location", "From Map Marker"}, FO_TransmitTargetLocation, {N, point, MGRS6}) + end + missionCommands.addCommandForGroup(Mission[N].FO_id, "Refresh List", {"Fire Support", "Transmit Target Location", "From Map Marker"}, StartMarkEntry, nil) + end + + + ----- Manual grid entry ----- + local function InsertMGRS_Northing(MGRS) --populate the radio menu to enter the MGRS Northing coordinates + Mission[N].idle = timer.getTime() + 180 + trigger.action.outTextForUnit(Unit.getByName(Mission[N].FO_name):getID(), "Manual Grid Entry: " .. MGRS.MGRSDigraph .. " " .. MGRS.e1 .. MGRS.e2 .. MGRS.e3 .. " ...", 3) + missionCommands.removeItemForGroup(Mission[N].FO_id, {"Fire Support"}) + missionCommands.addSubMenuForGroup(Mission[N].FO_id, "Fire Support") + missionCommands.addSubMenuForGroup(Mission[N].FO_id, "Enter MGRS Northing (3 digit)", {"Fire Support"}) + missionCommands.addCommandForGroup(Mission[N].FO_id, "Cancel Grid Entry", {"Fire Support"}, TargetLocationMenu, nil) + missionCommands.addCommandForGroup(Mission[N].FO_id, "Cancel Call For Fire", {"Fire Support"}, FO_EndFireMission, N) + + for n1 = 1, 10 do + if n1 == 10 then + n1 = 0 + end + missionCommands.addSubMenuForGroup(Mission[N].FO_id, n1, {"Fire Support", "Enter MGRS Northing (3 digit)"}) + + for n2 = 1, 10 do + if n2 == 10 then + n2 = 0 + end + missionCommands.addSubMenuForGroup(Mission[N].FO_id, n2, {"Fire Support", "Enter MGRS Northing (3 digit)", n1}) + + for n3 = 1, 10 do + if n3 == 10 then + n3 = 0 + end + + MGRS.Easting = tonumber(MGRS.e1 .. MGRS.e2 .. MGRS.e3 .. 0 .. 0) + MGRS.Northing = tonumber(n1 .. n2 .. n3 .. 0 .. 0) + local lat, lon = coord.MGRStoLL(MGRS) --target coordinates in lat-lon + local point = coord.LLtoLO(lat, lon) --target position as vec3 + local MGRS6 = MGRS.MGRSDigraph .. " " .. MGRS.e1 .. MGRS.e2 .. MGRS.e3 .. " " .. n1 .. n2 .. n3 --simplified MGRS grid to 100m precision + missionCommands.addCommandForGroup(Mission[N].FO_id, n3, {"Fire Support", "Enter MGRS Northing (3 digit)", n1, n2}, FO_TransmitTargetLocation, {N, point, MGRS6}) + end + end + end + end + + local function InsertMGRS_Easting(MGRS) --popuate the radio menu to enter the MGRS Easting Coordinates + Mission[N].idle = timer.getTime() + 180 + trigger.action.outTextForUnit(Unit.getByName(Mission[N].FO_name):getID(), "Manual Grid Entry: " .. MGRS.MGRSDigraph .. " ...", 3) + missionCommands.removeItemForGroup(Mission[N].FO_id, {"Fire Support"}) + missionCommands.addSubMenuForGroup(Mission[N].FO_id, "Fire Support") + missionCommands.addSubMenuForGroup(Mission[N].FO_id, "Enter MGRS Easting (3 digit)", {"Fire Support"}) + missionCommands.addCommandForGroup(Mission[N].FO_id, "Cancel Grid Entry", {"Fire Support"}, TargetLocationMenu, nil) + missionCommands.addCommandForGroup(Mission[N].FO_id, "Cancel Call For Fire", {"Fire Support"}, FO_EndFireMission, N) + + for e1 = 1, 10 do + if e1 == 10 then + e1 = 0 + end + missionCommands.addSubMenuForGroup(Mission[N].FO_id, e1, {"Fire Support", "Enter MGRS Easting (3 digit)"}) + + for e2 = 1, 10 do + if e2 == 10 then + e2 = 0 + end + missionCommands.addSubMenuForGroup(Mission[N].FO_id, e2, {"Fire Support", "Enter MGRS Easting (3 digit)", e1}) + + for e3 = 1, 10 do + if e3 == 10 then + e3 = 0 + end + local MGRS_copy = { + UTMZone = MGRS.UTMZone, + MGRSDigraph = MGRS.MGRSDigraph, + e1 = e1, + e2 = e2, + e3 = e3, + } + missionCommands.addCommandForGroup(Mission[N].FO_id, e3, {"Fire Support", "Enter MGRS Easting (3 digit)", e1, e2}, InsertMGRS_Northing, MGRS_copy) + end + end + end + end + + local function StartGridEntry() --populate the radio menu to enter the MGRSDigraph (100km grid) of the target + Mission[N].idle = timer.getTime() + 180 + missionCommands.addSubMenuForGroup(Mission[N].FO_id, "Manual Grid Entry", {"Fire Support", "Transmit Target Location"}) + --find the nine 100km grids around the FO + local GridTable = {} --table to store the details of the nine 100km grids around the FO + local point = Unit.getByName(Mission[N].FO_name):getPoint() --current position of FO + local lat, lon = coord.LOtoLL(point) --convert position to lat-lon coordinates + local MGRS = coord.LLtoMGRS(lat, lon) --convert position to MGRS grid + + for n1 = -1, 1 do --get the 100 km grids to the south, center and north of FO + local northing_mod = MGRS.Northing + n1 * 100000 --modify MGRS Northing with -100 km, 0 and +100 km + for n2 = -1, 1 do --get the 100 km grids to the weast, center and east of FO + local grid --to store the 100 km grid name ("AB" etc.). 9 iterations total + for dist = 100000, 0, -10000 do --distance to modify the MGRS Easting. Due to earth curviture, some 100 km grids are skipped on the longitudinal axis. Distance between the grids is therefore lowered until we have found the next adjecent grid. + local MGRS_mod = { --to store the full MGRS coordinates of the next grid + UTMZone = MGRS.UTMZone, + MGRSDigraph = MGRS.MGRSDigraph, + Easting = MGRS.Easting + n2 * dist, --modify MGRS Easting with distance variable to the west (-), center (0) and east (+) + Northing = northing_mod, --take the modified northing which we have already done above + } + local lat_mod, lon_mod = coord.MGRStoLL(MGRS_mod) --convert modified MGRS to lat-lon + MGRS_mod = coord.LLtoMGRS(lat_mod, lon_mod) --convert the modified MGRS back to MGRS (this will update the UTMZone and MGRSDigrap to the actual values) + + if MGRS_mod.UTMZone == MGRS.UTMZone then --if the UTMZone of the modified MGRS is the same as the original MGRS + if dist == 100000 then --this is the first try with the full 100 km distance + grid = { --this is the new 100 km grid we seek + UTMZone = MGRS_mod.UTMZone, + MGRSDigraph = MGRS_mod.MGRSDigraph, + } + end + break --do not check lower distances + end + grid = { --if the UTMZone is different, store the 100 km grid and continue to check with lower distances. If then a lower distances raches the same UTMZone, then the previous 100 km grid will be used. + UTMZone = MGRS_mod.UTMZone, + MGRSDigraph = MGRS_mod.MGRSDigraph, + } + end + + if #GridTable == 0 or GridTable[#GridTable].MGRSDigraph ~= grid.MGRSDigraph then --100 km grid is not the same as the last one (can happen when operating extremely close to UTMZone boundaries) + table.insert(GridTable, grid) --insert 100 km grid + end + end + end + + for g = 1, #GridTable do + missionCommands.addCommandForGroup(Mission[N].FO_id, GridTable[g].MGRSDigraph, {"Fire Support", "Transmit Target Location", "Manual Grid Entry"}, InsertMGRS_Easting, GridTable[g]) + end + end + + ----- Main Menu ----- + function TargetLocationMenu() + missionCommands.removeItemForGroup(Mission[N].FO_id, "Fire Support") + missionCommands.addSubMenuForGroup(Mission[N].FO_id, "Fire Support") + missionCommands.addSubMenuForGroup(Mission[N].FO_id, "Transmit Target Location", {"Fire Support"}) + missionCommands.addCommandForGroup(Mission[N].FO_id, "Cancel Call For Fire", {"Fire Support"}, FO_EndFireMission, N) + StartMarkEntry() + StartGridEntry() + end + TargetLocationMenu() + + ----- FOR DEBUG ----- + --local point = trigger.misc.getZone('TestZone').point + --local lat, lon = coord.LOtoLL(point) --target coordinates + --local MGRS = coord.LLtoMGRS(lat, lon) --target MGRS grid + --local MGRS6 = MGRS.MGRSDigraph .. " " .. math.floor(MGRS.Easting / 100) .. " " .. math.floor(MGRS.Northing / 100) --simplified MGRS grid to 100m precision + --missionCommands.addCommandForGroup(Mission[N].FO_id, "DEBUG: " .. MGRS6, {"Fire Support", "Transmit Target Location"}, FO_TransmitTargetLocation, {N, point, MGRS6}) +end + +----- Call for Fire Part 1 (observer identification and warning order) ----- +local function FS_AcknowledgeFireMission(arg) + local FO_name = arg[1] + local FO_callsign = Unit.getByName(arg[1]):getCallsign() + local a,b = string.find(FO_callsign, "%d%d") + FO_callsign = string.sub(FO_callsign, 1, a - 1) .. " " .. string.sub(FO_callsign, a, a) .. "-" .. string.sub(FO_callsign, b, b) --put callsign "Enfield11" into format "Enfield 1-1" + local FS_name = arg[2] + local FS_callsign = FS[arg[2]].callsign + local coalition = Unit.getByName(arg[1]):getCoalition() + local MissionType = arg[3] + + if Group.getByName(FS_name) and Group.getByName(FS_name):getUnit(1):isActive() then + if FS[FS_name].status then + trigger.action.outTextForCoalition(coalition, FS_callsign .. ": " .. FO_callsign .. " this is " .. FS_callsign .. ", negative, mission in progress, out.", 15) + trigger.action.outSoundForCoalition(coalition, SoundFilename) + elseif ReturnArtilleryAmmo(FS_name) == 0 then + trigger.action.outTextForCoalition(coalition, FS_callsign .. ": " .. FO_callsign .. " this is " .. FS_callsign .. ", negative, out of ammo, out.", 15) + trigger.action.outSoundForCoalition(coalition, SoundFilename) + else + local N = #Mission + 1 + Mission[N] = { + idle = timer.getTime() + 180, + FO_name = FO_name, + FO_callsign = FO_callsign, + FO_id = Unit.getByName(FO_name):getGroup():getID(), + FS_name = FS_name, + FS_callsign = FS_callsign, + MissionType = MissionType, + coalition = coalition, + } + FS[FS_name].status = "request" + FO_DetermineTargetLoction(N) + trigger.action.outTextForCoalition(coalition, FS_callsign .. ": " .. FO_callsign .. " this is " .. FS_callsign .. ", " .. MissionType .. ", out.", 15) + trigger.action.outSoundForCoalition(coalition, SoundFilename) + end + else + trigger.action.outTextForCoalition(coalition, FS_callsign .. ": " .. FO_callsign .. " this is " .. FS_callsign .. ", negative, unit out of action, out.", 15) + trigger.action.outSoundForCoalition(coalition, SoundFilename) + end +end + +local function FO_CallFireMission(arg) + local FO_callsign = Unit.getByName(arg[1]):getCallsign() + local a,b = string.find(FO_callsign, "%d%d") + FO_callsign = string.sub(FO_callsign, 1, a - 1) .. " " .. string.sub(FO_callsign, a, a) .. "-" .. string.sub(FO_callsign, b, b) --put callsign "Enfield11" into format "Enfield 1-1" + local FS_callsign = FS[arg[2]].callsign + local coalition = Unit.getByName(arg[1]):getCoalition() + local MissionType = arg[3] + + trigger.action.outTextForCoalition(coalition, FO_callsign .. ": " .. FS_callsign .. " this is " .. FO_callsign .. ", " .. MissionType .. ", over.", 15) + trigger.action.outSoundForCoalition(coalition, SoundFilename) + timer.scheduleFunction(FS_AcknowledgeFireMission, arg, timer.getTime() + 5) +end + + +----- Initialize Radiomenu ----- +function PopulateFreshRadioMenu(FO_name) + if Unit.getByName(FO_name) then + missionCommands.removeItemForGroup(FO[FO_name].id, "Fire Support") + missionCommands.addSubMenuForGroup(FO[FO_name].id, "Fire Support") + + for FS_name,v in pairs(FS) do + if Group.getByName(FS_name) and Group.getByName(FS_name):getCoalition() == Unit.getByName(FO_name):getCoalition() then + missionCommands.addSubMenuForGroup(FO[FO_name].id, v.callsign, {"Fire Support"}) + missionCommands.addCommandForGroup(FO[FO_name].id, "Call for Adjust Fire Mission", {"Fire Support", v.callsign}, FO_CallFireMission, {FO_name, FS_name, "adjust fire"}) + missionCommands.addCommandForGroup(FO[FO_name].id, "Call for Fire For Effect Mission", {"Fire Support", v.callsign}, FO_CallFireMission, {FO_name, FS_name, "fire for effect"}) + end + end + end +end + +local function FO_CheckActive() + for FO_name,v in pairs(FO) do + if Unit.getByName(FO_name) and v.active == nil then + v.active = true + v.id = Unit.getByName(FO_name):getGroup():getID() + PopulateFreshRadioMenu(FO_name) + elseif Unit.getByName(FO_name) == nil then + v.active = nil + end + end + return timer.getTime() + 1 +end +timer.scheduleFunction(FO_CheckActive, nil, timer.getTime() + 2) + + +----- Loop to check if a fire mission is idle for too long ----- +local function CheckIdleLoop() + for N = 1, #Mission do + if Mission[N].idle and timer.getTime() > Mission[N].idle then + Mission[N].idle = nil + PopulateFreshRadioMenu(Mission[N].FO_name) + FS[Mission[N].FS_name].status = nil + FS[Mission[N].FS_name].mission = nil + if Group.getByName(Mission[N].FS_name) then + Group.getByName(Mission[N].FS_name):getController():resetTask() --reset task + end + trigger.action.outTextForCoalition(Mission[N].coalition, Mission[N].FS_callsign .. ": End mission, out.", 10) + trigger.action.outSoundForCoalition(Mission[N].coalition, SoundFilename) + end + end + return timer.getTime() + 60 +end +timer.scheduleFunction(CheckIdleLoop, nil, timer.getTime() + 30) \ No newline at end of file diff --git a/resources/plugins/artymbot/artymbot-config.lua b/resources/plugins/artymbot/artymbot-config.lua new file mode 100644 index 00000000..93d83f19 --- /dev/null +++ b/resources/plugins/artymbot/artymbot-config.lua @@ -0,0 +1,27 @@ +------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- configuration file for Mbot's Call Artillery Script +-- +-- This configuration is tailored for a mission generated by DCS Retribution +-- see https://github.com/dcs-retribution/dcs-retribution +------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- artymbot plugin - configuration +if dcsRetribution then + -- retrieve specific options values + if dcsRetribution.plugins then + if dcsRetribution.plugins.artymbot then + env.info("DCSRetribution|Mbot's Call Artillery Script plugin - Setting Up") + for _, data in pairs(dcsRetribution.artilleryGroups.groundArtillery) do + AddFS(data.groupName) + end + if dcsRetribution.plugins.artymbot.shipArtilleryEnable then + for _, data in pairs(dcsRetribution.artilleryGroups.shipArtillery) do + AddFS(data.groupName) + end + end + for _, data in pairs(dcsRetribution.forwardObserverUnits) do + AddFO(data.unitName) + end + end + end +end diff --git a/resources/plugins/artymbot/beep.wav b/resources/plugins/artymbot/beep.wav new file mode 100644 index 0000000000000000000000000000000000000000..da023431a8a42cd5f22416e7eaf790b606bc3748 GIT binary patch literal 57574 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 literal 0 HcmV?d00001 diff --git a/resources/plugins/artymbot/plugin.json b/resources/plugins/artymbot/plugin.json new file mode 100644 index 00000000..b530a2ec --- /dev/null +++ b/resources/plugins/artymbot/plugin.json @@ -0,0 +1,26 @@ +{ + "nameInUI": "Mbot Call Artillery Script", + "defaultValue": false, + "specificOptions": [ + { + "nameInUI": "Enable ship artillery", + "mnemonic": "shipArtilleryEnable", + "defaultValue": true + } + ], + "scriptsWorkOrders": [ + { + "file": "CallArtilleryScript.lua", + "mnemonic": "artymbot" + } + ], + "configurationWorkOrders": [ + { + "file": "artymbot-config.lua", + "mnemonic": "artymbot-config" + } + ], + "otherResourceFiles": [ + "beep.wav" + ] +} diff --git a/resources/plugins/plugins.json b/resources/plugins/plugins.json index aeafbac8..0c0e7ef0 100644 --- a/resources/plugins/plugins.json +++ b/resources/plugins/plugins.json @@ -2,6 +2,7 @@ "base", "ctld", "arty", + "artymbot", "dismounts", "ewrj", "ewrs",