mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
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:
parent
e959933861
commit
2091fdbb27
@ -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 !
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
955
resources/plugins/artymbot/CallArtilleryScript.lua
Normal file
955
resources/plugins/artymbot/CallArtilleryScript.lua
Normal 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)
|
||||
27
resources/plugins/artymbot/artymbot-config.lua
Normal file
27
resources/plugins/artymbot/artymbot-config.lua
Normal 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
|
||||
BIN
resources/plugins/artymbot/beep.wav
Normal file
BIN
resources/plugins/artymbot/beep.wav
Normal file
Binary file not shown.
26
resources/plugins/artymbot/plugin.json
Normal file
26
resources/plugins/artymbot/plugin.json
Normal 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"
|
||||
]
|
||||
}
|
||||
@ -2,6 +2,7 @@
|
||||
"base",
|
||||
"ctld",
|
||||
"arty",
|
||||
"artymbot",
|
||||
"dismounts",
|
||||
"ewrj",
|
||||
"ewrs",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user