Version 2.2.3

Many changes, most relating to "Expansion", some community requests. Little Documentation of those changes available yet, though.
This commit is contained in:
Christian Franz 2024-05-09 09:39:22 +02:00
parent 2422d89a32
commit 07a32bd051
24 changed files with 2202 additions and 241 deletions

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,5 @@
FARPZones = {} FARPZones = {}
FARPZones.version = "2.0.0" FARPZones.version = "2.1.0"
FARPZones.verbose = false FARPZones.verbose = false
--[[-- --[[--
Version History Version History
@ -16,6 +16,15 @@ FARPZones.verbose = false
1.2.1 - now gracefully handles a FARP Zone that does not 1.2.1 - now gracefully handles a FARP Zone that does not
contain a FARP, but is placed beside it contain a FARP, but is placed beside it
2.0.0 - dmlZones 2.0.0 - dmlZones
2.0.1 - locking up FARPS for the first five seconds
loadMission handles lockup
FARPs now can show their name
showTitle attribute
refresh attribute (config)
2.0.2 - clean-up
verbosity enhancements
2.1.0 - integration with camp: needs repairs, produceResourceVehicles()
--]]-- --]]--
@ -23,6 +32,7 @@ FARPZones.requiredLibs = {
"dcsCommon", "dcsCommon",
"cfxZones", -- Zones, of course "cfxZones", -- Zones, of course
} }
FARPZones.lockup = {} -- for the first 5 seconds of the game, released later
-- *** DOES NOT EXTEND ZONES, USES OWN STRUCT *** -- *** DOES NOT EXTEND ZONES, USES OWN STRUCT ***
-- *** SETS ZONE.OWNER IF PRESENT, POSSIBLE CONFLICT -- *** SETS ZONE.OWNER IF PRESENT, POSSIBLE CONFLICT
@ -77,11 +87,11 @@ FARPZones.resourceTypes = {
FARPZones.spinUpDelay = 30 -- seconds until FARP becomes operational after capture FARPZones.spinUpDelay = 30 -- seconds until FARP becomes operational after capture
FARPZones.allFARPZones = {} FARPZones.allFARPZones = {} -- indexed by zone, returns dmlFARP struct
FARPZones.startingUp = false -- not needed / read anywhere FARPZones.startingUp = false -- not needed / read anywhere
-- FARP ZONE ACCESS -- FARP ZONE ACCESS
function FARPZones.addFARPZone(aFARP) function FARPZones.addFARPZone(aFARP) -- entry with dmlFARP struct
FARPZones.allFARPZones[aFARP.zone] = aFARP FARPZones.allFARPZones[aFARP.zone] = aFARP
end end
@ -89,8 +99,8 @@ function FARPZones.removeFARPZone(aFARP)
FARPZones.allFARPZones[aFARP.zone] = nil FARPZones.allFARPZones[aFARP.zone] = nil
end end
function FARPZones.getFARPForZone(aZone) function FARPZones.getFARPForZone(aZone) -- enter with zone
return FARPZones.allFARPZones[aZone] return FARPZones.allFARPZones[aZone] -- returns dmlFARP
end end
function FARPZones.getFARPZoneByName(aName) function FARPZones.getFARPZoneByName(aName)
@ -153,7 +163,12 @@ function FARPZones.createFARPFromZone(aZone)
theFarp.point = theFarp.mainFarp:getPoint() -- this is FARP, not zone!!! theFarp.point = theFarp.mainFarp:getPoint() -- this is FARP, not zone!!!
theFarp.owner = theFarp.mainFarp:getCoalition() theFarp.owner = theFarp.mainFarp:getCoalition()
aZone.owner = theFarp.owner aZone.owner = theFarp.owner
-- end -- lock up the faction until release in a few seconds after mission start
theFarp.mainFarp:autoCapture(false)
table.insert(FARPZones.lockup, theFarp.mainFarp) -- will be released in 5 seconds
if aZone.verbose then
trigger.action.outText("FARPzone <" .. aZone.name .. "> currently has owner <" .. aZone.owner .. ">", 30)
end
-- get r and phi for defenders -- get r and phi for defenders
local rPhi = aZone:getVectorFromZoneProperty("rPhiHDef",3) local rPhi = aZone:getVectorFromZoneProperty("rPhiHDef",3)
@ -191,7 +206,7 @@ function FARPZones.createFARPFromZone(aZone)
theFarp.hideBlue = aZone:getBoolFromZoneProperty("hideBlue", false) theFarp.hideBlue = aZone:getBoolFromZoneProperty("hideBlue", false)
theFarp.hideGrey = aZone:getBoolFromZoneProperty("hideGrey", false) theFarp.hideGrey = aZone:getBoolFromZoneProperty("hideGrey", false)
theFarp.hidden = aZone:getBoolFromZoneProperty("hidden", false) theFarp.hidden = aZone:getBoolFromZoneProperty("hidden", false)
theFarp.showTitle = aZone:getBoolFromZoneProperty("showTitle", true)
theFarp.neutralProduction = aZone:getBoolFromZoneProperty("neutralProduction", false) theFarp.neutralProduction = aZone:getBoolFromZoneProperty("neutralProduction", false)
return theFarp return theFarp
end end
@ -206,6 +221,12 @@ function FARPZones.drawFARPCircleInMap(theFarp)
theFarp.zone.markID = nil theFarp.zone.markID = nil
end end
if theFarp.zone and theFarp.zone.titleID then
-- remove previous mark
trigger.action.removeMark(theFarp.zone.titleID)
theFarp.zone.titleID = nil
end
if theFarp.hideRed and if theFarp.hideRed and
theFarp.owner == 1 then theFarp.owner == 1 then
-- hide only when red -- hide only when red
@ -251,39 +272,12 @@ function FARPZones.drawFARPCircleInMap(theFarp)
trigger.action.circleToAll(-1, markID, thePoint, 2000, lineColor, fillColor, 1, true, "") trigger.action.circleToAll(-1, markID, thePoint, 2000, lineColor, fillColor, 1, true, "")
aZone.markID = markID aZone.markID = markID
if theFarp.showTitle then
end aZone.titleID = aZone:drawText(aZone.name, 16, lineColor, {0.8, 0.8, 0.8, 0.0})
--[[--
function FARPZones.drawZoneInMap(aZone, owner)
-- owner is 0 = neutral, 1 = red, 2 = blue
-- will save markID in zone's markID
-- should be moved to cfxZones
-- should be able to only show owned
if aZone.markID then
trigger.action.removeMark(aZone.markID)
end end
local lineColor = {1.0, 0, 0, 1.0} -- red
local fillColor = {1.0, 0, 0, 0.2} -- red
if owner == 2 then
lineColor = {0.0, 0, 1.0, 1.0}
fillColor = {0.0, 0, 1.0, 0.2}
elseif owner == 0 then
lineColor = {0.8, 0.8, 0.8, 1.0}
fillColor = {0.8, 0.8, 0.8, 0.2}
end end
local theShape = 2 -- circle
local markID = dcsCommon.numberUUID()
trigger.action.circleToAll(-1, markID, aZone.point, aZone.radius, lineColor, fillColor, 1, true, "")
aZone.markID = markID
end
--]]--
function FARPZones.scheduedProduction(args) function FARPZones.scheduedProduction(args)
-- args contain [aFarp, owner] -- args contain [aFarp, owner]
@ -306,6 +300,35 @@ function FARPZones.scheduedProduction(args)
end end
end end
function FARPZones.serviceNeedsRepair(theFarp)
if theFarp.owner == 0 then return false end
if not theFarp.resources then return false end -- no group yet
if not Group.isExist(theFarp.resources) then return true end
if theFarp.resources:getSize() < #FARPZones.resourceTypes then
return true
end
return false
end
function FARPZones.produceResourceVehicles(theFarp, coa)
if theFarp.resources and Group.isExist(theFarp.resources) then --theFarp.resources:isExist() then
Group.destroy(theFarp.resources) --theFarp.resources:destroy()
theFarp.resources = nil
end
local unitTypes = FARPZones.resourceTypes -- an array
local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
coa,
theFarp.name .. "-R" .. theFarp.count, -- must be unique
theFarp.resZone,
unitTypes,
"line_v",
theFarp.resHeading)
theFarp.resources = theGroup
theFarp.resourceData = theData
-- update unique counter
theFarp.count = theFarp.count + 1
end
function FARPZones.produceVehicles(theFarp) function FARPZones.produceVehicles(theFarp)
local theZone = theFarp.zone local theZone = theFarp.zone
-- trigger.action.outText("entering veh prod run for farp zone <" .. theZone.name .. ">, owner is <" .. theFarp.owner .. ">", 30) -- trigger.action.outText("entering veh prod run for farp zone <" .. theZone.name .. ">, owner is <" .. theFarp.owner .. ">", 30)
@ -357,6 +380,9 @@ function FARPZones.produceVehicles(theFarp)
theFarp.defenderData = theData theFarp.defenderData = theData
end end
-- spawn resource vehicles
FARPZones.produceResourceVehicles(theFarp, theCoalition)
--[[--
unitTypes = FARPZones.resourceTypes unitTypes = FARPZones.resourceTypes
local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition ( local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
theCoalition, theCoalition,
@ -367,6 +393,7 @@ function FARPZones.produceVehicles(theFarp)
theFarp.resHeading) theFarp.resHeading)
theFarp.resources = theGroup theFarp.resources = theGroup
theFarp.resourceData = theData theFarp.resourceData = theData
--]]--
-- update unique counter -- update unique counter
theFarp.count = theFarp.count + 1 theFarp.count = theFarp.count + 1
end end
@ -453,6 +480,20 @@ function FARPZones.somethingHappened(event)
end end
--
-- Update / Refresh
--
function FARPZones.refreshMap()
timer.scheduleFunction(FARPZones.refreshMap, {}, timer.getTime() + FARPZones.refresh)
if FARPZones.verbose then
trigger.action.outText("+++Farp map refresh started", 30)
end
for idx, theFARP in pairs(FARPZones.allFARPZones) do
FARPZones.drawFARPCircleInMap(theFARP)
end
end
-- --
-- LOAD / SAVE -- LOAD / SAVE
@ -503,6 +544,8 @@ function FARPZones.loadMission()
if theFARP then if theFARP then
theFARP.owner = fData.owner theFARP.owner = fData.owner
theFARP.zone.owner = fData.owner theFARP.zone.owner = fData.owner
local theAB = theFARP.mainFarp
theAB:setCoalition(theFARP.owner) -- FARP is in lockup.
theFARP.defenderData = dcsCommon.clone(fData.defenderData) theFARP.defenderData = dcsCommon.clone(fData.defenderData)
local groupData = fData.defenderData local groupData = fData.defenderData
if groupData and #groupData.units > 0 then if groupData and #groupData.units > 0 then
@ -532,6 +575,14 @@ end
-- --
-- Start -- Start
-- --
function FARPZones.releaseFARPS()
-- trigger.action.outText("Releasing hold on FARPS", 30)
for idx, aFarp in pairs(FARPZones.lockup) do
aFarp:autoCapture(true)
-- trigger.action.outText("releasing farp <" .. aFarp:getName() .. ">", 30)
end
end
function FARPZones.readConfig() function FARPZones.readConfig()
local theZone = cfxZones.getZoneByName("farpZonesConfig") local theZone = cfxZones.getZoneByName("farpZonesConfig")
if not theZone then if not theZone then
@ -542,6 +593,8 @@ function FARPZones.readConfig()
FARPZones.spinUpDelay = theZone:getNumberFromZoneProperty( "spinUpDelay", 30) FARPZones.spinUpDelay = theZone:getNumberFromZoneProperty( "spinUpDelay", 30)
FARPZones.refresh = theZone:getNumberFromZoneProperty("refresh", -1)
end end
@ -598,6 +651,12 @@ function FARPZones.start()
FARPZones.startingUp = false -- not needed / read anywhere FARPZones.startingUp = false -- not needed / read anywhere
timer.scheduleFunction(FARPZones.releaseFARPS, {}, timer.getTime() + 5)
if FARPZones.refresh > 0 then
timer.scheduleFunction(FARPZones.refreshMap, {}, timer.getTime() + FARPZones.refresh)
end
trigger.action.outText("cf/x FARP Zones v" .. FARPZones.version .. " started", 30) trigger.action.outText("cf/x FARP Zones v" .. FARPZones.version .. " started", 30)
return true return true
end end

View File

@ -1,5 +1,5 @@
cfxSSBClient = {} cfxSSBClient = {}
cfxSSBClient.version = "3.0.1" cfxSSBClient.version = "4.0.0"
cfxSSBClient.verbose = false cfxSSBClient.verbose = false
cfxSSBClient.singleUse = false -- set to true to block crashed planes cfxSSBClient.singleUse = false -- set to true to block crashed planes
-- NOTE: singleUse (true) requires SSB to disable immediate respawn after kick -- NOTE: singleUse (true) requires SSB to disable immediate respawn after kick
@ -8,76 +8,16 @@ cfxSSBClient.reUseAfter = -1 -- seconds for re-use delay
cfxSSBClient.requiredLibs = { cfxSSBClient.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
"cfxGroups", -- for slot access "cfxMX", --"cfxGroups", -- for slot access
"cfxZones", -- Zones, of course "cfxZones", -- Zones, of course
} }
--[[-- --[[--
Version History Version History
1.0.0 - initial version 4.0.0 - dmlZones
1.1.0 - detect airfield by action and location, not group name - cfxMX instead of cfxGroups
1.1.1 - performance tuning. only read player groups once
- and remove in-air-start groups from scan. this requires
- ssb (server) be not modified
1.2.0 - API to close airfields: invoke openAirFieldNamed()
and closeAirfieldNamed() with name as string (exact match required)
to block an airfield for any player aircraft.
Works for FARPS as well
API to associate a player group with any airfied's status (nil for unbind):
cfxSSBClient.bindGroupToAirfield(group, airfieldName)
API shortcut to unbind groups: cfxSSBClient.unbindGroup(group)
verbose messages now identify better: "+++SSB:"
keepInAirGroups option
2.0.0 - include single-use ability: crashed airplanes are blocked from further use
- single-use can be turned off
- getPlayerGroupForGroupNamed()
- split setSlotAccess to single accessor
and interator
- reUseAfter option for single-use
- dcsCommon, cfxZones import
2.0.1 - stricter verbosity: moved more comments to verbose only
2.0.2 - health check code (initial)
- added verbosity
2.0.3 - getPlayerName nil-trap on cloned player planes guard
in onEvent
2.1.0 - slotState
- persistence
3.0.0 - closing an airfield will not kick players who are active
- much better verbosity
- open?
- close?
- also persists closed airfield list
3.0.1 - ability to detect if an airfield doesn't exist (late activate)
WHAT IT IS
SSB Client is a small script that forms the client-side counterpart to
Ciribob's simple slot block. It will block slots for all client airframes
that are on an airfield that does not belong to the faction that currently
owns the airfield.
REQUIRES CIRIBOB's SIMPLE SLOT BLOCK (SSB) TO RUN ON THE SERVER
If run without SSB, your planes will not be blocked.
In order to work, a plane that should be blocked when the airfield or
FARP doesn't belong to the player's faction, the group's first unit
must be within 3000 meters of the airfield and on the ground.
Previous versions of this script relied on group names. No longer.
WARNING:
If you modified ssb's flag values, this script will not work
YOU DO NOT NEED TO ACTIVATE SBB, THIS SCRIPT DOES SO AUTOMAGICALLY
--]]-- --]]--
-- below value for enabled MUST BE THE SAME AS THE VALUE OF THE SAME NAME
-- IN SSB. DEFAULT IS ZERO, AND THIS WILL WORK
cfxSSBClient.enabledFlagValue = 0 -- DO NOT CHANGE, MUST MATCH SSB cfxSSBClient.enabledFlagValue = 0 -- DO NOT CHANGE, MUST MATCH SSB
cfxSSBClient.disabledFlagValue = cfxSSBClient.enabledFlagValue + 100 -- DO NOT CHANGE cfxSSBClient.disabledFlagValue = cfxSSBClient.enabledFlagValue + 100 -- DO NOT CHANGE
cfxSSBClient.allowNeutralFields = false -- set to FALSE if players can't spawn on neutral airfields cfxSSBClient.allowNeutralFields = false -- set to FALSE if players can't spawn on neutral airfields
@ -124,26 +64,26 @@ end
-- read client zones -- read client zones
-- --
function cfxSSBClient.createClientZone(theZone) function cfxSSBClient.createClientZone(theZone)
local thePoint = cfxZones.getPoint(theZone) local thePoint = theZone:getPoint()
local theAF = cfxSSBClient.getClosestAirbaseTo(thePoint) local theAF = cfxSSBClient.getClosestAirbaseTo(thePoint)
local afName = theAF:getName() local afName = theAF:getName()
if cfxSSBClient.verbose or theZone.verbose then if cfxSSBClient.verbose or theZone.verbose then
trigger.action.outText("+++ssbc: zone <" .. theZone.name .. "> linked to AF/FARP <" .. afName .. ">", 30) trigger.action.outText("+++ssbc: zone <" .. theZone.name .. "> linked to AF/FARP <" .. afName .. ">", 30)
end end
theZone.afName = afName theZone.afName = afName
theZone.ssbTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "ssbTriggerMethod", "change") theZone.ssbTriggerMethod = theZone:getStringFromZoneProperty( "ssbTriggerMethod", "change")
if cfxZones.hasProperty(theZone, "open?") then if theZone:hasProperty("open?") then
theZone.ssbOpen = cfxZones.getStringFromZoneProperty(theZone, "open?", "none") theZone.ssbOpen = theZone:getStringFromZoneProperty("open?", "none")
theZone.lastSsbOpen = cfxZones.getFlagValue(theZone.ssbOpen, theZone) theZone.lastSsbOpen = cfxZones.getFlagValue(theZone.ssbOpen, theZone)
end end
if cfxZones.hasProperty(theZone, "close?") then if theZone:hasProperty("close?") then
theZone.ssbClose = cfxZones.getStringFromZoneProperty(theZone, "close?", "none") theZone.ssbClose = theZone:getStringFromZoneProperty("close?", "none")
theZone.lastSsbClose = cfxZones.getFlagValue(theZone.ssbClose, theZone) theZone.lastSsbClose = cfxZones.getFlagValue(theZone.ssbClose, theZone)
end end
theZone.ssbOpenOnStart = cfxZones.getBoolFromZoneProperty(theZone, "openOnStart", true) theZone.ssbOpenOnStart = theZone:getBoolFromZoneProperty( "openOnStart", true)
if not theZone.ssbOpenOnStart then if not theZone.ssbOpenOnStart then
cfxSSBClient.closeAirfieldNamed(theZone.afName) cfxSSBClient.closeAirfieldNamed(theZone.afName)
end end
@ -494,7 +434,7 @@ end
-- pre-process static player data to minimize -- pre-process static player data to minimize
-- processor load on checks -- processor load on checks
function cfxSSBClient.processPlayerData() function cfxSSBClient.processPlayerData()
cfxSSBClient.playerGroups = cfxGroups.getPlayerGroup() cfxSSBClient.playerGroups = cfxMX.getPlayerGroup()
local pGroups = cfxSSBClient.playerGroups local pGroups = cfxSSBClient.playerGroups
local filteredPlayers = {} local filteredPlayers = {}
for idx, theGroup in pairs(pGroups) do for idx, theGroup in pairs(pGroups) do
@ -511,7 +451,7 @@ end
-- add airfield information to each player group -- add airfield information to each player group
function cfxSSBClient.processGroupData() function cfxSSBClient.processGroupData()
local pGroups = cfxGroups.getPlayerGroup() -- we want the group.name attribute local pGroups = cfxMX.getPlayerGroup() -- we want the group.name attribute
for idx, theGroup in pairs(pGroups) do for idx, theGroup in pairs(pGroups) do
-- we always use the first player's plane as referenced -- we always use the first player's plane as referenced
local playerData = theGroup.playerUnits[1] local playerData = theGroup.playerUnits[1]

View File

@ -16,6 +16,7 @@ asw.fixes = {} -- all subs that we have a fix on. indexed by sub name
Version History Version History
1.0.0 - initial version 1.0.0 - initial version
1.0.1 - integration with playerScore 1.0.1 - integration with playerScore
1.0.2 - new useSmoke attribute
--]]-- --]]--
@ -151,7 +152,9 @@ function asw.dropBuoyFrom(theUnit)
local theBuoy = asw.createBuoyForUnit(theUnit) local theBuoy = asw.createBuoyForUnit(theUnit)
-- mark point -- mark point
if asw.useSmoke then
dcsCommon.markPointWithSmoke(theBuoy.point, theBuoy.smokeColor) dcsCommon.markPointWithSmoke(theBuoy.point, theBuoy.smokeColor)
end
theBuoy.smokeTimer = now + 5 * 60 theBuoy.smokeTimer = now + 5 * 60
-- add buoy to my inventory -- add buoy to my inventory
@ -202,7 +205,9 @@ function asw.dropBuoyFromZone(theZone)
local theBuoy = asw.createBuoyForZone(theZone) local theBuoy = asw.createBuoyForZone(theZone)
-- mark point -- mark point
if asw.useSmoke then
dcsCommon.markPointWithSmoke(theBuoy.point, theBuoy.smokeColor) dcsCommon.markPointWithSmoke(theBuoy.point, theBuoy.smokeColor)
end
theBuoy.smokeTimer = now + 5 * 60 theBuoy.smokeTimer = now + 5 * 60
-- add buoy to my inventory -- add buoy to my inventory
@ -369,7 +374,9 @@ function asw.updateBuoy(theBuoy, allSubs)
-- see if we need to resmoke -- see if we need to resmoke
if now > theBuoy.smokeTimer then if now > theBuoy.smokeTimer then
--env.info(" resmoking buoy, continue") --env.info(" resmoking buoy, continue")
if asw.useSmoke then
dcsCommon.markPointWithSmoke(theBuoy.point, theBuoy.smokeColor) dcsCommon.markPointWithSmoke(theBuoy.point, theBuoy.smokeColor)
end
theBuoy.smokeTimer = now + 5 * 60 theBuoy.smokeTimer = now + 5 * 60
--env.info(" resmoke done, continue") --env.info(" resmoke done, continue")
end end
@ -1042,6 +1049,7 @@ function asw.readConfigZone()
asw.smokeColor = cfxZones.getSmokeColorStringFromZoneProperty(theZone, "smokeColor", "red") asw.smokeColor = cfxZones.getSmokeColorStringFromZoneProperty(theZone, "smokeColor", "red")
asw.smokeColor = dcsCommon.smokeColor2Num(asw.smokeColor) asw.smokeColor = dcsCommon.smokeColor2Num(asw.smokeColor)
asw.useSmoke = theZone:getBoolFromZoneProperty("useSmoke", true)
asw.killScore = cfxZones.getNumberFromZoneProperty(theZone, "killScore", 0) asw.killScore = cfxZones.getNumberFromZoneProperty(theZone, "killScore", 0)

View File

@ -1,5 +1,5 @@
autoCSAR = {} autoCSAR = {}
autoCSAR.version = "2.0.1" autoCSAR.version = "2.1.0"
autoCSAR.requiredLibs = { autoCSAR.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
"cfxZones", -- Zones, of course "cfxZones", -- Zones, of course
@ -16,6 +16,7 @@ autoCSAR.trackedEjects = {} -- we start tracking on eject
2.0.0 - OOP, code clean-up 2.0.0 - OOP, code clean-up
2.0.1 - fix for coalition change when ejected player changes coas or is forced to neutral 2.0.1 - fix for coalition change when ejected player changes coas or is forced to neutral
- GC - GC
2.1.0 - persistence support
--]]-- --]]--
function autoCSAR.removeGuy(args) function autoCSAR.removeGuy(args)
@ -195,6 +196,33 @@ function autoCSAR.GC()
autoCSAR.pilotInfo = filtered autoCSAR.pilotInfo = filtered
end end
--
-- load/save
--
function autoCSAR.saveData()
local theData = {}
theData.counter = autoCSAR.counter
return theData, autoCSAR.sharedData
end
function autoCSAR.loadData()
if not persistence then return end
local theData = persistence.getSavedDataForModule("autoCSAR", autoCSAR.sharedData)
if not theData then
if autoCSAR.verbose then
trigger.action.outText("+++autoCSAR: no save data received, skipping.", 30)
end
return
end
if theData.counter then
autoCSAR.counter = theData.counter
end
end
--
-- GO!
--
function autoCSAR.start() function autoCSAR.start()
-- lib check -- lib check
if not dcsCommon.libCheck then if not dcsCommon.libCheck then
@ -211,6 +239,16 @@ function autoCSAR.start()
-- connect event handler -- connect event handler
world.addEventHandler(autoCSAR) world.addEventHandler(autoCSAR)
-- do persistence
if persistence then
-- sign up for persistence
callbacks = {}
callbacks.persistData = autoCSAR.saveData
persistence.registerModule("autoCSAR", callbacks)
-- now load my data
autoCSAR.loadData()
end
-- start GC -- start GC
timer.scheduleFunction(autoCSAR.GC, {}, timer.getTime() + 1) timer.scheduleFunction(autoCSAR.GC, {}, timer.getTime() + 1)

View File

@ -1,10 +1,10 @@
bank = {} bank = {}
bank.version = "0.0.0" bank.version = "1.0.0"
bank.requiredLibs = { bank.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
"cfxZones", -- Zones, of course "cfxZones", -- Zones, of course
} }
bank.acts = {} bank.acts = {} -- 'accounts'
function bank.addFunds(act, amt) function bank.addFunds(act, amt)
if not act then act = "!!NIL!!" end if not act then act = "!!NIL!!" end
@ -57,9 +57,9 @@ function bank.getBalance(act)
return true, curVal return true, curVal
end end
function bank.openAccount(act, amount) function bank.openAccount(act, amount, oride)
if not amount then amount = 0 end if not amount then amount = 0 end
if bank.acts[act] then return false end -- account exists if bank.acts[act] and not oride then return false end -- account exists
bank.acts[act] = amount bank.acts[act] = amount
return true return true
end end
@ -79,9 +79,43 @@ function bank.readConfigZone()
bank.acts["blue"] = bank.blue bank.acts["blue"] = bank.blue
bank.acts["neutral"] = bank.neutral bank.acts["neutral"] = bank.neutral
if theZone:hasProperty("sharedData") then -- future-proof
bank.sharedData = theZone:getStringFromZoneProperty("sharedData", "cfxNameMissing")
end
bank.verbose = theZone.verbose bank.verbose = theZone.verbose
end end
--
-- load / save (persistence)
--
function bank.saveData()
local theData = {}
-- save current score list. simple clone
local acts = dcsCommon.clone(bank.acts)
theData.acts = acts
return theData, bank.sharedData
end
function bank.loadData()
if not persistence then return end
local theData = persistence.getSavedDataForModule("bank", bank.sharedData)
if not theData then
if bank.verbose then
trigger.action.outText("+++bank: no save data received, skipping.", 30)
end
return
end
local acts = theData.acts
bank.acts = acts
end
--
-- start
--
function bank.start() function bank.start()
-- lib check -- lib check
if not dcsCommon.libCheck then if not dcsCommon.libCheck then
@ -95,6 +129,17 @@ function bank.start()
-- read config -- read config
bank.readConfigZone() bank.readConfigZone()
-- load data if persisted
if persistence then
-- sign up for persistence
callbacks = {}
callbacks.persistData = bank.saveData
persistence.registerModule("bank", callbacks)
-- now load my data
bank.loadData()
end
trigger.action.outText("bank v" .. bank.version .. " started.", 30) trigger.action.outText("bank v" .. bank.version .. " started.", 30)
return true return true
end end

View File

@ -1,5 +1,5 @@
bombRange = {} bombRange = {}
bombRange.version = "1.1.1" bombRange.version = "1.1.2"
bombRange.dh = 1 -- meters above ground level burst bombRange.dh = 1 -- meters above ground level burst
bombRange.requiredLibs = { bombRange.requiredLibs = {
@ -20,6 +20,8 @@ VERSION HISTORY
also sampling kill events also sampling kill events
1.1.1 - fixed reading smoke color for zone 1.1.1 - fixed reading smoke color for zone
minor clean-up minor clean-up
1.1.2 - corrected bug when no bomb range is detected
--]]-- --]]--
bombRange.bombs = {} -- live tracking bombRange.bombs = {} -- live tracking
bombRange.collector = {} -- post-impact collections for 0.5 secs bombRange.collector = {} -- post-impact collections for 0.5 secs
@ -492,7 +494,7 @@ function bombRange.impacted(weapon, target, finalPass)
-- see if inside a range -- see if inside a range
if #bombRange.ranges < 1 then if #bombRange.ranges < 1 then
trigger.action.outText("+++bRng: No Bomb Ranges detected!") trigger.action.outText("+++bRng: No Bomb Ranges detected!", 30)
return -- no need to update anything return -- no need to update anything
end end
local minDist = math.huge local minDist = math.huge

View File

@ -1,15 +1,27 @@
camp = {} camp = {}
camp.ups = 1 camp.ups = 1
camp.version = "0.0.0" camp.version = "1.0.2"
camp.requiredLibs = { camp.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
"cfxZones", -- Zones, of course "cfxZones", -- Zones, of course
"cfxMX", "cfxMX",
"bank" "bank"
} }
-- AUTOMATICALLY INTEGRATES WITH income MODULE IF PRESENT
-- REQUIRES CLONEZONES TO RUN (BUT NOT TO START)
--[[--
VERSION HISTORY
1.0.0 - initial version
1.0.1 - changed "Ground Repairs / Upgrades" to "Funds / Repairs / Upgrades"
- provided income info for camp if it exists
- provide income total if exists
- actionSound
- output sound with communications
1.0.2 - integration with FARPZones
--]]--
-- --
-- CURRENTLY REQUIRES SINGLE-UNIT PLAYER GROUPS -- CURRENTLY REQUIRES SINGLE-UNIT PLAYER GROUPS
-- REQUIRES CLONEZONES MODULE TO BE RUNNING, BUT NOT TO BE LOADED ON START
-- --
camp.camps = {} -- all camps on the map camp.camps = {} -- all camps on the map
camp.roots = {} -- all player group comms roots camp.roots = {} -- all player group comms roots
@ -18,16 +30,27 @@ function camp.addCamp(theZone)
camp.camps[theZone.name] = theZone camp.camps[theZone.name] = theZone
end end
function camp.getMyCurrentCamp(theUnit) -- returns first hit plaayer is in function camp.getMyCurrentCamp(theUnit) -- returns first hit player is in
local coa = theUnit:getCoalition()
local p = theUnit:getPoint() local p = theUnit:getPoint()
for idx, theCamp in pairs(camp.camps) do for idx, theCamp in pairs(camp.camps) do
if theCamp:pointInZone(p) then if theCamp.owner == coa and theCamp:pointInZone(p) then
return theCamp return theCamp
end end
end end
return nil return nil
end end
function camp.getCampsForCoa(coa)
local myCamps = {}
for idx, theCamp in pairs(camp.camps) do
if theCamp.owner == coa then
table.insert(myCamps, theCamp)
end
end
return myCamps
end
function camp.createCampWithZone(theZone) function camp.createCampWithZone(theZone)
-- look for all cloners inside my zone -- look for all cloners inside my zone
if theZone.verbose or camp.verbose then if theZone.verbose or camp.verbose then
@ -70,6 +93,12 @@ function camp.createCampWithZone(theZone)
theZone.upgradable = theZone:getBoolFromZoneProperty("upgrade", true) theZone.upgradable = theZone:getBoolFromZoneProperty("upgrade", true)
theZone.repairCost = theZone:getNumberFromZoneProperty("repairCost", 100) theZone.repairCost = theZone:getNumberFromZoneProperty("repairCost", 100)
theZone.upgradeCost = theZone:getNumberFromZoneProperty("upgradeCost", 3 * theZone.repairCost) theZone.upgradeCost = theZone:getNumberFromZoneProperty("upgradeCost", 3 * theZone.repairCost)
if theZone:hasProperty("FARP") then
theZone.isAlsoFARP = true
if theZone.verbose or camp.verbose then
trigger.action.outText("+++camp: <" .. theZone.name .. "> has FARP attached", 30)
end
end
end end
-- --
@ -93,7 +122,7 @@ function camp.processPlayers()
for idx, gData in pairs(cfxMX.playerGroupByName) do for idx, gData in pairs(cfxMX.playerGroupByName) do
gID = gData.groupId gID = gData.groupId
gName = gData.name gName = gData.name
local theRoot = missionCommands.addSubMenuForGroup(gID, "Ground Repairs / Upgrades") local theRoot = missionCommands.addSubMenuForGroup(gID, "Funds / Repairs / Upgrades")
camp.roots[gName] = theRoot camp.roots[gName] = theRoot
local c00 = missionCommands.addCommandForGroup(gID, "Theatre Overview", theRoot, camp.redirectTFunds, {gName, gID, "tfunds"}) local c00 = missionCommands.addCommandForGroup(gID, "Theatre Overview", theRoot, camp.redirectTFunds, {gName, gID, "tfunds"})
local c0 = missionCommands.addCommandForGroup(gID, "Local Funds & Status Overview", theRoot, camp.redirectFunds, {gName, gID, "funds"}) local c0 = missionCommands.addCommandForGroup(gID, "Local Funds & Status Overview", theRoot, camp.redirectFunds, {gName, gID, "funds"})
@ -126,11 +155,15 @@ function camp.doTFunds(args)
local hasBalance, amount = bank.getBalance(coa) local hasBalance, amount = bank.getBalance(coa)
if not hasBalance then return end if not hasBalance then return end
local msg = "\nYour faction currently has §" .. amount .. " available for repairs/upgrades.\n" local msg = "\nYour faction currently has §" .. amount .. " available for repairs/upgrades.\n"
local income = 0
-- now iterate all camps that are on my side -- now iterate all camps that are on my side
for idx, theZone in pairs(camp.camps) do for idx, theZone in pairs(camp.camps) do
if theZone.owner == coa then if theZone.owner == coa then
msg = msg .. "\n - <" .. theZone.name .. ">" msg = msg .. "\n - <" .. theZone.name .. ">"
if theZone.income then
msg = msg .. " Income: §" .. theZone.income
income = income + theZone.income
end
if theZone.repairable and theZone.upgradable then if theZone.repairable and theZone.upgradable then
msg = msg .. "" .. theZone.repairCost .. "" .. theZone.upgradeCost .. ")" msg = msg .. "" .. theZone.repairCost .. "" .. theZone.upgradeCost .. ")"
@ -165,8 +198,12 @@ function camp.doTFunds(args)
end end
end end
end end
if income > 0 then
msg = msg .. "\n\nTotal Income: §" .. income
end
msg = msg .. "\n" msg = msg .. "\n"
trigger.action.outTextForGroup(gID, msg, 30) trigger.action.outTextForGroup(gID, msg, 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
end end
function camp.doFunds(args) function camp.doFunds(args)
@ -183,11 +220,13 @@ function camp.doFunds(args)
if not Unit.isExist(theUnit) or theUnit:getLife() < 1 or if not Unit.isExist(theUnit) or theUnit:getLife() < 1 or
theUnit:inAir() or dcsCommon.getUnitSpeed(theUnit) > 1 then theUnit:inAir() or dcsCommon.getUnitSpeed(theUnit) > 1 then
trigger.action.outTextForGroup(gID, msg, 30) trigger.action.outTextForGroup(gID, msg, 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
return return
end end
local theZone = camp.getMyCurrentCamp(theUnit) local theZone = camp.getMyCurrentCamp(theUnit)
if not theZone or (not theZone.repairable) or theZone.owner ~= theUnit:getCoalition() then if not theZone or (not theZone.repairable) or theZone.owner ~= theUnit:getCoalition() then
trigger.action.outTextForGroup(gID, msg, 30) trigger.action.outTextForGroup(gID, msg, 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
return return
end end
@ -204,17 +243,30 @@ function camp.doFunds(args)
msg = msg .. "\nZone <" .. theZone.name .. "> is fully upgraded.\n" msg = msg .. "\nZone <" .. theZone.name .. "> is fully upgraded.\n"
end end
trigger.action.outTextForGroup(gID, msg, 30) trigger.action.outTextForGroup(gID, msg, 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
end end
-- --
-- REPAIRS -- REPAIRS
-- --
function camp.zoneNeedsRepairs(theZone, coa) function camp.zoneNeedsRepairs(theZone, coa)
-- return true if this zone needs repairs, i.e. it has cloners that have a damaged clone set -- return true if this zone needs repairs, i.e. it has cloners that have a damaged clone set or FARP resource vehicles are incomplete
if theZone.isAlsoFARP and FARPZones then
local theFarp = FARPZones.getFARPForZone(theZone)
if FARPZones.serviceNeedsRepair(theFarp) then
if theZone.verbose or camp.verbose then
trigger.action.outText("camp: <" .. theZone.name .. "> has FARP service is dinged up...", 30)
end
return true
-- WARNING: RETURNS BOOLEAN, not a dmlZone!
end
end
local myCloners = theZone.cloners local myCloners = theZone.cloners
if not coa then if not coa then
trigger.action.outText("+++camp: warning: no coa on zoneNeedsRepair for zone <" .. theZone.name .. ">", 30) trigger.action.outText("+++camp: warning: no coa on zoneNeedsRepairs for zone <" .. theZone.name .. ">", 30)
elseif coa == 1 then elseif coa == 1 then
myCloners = theZone.redCloners myCloners = theZone.redCloners
elseif coa == 2 then elseif coa == 2 then
@ -251,19 +303,23 @@ function camp.doRepairs(args)
if theUnit:getLife() < 1 then return end if theUnit:getLife() < 1 then return end
if theUnit:inAir() then if theUnit:inAir() then
trigger.action.outTextForGroup(gID, "\nPlease land inside a fortified zone to order repairs\n", 30) trigger.action.outTextForGroup(gID, "\nPlease land inside a fortified zone to order repairs\n", 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
return return
end end
if dcsCommon.getUnitSpeed(theUnit) > 1 then if dcsCommon.getUnitSpeed(theUnit) > 1 then
trigger.action.outTextForGroup(gID, "\nYou must come to a complete stop before being able to order repairs\n", 30) trigger.action.outTextForGroup(gID, "\nYou must come to a complete stop before being able to order repairs\n", 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
return return
end end
local theZone = camp.getMyCurrentCamp(theUnit) local theZone = camp.getMyCurrentCamp(theUnit)
if not theZone or not theZone.repairable then if not theZone or not theZone.repairable then
trigger.action.outTextForGroup(gID, "\nYou are not inside a zone that can be repaired.\n", 30) trigger.action.outTextForGroup(gID, "\nYou are not inside a zone that can be repaired.\n", 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
return return
end end
if theZone.owner ~= theUnit:getCoalition() then if theZone.owner ~= theUnit:getCoalition() then
trigger.action.outTextForGroup(gID, "\nYou currently do not own zone <" .. theZone.name .. ">. Capture it first.\n", 30) trigger.action.outTextForGroup(gID, "\nYou currently do not own zone <" .. theZone.name .. ">. Capture it first.\n", 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
return return
end end
@ -274,6 +330,7 @@ function camp.doRepairs(args)
msg = msg .. "\nZone <" .. theZone.name .. "> can be upgraded.\n" msg = msg .. "\nZone <" .. theZone.name .. "> can be upgraded.\n"
end end
trigger.action.outTextForGroup(gID, msg, 30) trigger.action.outTextForGroup(gID, msg, 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
return return
end end
@ -285,7 +342,9 @@ function camp.doRepairs(args)
end end
if amount < theZone.repairCost then if amount < theZone.repairCost then
trigger.action.outTextForGroup(gID, "\nYou curently cannot afford repairs here\n", 30) -- trigger.action.outTextForGroup(gID, "\nYou curently cannot afford repairs here\n", 30)
trigger.action.outTextForGroup(gID, "\nYou curently cannot afford repairs here (§" .. theZone.repairCost .. " required, you have §" .. amount .. ")\n", 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
return return
end end
@ -296,13 +355,23 @@ function camp.doRepairs(args)
-- cloneZones.spawnWithCloner(theCloner) -- cloneZones.spawnWithCloner(theCloner)
bank.withdawFunds(coa, theZone.repairCost) bank.withdawFunds(coa, theZone.repairCost)
local ignore, remain = bank.getBalance(coa) local ignore, remain = bank.getBalance(coa)
trigger.action.outTextForCoalition(coa, "\nZone <" .. theZone.name .. "> was repaired by <" .. pName .. trigger.action.outTextForCoalition(coa, "\nZone <" .. theZone.name .. "> was ordered repaired by <" .. pName ..
"> for §" .. theZone.repairCost .. ".\nFaction has §" .. remain .. " remaining funds.\n", 30) "> for §" .. theZone.repairCost .. ".\nFaction has §" .. remain .. " remaining funds.\n", 30)
trigger.action.outSoundForCoalition(coa, camp.actionSound)
end end
function camp.repairZone(theZone, coa) function camp.repairZone(theZone, coa)
theCloner = camp.zoneNeedsRepairs(theZone, coa) theCloner = camp.zoneNeedsRepairs(theZone, coa)
if not theCloner then return end if not theCloner then return end
if type(theCloner) == "boolean" then -- at least farp was dinged up
local theFarp = FARPZones.getFARPForZone(theZone)
FARPZones.produceResourceVehicles(theFarp, coa)
if theZone.verbose or camp.verbose then
trigger.action.outText("+++camp: repaired FARP in camp <" .. theZone.name .. ">", 30)
end
end
theCloner = camp.zoneNeedsRepairs(theZone, coa) -- do again to see if other repairs are needed. FARP repairs come free with first fix
if not theCloner then return end
cloneZones.despawnAll(theCloner) cloneZones.despawnAll(theCloner)
cloneZones.spawnWithCloner(theCloner) cloneZones.spawnWithCloner(theCloner)
end end
@ -311,7 +380,7 @@ end
-- --
function camp.zoneNeedsUpgrades(theZone, coa) function camp.zoneNeedsUpgrades(theZone, coa)
-- return true if this zone can be upgraded, i.e. it has cloners that have an empty clone set -- returns first cloner in this zone that can be upgraded, i.e. it has cloners that have an empty clone set
if not theZone.upgradable then return nil end if not theZone.upgradable then return nil end
local myCloners = theZone.cloners local myCloners = theZone.cloners
@ -352,30 +421,36 @@ function camp.doUpgrades(args)
if not pName then pName = "<Big Err>" end if not pName then pName = "<Big Err>" end
if theUnit:inAir() then if theUnit:inAir() then
trigger.action.outTextForGroup(gID, "\nPlease land inside a fortified zone to order upgrades.\n", 30) trigger.action.outTextForGroup(gID, "\nPlease land inside a fortified zone to order upgrades.\n", 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
return return
end end
if dcsCommon.getUnitSpeed(theUnit) > 1 then if dcsCommon.getUnitSpeed(theUnit) > 1 then
trigger.action.outTextForGroup(gID, "\nYou must come to a complete stop before being able to order upgrades\n", 30) trigger.action.outTextForGroup(gID, "\nYou must come to a complete stop before being able to order upgrades\n", 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
return return
end end
local theZone = camp.getMyCurrentCamp(theUnit) local theZone = camp.getMyCurrentCamp(theUnit)
if not theZone or not theZone.upgradable then if not theZone or not theZone.upgradable then
trigger.action.outTextForGroup(gID, "\nYou are not inside a zone that can be upgraded.\n", 30) trigger.action.outTextForGroup(gID, "\nYou are not inside a zone that can be upgraded.\n", 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
return return
end end
if theZone.owner ~= theUnit:getCoalition() then if theZone.owner ~= theUnit:getCoalition() then
trigger.action.outTextForGroup(gID, "\nYou currently do not own zone <" .. theZone.name .. ">. Capture it first.\n", 30) trigger.action.outTextForGroup(gID, "\nYou currently do not own zone <" .. theZone.name .. ">. Capture it first.\n", 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
return return
end end
if camp.zoneNeedsRepairs(theZone, coa) then if camp.zoneNeedsRepairs(theZone, coa) then
trigger.action.outTextForGroup(gID, "\nZone <" .. theZone.name .. "> requires repairs before it can be upgraded.\n", 30) trigger.action.outTextForGroup(gID, "\nZone <" .. theZone.name .. "> requires repairs before it can be upgraded.\n", 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
return return
end end
-- if we get here, we are inside a zone that can be upgraded. see if it needs upgrades and then get upgrade cost and see if we have enough fund to do it -- if we get here, we are inside a zone that can be upgraded. see if it needs upgrades and then get upgrade cost and see if we have enough fund to do it
if not camp.zoneNeedsUpgrades(theZone, coa) then if not camp.zoneNeedsUpgrades(theZone, coa) then
trigger.action.outTextForGroup(gID, "\nZone <" .. theZone.name .. "> has been fully upgraded.\n", 30) trigger.action.outTextForGroup(gID, "\nZone <" .. theZone.name .. "> has been fully upgraded.\n", 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
return return
end end
@ -387,7 +462,8 @@ function camp.doUpgrades(args)
end end
if amount < theZone.upgradeCost then if amount < theZone.upgradeCost then
trigger.action.outTextForGroup(gID, "\nYou curently cannot afford an upgrade here\n", 30) trigger.action.outTextForGroup(gID, "\nYou curently cannot afford an upgrade here (§" .. theZone.upgradeCost .. " required, you have §" .. amount .. ")\n", 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
return return
end end
@ -398,8 +474,9 @@ function camp.doUpgrades(args)
-- bill it to side -- bill it to side
bank.withdawFunds(coa, theZone.upgradeCost) bank.withdawFunds(coa, theZone.upgradeCost)
local ignore, remain = bank.getBalance(coa) local ignore, remain = bank.getBalance(coa)
trigger.action.outTextForCoalition(coa, "\nZone <" .. theZone.name .. "> was upgraded by <" .. pName .. trigger.action.outTextForCoalition(coa, "\nZone <" .. theZone.name .. "> was ordered upgraded by <" .. pName ..
"> for §" .. theZone.upgradeCost .. ".\nFaction has §" .. remain .. " remaining funds.\n", 30) "> for §" .. theZone.upgradeCost .. ".\nFaction has §" .. remain .. " remaining funds.\n", 30)
trigger.action.outSoundForCoalition(coa, camp.actionSound)
end end
-- can be called externally -- can be called externally
@ -408,6 +485,32 @@ function camp.upgradeZone(theZone, coa)
if not theCloner then return end if not theCloner then return end
cloneZones.spawnWithCloner(theCloner) cloneZones.spawnWithCloner(theCloner)
end end
--
-- API
--
function camp.campsThatNeedRepairs(coa) -- returns the zones that need repairs
local repairs = {}
for idx, theZone in pairs(camp.camps) do
if theZone.repairable and theZone.owner == coa and camp.zoneNeedsRepairs(theZone, coa) then
table.insert(repairs, theZone)
end
end
return repairs
end
function camp.campsThatNeedUpgrades(coa) -- returns the zones that can be upgraded
local repairs = {}
for idx, theZone in pairs(camp.camps) do
if theZone.upgradable and theZone.owner == coa and camp.zoneNeedsUpgrades(theZone, coa) then
table.insert(repairs, theZone)
end
end
return repairs
end
-- --
-- Config & Go -- Config & Go
-- --
@ -417,7 +520,7 @@ function camp.readConfigZone()
if not theZone then if not theZone then
theZone = cfxZones.createSimpleZone("campConfig") theZone = cfxZones.createSimpleZone("campConfig")
end end
camp.actionSound = theZone:getStringFromZoneProperty("actionSound", "Quest Snare 3.wav")
camp.verbose = theZone.verbose camp.verbose = theZone.verbose
end end

View File

@ -1,5 +1,5 @@
cfxZones = {} cfxZones = {}
cfxZones.version = "4.3.1" cfxZones.version = "4.3.2"
-- cf/x zone management module -- cf/x zone management module
-- reads dcs zones and makes them accessible and mutable -- reads dcs zones and makes them accessible and mutable
@ -50,6 +50,8 @@ cfxZones.version = "4.3.1"
- randomDelayFromPositiveRange also allows 0 - randomDelayFromPositiveRange also allows 0
- 4.3.1 - new drawText() for zones - 4.3.1 - new drawText() for zones
- dmlZones:getClosestZone() bridge - dmlZones:getClosestZone() bridge
- 4.3.2 - new getListFromZoneProperty()
--]]-- --]]--
-- --
@ -2097,7 +2099,7 @@ end
function cfxZones.drawZone(theZone, lineColor, fillColor, markID) function cfxZones.drawZone(theZone, lineColor, fillColor, markID)
if not theZone then return 0 end if not theZone then return 0 end
if not lineColor then lineColor = {0.8, 0.8, 0.8, 1.0} end if not lineColor then lineColor = {0.8, 0.8, 0.8, 1.0} end
if not fillColor then fillColor = {0.8, 0.8, 0.8, 0.2} end if not fillColor then fillColor = {0.8, 0.8, 0.8, 0.0} end
if not markID then markID = dcsCommon.numberUUID() end if not markID then markID = dcsCommon.numberUUID() end
if theZone.isCircle then if theZone.isCircle then
@ -2118,7 +2120,7 @@ function cfxZones.drawText(theZone, theText, fSize, lineColor, fillColor)
if not theZone then return end if not theZone then return end
if not fSize then fSize = 12 end if not fSize then fSize = 12 end
if not lineColor then lineColor = {0.8, 0.8, 0.8, 1.0} end if not lineColor then lineColor = {0.8, 0.8, 0.8, 1.0} end
if not fillColor then fillColor = lineColor end if not fillColor then fillColor = {0, 0, 0, 0} end
local markID = dcsCommon.numberUUID() local markID = dcsCommon.numberUUID()
local p = theZone:getPoint() local p = theZone:getPoint()
local offset = {x = p.x, y = 0, z = p.z} local offset = {x = p.x, y = 0, z = p.z}
@ -2322,6 +2324,24 @@ function dmlZone:getPositiveRangeFromZoneProperty(theProperty, default, defaultm
return lo, up return lo, up
end end
function cfxZones.getListFromZoneProperty(theZone, theProperty, defaultItem) -- comma delimited
if not defaultItem then defaultItem = "default" end
local theString = theZone:getStringFromZoneProperty(theProperty, defaultItem)
if dcsCommon.containsString(theString, ",") then
local theArray = dcsCommon.splitString(theString, ',')
theArray = dcsCommon.trimArray(theArray)
return theArray
else
return {theString}
end
return nil
end
function dmlZone:getListFromZoneProperty(theProperty, defaultItem)
return cfxZones.getListFromZoneProperty(self, theProperty, defaultItem)
end
function cfxZones.hasProperty(theZone, theProperty) function cfxZones.hasProperty(theZone, theProperty)
if not theProperty then if not theProperty then

View File

@ -1,5 +1,5 @@
cloneZones = {} cloneZones = {}
cloneZones.version = "2.2.0" cloneZones.version = "2.2.1"
cloneZones.verbose = false cloneZones.verbose = false
cloneZones.requiredLibs = { cloneZones.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
@ -50,6 +50,8 @@ cloneZones.respawnOnGroupID = true
- damaged! output - damaged! output
- health# output - health# output
- persistence: persist oSize and set lastSize - persistence: persist oSize and set lastSize
2.2.1 - verbosity updates for post-check
- if cloned group is late activation, turn it off
--]]-- --]]--
-- --
@ -1054,7 +1056,12 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
local dataToSpawn = {} -- temp save so we can connect in-group references local dataToSpawn = {} -- temp save so we can connect in-group references
for idx, aGroupName in pairs(theZone.cloneNames) do for idx, aGroupName in pairs(theZone.cloneNames) do
local rawData, cat, ctry = cfxMX.getGroupFromDCSbyName(aGroupName) local rawData, cat, ctry = cfxMX.getGroupFromDCSbyName(aGroupName) -- fetches a clone!
-- sanity checks: lateActivation etc
if rawData.lateActivation then
trigger.action.outText("+++clnZ: WARNING - clone group <" .. rawData.name .. "> in cloner <" .. theZone.name .. "> is set to 'late activation'. Ignored.", 30)
rawData.lateActivation = false
end
rawData.CZorigName = rawData.name -- save original group name rawData.CZorigName = rawData.name -- save original group name
local origID = rawData.groupId -- save original group ID local origID = rawData.groupId -- save original group ID
rawData.CZorigID = origID rawData.CZorigID = origID
@ -1287,9 +1294,11 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
end end
cloneZones.unitXlate[aUnit.CZorigID] = uID cloneZones.unitXlate[aUnit.CZorigID] = uID
else else
if spawnZone.verbose then
trigger.action.outText("clnZ: post-clone verifiaction failed for unit <" .. uName .. ">: not found", 30) trigger.action.outText("clnZ: post-clone verifiaction failed for unit <" .. uName .. ">: not found", 30)
end end
end end
end
-- check if our assigned ID matches the one handed out by -- check if our assigned ID matches the one handed out by
-- DCS. Mismatches can happen, and are only noted -- DCS. Mismatches can happen, and are only noted

View File

@ -1,5 +1,5 @@
csarManager = {} csarManager = {}
csarManager.version = "3.2.7" csarManager.version = "3.4.0"
csarManager.ups = 1 csarManager.ups = 1
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
@ -42,12 +42,15 @@ csarManager.ups = 1
- useRanks option - useRanks option
3.2.6 - inBuiltup analogon to cloner 3.2.6 - inBuiltup analogon to cloner
3.2.7 - createCSARForParachutist now supports optional coa (autoCSAR) 3.2.7 - createCSARForParachutist now supports optional coa (autoCSAR)
3.3.0 - persistence support
3.4.0 - global timeLimit option in config zone
- fixes expiration bug when persisting data
INTEGRATES AUTOMATICALLY WITH playerScore INTEGRATES AUTOMATICALLY WITH playerScore
INTEGRATES WITH LIMITED AIRFRAMES INTEGRATES WITH LIMITED AIRFRAMES
INTEGRATES AUTOMATICALLY WITH SCRIBE INTEGRATES AUTOMATICALLY WITH SCRIBE
SUPPORTS PERSISTENCE
--]]-- --]]--
-- modules that need to be loaded BEFORE I run -- modules that need to be loaded BEFORE I run
@ -201,10 +204,22 @@ function csarManager.createCSARMissionData(point, theSide, freq, name, numCrew,
newMission.timeStamp = timer.getTime() -- now newMission.timeStamp = timer.getTime() -- now
-- if no time limit given but csarManager's own global dictates it,
-- set it now
if not timeLimit and csarManager.timeLimit then
if csarManager.verbose then
trigger.action.outText("+++csar: setting GLOBAL csar time limit (" .. csarManager.timeLimit[1] .. "," .. csarManager.timeLimit[2] .. ") for new mission " .. name, 30)
end
timeLimit = csarManager.timeLimit
end
-- set timeLimit if enabled -- set timeLimit if enabled
if timeLimit then if timeLimit then
local theLimit = cfxZones.randomDelayFromPositiveRange(timeLimit[1], timeLimit[2]) * 60 local theLimit = cfxZones.randomDelayFromPositiveRange(timeLimit[1], timeLimit[2]) * 60
newMission.expires = timer.getTime() + theLimit newMission.expires = timer.getTime() + theLimit
if csarManager.verbose then
trigger.action.outText("+++csar: setting time limit to expire in (" .. math.floor(theLimit/60) .. ") minutes for mission " .. name, 30)
end
end end
-- update counter and return -- update counter and return
@ -794,8 +809,8 @@ function csarManager.doListCSARRequests(args)
local status = "alive" local status = "alive"
if mission.expires then if mission.expires then
delta = math.floor ((mission.expires - now) / 60) delta = math.floor ((mission.expires - now) / 60)
if delta < 10 then status = "+deteriorating+" end if delta < 30 then status = "+deteriorating+" end
if delta < 5 then status = "*critical*" end if delta < 15 then status = "*critical*" end
if csarManager.verbose then if csarManager.verbose then
status = status .. " [" .. delta .. "]" -- remove me status = status .. " [" .. delta .. "]" -- remove me
end end
@ -841,9 +856,9 @@ function csarManager.doStatusCarrying(args)
report = report .. "\n".. i .. ") " .. evacMission.name report = report .. "\n".. i .. ") " .. evacMission.name
if evacMission.expires then if evacMission.expires then
delta = math.floor ((evacMission.expires - now) / 60) delta = math.floor ((evacMission.expires - now) / 60)
if delta > 20 then if delta > 30 then
report = report .. " is hurt but stable" report = report .. " is hurt and stable"
elseif delta > 10 then elseif delta > 15 then
report = report .. " is badly hurt" report = report .. " is badly hurt"
else else
report = report .. " is in critical condition" -- or 'beat up, but will live' report = report .. " is in critical condition" -- or 'beat up, but will live'
@ -1438,8 +1453,12 @@ function csarManager.readCSARZone(theZone)
-- add to list of startable csar -- add to list of startable csar
if theZone.startCSAR then if theZone.startCSAR then
if persistence and persistence.hasDate then
-- we load data instead of spawning on start
else
csarManager.addCSARZone(theZone) csarManager.addCSARZone(theZone)
end end
end
if (not deferred) then if (not deferred) then
local theMission = csarManager.createCSARMissionFromZone(theZone) local theMission = csarManager.createCSARMissionFromZone(theZone)
@ -1593,11 +1612,87 @@ function csarManager.readConfigZone()
typeArray = dcsCommon.trimArray(typeArray) typeArray = dcsCommon.trimArray(typeArray)
csarManager.rescueTypes = typeArray csarManager.rescueTypes = typeArray
if theZone:hasProperty("timeLimit") then
local tmin, tmax = theZone:getPositiveRangeFromZoneProperty("timeLimit", 1)
csarManager.timeLimit = {tmin, tmax}
else
csarManager.timeLimit = nil
end
if csarManager.verbose then if csarManager.verbose then
trigger.action.outText("+++csar: read config", 30) trigger.action.outText("+++csar: read config", 30)
end end
end end
--
-- Save and Load Data
--
function csarManager.saveData()
local now = timer.getTime()
local theData = {}
local missions = {}
-- gather dater from all currently open missions and
-- place them in a new array
for idx, aMission in pairs(csarManager.openMissions) do
m = {}
m.point = aMission.zone:getPoint()
m.side = aMission.side
m.freq = aMission.freq
m.name = aMission.name
m.score = aMission.score
if aMission.expires then
remains = (aMission.expires - now) / 60 -- limit in minutes!
m.timeLimit = {remains, remains}
end
table.insert(missions, m)
end
theData.missions = missions
theData.missionID = csarManager.missionID
return theData, csarManager.sharedData
end
function csarManager.loadData()
if not persistence then return end
local theData = persistence.getSavedDataForModule("csarManager", csarManager.sharedData)
if not theData then
if csarManager.verbose then
trigger.action.outText("+++csarManager: no save data received, skipping.", 30)
end
return
end
if theData.missionID then
csarManager.missionID = theData.missionID
end
if theData.missions then
for idx, m in pairs(theData.missions) do
-- csarManager.createCSARMissionData(point, theSide, freq, name, numCrew, timeLimit, mapMarker, inRadius, parashootUnit)
if m.timeLimit and csarManager.verbose then
trigger.action.outText("+++csar: loadData - timelimit of [" .. m.timeLimit[1] .. "," .. m.timeLimit[2] .. "] read for csar <" .. m.name .. ">", 30)
end
local theMission = csarManager.createCSARMissionData(
m.point, -- point,
m.side, -- theSide,
m.freq, -- freq,
m.name, -- name,
nil, -- numCrew,
m.timeLimit, -- timeLimit, can be nil or {lower, upper}
nil, --mapMarker,
0.1, -- inRadius,
nil -- parashootUnit)
)
theMission.score = m.score
csarManager.addMission(theMission)
if csarManager.verbose then
trigger.action.outText("+++csarM (persitence): restored csar mission <" .. m.name .. ">", 30)
end
end
end
end
--
-- Start
--
function csarManager.start() function csarManager.start()
-- make sure we have loaded all relevant libraries -- make sure we have loaded all relevant libraries
@ -1624,6 +1719,16 @@ function csarManager.start()
csarManager.setCommsMenu(aUnit) csarManager.setCommsMenu(aUnit)
end end
-- connect to persistence if it exists
if persistence then
-- sign up for persistence
callbacks = {}
callbacks.persistData = csarManager.saveData
persistence.registerModule("csarManager", callbacks)
-- now load my data
csarManager.loadData()
end
-- start updating and track all helicopters in the air against missions -- start updating and track all helicopters in the air against missions
csarManager.update() csarManager.update()

View File

@ -1,5 +1,5 @@
dcsCommon = {} dcsCommon = {}
dcsCommon.version = "3.0.5" dcsCommon.version = "3.0.6"
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
3.0.0 - removed bad bug in stringStartsWith, only relevant if caseSensitive is false 3.0.0 - removed bad bug in stringStartsWith, only relevant if caseSensitive is false
- point2text new intsOnly option - point2text new intsOnly option
@ -18,6 +18,9 @@ dcsCommon.version = "3.0.5"
- new getFirstItem() - new getFirstItem()
- arrayContainsString() can handle dicts - arrayContainsString() can handle dicts
- new pointXpercentYdegOffAB() - new pointXpercentYdegOffAB()
3.0.6 - new arrayContainsStringCaseInsensitive()
3.0.7 - fixed small bug in wildArrayContainsString
--]]-- --]]--
-- dcsCommon is a library of common lua functions -- dcsCommon is a library of common lua functions
@ -2093,7 +2096,7 @@ end
if not caseSensitive then theString = string.upper(theString) end if not caseSensitive then theString = string.upper(theString) end
local wildIn = dcsCommon.stringEndsWith(theString, "*") local wildIn = dcsCommon.stringEndsWith(theString, "*")
if wildIn then dcsCommon.removeEnding(theString, "*") end if wildIn then theString = dcsCommon.removeEnding(theString, "*") end
for idx, theElement in pairs(theArray) do -- i = 1, #theArray do for idx, theElement in pairs(theArray) do -- i = 1, #theArray do
if not caseSensitive then theElement = string.upper(theElement) end if not caseSensitive then theElement = string.upper(theElement) end
local wildEle = dcsCommon.stringEndsWith(theElement, "*") local wildEle = dcsCommon.stringEndsWith(theElement, "*")
@ -2133,6 +2136,19 @@ end
return false return false
end end
function dcsCommon.arrayContainsStringCaseInsensitive(theArray, theString) -- case insensitive
if not theArray then return false end
if not theString then return false end
if type(theArray) ~= "table" then
trigger.action.outText("***arrayContainsStringCI: theArray is not type <table> but <" .. type(theArray) .. ">", 30)
end
theString = string.upper(theString)
for idx, item in pairs(theArray) do
if string.upper(item) == theString then return true end
end
return false
end
function dcsCommon.splitString(inputstr, sep) function dcsCommon.splitString(inputstr, sep)
if sep == nil then if sep == nil then
sep = "%s" sep = "%s"

View File

@ -35,25 +35,40 @@ function income.update()
-- schedule next round -- schedule next round
timer.scheduleFunction(income.update, {}, timer.getTime() + income.interval) timer.scheduleFunction(income.update, {}, timer.getTime() + income.interval)
local neuI, redI, blueI = income.neutral, income.red, income.blue
-- base income -- base income
bank.addFunds(0, income.neutral) -- bank.addFunds(0, income.neutral)
bank.addFunds(1, income.red) -- bank.addFunds(1, income.red)
bank.addFunds(2, income.blue) -- bank.addFunds(2, income.blue)
for idx, theZone in pairs(income.sources) do for idx, theZone in pairs(income.sources) do
bank.addFunds(0, income.getIncomeForZoneAndCoa(theZone, 0)) local ni = income.getIncomeForZoneAndCoa(theZone, 0)
bank.addFunds(1, income.getIncomeForZoneAndCoa(theZone, 1)) local ri = income.getIncomeForZoneAndCoa(theZone, 1)
bank.addFunds(2, income.getIncomeForZoneAndCoa(theZone, 2)) local bi = income.getIncomeForZoneAndCoa(theZone, 2)
redI = redI + ri
blueI = blueI + bi
neuI = neuI + ni
end end
bank.addFunds(0, neuI)
bank.addFunds(1, redI)
bank.addFunds(2, blueI)
if income.announceTicks then if income.announceTicks then
-- trigger.action.outText(income.tickMessage, 30) -- trigger.action.outText(income.tickMessage, 30)
local has, balance = bank.getBalance(0) local has, balance = bank.getBalance(0)
trigger.action.outTextForCoalition(0, "\n" .. income.tickMessage .. "\nNew balance: §" .. balance .. "\n", 30) local tick = string.gsub(income.tickMessage, "<i>", neuI)
trigger.action.outTextForCoalition(0, "\n" .. tick .. "\nNew balance: §" .. balance .. "\n", 30)
has, balance = bank.getBalance(1) has, balance = bank.getBalance(1)
trigger.action.outTextForCoalition(1, "\n" .. income.tickMessage .. "\nNew balance: §" .. balance .. "\n", 30) tick = string.gsub(income.tickMessage, "<i>", redI)
trigger.action.outTextForCoalition(1, "\n" .. tick .. "\nNew balance: §" .. balance .. "\n", 30)
has, balance = bank.getBalance(2) has, balance = bank.getBalance(2)
trigger.action.outTextForCoalition(2, "\n" .. income.tickMessage .. "\nNew balance: §" .. balance .. "\n", 30) tick = string.gsub(income.tickMessage, "<i>", blueI)
trigger.action.outTextForCoalition(2, "\n" .. tick .. "\nNew balance: §" .. balance .. "\n", 30)
end end
end end
@ -71,7 +86,7 @@ function income.readConfigZone()
income.neutral = theZone:getNumberFromZoneProperty ("neutral", income.base) income.neutral = theZone:getNumberFromZoneProperty ("neutral", income.base)
income.interval = theZone:getNumberFromZoneProperty("interval", 10 * 60) -- every 10 minutes income.interval = theZone:getNumberFromZoneProperty("interval", 10 * 60) -- every 10 minutes
income.tickMessage = theZone:getStringFromZoneProperty("tickMessage", "New funds from income available.") income.tickMessage = theZone:getStringFromZoneProperty("tickMessage", "New funds from income available: §<i>")
income.announceTicks = theZone:getBoolFromZoneProperty("announceTicks", true) income.announceTicks = theZone:getBoolFromZoneProperty("announceTicks", true)
income.verbose = theZone.verbose income.verbose = theZone.verbose
end end

View File

@ -6,8 +6,6 @@ jtacGrpUI.requiredLibs = {
"cfxGroundTroops", "cfxGroundTroops",
} }
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
- 1.0.2 - also include idling JTACS
- add positional info when using owned zones
- 2.0.0 - dmlZones - 2.0.0 - dmlZones
- sanity checks upon load - sanity checks upon load
- eliminated cfxPlayer dependence - eliminated cfxPlayer dependence
@ -21,14 +19,6 @@ jtacGrpUI.requiredLibs = {
jtacGrpUI.groupConfig = {} -- all inited group private config data, indexed by group name. jtacGrpUI.groupConfig = {} -- all inited group private config data, indexed by group name.
jtacGrpUI.simpleCommands = true -- if true, f10 other invokes directly jtacGrpUI.simpleCommands = true -- if true, f10 other invokes directly
--
-- C O N F I G H A N D L I N G
-- =============================
--
-- Each group has their own config block that can be used to
-- store group-private data and configuration items.
--
function jtacGrpUI.resetConfig(conf) function jtacGrpUI.resetConfig(conf)
end end

126
modules/milGround.lua Normal file
View File

@ -0,0 +1,126 @@
milGround = {}
milGround.version = "0.0.0"
milGround.requiredLibs = {
"dcsCommon",
"cfxZones",
"cfxMX",
"cloneZones",
}
milGround.ups = 0.5 -- every 2 seconds is enough
milGround.zones = {}
function milGround.addMilGroundZone(theZone)
milGround.zones[theZone.name] = theZone
end
--
-- Reading zones
--
function milGround.readMilGroundZone(theZone)
-- first, check if this zone is also a cloner
if not theZone:hasProperty("cloner") then
trigger.action.outText("mGnd: WARNING: milGround zone <" .. theZone.name .. "> has no 'cloner' interface, will fail!", 30)
end
-- now get the target zone. it's inside the milGround property
local tzn = theZone:getStringFromZoneProperty("milGround", "cfxNone")
local tz = cfxZones.getZoneByName(tzn)
if not tz then
trigger.action.outText("mGnd: target zone <" .. tzn .. "> not found for milGroundZone <" .. theZone.name .. ">, will fail!", 30)
end
theZone.targetZone = tz
if theZone:hasProperty("coalition") then
theZone.owner = theZone:getCoalitionFromZoneProperty("coalition")
end
end
--
-- Update
--
function milGround.update()
timer.scheduleFunction(milGround.update, {}, timer.getTime() + 1/milGround.ups)
for zName, theZone in pairs(milGround.zones) do
-- synch owner and coa
local mo = theZone.masterowner
if mo then
theZone.owner = mo.owner
if theZone.isDynamic then
theZone.coa = theZone.owner
end
end
end
end
--
-- config
--
function milGround.readConfigZone()
local theZone = cfxZones.getZoneByName("milGroundConfig")
if not theZone then
theZone = cfxZones.createSimpleZone("milGroundConfig")
end
milGround.verbose = theZone.verbose
end
--
-- API
--
function milGround.getAttackersForEnemiesOfCoa(coa, addNeutral)
-- return all milGround zones that attack zones that belong to
-- the enemy of coa
local theOtherSide = dcsCommon.getEnemyCoalitionFor(coa)
local attackers = {}
for zName, theZone in pairs(milGround.zones) do
local tz = theZone.targetZone
if tz.owner ~= coa then
if addNeutral then
table.insert(attackers, theZone)
else
if tz.owner == theOtherSide then
table.insert(attackers, theZone)
end
end
end
end
return attackers
end
function milGround.startAttackFrom(theZone)
cloneZones.spawnWithCloner(theZone) -- that's all, folx
end
--
-- start up
--
function milGround.start()
-- lib check
if not dcsCommon.libCheck then
trigger.action.outText("cfx mil ground requires dcsCommon", 30)
return false
end
if not dcsCommon.libCheck("cfx mil ground", milGround.requiredLibs) then
return false
end
-- read config
milGround.readConfigZone()
-- process milGround Zones
local attrZones = cfxZones.getZonesWithAttributeNamed("milGround")
for k, aZone in pairs(attrZones) do
milGround.readMilGroundZone(aZone) -- process attributes
milGround.addMilGroundZone(aZone) -- add to list
end
-- start update in 5 seconds
timer.scheduleFunction(milGround.update, {}, timer.getTime() + 1/milGround.ups)
-- say hi
trigger.action.outText("milGround v" .. milGround.version .. " started.", 30)
return true
end
if not milGround.start() then
trigger.action.outText("milGround failed to start.", 30)
milGround = nil
end

View File

@ -1,5 +1,5 @@
milHelo = {} milHelo = {}
milHelo.version = "0.0.0" milHelo.version = "1.0.0"
milHelo.requiredLibs = { milHelo.requiredLibs = {
"dcsCommon", "dcsCommon",
"cfxZones", "cfxZones",
@ -7,7 +7,7 @@ milHelo.requiredLibs = {
} }
milHelo.zones = {} milHelo.zones = {}
milHelo.targetKeywords = { milHelo.targetKeywords = {
"milTarget", -- my own "milTarget", -- my own zone
"camp", -- camps "camp", -- camps
"airfield", -- airfields "airfield", -- airfields
"FARP", -- FARPzones "FARP", -- FARPzones
@ -74,6 +74,19 @@ function milHelo.readMilHeloZone(theZone) -- process attributes
theZone.msnType = "cas" theZone.msnType = "cas"
end end
-- see if our ownership is tied to a master
-- adds dynamic coalition capability
if theZone:hasProperty("masterOwner") then
local mo = theZone:getStringFromZoneProperty("masterOwner")
local mz = cfxZones.getZoneByName(mo)
if not mz then
trigger.action.outText("+++milH: WARNING: Master Owner <" .. mo .. "> for zone <" .. theZone.name .. "> does not exist!", 30)
else
theZone.masterOwner = mz
end
theZone.isDynamic = theZone:getBoolFromZoneProperty("dynamic", true)
end
-- get all groups inside me -- get all groups inside me
local myGroups, count = milHelo.allGroupsInZoneByData(theZone) local myGroups, count = milHelo.allGroupsInZoneByData(theZone)
theZone.myGroups = myGroups theZone.myGroups = myGroups
@ -179,6 +192,9 @@ function milHelo.createROETask(num, roe)
end end
function milHelo.createEngageIZTask(num, theZone) function milHelo.createEngageIZTask(num, theZone)
-- trigger.action.outText("Creating engage in zone task for zone <" .. theZone.name .. ">, marking on map", 30)
-- theZone:drawZone()
-- theZone:drawText("casz - " .. theZone.name, 20)
local p = theZone:getPoint() local p = theZone:getPoint()
if not num then num = 1 end if not num then num = 1 end
local task = {} local task = {}
@ -266,7 +282,8 @@ function milHelo.createCommandTask(theCommand, num)
return t return t
end end
function milHelo.createTakeOffWP(theZone, engageInZone, engageZone) function milHelo.createTakeOffWP(theZone, engageInZone, engageZone, ROE)
if not ROE then ROE = 0 end -- wepons free
local WP = {} local WP = {}
WP.alt = 500 -- theZone.alt WP.alt = 500 -- theZone.alt
WP.alt_type = "BARO" WP.alt_type = "BARO"
@ -281,7 +298,7 @@ function milHelo.createTakeOffWP(theZone, engageInZone, engageZone)
local tasks = {} local tasks = {}
local casTask = milHelo.createCASTask(1) local casTask = milHelo.createCASTask(1)
tasks[1] = casTask tasks[1] = casTask
local roeTask = milHelo.createROETask(2,0) -- 0 = weapons free local roeTask = milHelo.createROETask(2,ROE) -- 0 = weapons free, 4 = weapon hold
tasks[2] = roeTask tasks[2] = roeTask
if engageInZone then if engageInZone then
if not engageZone then if not engageZone then
@ -353,7 +370,7 @@ function milHelo.createLandWP(gName, theZone, targetZone)
return toWP return toWP
end end
function milHelo.createOMWCallbackWP(gName, number, pt, alt, speed, action) -- name is group name function milHelo.createOMWCallbackWP(gName, number, pt, alt, speed, action, ROE) -- name is group name
if not action then action = "none" end if not action then action = "none" end
local omwWP = dcsCommon.createSimpleRoutePointData(pt, alt, speed) local omwWP = dcsCommon.createSimpleRoutePointData(pt, alt, speed)
omwWP.alt_type = "RADIO" omwWP.alt_type = "RADIO"
@ -364,29 +381,23 @@ function milHelo.createOMWCallbackWP(gName, number, pt, alt, speed, action) -- n
local ttsk = {} local ttsk = {}
local command = "milHelo.reachedWP('" .. gName .. "', '" .. number .. "', '" .. action .."')" local command = "milHelo.reachedWP('" .. gName .. "', '" .. number .. "', '" .. action .."')"
ttsk[1] = milHelo.createCommandTask(command,1) ttsk[1] = milHelo.createCommandTask(command,1)
if ROE then
ttsk[2] = milHelo.createROETask(2, ROE)
end
task.params.tasks = ttsk task.params.tasks = ttsk
omwWP.task = task omwWP.task = task
return omwWP return omwWP
end end
-- a point yDegrees off the path from AB, xPercent of the total distance
-- between A and B away from A
--[[--
function milHelo.pointXpercentYdegOffAB(A, B, xPer, yDeg) -- rets xzz point
local bearingRad = dcsCommon.bearingFromAtoB(A, B)
local dist = dcsCommon.dist(A, B)
local deviation = bearingRad + yDeg * 0.0174533
local newDist = dist * xPer/100
local newPoint = dcsCommon.pointInDirectionOfPointXYY(deviation, newDist, A)
return newPoint
end
--]]--
function milHelo.spawnForZone(theZone, targetZone) function milHelo.spawnForZone(theZone, targetZone)
-- note that each zone only has a single msnType, so zone
-- defines msn type
local n = dcsCommon.randomBetween(1, theZone.hCount) local n = dcsCommon.randomBetween(1, theZone.hCount)
local theRawData = dcsCommon.getNthItem(theZone.hGroups, n) local theRawData = dcsCommon.getNthItem(theZone.hGroups, n)
local gData = dcsCommon.clone(theRawData) local gData = dcsCommon.clone(theRawData)
local oName = gData.name local oName = gData.name
gData.lateActivation = false
-- pre-process gData: names, id etc -- pre-process gData: names, id etc
gData.name = dcsCommon.uuid(gData.name) gData.name = dcsCommon.uuid(gData.name)
@ -414,41 +425,59 @@ function milHelo.spawnForZone(theZone, targetZone)
trigger.action.outText("Setting up casZ for <" .. theZone.name .. "> to <" .. targetZone.name .. ">", 30) trigger.action.outText("Setting up casZ for <" .. theZone.name .. "> to <" .. targetZone.name .. ">", 30)
end end
local wpTOff = milHelo.createTakeOffWP(theZone, casInZone, targetZone) local wpTOff = milHelo.createTakeOffWP(theZone, casInZone, targetZone) -- no ROE = weapons free
-- depending on mission, create an orbit or land WP -- depending on mission, create an orbit or land WP
local dest = targetZone:getPoint() local dest = targetZone:getPoint()
local B = dest local B = dest
local A = theZone:getPoint() local A = theZone:getPoint()
if theZone.msnType == "cas" or theZone.msnType == "patrol" then if theZone.msnType == "cas" or theZone.msnType == "patrol" then
dcsCommon.addRoutePointForGroupData(gData, wpTOff) -- patrol and cas go straight to the target, they do not
-- have an ingress. Meaning: they have the same route
-- profile as 'insert'
wpTOff = milHelo.createTakeOffWP(theZone, casInZone, targetZone, 4) -- 4 = weapons HOLD
dcsCommon.addRoutePointForGroupData(gData, wpTOff) -- wp 1
-- on approach, 70% at target, go weapons hot
local apr = dcsCommon.vLerp(A, B, 0.75)
local omw2 = milHelo.createOMWCallbackWP(gName, 2, apr, theZone.alt, theZone.speed, "weapons free", 0) -- wp 2
dcsCommon.addRoutePointForGroupData(gData, omw2)
-- possible expandion: if cas, we have an ingress point?
local wpDest = milHelo.createOrbitWP(theZone, dest) local wpDest = milHelo.createOrbitWP(theZone, dest)
dcsCommon.addRoutePointForGroupData(gData, wpDest) dcsCommon.addRoutePointForGroupData(gData, wpDest) -- wp 3
local retPt = milHelo.createLandWP(gName, theZone, theZone) --local retPt = milHelo.createLandWP(gName, theZone, theZone)
dcsCommon.addRoutePointForGroupData(gData, retPt) --dcsCommon.addRoutePointForGroupData(gData, retPt)
--dcsCommon.dumpVar2Str("caser group", gData) local retpt = theZone:getPoint()
local omw4 = milHelo.createOMWCallbackWP(gName, 4, retpt, theZone.alt, theZone.speed, "remove")
dcsCommon.addRoutePointForGroupData(gData, omw4) -- wp 4
elseif theZone.msnType == "casz" then elseif theZone.msnType == "casz" then
dcsCommon.addRoutePointForGroupData(gData, wpTOff) wpTOff = milHelo.createTakeOffWP(theZone, casInZone, targetZone, 4) -- 4 = ROE weapons hold
dcsCommon.addRoutePointForGroupData(gData, wpTOff) -- wp 1
-- go to CAS destination with Engage in Zone active -- go to CAS destination with Engage in Zone active
-- we may want to make ingress and egress wp before heading to -- we may want to make ingress and egress wp before heading to
-- the 'real' CASZ point -- the 'real' CASZ point
-- make ingress point, in direction of target, 30 degrees to the right, half distance. -- make ingress point, in direction of target, 30 degrees to the right, half distance.
local ingress = dcsCommon.pointXpercentYdegOffAB(A, B, math.random(50,80), math.random(20,50)) local ingress = dcsCommon.pointXpercentYdegOffAB(A, B, math.random(50,80), math.random(20,50))
--local pt = targetZone:getPoint() --local pt = targetZone:getPoint()
local omw1 = milHelo.createOMWCallbackWP(gName, 2, ingress, theZone.alt, theZone.speed, "none") local omw1 = milHelo.createOMWCallbackWP(gName, 2, ingress, theZone.alt, theZone.speed, "weapons free", 0) -- wp 2
dcsCommon.addRoutePointForGroupData(gData, omw1) dcsCommon.addRoutePointForGroupData(gData, omw1)
local omw2 = milHelo.createOMWCallbackWP(gName, 3, B, theZone.alt, theZone.speed, "none") local omw2 = milHelo.createOMWCallbackWP(gName, 3, B, theZone.alt, theZone.speed, "none")
dcsCommon.addRoutePointForGroupData(gData, omw2) dcsCommon.addRoutePointForGroupData(gData, omw2) -- wp 3
-- egress point -- egress point
local egress = dcsCommon.pointXpercentYdegOffAB(B, A, math.random(20, 50), math.random(20,50)) local egress = dcsCommon.pointXpercentYdegOffAB(B, A, math.random(20, 50), math.random(20,50))
local omw3 = milHelo.createOMWCallbackWP(gName, 4, egress, theZone.alt, theZone.speed, "none") local omw3 = milHelo.createOMWCallbackWP(gName, 4, egress, theZone.alt, theZone.speed, "none")
dcsCommon.addRoutePointForGroupData(gData, omw3) dcsCommon.addRoutePointForGroupData(gData, omw3) -- wp 4
local retPt = milHelo.createLandWP(gName, theZone, theZone) -- return to aerodrome, deallocate
dcsCommon.addRoutePointForGroupData(gData, retPt) local retpt = theZone:getPoint()
local omw4 = milHelo.createOMWCallbackWP(gName, 5, retpt, theZone.alt, theZone.speed, "remove")
dcsCommon.addRoutePointForGroupData(gData, omw4) -- wp 5
elseif theZone.msnType == "insert" then elseif theZone.msnType == "insert" then
local wpDest = milHelo.createLandWP(gName, theZone, targetZone) local wpDest = milHelo.createLandWP(gName, theZone, targetZone)
dcsCommon.addRoutePointForGroupData(gData, wpTOff) dcsCommon.addRoutePointForGroupData(gData, wpTOff) -- wp 1
dcsCommon.addRoutePointForGroupData(gData, wpDest) dcsCommon.addRoutePointForGroupData(gData, wpDest) -- wp 2
-- will land and dealloc there after spawning troops
end end
-- make coa a cty -- make coa a cty
@ -481,6 +510,7 @@ function milHelo.insertTroops(theUnit, targetZone, srcZone)
end end
local gData = dcsCommon.clone(theRawData) local gData = dcsCommon.clone(theRawData)
gData.lateActivation = false -- force false
-- deploy in ring formation -- deploy in ring formation
-- remove all routes -- remove all routes
-- mayhaps prepare for orders and formation -- mayhaps prepare for orders and formation
@ -511,7 +541,7 @@ function milHelo.insertTroops(theUnit, targetZone, srcZone)
local groupCat = Group.Category.GROUND local groupCat = Group.Category.GROUND
local theSpawnedGroup = coalition.addGroup(cty, groupCat, gData) local theSpawnedGroup = coalition.addGroup(cty, groupCat, gData)
trigger.action.outText("Inserted troops <" .. gName .. ">", 30) --trigger.action.outText("Inserted troops <" .. gName .. ">", 30)
return theSpawnedGroup, gData return theSpawnedGroup, gData
end end
@ -580,12 +610,24 @@ function milHelo.spawnImpostorsFromData(rawData, cat, ctry)
end end
function milHelo.reachedWP(gName, wpNum, action) function milHelo.reachedWP(gName, wpNum, action)
trigger.action.outText("MilH group <" .. gName .. " reached wp #" .. wpNum .. ".", 30) if not action then action = "NIL" end
if milHelo.verbose then
trigger.action.outText("MilH group <" .. gName .. " reached wp #" .. wpNum .. " with action <" .. action .. ">.", 30)
end
if action == "remove" then
theGroup = Group.getByName(gName)
if theGroup and Group.isExist(theGroup) then
if milHelo.verbose then
trigger.action.outText("%%%%%%%%%% removing mil hel <" .. gName .. ">", 30)
end
Group.destroy(theGroup)
end
end
end end
function milHelo.landedCB(who, where, from) -- who group name, where a zone function milHelo.landedCB(who, where, from) -- who group name, where a zone
trigger.action.outText("milhelo landed CB for group <" .. who .. ">", 30) -- trigger.action.outText("milhelo landed CB for group <" .. who .. ">", 30)
-- step 1: remove the flight -- step 1: remove the flight
local theGroup = Group.getByName(who) local theGroup = Group.getByName(who)
if theGroup then if theGroup then
@ -601,7 +643,8 @@ function milHelo.landedCB(who, where, from) -- who group name, where a zone
local theFlight = milHelo.flights[who] local theFlight = milHelo.flights[who]
local oName = theFlight.oName local oName = theFlight.oName
local theZone = theFlight.origin local theZone = theFlight.origin
if theZone.msn == "insertion" then -- note: "insertion" is probably wrong, remove in line below
if theZone.msnType == "insertion" or theZone.msnType == "insert" then
-- create a static stand-in for scenery -- create a static stand-in for scenery
local rawData, cat, ctry = milHelo.getRawDataFromGroupNamed(who, oName) local rawData, cat, ctry = milHelo.getRawDataFromGroupNamed(who, oName)
Group.destroy(aGroup) Group.destroy(aGroup)
@ -620,6 +663,17 @@ end
-- --
function milHelo.update() function milHelo.update()
timer.scheduleFunction(milHelo.update, {}, timer.getTime() + 1) timer.scheduleFunction(milHelo.update, {}, timer.getTime() + 1)
-- update all master owners
for idx, theZone in pairs (milHelo.zones) do
local mo = theZone.masterOwner
if mo then
theZone.owner = mo.owner
if theZone.isDynamic then
theZone.coa = mo.owner
end
end
end
end end
function milHelo.GCcollected(gName) function milHelo.GCcollected(gName)
@ -672,10 +726,16 @@ function milHelo:onEvent(theEvent)
else else
-- maybe its a return flight -- maybe its a return flight
if srcZone:pointInZone(p) then if srcZone:pointInZone(p) then
trigger.action.outText("Flight <" .. gName .. "> originating from <" .. srcZone.name .. "> landed back home", 30) -- trigger.action.outText("Flight <" .. gName .. "> originating from <" .. srcZone.name .. "> landed back home", 30)
else else
trigger.action.outText("Flight <" .. gName .. "> originating from <" .. srcZone.name .. "> landed OUTSIDE of src or target zone <" .. tgtZone.name .. ">", 30) -- trigger.action.outText("Flight <" .. gName .. "> originating from <" .. srcZone.name .. "> landed OUTSIDE of src or target zone <" .. tgtZone.name .. ">, clearing.", 30)
end end
-- remove it now
local theGroup = Group.getByName(gName)
if theGroup and Group.isExist(theGroup) then
Group.destroy(theGroup)
end
end end
end end
@ -690,7 +750,7 @@ function milHelo.getMilSources(side, msnType) -- msnType is optional
if side == "blue" then side = 2 end if side == "blue" then side = 2 end
local sources = {} local sources = {}
for idx, theZone in pairs(milHelo.zones) do for idx, theZone in pairs(milHelo.zones) do
if theZone.owner == side then -- must be owned by same side if theZone.coa == side then -- coa must be same side, use masterOwner for dynamism
if msnType then if msnType then
if theZone.msnType == msnType then if theZone.msnType == msnType then
table.insert(sources, theZone) table.insert(sources, theZone)
@ -703,13 +763,18 @@ function milHelo.getMilSources(side, msnType) -- msnType is optional
return sources -- an array, NOT dict so we can pickrandom return sources -- an array, NOT dict so we can pickrandom
end end
function milHelo.getMilTargets(side) -- gets mil targets that DO NOT belong to side function milHelo.getMilTargets(side, ignoreNeutral) -- gets mil targets that DO NOT belong to side
if side == "red" then side = 1 end -- better safe... if side == "red" then side = 1 end -- better safe...
if side == "blue" then side = 2 end if side == "blue" then side = 2 end
local tgt = {} local tgt = {}
for idx, theZone in pairs(milHelo.targets) do for idx, theZone in pairs(milHelo.targets) do
-- we use OWNER, not COA here!
if theZone.owner ~= side then -- must NOT be owned by same side if theZone.owner ~= side then -- must NOT be owned by same side
if ignoreNeutral and theZone.owner == 0 then
else
table.insert(tgt, theZone) table.insert(tgt, theZone)
--trigger.action.outText("zone <" .. theZone.name .. "> owned by <" .. theZone.owner .. "> is possible target for coa <" .. side .. ">", 30)
end
end end
end end
return tgt return tgt
@ -730,7 +795,7 @@ end
function milHelo.start() function milHelo.start()
-- lib check -- lib check
if not dcsCommon.libCheck then if not dcsCommon.libCheck then
trigger.action.outText("cfx civ helo requires dcsCommon", 30) trigger.action.outText("cfx mil helo requires dcsCommon", 30)
return false return false
end end
if not dcsCommon.libCheck("cfx mil helo", milHelo.requiredLibs) then if not dcsCommon.libCheck("cfx mil helo", milHelo.requiredLibs) then
@ -758,6 +823,9 @@ function milHelo.start()
-- start update in 5 seconds -- start update in 5 seconds
timer.scheduleFunction(milHelo.update, {}, timer.getTime() + 1/milHelo.ups) timer.scheduleFunction(milHelo.update, {}, timer.getTime() + 1/milHelo.ups)
-- start GC
milHelo.GC()
-- install event handler -- install event handler
world.addEventHandler(milHelo) world.addEventHandler(milHelo)

572
modules/milWings.lua Normal file
View File

@ -0,0 +1,572 @@
milWings = {}
milWings.version = "0.9.5"
milWings.requiredLibs = {
"dcsCommon",
"cfxZones",
"cfxMX",
"wingTaskHelper",
}
milWings.zones = {} -- mil wings zones for flights. can have master owner
milWings.targetKeywords = { -- same as mil helo plus x
"wingTarget", -- my own zone
"milTarget", -- milH zones
"camp", -- camps
"airfield", -- airfields
"FARP", -- FARPzones
}
milWings.targets = {} -- targets for mil wings. can have master owner
-- includes wingTarget and other keywors
milWings.pureTargets = {} -- targets with wingTarget keyword
milWings.seadTargets = {}
milWings.flights = {} -- all currently active mil helo flights
milWings.ups = 1
milWings.missionTypes = {
"cas", -- standard cas
"cap", -- orbit over zone for duration
"sead", -- engage in zone for target zone's radius
"bomb",
}
function milWings.addMilWingsZone(theZone)
milWings.zones[theZone.name] = theZone
end
function milWings.addMilWingsTargetZone(theZone)
milWings.targets[theZone.name] = theZone -- overwrite if duplicate
if theZone:hasProperty("SEAD") then
milWings.seadTargets[theZone.name] = theZone
end
if theZone:hasProperty("wingTarget") then
milWings.pureTargets[theZone.name] = theZone
theZone.wingTargetName = theZone:getStringFromZoneProperty("wingTarget", "<*" .. theZone.name .. ">")
end
end
--
-- Reading / Processing milWings Zones
--
function milWings.partOfGroupDataInZone(theZone, theUnits) -- move to mx?
local zP = theZone:getDCSOrigin() -- don't use getPoint now.
zP.y = 0
for idx, aUnit in pairs(theUnits) do
local uP = {}
uP.x = aUnit.x
uP.y = 0
uP.z = aUnit.y -- !! y-z
if theZone:pointInZone(uP) then return true end
end
return false
end
function milWings.allGroupsInZoneByData(theZone) -- move to MX?
local theGroupsInZone = {}
local count = 0
for groupName, groupData in pairs(cfxMX.groupDataByName) do
if groupData.units then
if milWings.partOfGroupDataInZone(theZone, groupData.units) then
theGroupsInZone[groupName] = groupData -- DATA! work on clones!
count = count + 1
if theZone.verbose then
trigger.action.outText("+++milH: added group <" .. groupName .. "> for zone <" .. theZone.name .. ">", 30)
end
end
end
end
return theGroupsInZone, count
end
function milWings.readMilWingsZone(theZone) -- process attributes
theZone.msnType = string.lower(theZone:getStringFromZoneProperty("milWings", "cas"))
if dcsCommon.arrayContainsString(milWings.missionTypes, theZone.msnType) then
-- great, mission type is known
else
trigger.action.outText("+++milW: zone <" .. theZone.name .. ">: unknown wing mission type <" .. theZone.msnType .. ">, defaulting to 'CAS'", 30)
theZone.msnType = "cas"
end
-- see if our ownership is tied to a master
-- this adds dynamic coalition capability
if theZone:hasProperty("masterOwner") then
local mo = theZone:getStringFromZoneProperty("masterOwner")
local mz = cfxZones.getZoneByName(mo)
if not mz then
trigger.action.outText("+++milW: WARNING: Master Owner <" .. mo .. "> for zone <" .. theZone.name .. "> does not exist!", 30)
else
theZone.masterOwner = mz
end
theZone.isDynamic = theZone:getBoolFromZoneProperty("dynamic", true)
if theZone.verbose then
trigger.action.outText("milwing target <" .. theZone.name .. "> has masterOwner <" .. theZone.mz.name .. ">", 30)
if theZone.isDynamic then
trigger.action.outText("and coa is dynamically linked to owner.", 30)
end
end
end
-- get all groups inside me
local myGroups, count = milWings.allGroupsInZoneByData(theZone)
theZone.fGroups = {}
theZone.fCount = 0
-- sort into ground, helo and fixed
for groupName, data in pairs(myGroups) do
local catRaw = cfxMX.groupTypeByName[groupName]
if theZone.verbose then
trigger.action.outText("Proccing zone <" .. theZone.name .. ">: group <" .. groupName .. "> - type <" .. catRaw .. ">", 30)
end
if catRaw == "plane" then
theZone.fGroups[groupName] = data
theZone.fCount = theZone.fCount + 1
else
trigger.action.outText("+++milH: ignored group <" .. groupName .. ">: wrong type <" .. catRaw .. ">", 30)
end
end
theZone.coa = theZone:getCoalitionFromZoneProperty("coalition", 0)
theZone.hot = theZone:getBoolFromZoneProperty("hot", false)
theZone.inAir = theZone:getBoolFromZoneProperty("inAir", false)
theZone.speed = theZone:getNumberFromZoneProperty("speed", 220) -- 800 kmh
theZone.alt = theZone:getNumberFromZoneProperty("alt", 6000) -- we are always radar alt
theZone.loiter = theZone:getNumberFromZoneProperty("loiter", 3600) -- 1 hour loiter default
-- wipe all existing
for groupName, data in pairs(theZone.fGroups) do
local g = Group.getByName(groupName)
if g then
Group.destroy(g)
end
end
if theZone.verbose or milWings.verbose then
trigger.action.outText("+++milW: processed milWings zone <" .. theZone.name .. ">", 30)
end
end
function milWings.readMilWingsTargetZone(theZone)
-- can also be "camp", "farp", "airfield"
theZone.wingRadius = theZone:getNumberFromZoneProperty("wingRadius", theZone.radius)
if (not theZone.isCircle) and (not theZone:hasProperty("wingRadius")) then
-- often when we have a camp there is no cas radius, use 60km
-- and zone is ploygonal
if theZone.verbose then
trigger.action.outText("+++milH: Warning - milH target zone <" .. theZone.name .. "> is polygonal and has no CAS radius attribute. Defaulting to 80km", 30)
end
-- theZone.casRadius = 80000
theZone.wingRadius = 80000
end
if theZone:hasProperty("wingTypes") then -- if present, else all good
theZone.wingTypes = theZone:getListFromZoneProperty("wingTypes", "empty")
end
if theZone.verbose or milWings.verbose then
trigger.action.outText("+++milH: processed milWings TARGET zone <" .. theZone.name .. ">", 30)
end
if theZone:hasProperty("masterOwner") then
local mo = theZone:getStringFromZoneProperty("masterOwner")
local mz = cfxZones.getZoneByName(mo)
if not mz then
trigger.action.outText("+++milW: WARNING: Master Owner <" .. mo .. "> for zone <" .. theZone.name .. "> does not exist!", 30)
else
theZone.masterOwner = mz
end
end
end
--
-- creating flights
--
function milWings.createTakeOffWayPoint(theZone, targetZone)
local wp = {}
wp.alt = theZone.alt
wp.action = "Turning Point" -- default. overrides hot
wp.type = "Turning Point"
if not theZone.inAir then
if theZone.hot then
wp.action = "From Parking Area Hot"
wp.type = "TakeOffParkingHot"
else
wp.action = "From Parking Area"
wp.type = "TakeOffParking"
end
wp.alt = 0
wp.speed = 0
local af = dcsCommon.getClosestAirbaseTo(theZone:getPoint(), 0)
-- trigger.action.outText("closest airfield for this flight is <" .. af:getName() .. ">", 30)
wp.airdromeId = af:getID()
end
-- trigger.action.outText("flight has action <" .. wp.action .. "> and type <" .. wp.type .. ">", 30)
wp.speed = theZone.speed
local p = theZone:getPoint()
wp.x = p.x
wp.y = p.z
wp.formation_template= ""
if theZone.msnType == "cas" then
wp.task = dcsCommon.clone(wingTaskHelper.casTOTask)
-- now simply change some bits from the template
p = targetZone:getPoint()
wp.task.params.tasks[8].params.x = p.x
wp.task.params.tasks[8].params.y = p.z
if targetZone.wingRadius then -- should ALWAYS be true
wp.task.params.tasks[8].params.zoneRadius = targetZone.wingRadius
else
wp.task.params.tasks[8].params.zoneRadius = 80000
trigger.action.outText("WARNING: creating CAS flight <" .. theZone.name .. "> with no radius for target zone <" .. targetZone.name .. ">", 30)
end
elseif theZone.msnType == "cap" then
wp.task = dcsCommon.clone(wingTaskHelper.capTOTask)
p = targetZone:getPoint()
wp.task.params.tasks[6].params.x = p.x
wp.task.params.tasks[6].params.y = p.z
-- wp.task.params.tasks[6].params.zoneRadius = theZone.capRadius
if targetZone.wingRadius then -- should ALWAYS be true
wp.task.params.tasks[6].params.zoneRadius = targetZone.wingRadius
else
wp.task.params.tasks[6].params.zoneRadius = 80000
-- trigger.action.outText("WARNING: creating CAP flight <" .. theZone.name .. "> with no radius for target zone <" .. targetZone.name .. ">", 30)
end
elseif theZone.msnType == "sead" then
wp.task = dcsCommon.clone(wingTaskHelper.seadTOTask)
elseif theZone.msnType == "bomb" then
wp.task = dcsCommon.clone(wingTaskHelper.bombTOTask)
else
trigger.action.outText("milW: unknown msnType <" .. theZone.msnType .. "> in zone <" .. theZone.name .. ">", 30)
end
return wp
end
function milWings.createActionWaypoint(theZone, targetZone)
local p = targetZone:getPoint()
local wp = dcsCommon.createSimpleRoutePointData(p, theZone.alt, theZone.speed)
if theZone.msnType == "bomb" then
local task = dcsCommon.clone(wingTaskHelper.bombActionTask)
task.params.tasks[1].params.x = p.x
task.params.tasks[1].params.y = p.z
task.params.tasks[1].params.altitude = theZone.alt
task.params.tasks[1].params.speed = theZone.speed
wp.task = task
end
return wp
end
function milWings.createCommandTask(theCommand, num)
if not num then num = 1 end
local t = {}
t.enabled = true
t.auto = false
t.id = "WrappedAction"
t.number = num
local params = {}
t.params = params
local action = {}
params.action = action
action.id = "Script"
local p2 = {}
action.params = p2
p2.command = theCommand
return t
end
function milWings.createCallbackWP(gName, number, pt, alt, speed, action) -- name is group name
if not action then action = "none" end
local omwWP = dcsCommon.createSimpleRoutePointData(pt, alt, speed)
omwWP.alt_type = "BARO"
-- create a command waypoint
local task = {}
task.id = "ComboTask"
task.params = {}
local ttsk = {}
local command = "milWings.inFlightCB('" .. gName .. "', '" .. number .. "', '" .. action .."')"
ttsk[1] = milWings.createCommandTask(command,1)
task.params.tasks = ttsk
omwWP.task = task
return omwWP
end
function milWings.spawnForZone(theZone, targetZone)
-- pick one of the flight groups
if not theZone.fCount or theZone.fCount < 1 then
trigger.action.outText("+++milW: WARNING - no f-groups in zone <" .. theZone.name .. "> at spawnForZone", 30)
return nil, nil
end
local n = dcsCommon.randomBetween(1, theZone.fCount)
local theRawData = dcsCommon.getNthItem(theZone.fGroups, n)
local gData = dcsCommon.clone(theRawData)
if not gData then
trigger.action.outText("+++milW: WARNING: NIL gData in spawnForZone for <" .. theZone.name .. ">", 30)
return nil, nil
end
gData.lateActivation = false
local oName = gData.name
-- pre-process gData: names, id etc
gData.name = dcsCommon.uuid(gData.name)
local gName = gData.name
for idx, uData in pairs(gData.units) do
uData.name = dcsCommon.uuid(uData.name)
uData.alt = theZone.alt
uData.alt_type = "BARO"
uData.speed = theZone.speed
uData.unitId = nil
end
gData.groupId = nil
-- set task for group
gData.task = "CAS" -- default
if theZone.msnType == "cap" then
gData.task = "CAP"
elseif theZone.msnType == "sead" then
gData.task = "SEAD"
elseif theZone.msnType == "bomb" then
gData.task = "Ground Attack"
end
-- trigger.action.outText("main task is " .. gData.task, 30)
-- create route
local route = {}
route.routeRelativeTOT = true
route.points = {}
gData.route = route
-- create take-off waypoint for this flight
local wpTOff = milWings.createTakeOffWayPoint(theZone, targetZone)
dcsCommon.addRoutePointForGroupData(gData, wpTOff)
-- ingress point
local dest = targetZone:getPoint()
local B = dest
local A = theZone:getPoint()
local ingress = dcsCommon.pointXpercentYdegOffAB(A, B, math.random(50,80), math.random(20,50))
local omwWP = dcsCommon.createSimpleRoutePointData(ingress, theZone.alt, theZone.speed)
dcsCommon.addRoutePointForGroupData(gData, omwWP)
-- action waypoint
local awp = milWings.createActionWaypoint(theZone, targetZone)
dcsCommon.addRoutePointForGroupData(gData, awp)
-- egress
local egress = dcsCommon.pointXpercentYdegOffAB(B, A, math.random(20, 50), math.random(20,50))
local egWP = dcsCommon.createSimpleRoutePointData(egress, theZone.alt, theZone.speed)
dcsCommon.addRoutePointForGroupData(gData, egWP)
-- maybe add another to safety and then dealloc?
local final = milWings.createCallbackWP(gData.name, 4, theZone:getPoint(), theZone.alt, theZone.speed, "delete")
dcsCommon.addRoutePointForGroupData(gData, final)
-- spawn and return
local cty = dcsCommon.getACountryForCoalition(theZone.coa)
-- spawn
local groupCat = Group.Category.AIRPLANE
local theSpawnedGroup = coalition.addGroup(cty, groupCat, gData)
local theFlight = {}
theFlight.oName = oName
theFlight.spawn = theSpawnedGroup
theFlight.origin = theZone
theFlight.destination = targetZone
milWings.flights[gName] = theFlight --theSpawnedGroup
return theSpawnedGroup, gData
end
--
-- Update
--
function milWings.update()
timer.scheduleFunction(milWings.update, {}, timer.getTime() + 1)
-- update all master owners
for idx, theZone in pairs (milWings.zones) do
local mo = theZone.masterOwner
if mo then
theZone.owner = mo.owner
if theZone.isDynamic then
theZone.coa = mo.owner
end
if theZone.verbose then
trigger.action.outText("Copied master onwer <" .. mo.owner .. "> from <" .. mo.name .. "> to <" .. theZone.name .. ">", 30)
end
end
end
for idx, theZone in pairs (milWings.targets) do
local mo = theZone.masterOwner
if mo then
theZone.owner = mo.owner
if theZone.verbose then
trigger.action.outText("Copied master onwer <" .. mo.owner .. "> from <" .. mo.name .. "> to <" .. theZone.name .. ">", 30)
end
end
end
end
--
-- Event Handler
--
function milWings:onEvent(theEvent)
if not theEvent then return end
if not theEvent.initiator then return end
local theUnit = theEvent.initiator
if not theUnit.getGroup then return end
local theGroup = theUnit:getGroup()
if not theGroup then return end
local gName = theGroup:getName()
if not gName then return end
local theFlight = milWings.flights[gName]
if not theFlight then return end -- none of ours
local id = theEvent.id
if id == 4 then
-- flight landed -- milFlights currently do not land
-- except later transport flights -- we'll deal with those
-- later
-- trigger.action.outText("+++milW: flight <> landed (and removed)", 30)
if Group.isExist(theGroup) then
-- maybe schedule in a few seconds?
Group.destroy(theGroup)
end
end -- if landed
end
--
-- callback from the flight
--
function milWings.inFlightCB(gName)
-- trigger.action.outText("*****===***** callback in-flight for group <" .. gName .. ">", 30)
local theGroup = Group.getByName(gName)
if theGroup and Group.isExist(theGroup) then Group.destroy(theGroup) end
end
--
-- GC
--
function milWings.GCcollected(gName)
-- do some housekeeping?
if milWings.verbose then
trigger.action.outText("removed MIL flight <" .. gName .. ">", 30)
end
end
function milWings.GC()
timer.scheduleFunction(milWings.GC, {}, timer.getTime() + 5)
local filtered = {}
for gName, theFlight in pairs(milWings.flights) do
local theGroup = Group.getByName(gName)
if theGroup and Group.isExist(theGroup) then
-- all fine, keep it
filtered[gName] = theFlight
else
milWings.GCcollected(gName)
end
end
milWings.flights = filtered
end
--
-- API
--
function milWings.getMilWingSources(side, msnType) -- msnType is optional
if side == "red" then side = 1 end -- better safe...
if side == "blue" then side = 2 end
local sources = {}
for idx, theZone in pairs(milWings.zones) do
if theZone.coa == side then -- coa must be same side, use masterOwner for dynamism
if msnType then
if theZone.msnType == msnType then
table.insert(sources, theZone)
end
else
table.insert(sources, theZone)
end
end
end
return sources -- an array, NOT dict so we can pickrandom
end
function milWings.getMilWingTargets(side, msnType, ignoreNeutral, pure) -- gets mil targets that DO NOT belong to side
-- enter with side = -1 to get all
local source = milWings.targets
if pure then source = milWings.pureTargets end
if side == "red" then side = 1 end -- better safe...
if side == "blue" then side = 2 end
local tgt = {}
for idx, theZone in pairs(source) do
if theZone.owner ~= side then -- must NOT be owned by same side
if ignoreNeutral and theZone.owner == 0 then
-- neutral ignored
else
-- now see if we need to filter by zone's msnType
if msnType then
if theZone.wingTypes and dcsCommon.arrayContainsStringCaseInsensitive(theZone.wingTypes, msnType) then
table.insert(tgt, theZone)
end
else
table.insert(tgt, theZone)
end
end
end
end
return tgt
end
--
-- config
--
function milWings.readConfigZone()
local theZone = cfxZones.getZoneByName("milWingsConfig")
if not theZone then
theZone = cfxZones.createSimpleZone("milWingsConfig")
end
milWings.verbose = theZone.verbose
end
--
-- Start
--
function milWings.start()
-- lib check
if not dcsCommon.libCheck then
trigger.action.outText("cfx mil wings requires dcsCommon", 30)
return false
end
if not dcsCommon.libCheck("cfx mil wings", milWings.requiredLibs) then
return false
end
-- read config
milWings.readConfigZone()
-- process milWings Zones
local attrZones = cfxZones.getZonesWithAttributeNamed("milWings")
for k, aZone in pairs(attrZones) do
milWings.readMilWingsZone(aZone) -- process attributes
milWings.addMilWingsZone(aZone) -- add to list
end
for idx, keyWord in pairs(milWings.targetKeywords) do
attrZones = cfxZones.getZonesWithAttributeNamed(keyWord)
for k, aZone in pairs(attrZones) do
milWings.readMilWingsTargetZone(aZone) -- process attributes
milWings.addMilWingsTargetZone(aZone) -- add to list
end
end
-- start update in 5 seconds
timer.scheduleFunction(milWings.update, {}, timer.getTime() + 1/milWings.ups)
timer.scheduleFunction(milWings.GC, {}, timer.getTime() + 1/milWings.ups * 5)
-- install event handler
world.addEventHandler(milWings)
-- say hi
trigger.action.outText("milWings v" .. milWings.version .. " started.", 30)
return true
end
if not milWings.start() then
trigger.action.outText("milWings failed to start.", 30)
milWings = nil
end

View File

@ -1,5 +1,5 @@
cfxOwnedZones = {} cfxOwnedZones = {}
cfxOwnedZones.version = "2.3.0" cfxOwnedZones.version = "2.3.1"
cfxOwnedZones.verbose = false cfxOwnedZones.verbose = false
cfxOwnedZones.announcer = true cfxOwnedZones.announcer = true
cfxOwnedZones.name = "cfxOwnedZones" cfxOwnedZones.name = "cfxOwnedZones"
@ -42,6 +42,7 @@ cfxOwnedZones.name = "cfxOwnedZones"
- per-zone local numkeep - per-zone local numkeep
- title attribute - title attribute
- code clean-up - code clean-up
2.3.1 - restored getNearestOwnedZoneToPoint
--]]-- --]]--
cfxOwnedZones.requiredLibs = { cfxOwnedZones.requiredLibs = {
"dcsCommon", "dcsCommon",
@ -250,7 +251,7 @@ function cfxOwnedZones.zoneConquered(aZone, theSide, formerOwner) -- 0 = neutral
elseif theSide == 0 then who = "NEUTRAL" end elseif theSide == 0 then who = "NEUTRAL" end
aZone.owner = theSide -- just to be sure aZone.owner = theSide -- just to be sure
if aZone.announcer then if cfxOwnedZones.announcer or aZone.announcer then
if theSide == 0 then if theSide == 0 then
trigger.action.outText(aZone.name .. " has become NEUTRAL", 30) trigger.action.outText(aZone.name .. " has become NEUTRAL", 30)
else else
@ -632,6 +633,7 @@ end
-- collect zones can filter owned zones. -- collect zones can filter owned zones.
-- by default it filters all zones that are in water -- by default it filters all zones that are in water
-- includes all managed-owner zones -- includes all managed-owner zones
-- called from external sources
function cfxOwnedZones.collectZones(mode) function cfxOwnedZones.collectZones(mode)
if not mode then mode = "land" end if not mode then mode = "land" end
if mode == "land" then if mode == "land" then
@ -651,6 +653,12 @@ function cfxOwnedZones.collectZones(mode)
end end
end end
-- getNearestOwnedZoneToPoint invoked by heloTroops
function cfxOwnedZones.getNearestOwnedZoneToPoint(p)
local allZones = cfxOwnedZones.collectZones()
return cfxZones.getClosestZone(p, allZones)
end
-- getNearestEnemyOwnedZone invoked by cfxGroundTroops -- getNearestEnemyOwnedZone invoked by cfxGroundTroops
function cfxOwnedZones.getNearestEnemyOwnedZone(theZone, targetNeutral) function cfxOwnedZones.getNearestEnemyOwnedZone(theZone, targetNeutral)
if not targetNeutral then targetNeutral = false else targetNeutral = true end if not targetNeutral then targetNeutral = false else targetNeutral = true end

View File

@ -1023,6 +1023,7 @@ function cfxPlayerScore.scheduledAward(args)
cfxPlayerScore.coalitionScore[playerSide] = cfxPlayerScore.coalitionScore[playerSide] + theScore.scoreaccu cfxPlayerScore.coalitionScore[playerSide] = cfxPlayerScore.coalitionScore[playerSide] + theScore.scoreaccu
if bank and bank.addFunds then if bank and bank.addFunds then
bank.addFunds(playerSide, cfxPlayerScore.score2finance * theScore.scoreaccu) bank.addFunds(playerSide, cfxPlayerScore.score2finance * theScore.scoreaccu)
desc = desc .. "(transferred §" .. cfxPlayerScore.score2finance * theScore.scoreaccu .. " to funding)\n"
end end
theScore.scoreaccu = 0 theScore.scoreaccu = 0
hasAward = true hasAward = true
@ -1360,14 +1361,21 @@ function cfxPlayerScore.update()
-- score! -- score!
cfxPlayerScore.coalitionScore[coa] = cfxPlayerScore.coalitionScore[coa] + cfxPlayerScore.blueTriggerScore[tName] cfxPlayerScore.coalitionScore[coa] = cfxPlayerScore.coalitionScore[coa] + cfxPlayerScore.blueTriggerScore[tName]
cfxPlayerScore.blueTriggerFlags[tName] = newVal cfxPlayerScore.blueTriggerFlags[tName] = newVal
-- bank it if exists
if bank and bank.addFunds then
bank.addFunds(coa, cfxPlayerScore.score2finance * cfxPlayerScore.blueTriggerScore[tName])
end
if cfxPlayerScore.announcer then if cfxPlayerScore.announcer then
trigger.action.outTextForCoalition(coa, "BLUE goal [" .. tName .. "] achieved, new BLUE coalition score is " .. cfxPlayerScore.coalitionScore[coa], 30) trigger.action.outTextForCoalition(coa, "BLUE goal [" .. tName .. "] achieved, new BLUE coalition score is " .. cfxPlayerScore.coalitionScore[coa], 30)
trigger.action.outSoundForCoalition(coa, cfxPlayerScore.scoreSound) trigger.action.outSoundForCoalition(coa, cfxPlayerScore.scoreSound)
end end
-- bank it if exists
local amount
if bank and bank.addFunds then
amount = cfxPlayerScore.score2finance * cfxPlayerScore.blueTriggerScore[tName]
bank.addFunds(coa, amount)
if cfxPlayerScore.announcer then
trigger.action.outTextForCoalition(coa, "Transferred §" .. amount .. " to funds.", 30)
end
end
end end
end end
end end
@ -1380,13 +1388,23 @@ function cfxPlayerScore.update()
cfxPlayerScore.coalitionScore[coa] = cfxPlayerScore.coalitionScore[coa] + cfxPlayerScore.redTriggerScore[tName] cfxPlayerScore.coalitionScore[coa] = cfxPlayerScore.coalitionScore[coa] + cfxPlayerScore.redTriggerScore[tName]
cfxPlayerScore.redTriggerFlags[tName] = newVal cfxPlayerScore.redTriggerFlags[tName] = newVal
if bank and bank.addFunds then --if bank and bank.addFunds then
bank.addFunds(coa, cfxPlayerScore.score2finance * cfxPlayerScore.blueTriggerScore[tName]) -- bank.addFunds(coa, cfxPlayerScore.score2finance * cfxPlayerScore.blueTriggerScore[tName])
end --end
if cfxPlayerScore.announcer then if cfxPlayerScore.announcer then
trigger.action.outTextForCoalition(coa, "RED goal [" .. tName .. "] achieved, new RED coalition score is " .. cfxPlayerScore.coalitionScore[coa], 30) trigger.action.outTextForCoalition(coa, "RED goal [" .. tName .. "] achieved, new RED coalition score is " .. cfxPlayerScore.coalitionScore[coa], 30)
trigger.action.outSoundForCoalition(coa, cfxPlayerScore.scoreSound) trigger.action.outSoundForCoalition(coa, cfxPlayerScore.scoreSound)
end end
-- bank it if exists
local amount
if bank and bank.addFunds then
amount = cfxPlayerScore.score2finance * cfxPlayerScore.redTriggerScore[tName]
bank.addFunds(coa, amount)
if cfxPlayerScore.announcer then
trigger.action.outTextForCoalition(coa, "Transferred §" .. amount .. " to funds.", 30)
end
end
end end
end end
end end

View File

@ -1,5 +1,5 @@
radioMenu = {} radioMenu = {}
radioMenu.version = "2.2.1" radioMenu.version = "2.3.0"
radioMenu.verbose = false radioMenu.verbose = false
radioMenu.ups = 1 radioMenu.ups = 1
radioMenu.requiredLibs = { radioMenu.requiredLibs = {
@ -19,6 +19,8 @@ radioMenu.menus = {}
2.1.1 - outMessage now works correctly 2.1.1 - outMessage now works correctly
2.2.0 - clean-up 2.2.0 - clean-up
2.2.1 - corrected ackD 2.2.1 - corrected ackD
2.3.0 - added wildcard "*" ability for group name match
- added ackASnd .. ackDSnd sounds as options
--]]-- --]]--
function radioMenu.addRadioMenu(theZone) function radioMenu.addRadioMenu(theZone)
@ -114,7 +116,24 @@ function radioMenu.filterPlayerIDForGroup(theZone)
end end
for idx, gName in pairs(allGroups) do for idx, gName in pairs(allGroups) do
-- if gName ends in wildcard "*" we process differently
gName = dcsCommon.trim(gName) gName = dcsCommon.trim(gName)
if dcsCommon.stringEndsWith(gName, "*") then
-- we must check all group names if they start with the
-- the same root. WARNING: CASE-SENSITIVE!!!!
gName = dcsCommon.removeEnding(gName, "*")
for mxName, theGroupData in pairs(cfxMX.playerGroupByName) do
if dcsCommon.stringStartsWith(mxName, gName) then
-- group match, install menu
local gID = theGroupData.groupId
table.insert(theIDs, gID)
if theZone.verbose or radioMenu.verbose then
trigger.action.outText("+++menu: WILDCARD Player Group <" .. gName .. "*> matched with <" .. mxName .. ">: gID = <" .. gID .. ">", 30)
end
else
end
end
else
local theGroup = cfxMX.playerGroupByName[gName] local theGroup = cfxMX.playerGroupByName[gName]
if theGroup then if theGroup then
local gID = theGroup.groupId local gID = theGroup.groupId
@ -126,6 +145,7 @@ function radioMenu.filterPlayerIDForGroup(theZone)
trigger.action.outText("+++menu: Player Group <" .. gName .. "> does not exist", 30) trigger.action.outText("+++menu: Player Group <" .. gName .. "> does not exist", 30)
end end
end end
end
return theIDs return theIDs
end end
@ -272,6 +292,9 @@ function radioMenu.createRadioMenuWithZone(theZone)
if theZone:hasProperty("ackA") then if theZone:hasProperty("ackA") then
theZone.ackA = theZone:getStringFromZoneProperty("ackA", "Acknowledged: A") theZone.ackA = theZone:getStringFromZoneProperty("ackA", "Acknowledged: A")
end end
if theZone:hasProperty("ackASnd") then
theZone.ackASnd = theZone:getStringFromZoneProperty("ackASnd", "<none>")
end
theZone.itemBChosen = theZone:getStringFromZoneProperty("B!", "*<none>") theZone.itemBChosen = theZone:getStringFromZoneProperty("B!", "*<none>")
theZone.cooldownB = theZone:getNumberFromZoneProperty("cooldownB", 0) theZone.cooldownB = theZone:getNumberFromZoneProperty("cooldownB", 0)
@ -282,6 +305,9 @@ function radioMenu.createRadioMenuWithZone(theZone)
if theZone:hasProperty("ackB") then if theZone:hasProperty("ackB") then
theZone.ackB = theZone:getStringFromZoneProperty("ackB", "Acknowledged: B") theZone.ackB = theZone:getStringFromZoneProperty("ackB", "Acknowledged: B")
end end
if theZone:hasProperty("ackBSnd") then
theZone.ackBSnd = theZone:getStringFromZoneProperty("ackBSnd", "<none>")
end
theZone.itemCChosen = theZone:getStringFromZoneProperty("C!", "*<none>") theZone.itemCChosen = theZone:getStringFromZoneProperty("C!", "*<none>")
theZone.cooldownC = theZone:getNumberFromZoneProperty("cooldownC", 0) theZone.cooldownC = theZone:getNumberFromZoneProperty("cooldownC", 0)
@ -292,6 +318,9 @@ function radioMenu.createRadioMenuWithZone(theZone)
if theZone:hasProperty("ackC") then if theZone:hasProperty("ackC") then
theZone.ackC = theZone:getStringFromZoneProperty("ackC", "Acknowledged: C") theZone.ackC = theZone:getStringFromZoneProperty("ackC", "Acknowledged: C")
end end
if theZone:hasProperty("ackCSnd") then
theZone.ackCSnd = theZone:getStringFromZoneProperty("ackCSnd", "<none>")
end
theZone.itemDChosen = theZone:getStringFromZoneProperty("D!", "*<none>") theZone.itemDChosen = theZone:getStringFromZoneProperty("D!", "*<none>")
theZone.cooldownD = theZone:getNumberFromZoneProperty("cooldownD", 0) theZone.cooldownD = theZone:getNumberFromZoneProperty("cooldownD", 0)
@ -302,6 +331,9 @@ function radioMenu.createRadioMenuWithZone(theZone)
if theZone:hasProperty("ackD") then if theZone:hasProperty("ackD") then
theZone.ackD = theZone:getStringFromZoneProperty("ackD", "Acknowledged: D") theZone.ackD = theZone:getStringFromZoneProperty("ackD", "Acknowledged: D")
end end
if theZone:hasProperty("ackDSnd") then
theZone.ackDSnd = theZone:getStringFromZoneProperty("ackDSnd", "<none>")
end
if theZone:hasProperty("removeMenu?") then if theZone:hasProperty("removeMenu?") then
theZone.removeMenu = theZone:getStringFromZoneProperty( "removeMenu?", "*<none>") theZone.removeMenu = theZone:getStringFromZoneProperty( "removeMenu?", "*<none>")
@ -395,6 +427,7 @@ function radioMenu.doMenuX(args)
local theFlag = theZone.itemAChosen local theFlag = theZone.itemAChosen
local outVal = theZone.outValA local outVal = theZone.outValA
local ack = theZone.ackA local ack = theZone.ackA
local ackSnd = theZone.ackASnd
-- decode A..X -- decode A..X
if theItemIndex == "B"then if theItemIndex == "B"then
@ -403,18 +436,21 @@ function radioMenu.doMenuX(args)
theFlag = theZone.itemBChosen theFlag = theZone.itemBChosen
outVal = theZone.outValB outVal = theZone.outValB
ack = theZone.ackB ack = theZone.ackB
ackSnd = theZone.ackBSnd
elseif theItemIndex == "C" then elseif theItemIndex == "C" then
cd = radioMenu.cdByGID(theZone.mcdC, theZone, theGroup) -- theZone.mcdC cd = radioMenu.cdByGID(theZone.mcdC, theZone, theGroup) -- theZone.mcdC
busy = theZone.busyC busy = theZone.busyC
theFlag = theZone.itemCChosen theFlag = theZone.itemCChosen
outVal = theZone.outValC outVal = theZone.outValC
ack = theZone.ackC ack = theZone.ackC
ackSnd = theZone.ackCSnd
elseif theItemIndex == "D" then elseif theItemIndex == "D" then
cd = radioMenu.cdByGID(theZone.mcdD, theZone, theGroup) -- theZone.mcdD cd = radioMenu.cdByGID(theZone.mcdD, theZone, theGroup) -- theZone.mcdD
busy = theZone.busyD busy = theZone.busyD
theFlag = theZone.itemDChosen theFlag = theZone.itemDChosen
outVal = theZone.outValD outVal = theZone.outValD
ack = theZone.ackD ack = theZone.ackD
ackSnd = theZone.ackDSnd
end end
-- see if we are on cooldown -- see if we are on cooldown
@ -427,10 +463,13 @@ function radioMenu.doMenuX(args)
return return
else else
-- see if we have an acknowledge -- see if we have an acknowledge
if ack then
local gid = theGroup local gid = theGroup
if ack then
radioMenu.radioOutMsg(ack, gid, theZone) radioMenu.radioOutMsg(ack, gid, theZone)
end end
if ackSnd then
trigger.action.outSoundForGroup(gid, ackSnd)
end
end end
-- set new cooldown -- needs own decoder A..X -- set new cooldown -- needs own decoder A..X

133
modules/sweeper.lua Normal file
View File

@ -0,0 +1,133 @@
sweeper = {}
sweeper.version = "1.0.0"
sweeper.requiredLibs = {
"dcsCommon",
"cfxZones",
}
-- remove all units that are detected twice in a row in the same
-- zone after a time interval. Used to remove deadlocked units.
sweeper.zones = {}
sweeper.interval = 5 * 60 -- 5 mins (max 10 mins) in zone will kill you
sweeper.verbose = false
sweeper.flights = {}
function sweeper.addSweeperZone(theZone)
sweeper.zones[theZone.name] = theZone
end
function sweeper.readSweeperZone(theZone)
theZone.aircraft = theZone:getBoolFromZoneProperty("aircraft", true)
theZone.helos = theZone:getBoolFromZoneProperty("helos", false)
end
function sweeper.update()
timer.scheduleFunction(sweeper.update, {}, timer.getTime() + sweeper.interval)
local toKill = {}
local newFlights = {}
for idx, theZone in pairs(sweeper.zones) do
for i= 0, 2 do
local allGroups = coalition.getGroups(i, 2) -- get all ground
for idy, theGroup in pairs(allGroups) do
local allUnits = theGroup:getUnits()
for idz, theUnit in pairs(allUnits) do
if theZone:unitInZone(theUnit) then
table.insert(toKill, theUnit)
end
end
end
if theZone.aircraft then
local allGroups = coalition.getGroups(i, 0) -- get all planes
for idy, theGroup in pairs(allGroups) do
local allUnits = theGroup:getUnits()
for idz, theUnit in pairs(allUnits) do
if theZone:unitInZone(theUnit) then
-- see if this was was already noted
uName = theUnit:getName()
if sweeper.flights[uName] then
table.insert(toKill, theUnit)
if sweeper.verbose then
trigger.action.outText("Sweeping aircraft <" .. uName .. "> off zone for obstruction", 30)
end
else
newFlights[uName] = true
if sweeper.verbose then
trigger.action.outText("sweep: aircraft <" .. uName .. "> on notice", 30)
end
end
end
end
end
end
if theZone.helos then
local allGroups = coalition.getGroups(i, 1) -- get all helos
for idy, theGroup in pairs(allGroups) do
local allUnits = theGroup:getUnits()
for idz, theUnit in pairs(allUnits) do
if theZone:unitInZone(theUnit) then
-- see if this was was already noted
uName = theUnit:getName()
if sweeper.flights[uName] then
table.insert(toKill, theUnit)
if sweeper.verbose then
trigger.action.outText("Sweeping helicopter <" .. uName .. "> off zone for obstruction", 30)
end
else
newFlights[uName] = true
if sweeper.verbose then
trigger.action.outText("sweep: helicopter <" .. uName .. "> on notice", 30)
end
end
end
end
end
end
end
end
-- remove all units in my kill list
for idx, theUnit in pairs(toKill) do
if theUnit.getPlayerName and theUnit:getPlayerName() then
-- we do not sweep players
else
if sweeper.verbose then
trigger.action.outText("*** sweeper: sweeping <" .. theUnit:getName() .. ">", 30)
end
if Unit.isExist(theUnit) then Unit.destroy(theUnit) end
end
end
-- remember new list, forget old
sweeper.flights = newFlights
end
function sweeper.start()
-- lib check
if not dcsCommon.libCheck then
trigger.action.outText("cfx sweeper requires dcsCommon", 30)
return false
end
if not dcsCommon.libCheck("cfx sweeper", sweeper.requiredLibs) then
return false
end
-- process sweeper Zones
local attrZones = cfxZones.getZonesWithAttributeNamed("sweeper")
for k, aZone in pairs(attrZones) do
sweeper.readSweeperZone(aZone) -- process attributes
sweeper.addSweeperZone(aZone) -- add to list
end
-- start update in 5 seconds
timer.scheduleFunction(sweeper.update, {}, timer.getTime() + sweeper.interval)
-- say hi
trigger.action.outText("sweeper v" .. sweeper.version .. " started.", 30)
return true
end
if not sweeper.start() then
trigger.action.outText("sweeper failed to start.", 30)
sweeper = nil
end

View File

@ -1,10 +1,13 @@
tacan = {} tacan = {}
tacan.version = "1.1.0" tacan.version = "1.2.2"
--[[-- --[[--
Version History Version History
1.0.0 - initial version 1.0.0 - initial version
1.1.0 - OOP cfxZones 1.1.0 - OOP cfxZones
1.2.0 - desc attribute
1.2.1 - actionSound config attribute
- sound with output
1.2.2 - corrected typo when reading sound file for actionSound
--]]-- --]]--
tacan.verbose = false tacan.verbose = false
tacan.requiredLibs = { tacan.requiredLibs = {
@ -61,6 +64,10 @@ function tacan.createTacanZone(theZone)
theZone.announcer = theZone:getBoolFromZoneProperty("announcer", false) theZone.announcer = theZone:getBoolFromZoneProperty("announcer", false)
if theZone:hasProperty("desc") then
theZone.desc = theZone:getStringFromZoneProperty("desc", "<none>")
end
-- interface to groupTracker -- interface to groupTracker
if theZone:hasProperty("trackWith:") then if theZone:hasProperty("trackWith:") then
theZone.trackWith = theZone:getStringFromZoneProperty( "trackWith:", "<None>") theZone.trackWith = theZone:getStringFromZoneProperty( "trackWith:", "<None>")
@ -138,6 +145,7 @@ function tacan.createTacanInZone(theZone, channel, mode, callsign)
t.activeChan = channel t.activeChan = channel
t.theGroup = theGroup t.theGroup = theGroup
t.theData = theCopy t.theData = theCopy
t.desc = theZone.desc
table.insert(theZone.spawnedTACANS, t) table.insert(theZone.spawnedTACANS, t)
-- run a GC cycle -- run a GC cycle
@ -175,8 +183,10 @@ function tacan.TacanFromZone(theZone, silent)
local str = "NOTAM: Deployed new TACAN " .. theZone.name .. " <" .. callsign .. ">, channel " .. channel .. mode .. ", active now" local str = "NOTAM: Deployed new TACAN " .. theZone.name .. " <" .. callsign .. ">, channel " .. channel .. mode .. ", active now"
if theZone.coa == 0 then if theZone.coa == 0 then
trigger.action.outText(str, 30) trigger.action.outText(str, 30)
trigger.action.outSound(tacan.actionSound)
else else
trigger.action.outTextForCoalition(theZone.coa, str, 30) trigger.action.outTextForCoalition(theZone.coa, str, 30)
trigger.action.outSoundForCoalition(theZone.coa, tacan.actionSound)
end end
end end
end end
@ -193,6 +203,7 @@ function tacan.destroyTacan(theZone, announce)
trigger.action.outText(str, 30) trigger.action.outText(str, 30)
else else
trigger.action.outTextForCoalition(coa, str, 30) trigger.action.outTextForCoalition(coa, str, 30)
trigger.action.outSoundForCoalition(coa, tacan.actionSound)
end end
end end
@ -331,7 +342,6 @@ end
function tacan.installComms() function tacan.installComms()
tacan.redC = missionCommands.addCommandForCoalition(1, "Available TACAN stations", nil, tacan.listTacan, 1) tacan.redC = missionCommands.addCommandForCoalition(1, "Available TACAN stations", nil, tacan.listTacan, 1)
tacan.blueC = missionCommands.addCommandForCoalition(2, "Available TACAN stations", nil, tacan.listTacan, 2) tacan.blueC = missionCommands.addCommandForCoalition(2, "Available TACAN stations", nil, tacan.listTacan, 2)
end end
function tacan.listTacan(side) function tacan.listTacan(side)
@ -353,6 +363,7 @@ function tacan.doListTacan(args)
if #theTs < 1 then if #theTs < 1 then
trigger.action.outTextForCoalition(args, "No active TACAN.", 30) trigger.action.outTextForCoalition(args, "No active TACAN.", 30)
trigger.action.outSoundForCoalition(args, tacan.actionSound)
return return
end end
@ -360,9 +371,13 @@ function tacan.doListTacan(args)
for idx, aTacan in pairs(theTs) do for idx, aTacan in pairs(theTs) do
msg = msg .. "\n - " .. aTacan.activeCallsign .. ": " .. aTacan.activeChan .. aTacan.activeMode msg = msg .. "\n - " .. aTacan.activeCallsign .. ": " .. aTacan.activeChan .. aTacan.activeMode
if aTacan.desc then
msg = msg .. " - " .. aTacan.desc
end
end end
msg = msg .. "\n" msg = msg .. "\n"
trigger.action.outTextForCoalition(args, msg, 30) trigger.action.outTextForCoalition(args, msg, 30)
trigger.action.outSoundForCoalition(args, tacan.actionSound)
end end
-- --
@ -379,7 +394,7 @@ function tacan.readConfigZone()
if theZone:hasProperty("GUI") then if theZone:hasProperty("GUI") then
tacan.list = theZone:getBoolFromZoneProperty("GUI", false) tacan.list = theZone:getBoolFromZoneProperty("GUI", false)
end end
tacan.actionSound = theZone:getStringFromZoneProperty("actionSound", "Quest Snare 3.wav")
if tacan.verbose then if tacan.verbose then
trigger.action.outText("+++tcn: read config", 30) trigger.action.outText("+++tcn: read config", 30)
end end

632
modules/wingTaskHelper.lua Normal file
View File

@ -0,0 +1,632 @@
wingTaskHelper = {}
-- WP 1 Task for a CAS flight
wingTaskHelper.casTOTask = {
["id"] = "ComboTask",
["params"] =
{
["tasks"] =
{
[1] =
{
["enabled"] = true,
["auto"] = true,
["id"] = "EngageTargets",
["number"] = 1,
["key"] = "CAS",
["params"] =
{
["targetTypes"] =
{
[1] = "Helicopters",
[2] = "Ground Units",
[3] = "Light armed ships",
}, -- end of ["targetTypes"]
["priority"] = 0,
}, -- end of ["params"]
}, -- end of [1]
[2] =
{
["number"] = 2,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["value"] = 2,
["name"] = 1,
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [2]
[3] =
{
["number"] = 3,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["value"] = 1,
["name"] = 3,
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [3]
[4] =
{
["number"] = 4,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["variantIndex"] = 2,
["name"] = 5,
["formationIndex"] = 2,
["value"] = 131074, -- trail formation
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [4]
[5] =
{
["number"] = 5,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["value"] = true,
["name"] = 15,
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [5]
[6] =
{
["number"] = 6,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["targetTypes"] =
{
}, -- end of ["targetTypes"]
["name"] = 21,
["value"] = "none;",
["noTargetTypes"] =
{
[1] = "Fighters",
[2] = "Multirole fighters",
[3] = "Bombers",
[4] = "Helicopters",
[5] = "Infantry",
[6] = "Fortifications",
[7] = "Tanks",
[8] = "IFV",
[9] = "APC",
[10] = "Artillery",
[11] = "Unarmed vehicles",
[12] = "AAA",
[13] = "SR SAM",
[14] = "MR SAM",
[15] = "LR SAM",
[16] = "Aircraft Carriers",
[17] = "Cruisers",
[18] = "Destroyers",
[19] = "Frigates",
[20] = "Corvettes",
[21] = "Light armed ships",
[22] = "Unarmed ships",
[23] = "Submarines",
[24] = "Cruise missiles",
[25] = "Antiship Missiles",
[26] = "AA Missiles",
[27] = "AG Missiles",
[28] = "SA Missiles",
[29] = "UAVs",
}, -- end of ["noTargetTypes"]
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [6]
[7] =
{
["number"] = 7,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["value"] = true,
["name"] = 19,
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [7]
[8] =
{
["number"] = 8,
["auto"] = false,
["id"] = "EngageTargetsInZone",
["enabled"] = true,
["params"] =
{
["targetTypes"] =
{
[1] = "All",
}, -- end of ["targetTypes"]
["x"] = 19632.860434462,
["y"] = 323453.87978006,
["value"] = "All;",
["noTargetTypes"] =
{
}, -- end of ["noTargetTypes"]
["priority"] = 0,
["zoneRadius"] = 76200,
}, -- end of ["params"]
}, -- end of [8]
}, -- end of ["tasks"]
}, -- end of ["params"]
} -- end of ["task"]
wingTaskHelper.capTOTask = {
["id"] = "ComboTask",
["params"] =
{
["tasks"] =
{
[1] =
{
["number"] = 1,
["key"] = "CAP",
["id"] = "EngageTargets",
["enabled"] = true,
["auto"] = true,
["params"] =
{
["targetTypes"] =
{
[1] = "Air",
}, -- end of ["targetTypes"]
["priority"] = 0,
}, -- end of ["params"]
}, -- end of [1]
[2] =
{
["number"] = 2,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["value"] = true,
["name"] = 17, -- restrict ground attack
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [2]
[3] =
{
["number"] = 3,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["value"] = 0,
["name"] = 18, -- max range launch
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [3]
[4] =
{
["number"] = 4,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["value"] = true,
["name"] = 19, -- no reporting
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [4]
[5] =
{
["number"] = 5,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["targetTypes"] =
{
}, -- end of ["targetTypes"]
["name"] = 21,
["value"] = "none;",
["noTargetTypes"] =
{
[1] = "Fighters",
[2] = "Multirole fighters",
[3] = "Bombers",
[4] = "Helicopters",
[5] = "Infantry",
[6] = "Fortifications",
[7] = "Tanks",
[8] = "IFV",
[9] = "APC",
[10] = "Artillery",
[11] = "Unarmed vehicles",
[12] = "AAA",
[13] = "SR SAM",
[14] = "MR SAM",
[15] = "LR SAM",
[16] = "Aircraft Carriers",
[17] = "Cruisers",
[18] = "Destroyers",
[19] = "Frigates",
[20] = "Corvettes",
[21] = "Light armed ships",
[22] = "Unarmed ships",
[23] = "Submarines",
[24] = "Cruise missiles",
[25] = "Antiship Missiles",
[26] = "AA Missiles",
[27] = "AG Missiles",
[28] = "SA Missiles",
[29] = "UAVs",
}, -- end of ["noTargetTypes"]
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [5]
[6] =
{
["number"] = 6,
["auto"] = false,
["id"] = "EngageTargetsInZone",
["enabled"] = true,
["params"] =
{
["targetTypes"] =
{
[1] = "Planes",
}, -- end of ["targetTypes"]
["x"] = -1421.6419952991,
["y"] = 311601.25461373,
["value"] = "Planes;",
["noTargetTypes"] =
{
}, -- end of ["noTargetTypes"]
["priority"] = 0,
["zoneRadius"] = 76200,
}, -- end of ["params"]
}, -- end of [6]
}, -- end of ["tasks"]
}, -- end of ["params"]
} -- end of ["task"]
wingTaskHelper.seadTOTask = {
["id"] = "ComboTask",
["params"] =
{
["tasks"] =
{
[1] =
{
["number"] = 1,
["key"] = "SEAD",
["id"] = "EngageTargets",
["enabled"] = true,
["auto"] = true,
["params"] =
{
["targetTypes"] =
{
[1] = "Air Defence",
}, -- end of ["targetTypes"]
["priority"] = 0,
}, -- end of ["params"]
}, -- end of [1]
[2] =
{
["number"] = 2,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["value"] = 2,
["name"] = 1,
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [2]
[3] =
{
["number"] = 3,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["value"] = 2,
["name"] = 13,
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [3]
[4] =
{
["number"] = 4,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["value"] = true,
["name"] = 19,
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [4]
[5] =
{
["number"] = 5,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["targetTypes"] =
{
[1] = "Air Defence",
}, -- end of ["targetTypes"]
["name"] = 21,
["value"] = "Air Defence;",
["noTargetTypes"] =
{
[1] = "Fighters",
[2] = "Multirole fighters",
[3] = "Bombers",
[4] = "Helicopters",
[5] = "Infantry",
[6] = "Fortifications",
[7] = "Tanks",
[8] = "IFV",
[9] = "APC",
[10] = "Artillery",
[11] = "Unarmed vehicles",
[12] = "Aircraft Carriers",
[13] = "Cruisers",
[14] = "Destroyers",
[15] = "Frigates",
[16] = "Corvettes",
[17] = "Light armed ships",
[18] = "Unarmed ships",
[19] = "Submarines",
[20] = "Cruise missiles",
[21] = "Antiship Missiles",
[22] = "AA Missiles",
[23] = "AG Missiles",
[24] = "SA Missiles",
[25] = "UAVs",
}, -- end of ["noTargetTypes"]
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [5]
[6] =
{
["number"] = 6,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "EPLRS",
["params"] =
{
["value"] = true,
["groupId"] = 2,
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [6]
[7] =
{
["number"] = 7,
["auto"] = false,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["value"] = true,
["name"] = 15,
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [7]
}, -- end of ["tasks"]
}, -- end of ["params"]
} -- end of ["task"]
wingTaskHelper.bombTOTask = {
["id"] = "ComboTask",
["params"] =
{
["tasks"] =
{
[1] =
{
["number"] = 1,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["value"] = 2,
["name"] = 1,
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [1]
[2] =
{
["number"] = 2,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["value"] = true,
["name"] = 15,
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [2]
[3] =
{
["number"] = 3,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "EPLRS",
["params"] =
{
["value"] = true,
["groupId"] = 3,
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [3]
}, -- end of ["tasks"]
}, -- end of ["params"]
} -- end of ["task"]
wingTaskHelper.bombActionTask = {
["id"] = "ComboTask",
["params"] =
{
["tasks"] =
{
[1] =
{
["number"] = 1,
["auto"] = false,
["id"] = "CarpetBombing",
["enabled"] = true,
["params"] =
{
["attackType"] = "Carpet",
["attackQtyLimit"] = false,
["attackQty"] = 1,
["expend"] = "All", -- yay!
["altitude"] = 7620,
["x"] = -7222.3894291577,
["carpetLength"] = 500,
["y"] = 294267.9527197,
["altitudeEnabled"] = false,
["weaponType"] = 9663676414,
["groupAttack"] = false,
}, -- end of ["params"]
}, -- end of [1]
}, -- end of ["tasks"]
}, -- end of ["params"]
} -- end of ["task"]