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
This commit is contained in:
Cedric Menard 2024-11-03 08:02:36 -05:00 committed by GitHub
parent e959933861
commit 2091fdbb27
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 1111 additions and 1 deletions

View File

@ -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 !

View File

@ -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

View File

@ -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,

View File

@ -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:

View File

@ -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)

View File

@ -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
)

View File

@ -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)

View File

@ -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

Binary file not shown.

View File

@ -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"
]
}

View File

@ -2,6 +2,7 @@
"base",
"ctld",
"arty",
"artymbot",
"dismounts",
"ewrj",
"ewrs",