Version 2.2.5

reaper, radioMainMenu
This commit is contained in:
Christian Franz 2024-06-07 13:58:13 +02:00
parent 6022372a58
commit 2b6491b978
23 changed files with 1255 additions and 118 deletions

Binary file not shown.

Binary file not shown.

View File

@ -148,3 +148,8 @@ if not bank.start() then
trigger.action.outText("bank aborted: missing libraries", 30) trigger.action.outText("bank aborted: missing libraries", 30)
bank = nil bank = nil
end end
--[[--
Add 'creditLine' input to directly load a value into the bank?
--]]--

View File

@ -1,5 +1,5 @@
bombRange = {} bombRange = {}
bombRange.version = "1.1.3" bombRange.version = "2.0.0"
bombRange.dh = 1 -- meters above ground level burst bombRange.dh = 1 -- meters above ground level burst
bombRange.requiredLibs = { bombRange.requiredLibs = {
@ -22,6 +22,11 @@ VERSION HISTORY
minor clean-up minor clean-up
1.1.2 - corrected bug when no bomb range is detected 1.1.2 - corrected bug when no bomb range is detected
1.1.3 - added meters/feet distance when reporting impact 1.1.3 - added meters/feet distance when reporting impact
1.1.4 - code hardening against CA interference
2.0.0 - support for radioMainMenu
- support for types
- types can have wild cards
--]]-- --]]--
bombRange.bombs = {} -- live tracking bombRange.bombs = {} -- live tracking
@ -170,6 +175,10 @@ function bombRange.showStatsForPlayer(pName, gID, unitName)
if bombRange.mustCheckIn then if bombRange.mustCheckIn then
local comms = bombRange.unitComms[unitName] local comms = bombRange.unitComms[unitName]
if not comms then
-- player controlled CA vehicle calling. go away.
return
end
if comms.checkedIn then if comms.checkedIn then
msg = msg .. "\nYou are checked in with weapons range command.\n" msg = msg .. "\nYou are checked in with weapons range command.\n"
else else
@ -184,6 +193,11 @@ end
-- unit UI -- unit UI
-- --
function bombRange.initCommsForUnit(theUnit) function bombRange.initCommsForUnit(theUnit)
local mainMenu = nil
if bombRange.mainMenu then
mainMenu = radioMenu.getMainMenuFor(bombRange.mainMenu) -- nilling both next params will return menus[0]
end
local uName = theUnit:getName() local uName = theUnit:getName()
local pName = theUnit:getPlayerName() local pName = theUnit:getPlayerName()
local theGroup = theUnit:getGroup() local theGroup = theUnit:getGroup()
@ -199,7 +213,7 @@ function bombRange.initCommsForUnit(theUnit)
end end
comms = {} comms = {}
comms.checkedIn = false comms.checkedIn = false
comms.root = missionCommands.addSubMenuForGroup(gID, bombRange.menuTitle) comms.root = missionCommands.addSubMenuForGroup(gID, bombRange.menuTitle, mainMenu)
comms.getStat = missionCommands.addCommandForGroup(gID, "Get statistics for " .. pName, comms.root, bombRange.redirectComms, {"getStat", uName, pName, gID}) comms.getStat = missionCommands.addCommandForGroup(gID, "Get statistics for " .. pName, comms.root, bombRange.redirectComms, {"getStat", uName, pName, gID})
comms.reset = missionCommands.addCommandForGroup(gID, "RESET statistics for " .. pName, comms.root, bombRange.redirectComms, {"reset", uName, pName, gID}) comms.reset = missionCommands.addCommandForGroup(gID, "RESET statistics for " .. pName, comms.root, bombRange.redirectComms, {"reset", uName, pName, gID})
if bombRange.mustCheckIn then if bombRange.mustCheckIn then
@ -231,6 +245,10 @@ function bombRange.commsRequest(args)
if command == "check" then if command == "check" then
comms = bombRange.unitComms[uName] comms = bombRange.unitComms[uName]
if not comms then
-- CA player here. we don't talk to you (yet)
return
end
if comms.checkedIn then if comms.checkedIn then
comms.checkedIn = false -- we are now checked out comms.checkedIn = false -- we are now checked out
missionCommands.removeItemForGroup(gID, comms.checkin) missionCommands.removeItemForGroup(gID, comms.checkin)
@ -424,6 +442,10 @@ function bombRange:onEvent(event)
if event.id == 1 then -- shot event, from player if event.id == 1 then -- shot event, from player
if not event.weapon then return end if not event.weapon then return end
local uComms = bombRange.unitComms[uName] local uComms = bombRange.unitComms[uName]
if not uComms then
-- this is a player-controlled CA vehicle. bye bye
return
end
if bombRange.mustCheckIn and (not uComms.checkedIn) then if bombRange.mustCheckIn and (not uComms.checkedIn) then
if bombRange.verbose then if bombRange.verbose then
trigger.action.outText("+++bRng: Player <" .. pName .. "> not checked in.", 30) trigger.action.outText("+++bRng: Player <" .. pName .. "> not checked in.", 30)
@ -459,12 +481,39 @@ function bombRange:onEvent(event)
end end
end end
if event.id == 15 then if event.id == 15 then -- birth
if bombRange.types then
if not bombRange.typeCheck(theUnit) then return end
end
-- we could add unit filtering by group here, e.g. only some
-- planes, defined in config
-- for example, bomb range is silly for most helicopter
bombRange.initCommsForUnit(theUnit) bombRange.initCommsForUnit(theUnit)
end end
end end
function bombRange.typeCheck(theUnit)
local theGroup = theUnit:getGroup()
local cat = theGroup:getCategory() -- we use group, not unit. Airplane is 0, Heli is 1
-- check the type explicitly
local myType = theUnit:getTypeName()
if dcsCommon.wildArrayContainsString(bombRange.types, myType) then return true end
-- planes or plane perhaps?
-- if cat == 0 and dcsCommon.arrayContainsStringCaseInsensitive(bombRange.types, "planes") then return true end
if cat == 0 and dcsCommon.wildArrayContainsString(bombRange.types, "plan*") then return true end
-- helos?
if cat == 1 and dcsCommon.wildArrayContainsString(bombRange.types, "hel*") then return true end
-- if cat == 1 and dcsCommon.arrayContainsStringCaseInsensitive(bombRange.types, "helos") then return true end
-- if cat == 1 and dcsCommon.arrayContainsStringCaseInsensitive(bombRange.types, "helicopter") then return true end
return false
end
-- --
-- Update -- Update
-- --
@ -688,6 +737,25 @@ function bombRange.readConfigZone()
bombRange.signOut = theZone:getStringFromZoneProperty("signOut!", 30) bombRange.signOut = theZone:getStringFromZoneProperty("signOut!", 30)
end end
bombRange.method = theZone:getStringFromZoneProperty("method", "inc") bombRange.method = theZone:getStringFromZoneProperty("method", "inc")
if theZone:hasProperty("types") then
bombRange.types = theZone:getListFromZoneProperty("types", "<none>")
end
if theZone:hasProperty("attachTo:") then
local attachTo = theZone:getStringFromZoneProperty("attachTo:", "<none>")
if radioMenu then
local mainMenu = radioMenu.mainMenus[attachTo]
if mainMenu then
bombRange.mainMenu = mainMenu
else
trigger.action.outText("+++bombRange: cannot find super menu <" .. attachTo .. ">", 30)
end
else
trigger.action.outText("+++bombRange: REQUIRES radioMenu to run before bombRange. 'AttachTo:' ignored.", 30)
end
end
bombRange.verbose = theZone.verbose bombRange.verbose = theZone.verbose
end end

View File

@ -1,5 +1,5 @@
cfxZones = {} cfxZones = {}
cfxZones.version = "4.3.2" cfxZones.version = "4.3.4"
-- 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
@ -51,7 +51,8 @@ cfxZones.version = "4.3.2"
- 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() - 4.3.2 - new getListFromZoneProperty()
- 4.3.3 - hardened calculateZoneBounds
- 4.3.4 - rewrote zone bounds for poly zones
--]]-- --]]--
-- --
@ -221,7 +222,7 @@ function cfxZones.calculateZoneBounds(theZone)
if not (theZone) then return if not (theZone) then return
end end
local bounds = theZone.bounds -- copy ref! local bounds = theZone.bounds -- copy ref! -- DON'T BELIEVE THIS!
if theZone.isCircle then if theZone.isCircle then
-- aabb are easy: center +/- radius -- aabb are easy: center +/- radius
@ -234,38 +235,32 @@ function cfxZones.calculateZoneBounds(theZone)
bounds.ll = dcsCommon.createPoint(center.x - radius, 0, center.z + radius) bounds.ll = dcsCommon.createPoint(center.x - radius, 0, center.z + radius)
bounds.lr = dcsCommon.createPoint(center.x + radius, 0, center.z + radius) bounds.lr = dcsCommon.createPoint(center.x + radius, 0, center.z + radius)
-- write back
theZone.bounds = bounds
elseif theZone.isPoly then elseif theZone.isPoly then
local poly = theZone.poly -- ref copy! local poly = theZone.poly -- ref copy!
-- create the four points -- create the four points
local ll = cfxZones.createPointFromPoint(poly[1]) local p = cfxZones.createPointFromPoint(poly[1])
local lr = cfxZones.createPointFromPoint(poly[1])
local ul = cfxZones.createPointFromPoint(poly[1])
local ur = cfxZones.createPointFromPoint(poly[1])
local pRad = dcsCommon.dist(theZone.point, poly[1]) -- rRad is radius for polygon from theZone.point local pRad = dcsCommon.dist(theZone.point, poly[1]) -- rRad is radius for polygon from theZone.point
-- now iterate through all points and adjust bounds accordingly -- now iterate through all points and adjust bounds accordingly
for v=2, #poly do local lx, ly, mx, my = p.x, p.z, p.x, p.z
local vertex = poly[v] for vtx=1, #poly do
if (vertex.x < ll.x) then ll.x = vertex.x; ul.x = vertex.x end local v = poly[vtx]
if (vertex.x > lr.x) then lr.x = vertex.x; ur.x = vertex.x end if v.x < lx then lx = v.x end
if (vertex.z < ul.z) then ul.z = vertex.z; ur.z = vertex.z end if v.x > mx then mx = v.x end
--if (vertex.z > ll.z) then ll.z = vertex.z; lr.z = vertex.z end if v.z < ly then ly = v.z end
if (vertex.z > ur.z) then ur.z = vertex.z; ul.z = vertex.z end if v.z > my then my = v.z end
local dp = dcsCommon.dist(theZone.point, vertex) local dp = dcsCommon.dist(theZone.point, v)
if dp > pRad then pRad = dp end -- find largst distance to vertex if dp > pRad then pRad = dp end -- find largst distance to vertex
end end
-- now keep the new point references theZone.bounds.ul = dcsCommon.createPoint(lx, 0, my)
-- and store them in the zone's bounds theZone.bounds.ur = dcsCommon.createPoint(mx, 0, my)
bounds.ll = ll theZone.bounds.ll = dcsCommon.createPoint(lx, 0, ly)
bounds.lr = lr theZone.bounds.lr = dcsCommon.createPoint(mx, 0, ly)
bounds.ul = ul
bounds.ur = ur
-- we may need to ascertain why we need ul, ur, ll, lr instead of just ll and ur
-- store pRad -- store pRad
theZone.pRad = pRad -- not sure we'll ever need that, but at least we have it theZone.pRad = pRad -- not sure we'll ever need that, but at least we have it
else else
-- huston, we have a problem -- huston, we have a problem
if cfxZones.verbose then if cfxZones.verbose then

View File

@ -1,5 +1,5 @@
cloneZones = {} cloneZones = {}
cloneZones.version = "2.2.1" cloneZones.version = "2.3.0"
cloneZones.verbose = false cloneZones.verbose = false
cloneZones.requiredLibs = { cloneZones.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
@ -52,6 +52,10 @@ cloneZones.respawnOnGroupID = true
- persistence: persist oSize and set lastSize - persistence: persist oSize and set lastSize
2.2.1 - verbosity updates for post-check 2.2.1 - verbosity updates for post-check
- if cloned group is late activation, turn it off - if cloned group is late activation, turn it off
2.3.0 - added optional cWipe? attribute to resolve possible conflict
(undocumented, just to provide lazy people with a migration
path) with wiper module
- using "wipe?" will now create a warning
--]]-- --]]--
-- --
@ -238,6 +242,11 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
theZone.deSpawnFlag = theZone:getStringFromZoneProperty( "deClone?", "none") theZone.deSpawnFlag = theZone:getStringFromZoneProperty( "deClone?", "none")
elseif theZone:hasProperty("wipe?") then elseif theZone:hasProperty("wipe?") then
theZone.deSpawnFlag = theZone:getStringFromZoneProperty("wipe?", "none") theZone.deSpawnFlag = theZone:getStringFromZoneProperty("wipe?", "none")
trigger.action.outText("+++clnZ: WARNING - Clone Zone <" .. theZone.name .. ">: attribute 'wipe?' is deprecated for clone zones!", 30)
-- note possible conflict with wiper module, so we add the new
-- cWipe? attribute
elseif theZone:hasProperty("cWipe?") then
theZone.deSpawnFlag = theZone:getStringFromZoneProperty("cWipe?", "none")
end end
if theZone.deSpawnFlag then if theZone.deSpawnFlag then

View File

@ -1,5 +1,5 @@
csarManager = {} csarManager = {}
csarManager.version = "3.4.0" csarManager.version = "4.0.0"
csarManager.ups = 1 csarManager.ups = 1
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
@ -45,6 +45,7 @@ csarManager.ups = 1
3.3.0 - persistence support 3.3.0 - persistence support
3.4.0 - global timeLimit option in config zone 3.4.0 - global timeLimit option in config zone
- fixes expiration bug when persisting data - fixes expiration bug when persisting data
4.0.0 - support for mainMenu
INTEGRATES AUTOMATICALLY WITH playerScore INTEGRATES AUTOMATICALLY WITH playerScore
@ -713,8 +714,13 @@ function csarManager.setCommsMenu(theUnit)
-- reset all coms now -- reset all coms now
csarManager.removeCommsFromConfig(conf) csarManager.removeCommsFromConfig(conf)
local mainMenu = nil
if csarManager.mainMenu then
mainMenu = radioMenu.getMainMenuFor(csarManager.mainMenu) -- nilling both next params will return menus[0]
end
-- ok, first, if we don't have an F-10 menu, create one -- ok, first, if we don't have an F-10 menu, create one
conf.myMainMenu = missionCommands.addSubMenuForGroup(id, 'CSAR Missions') conf.myMainMenu = missionCommands.addSubMenuForGroup(id, 'CSAR Missions', mainMenu)
-- now we have a menu without submenus. -- now we have a menu without submenus.
-- add our own submenus -- add our own submenus
@ -1619,6 +1625,21 @@ function csarManager.readConfigZone()
csarManager.timeLimit = nil csarManager.timeLimit = nil
end end
if theZone:hasProperty("attachTo:") then
local attachTo = theZone:getStringFromZoneProperty("attachTo:", "<none>")
if radioMenu then
local mainMenu = radioMenu.mainMenus[attachTo]
if mainMenu then
csarManager.mainMenu = mainMenu
else
trigger.action.outText("+++csarManager: cannot find super menu <" .. attachTo .. ">", 30)
end
else
trigger.action.outText("+++csarManager: REQUIRES radioMenu to run before csarManager. 'AttachTo:' ignored.", 30)
end
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

View File

@ -1,5 +1,5 @@
dcsCommon = {} dcsCommon = {}
dcsCommon.version = "3.0.7" dcsCommon.version = "3.0.8"
--[[-- 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
@ -20,6 +20,9 @@ dcsCommon.version = "3.0.7"
- new pointXpercentYdegOffAB() - new pointXpercentYdegOffAB()
3.0.6 - new arrayContainsStringCaseInsensitive() 3.0.6 - new arrayContainsStringCaseInsensitive()
3.0.7 - fixed small bug in wildArrayContainsString 3.0.7 - fixed small bug in wildArrayContainsString
3.0.8 - deepCopy() and deepTableCopy() alternates to clone() to patch
around a strange DCS 2.9 issue
Kiowa added to Troop Carriers
--]]-- --]]--
@ -33,7 +36,7 @@ dcsCommon.version = "3.0.7"
-- globals -- globals
dcsCommon.cbID = 0 -- callback id for simple callback scheduling dcsCommon.cbID = 0 -- callback id for simple callback scheduling
dcsCommon.troopCarriers = {"Mi-8MT", "UH-1H", "Mi-24P"} -- Ka-50, Apache and Gazelle can't carry troops dcsCommon.troopCarriers = {"Mi-8MT", "UH-1H", "Mi-24P", "OH58D"} -- Ka-50, Apache and Gazelle can't carry troops
dcsCommon.coalitionSides = {0, 1, 2} dcsCommon.coalitionSides = {0, 1, 2}
dcsCommon.maxCountry = 86 -- number of countries defined in total dcsCommon.maxCountry = 86 -- number of countries defined in total
@ -1200,6 +1203,7 @@ dcsCommon.version = "3.0.7"
-- clone is a recursive clone which will also clone -- clone is a recursive clone which will also clone
-- deeper levels, as used in units -- deeper levels, as used in units
function dcsCommon.clone(orig, stripMeta) function dcsCommon.clone(orig, stripMeta)
--[[-- code seems to break with DCS 2.9
if not orig then return nil end if not orig then return nil end
local orig_type = type(orig) local orig_type = type(orig)
local copy local copy
@ -1226,7 +1230,57 @@ dcsCommon.version = "3.0.7"
copy = orig copy = orig
end end
return copy return copy
--]]--
if stripMeta then
trigger.action.outText("+++common: warning: stripmetatable no longer supported.", 30)
end end
return dcsCommon.deepTableCopy(orig)
end
-- deepCopy from http://lua-users.org/wiki/CopyTable
function dcsCommon.deepcopy(orig, copies)
if not orig then return nil end
copies = copies or {}
local orig_type = type(orig)
local copy
if orig_type == 'table' then
if copies[orig] then
copy = copies[orig]
else
copy = {}
copies[orig] = copy
for orig_key, orig_value in next, orig, nil do
copy[deepcopy(orig_key, copies)] = deepcopy(orig_value, copies)
end
setmetatable(copy, deepcopy(getmetatable(orig), copies))
end
else -- number, string, boolean, etc
copy = orig
end
return copy
end
-- deepTableCopy from Mist (thanks Grimes!)
function dcsCommon.deepTableCopy(object)
if not object then return nil end
local lookup_table = {}
local function _copy(object)
if type(object) ~= "table" then
return object
elseif lookup_table[object] then
return lookup_table[object] -- break cycles
end
local new_table = {}
lookup_table[object] = new_table
for index, value in pairs(object) do
new_table[_copy(index)] = _copy(value)
end
return setmetatable(new_table, getmetatable(object))
end
return _copy(object)
end
function dcsCommon.copyArray(inArray) function dcsCommon.copyArray(inArray)
if not inArray then return nil end if not inArray then return nil end

View File

@ -1,5 +1,5 @@
flareZone = {} flareZone = {}
flareZone.version = "1.1.0" flareZone.version = "1.2.0"
flareZone.verbose = false flareZone.verbose = false
flareZone.name = "flareZone" flareZone.name = "flareZone"
@ -8,6 +8,7 @@ flareZone.name = "flareZone"
1.1.0 - improvements to verbosity 1.1.0 - improvements to verbosity
- OOP - OOP
- small bugfix in doFlare assignment - small bugfix in doFlare assignment
1.2.0 - new rndLoc attribute
--]]-- --]]--
flareZone.requiredLibs = { flareZone.requiredLibs = {
"dcsCommon", "dcsCommon",
@ -50,6 +51,8 @@ function flareZone.addFlareZone(theZone)
theZone.salvoDurationL, theZone.salvoDurationH = theZone:getPositiveRangeFromZoneProperty("duration", 1) theZone.salvoDurationL, theZone.salvoDurationH = theZone:getPositiveRangeFromZoneProperty("duration", 1)
theZone.rndLoc = theZone:getBoolFromZoneProperty("rndLoc", flase)
if theZone.verbose or flareZone.verbose then if theZone.verbose or flareZone.verbose then
trigger.action.outText("+++flrZ: new flare <" .. theZone.name .. ">, color (" .. theZone.flareColor .. ")", 30) trigger.action.outText("+++flrZ: new flare <" .. theZone.name .. ">, color (" .. theZone.flareColor .. ")", 30)
end end
@ -59,15 +62,20 @@ end
function flareZone.launch(theZone) function flareZone.launch(theZone)
local color = theZone.flareColor local color = theZone.flareColor
if color < 0 then color = math.random(4) - 1 end if color < 0 then color = math.random(4) - 1 end
local loc
local loc = cfxZones.getPoint(theZone, true) -- with height if theZone.rndLoc then
locFlat = theZone:randomPointInZone()
loc = {x = locFlat.x, y = land.getHeight({x = locFlat.x, y = locFlat.z}), z = locFlat.z}
else
loc = cfxZones.getPoint(theZone, true) -- with height
end
loc.y = loc.y + theZone.flareAlt loc.y = loc.y + theZone.flareAlt
-- calculate azimuth -- calculate azimuth
local azimuth = cfxZones.randomInRange(theZone.azimuthL, theZone.azimuthH) -- in deg local azimuth = cfxZones.randomInRange(theZone.azimuthL, theZone.azimuthH) -- in deg
if flareZone.verbose or theZone.verbose then -- if flareZone.verbose or theZone.verbose then
trigger.action.outText("+++flrZ: launching <" .. theZone.name .. ">, c = " .. color .. " (" .. dcsCommon.flareColor2Text(color) .. "), azi <" .. azimuth .. "> [" .. theZone.azimuthL .. "-" .. theZone.azimuthH .. "]", 30) -- trigger.action.outText("+++flrZ: launching <" .. theZone.name .. ">, c = " .. color .. " (" .. dcsCommon.flareColor2Text(color) .. "), azi <" .. azimuth .. "> [" .. theZone.azimuthL .. "-" .. theZone.azimuthH .. "]", 30)
end -- end
azimuth = azimuth * 0.0174533 -- in rads azimuth = azimuth * 0.0174533 -- in rads
trigger.action.signalFlare(loc, color, azimuth) trigger.action.signalFlare(loc, color, azimuth)
@ -80,6 +88,9 @@ function flareZone.update()
-- launch if flag banged -- launch if flag banged
for idx, theZone in pairs(flareZone.flares) do for idx, theZone in pairs(flareZone.flares) do
if cfxZones.testZoneFlag(theZone, theZone.doFlare, theZone.flareTriggerMethod, "lastDoFlare") then if cfxZones.testZoneFlag(theZone, theZone.doFlare, theZone.flareTriggerMethod, "lastDoFlare") then
if flareZone.verbose or theZone.verbose then
trigger.action.outText("+++flr: triggerd flares for <" .. theZone.name .. "> on input? <" .. theZone.doFlare .. ">", 30)
end
local salvo = cfxZones.randomInRange(theZone.salvoSizeL, theZone.salvoSizeH) local salvo = cfxZones.randomInRange(theZone.salvoSizeL, theZone.salvoSizeH)
if salvo < 2 then if salvo < 2 then
-- one-shot -- one-shot

View File

@ -1,5 +1,5 @@
cfxGroundTroops = {} cfxGroundTroops = {}
cfxGroundTroops.version = "2.2.0" cfxGroundTroops.version = "2.2.1"
cfxGroundTroops.ups = 0.25 -- every 4 seconds cfxGroundTroops.ups = 0.25 -- every 4 seconds
cfxGroundTroops.verbose = false cfxGroundTroops.verbose = false
cfxGroundTroops.requiredLibs = { cfxGroundTroops.requiredLibs = {
@ -34,6 +34,7 @@ cfxGroundTroops.jtacCB = {} -- jtac callbacks, to be implemented
2.0.1 - small fiex ti checkPileUp() 2.0.1 - small fiex ti checkPileUp()
2.1.0 - captureandhold - oneshot attackowned 2.1.0 - captureandhold - oneshot attackowned
2.2.0 - moveFormation support 2.2.0 - moveFormation support
2.2.1 - reduced verbosity
an entry into the deployed troop table has the following attributes an entry into the deployed troop table has the following attributes
- group - the group - group - the group
@ -926,7 +927,7 @@ function cfxGroundTroops.createGroundTroops(inGroup, range, orders, moveFormatio
if orders:lower() == "lase" then if orders:lower() == "lase" then
orders = "laze" -- we use WRONG spelling here, cause we're cool. yeah, right. orders = "laze" -- we use WRONG spelling here, cause we're cool. yeah, right.
end end
trigger.action.outText("Enter createGT group <" .. inGroup:getName() .. "> with o=<" .. orders .. ">, mf=<" .. moveFormation .. ">", 30) -- trigger.action.outText("Enter createGT group <" .. inGroup:getName() .. "> with o=<" .. orders .. ">, mf=<" .. moveFormation .. ">", 30)
newTroops.insideDestination = false newTroops.insideDestination = false
newTroops.unscheduleCount = 0 -- will count up as we aren't scheduled newTroops.unscheduleCount = 0 -- will count up as we aren't scheduled
newTroops.speedWarning = 0 newTroops.speedWarning = 0

View File

@ -1,5 +1,5 @@
guardianAngel = {} guardianAngel = {}
guardianAngel.version = "3.0.5" guardianAngel.version = "3.0.6"
guardianAngel.ups = 10 guardianAngel.ups = 10
guardianAngel.name = "Guardian Angel" -- just in case someone accesses .name guardianAngel.name = "Guardian Angel" -- just in case someone accesses .name
guardianAngel.launchWarning = true -- detect launches and warn pilot guardianAngel.launchWarning = true -- detect launches and warn pilot
@ -65,6 +65,7 @@ guardianAngel.requiredLibs = {
- msgTime to control how long warnings remain on the screen - msgTime to control how long warnings remain on the screen
- disappear message now only on verbose - disappear message now only on verbose
- dmlZones - dmlZones
3.0.6 - Hardening of targets that aren't part of groups
This script detects missiles launched against protected aircraft an This script detects missiles launched against protected aircraft an
@ -167,9 +168,13 @@ function guardianAngel.createQItem(theWeapon, theTarget, threat, launcher)
trigger.action.outText("gA: tracking missile <" .. wName .. "> launched by <" .. launcherName .. ">", guardianAngel.msgTime) trigger.action.outText("gA: tracking missile <" .. wName .. "> launched by <" .. launcherName .. ">", guardianAngel.msgTime)
end end
theItem.theTarget = theTarget theItem.theTarget = theTarget
if theTarget.getGroup then -- some targets may not have a group
theItem.tGroup = theTarget:getGroup() theItem.tGroup = theTarget:getGroup()
theItem.tID = theItem.tGroup:getID() theItem.tID = theItem.tGroup:getID()
else
theItem.tGroup = nil
theItem.tID = nil
end
theItem.targetName = theTarget:getName() theItem.targetName = theTarget:getName()
theItem.launchTimeStamp = timer.getTime() theItem.launchTimeStamp = timer.getTime()
theItem.lastDistance = math.huge theItem.lastDistance = math.huge
@ -244,24 +249,10 @@ function guardianAngel.monitorItem(theItem)
local ID = theItem.tID local ID = theItem.tID
if not w then return false end if not w then return false end
if not w:isExist() then if not w:isExist() then
--if (not theItem.missed) and (not theItem.lostTrack) then
--[[--
if guardianAngel.announcer and theItem.threat then
local desc = theItem.weaponName .. ": DISAPPEARED"
if guardianAngel.private then
trigger.action.outTextForGroup(ID, desc, guardianAngel.msgTime)
else
trigger.action.outText(desc, guardianAngel.msgTime)
end
end
--]]--
if guardianAngel.verbose then if guardianAngel.verbose then
trigger.action.outText("+++gA: missile disappeared: <" .. theItem.weaponName .. ">, aimed at <" .. theItem.targetName .. ">",30) trigger.action.outText("+++gA: missile disappeared: <" .. theItem.weaponName .. ">, aimed at <" .. theItem.targetName .. ">",30)
end end
guardianAngel.invokeCallbacks("disappear", theItem.targetName, theItem.weaponName) guardianAngel.invokeCallbacks("disappear", theItem.targetName, theItem.weaponName)
-- end
return false return false
end end
@ -302,7 +293,7 @@ function guardianAngel.monitorItem(theItem)
if isThreat and guardianAngel.announcer and guardianAngel.active then if isThreat and guardianAngel.announcer and guardianAngel.active then
local desc = "Missile, missile, missile - now heading for " .. ctName .. "!" local desc = "Missile, missile, missile - now heading for " .. ctName .. "!"
if guardianAngel.private then if guardianAngel.private and ID then
trigger.action.outTextForGroup(ID, desc, guardianAngel.msgTime) trigger.action.outTextForGroup(ID, desc, guardianAngel.msgTime)
if guardianAngel.launchSound then if guardianAngel.launchSound then
local fileName = "l10n/DEFAULT/" .. guardianAngel.launchSound local fileName = "l10n/DEFAULT/" .. guardianAngel.launchSound
@ -364,7 +355,7 @@ function guardianAngel.monitorItem(theItem)
then then
desc = desc .. " ANGEL INTERVENTION" desc = desc .. " ANGEL INTERVENTION"
if guardianAngel.announcer then if guardianAngel.announcer and ID then
if guardianAngel.private then if guardianAngel.private then
trigger.action.outTextForGroup(ID, desc, guardianAngel.msgTime) trigger.action.outTextForGroup(ID, desc, guardianAngel.msgTime)
if guardianAngel.interventionSound then if guardianAngel.interventionSound then
@ -399,7 +390,7 @@ function guardianAngel.monitorItem(theItem)
--if theItem.lostTrack then desc = desc .. " (little sneak!)" end --if theItem.lostTrack then desc = desc .. " (little sneak!)" end
--if theItem.missed then desc = desc .. " (missed you!)" end --if theItem.missed then desc = desc .. " (missed you!)" end
if guardianAngel.announcer then if guardianAngel.announcer and ID then
if guardianAngel.private then if guardianAngel.private then
trigger.action.outTextForGroup(ID, desc, guardianAngel.msgTime) trigger.action.outTextForGroup(ID, desc, guardianAngel.msgTime)
else else

View File

@ -1,5 +1,5 @@
jtacGrpUI = {} jtacGrpUI = {}
jtacGrpUI.version = "2.0.0" jtacGrpUI.version = "3.0.0"
jtacGrpUI.requiredLibs = { jtacGrpUI.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
"cfxZones", "cfxZones",
@ -11,6 +11,8 @@ jtacGrpUI.requiredLibs = {
- eliminated cfxPlayer dependence - eliminated cfxPlayer dependence
- clean-up - clean-up
- jtacSound - jtacSound
3.0.0 - support for attachTo:
--]]-- --]]--
-- find & command cfxGroundTroops-based jtacs -- find & command cfxGroundTroops-based jtacs
-- UI installed via OTHER for all groups with players -- UI installed via OTHER for all groups with players
@ -143,6 +145,11 @@ function jtacGrpUI.setCommsMenu(theGroup)
if not Group.isExist(theGroup) then return end if not Group.isExist(theGroup) then return end
if not jtacGrpUI.isEligibleForMenu(theGroup) then return end if not jtacGrpUI.isEligibleForMenu(theGroup) then return end
local mainMenu = nil
if jtacGrpUI.mainMenu then
mainMenu = radioMenu.getMainMenuFor(jtacGrpUI.mainMenu) -- nilling both next params will return menus[0]
end
local conf = jtacGrpUI.getConfigForGroup(theGroup) local conf = jtacGrpUI.getConfigForGroup(theGroup)
conf.id = theGroup:getID(); -- we always do this ALWAYS conf.id = theGroup:getID(); -- we always do this ALWAYS
@ -151,7 +158,7 @@ function jtacGrpUI.setCommsMenu(theGroup)
if not conf.myMainMenu then if not conf.myMainMenu then
local commandTxt = "jtac Lasing Report" local commandTxt = "jtac Lasing Report"
local theCommand = missionCommands.addCommandForGroup( local theCommand = missionCommands.addCommandForGroup(
conf.id, commandTxt, nil, jtacGrpUI.redirectCommandX, {conf, "lasing report"}) conf.id, commandTxt, mainMenu, jtacGrpUI.redirectCommandX, {conf, "lasing report"})
conf.myMainMenu = theCommand conf.myMainMenu = theCommand
end end
@ -160,7 +167,7 @@ function jtacGrpUI.setCommsMenu(theGroup)
-- ok, first, if we don't have an F-10 menu, create one -- ok, first, if we don't have an F-10 menu, create one
if not (conf.myMainMenu) then if not (conf.myMainMenu) then
conf.myMainMenu = missionCommands.addSubMenuForGroup(conf.id, 'jtac') conf.myMainMenu = missionCommands.addSubMenuForGroup(conf.id, 'jtac', mainMenu)
end end
-- clear out existing commands -- clear out existing commands
@ -317,12 +324,27 @@ function jtacGrpUI.readConfigZone()
if not theZone then if not theZone then
theZone = cfxZones.createSimpleZone("jtacGrpUIConfig") theZone = cfxZones.createSimpleZone("jtacGrpUIConfig")
end end
jtacGrpUI.name = "jtacGrpUI"
jtacGrpUI.jtacTypes = theZone:getStringFromZoneProperty("jtacTypes", "all") jtacGrpUI.jtacTypes = theZone:getStringFromZoneProperty("jtacTypes", "all")
jtacGrpUI.jtacTypes = string.lower(jtacGrpUI.jtacTypes) jtacGrpUI.jtacTypes = string.lower(jtacGrpUI.jtacTypes)
jtacGrpUI.jtacSound = theZone:getStringFromZoneProperty("jtacSound", "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav") jtacGrpUI.jtacSound = theZone:getStringFromZoneProperty("jtacSound", "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav")
if theZone:hasProperty("attachTo:") then
local attachTo = theZone:getStringFromZoneProperty("attachTo:", "<none>")
if radioMenu then
local mainMenu = radioMenu.mainMenus[attachTo]
if mainMenu then
jtacGrpUI.mainMenu = mainMenu
else
trigger.action.outText("+++jtacGrpUI: cannot find super menu <" .. attachTo .. ">", 30)
end
else
trigger.action.outText("+++jtacGrpUI: REQUIRES radioMenu to run before jtacGrpUI. 'AttachTo:' ignored.", 30)
end
end
jtacGrpUI.verbose = theZone.verbose jtacGrpUI.verbose = theZone.verbose
end end

View File

@ -1,5 +1,5 @@
launchPlatform = {} launchPlatform = {}
launchPlatform.version = "0.0.0" launchPlatform.version = "0.5.0"
launchPlatform.requiredLibs = { launchPlatform.requiredLibs = {
"dcsCommon", "dcsCommon",
"cfxZones", "cfxZones",
@ -55,7 +55,9 @@ function launchPlatform.launchAtTargetZone(coa, tgtZone, theType) -- gets closes
-- get closest launcher for target -- get closest launcher for target
local tgtPoint = tgtZone:getPoint() local tgtPoint = tgtZone:getPoint()
local src, dist = cfxZones.getClosestZone(tgtPoint, platforms) local src, dist = cfxZones.getClosestZone(tgtPoint, platforms)
if launchPlatform.verbose then
trigger.action.outText("+++LP: chosen <" .. src.name .. "> as launch platform", 30) trigger.action.outText("+++LP: chosen <" .. src.name .. "> as launch platform", 30)
end
local theLauncher = launchPlatform.launchForPlatform(coa, src, tgtPoint, tgtZone) local theLauncher = launchPlatform.launchForPlatform(coa, src, tgtPoint, tgtZone)
if not theLauncher then if not theLauncher then
@ -74,7 +76,9 @@ function launchPlatform.launchAtTargetZone(coa, tgtZone, theType) -- gets closes
end end
function launchPlatform.asynchRemovePlatform(args) function launchPlatform.asynchRemovePlatform(args)
trigger.action.outText("LP: asynch remove for group <" .. args .. ">", 30) if launchPlatform.verbose then
trigger.action.outText("+++LP: asynch remove for group <" .. args .. ">", 30)
end
local theGroup = Group.getByName(args) local theGroup = Group.getByName(args)
if not theGroup then return end if not theGroup then return end
Group.destroy(theGroup) Group.destroy(theGroup)
@ -83,11 +87,11 @@ end
function launchPlatform.createData(thePoint, theTarget, targetZone, radius, name, num, wType) function launchPlatform.createData(thePoint, theTarget, targetZone, radius, name, num, wType)
-- if present, we can use targetZone with some intelligence -- if present, we can use targetZone with some intelligence
if not thePoint then if not thePoint then
trigger.action.outText("NO POINT", 30) trigger.action.outText("+++LP: NO POINT", 30)
return nil return nil
end end
if not theTarget then if not theTarget then
trigger.action.outText("NO TARGET", 30) trigger.action.outText("+++LP: NO TARGET", 30)
return nil return nil
end end
@ -168,7 +172,9 @@ function launchPlatform.createData(thePoint, theTarget, targetZone, radius, name
-- if inside camp -- if inside camp
local hiPrioTargets local hiPrioTargets
if targetZone and targetZone.cloners and #targetZone.cloners > 0 then if targetZone and targetZone.cloners and #targetZone.cloners > 0 then
if launchPlatform.verbose then
trigger.action.outText("+++LP: detected <" .. targetZone.name .. "> is camp with <" .. #targetZone.cloners .. "> res-points, re-targeting hi-prio", 30) trigger.action.outText("+++LP: detected <" .. targetZone.name .. "> is camp with <" .. #targetZone.cloners .. "> res-points, re-targeting hi-prio", 30)
end
hiPrioTargets = targetZone.cloners hiPrioTargets = targetZone.cloners
radius = radius / 10 -- much smaller error radius = radius / 10 -- much smaller error
end end

View File

@ -1,5 +1,5 @@
cfxPlayerScore = {} cfxPlayerScore = {}
cfxPlayerScore.version = "3.2.0" cfxPlayerScore.version = "3.3.0"
cfxPlayerScore.name = "cfxPlayerScore" -- compatibility with flag bangers cfxPlayerScore.name = "cfxPlayerScore" -- compatibility with flag bangers
cfxPlayerScore.badSound = "Death BRASS.wav" cfxPlayerScore.badSound = "Death BRASS.wav"
cfxPlayerScore.scoreSound = "Quest Snare 3.wav" cfxPlayerScore.scoreSound = "Quest Snare 3.wav"
@ -15,6 +15,7 @@ cfxPlayerScore.firstSave = true -- to force overwrite
3.0.2 - interface with ObjectDestructDetector for scoring scenery objects 3.0.2 - interface with ObjectDestructDetector for scoring scenery objects
3.1.0 - shared data for persistence 3.1.0 - shared data for persistence
3.2.0 - integration with bank 3.2.0 - integration with bank
3.3.0 - case INsensitivity for all typeScore objects
--]]-- --]]--
cfxPlayerScore.requiredLibs = { cfxPlayerScore.requiredLibs = {
@ -34,7 +35,7 @@ cfxPlayerScore.killZones = {} -- when set, kills only count here
-- typeScore: dictionary sorted by typeString for score -- typeScore: dictionary sorted by typeString for score
-- extend to add more types. It is used by unitType2score to -- extend to add more types. It is used by unitType2score to
-- determine the base unit score -- determine the base unit score
cfxPlayerScore.typeScore = {} cfxPlayerScore.typeScore = {} -- ALL UPPERCASE NOW!!!
cfxPlayerScore.lastPlayerLanding = {} -- timestamp, by player name cfxPlayerScore.lastPlayerLanding = {} -- timestamp, by player name
cfxPlayerScore.delayBetweenLandings = 30 -- seconds to count as separate landings, also set during take-off to prevent janky t/o to count. cfxPlayerScore.delayBetweenLandings = 30 -- seconds to count as separate landings, also set during take-off to prevent janky t/o to count.
cfxPlayerScore.aircraft = 50 cfxPlayerScore.aircraft = 50
@ -214,9 +215,14 @@ function cfxPlayerScore.object2score(inVictim, killSide) -- does not have group
if not inVictim then return 0 end if not inVictim then return 0 end
if not killSide then killSide = -1 end if not killSide then killSide = -1 end
local inName = inVictim:getName() local inName = inVictim:getName()
if cfxPlayerScore.verbose then
trigger.action.outText("+++PScr: ob2sc entry to resolve name <" .. inName .. ">", 30)
end
if dcsCommon.isSceneryObject(inVictim) then if dcsCommon.isSceneryObject(inVictim) then
local desc = inVictim:getDesc() local desc = inVictim:getDesc()
if not desc then return 0 end if not desc then
return 0
end
-- same as object destruct detector to -- same as object destruct detector to
-- avoid ID changes -- avoid ID changes
inName = desc.typeName inName = desc.typeName
@ -231,14 +237,19 @@ function cfxPlayerScore.object2score(inVictim, killSide) -- does not have group
if type(inName) == "number" then if type(inName) == "number" then
inName = tostring(inName) inName = tostring(inName)
end end
if cfxPlayerScore.verbose then
trigger.action.outText("+++PScr: stage II inName: <" .. inName .. ">", 30)
end
-- since 2.7x DCS turns units into static objects for -- since 2.7x DCS turns units into static objects for
-- cooking off, so first thing we need to do is do a name check -- cooking off, so first thing we need to do is do a name check
local objectScore = cfxPlayerScore.typeScore[inName] local objectScore = cfxPlayerScore.typeScore[inName:upper()]
if not objectScore then if not objectScore then
-- try the type desc -- try the type desc
local theType = inVictim:getTypeName() local theType = inVictim:getTypeName()
objectScore = cfxPlayerScore.typeScore[theType] if theType then
objectScore = cfxPlayerScore.typeScore[theType:upper()]
end
end end
if type(objectScore) == "string" then if type(objectScore) == "string" then
@ -272,17 +283,17 @@ function cfxPlayerScore.unit2score(inUnit)
-- simply extend by adding items to the typescore table.concat -- simply extend by adding items to the typescore table.concat
-- we first try by unit name. This allows individual -- we first try by unit name. This allows individual
-- named hi-value targets to have individual scores -- named hi-value targets to have individual scores
local uScore = cfxPlayerScore.typeScore[vicName] local uScore = cfxPlayerScore.typeScore[vicName:upper()]
-- see if all members of group score -- see if all members of group score
if not uScore then if (not uScore) and vicGroup then
local grpName = vicGroup:getName() local grpName = vicGroup:getName()
uScore = cfxPlayerScore.typeScore[grpName] uScore = cfxPlayerScore.typeScore[grpName:upper()]
end end
if uScore == nil then if not uScore then
-- WE NOW TRY TO ACCESS BY VICTIM'S TYPE STRING -- WE NOW TRY TO ACCESS BY VICTIM'S TYPE STRING
uScore = cfxPlayerScore.typeScore[vicType] uScore = cfxPlayerScore.typeScore[vicType:upper()]
else else
end end
@ -291,7 +302,7 @@ function cfxPlayerScore.unit2score(inUnit)
uScore = tonumber(uScore) uScore = tonumber(uScore)
end end
if uScore == nil then uScore = 0 end if not uScore then uScore = 0 end
if uScore > 0 then return uScore end if uScore > 0 then return uScore end
-- only apply base scores when the lookup did not give a result -- only apply base scores when the lookup did not give a result
@ -546,13 +557,13 @@ function cfxPlayerScore.isNamedUnit(theUnit)
local theName = "(cfx_none)" local theName = "(cfx_none)"
if type(theUnit) == "string" then if type(theUnit) == "string" then
theName = theUnit -- direct name assignment theName = theUnit -- direct name assignment
-- WARNING: NO EXIST CHECK DONE!
else else
-- WARNING: NO EXIST CHECK DONE!
-- after kill, unit is dead, so will no longer exist! -- after kill, unit is dead, so will no longer exist!
theName = theUnit:getName() theName = theUnit:getName()
if not theName then return false end if not theName then return false end
end end
if cfxPlayerScore.typeScore[theName] then if cfxPlayerScore.typeScore[theName:upper()] then
return true return true
end end
return false return false
@ -743,6 +754,9 @@ function cfxPlayerScore.killDetected(theEvent)
-- we are only getting called when and if -- we are only getting called when and if
-- a kill occured and killer was a player -- a kill occured and killer was a player
-- and target exists -- and target exists
if cfxPlayerScore.verbose then
trigger.action.outText("+++PScr: enter kill detected", 30)
end
local killer = theEvent.initiator local killer = theEvent.initiator
local killerName = killer:getPlayerName() local killerName = killer:getPlayerName()
if not killerName then killerName = "<nil>" end if not killerName then killerName = "<nil>" end
@ -757,6 +771,9 @@ function cfxPlayerScore.killDetected(theEvent)
-- was it a scenery object? -- was it a scenery object?
local wasBuilding = dcsCommon.isSceneryObject(victim) local wasBuilding = dcsCommon.isSceneryObject(victim)
if wasBuilding then if wasBuilding then
if cfxPlayerScore.verbose then
trigger.action.outText("+++PScr: killed objectz was a map/scenery object", 30)
end
-- these objects have no coalition; we simply award the score if -- these objects have no coalition; we simply award the score if
-- it exists in look-up table. -- it exists in look-up table.
local staticScore = cfxPlayerScore.object2score(victim, killSide) local staticScore = cfxPlayerScore.object2score(victim, killSide)
@ -1431,8 +1448,19 @@ function cfxPlayerScore.start()
-- identify and process a score table zones -- identify and process a score table zones
local theZone = cfxZones.getZoneByName("playerScoreTable") local theZone = cfxZones.getZoneByName("playerScoreTable")
if theZone then if theZone then
-- trigger.action.outText("Reading custom player score table", 30)
-- read all into my types registry, replacing whatever is there -- read all into my types registry, replacing whatever is there
cfxPlayerScore.typeScore = cfxZones.getAllZoneProperties(theZone) cfxPlayerScore.typeScore = theZone:getAllZoneProperties(true) -- true = get all properties in UPPER case
-- local n = dcsCommon.getSizeOfTable(cfxPlayerScore.typeScore)
-- trigger.action.outText("Table has <" .. n .. "> entries:", 30)
if true then
--trigger.action.outText("Custom PlayerScore Type Score Table:", 30)
for name, val in pairs (cfxPlayerScore.typeScore) do
-- trigger.action.outText("ps[" .. name .. "]=<" .. val .. ">", 30)
end
end
else
--trigger.action.outText("No custom score defined", 30)
end end
-- read score tiggers and values -- read score tiggers and values

View File

@ -1,5 +1,5 @@
radioMenu = {} radioMenu = {}
radioMenu.version = "2.3.0" radioMenu.version = "3.0.0"
radioMenu.verbose = false radioMenu.verbose = false
radioMenu.ups = 1 radioMenu.ups = 1
radioMenu.requiredLibs = { radioMenu.requiredLibs = {
@ -7,6 +7,7 @@ radioMenu.requiredLibs = {
"cfxZones", -- Zones, of course "cfxZones", -- Zones, of course
} }
radioMenu.menus = {} radioMenu.menus = {}
radioMenu.mainMenus = {} -- dict
--[[-- --[[--
Version History Version History
@ -21,12 +22,19 @@ radioMenu.menus = {}
2.2.1 - corrected ackD 2.2.1 - corrected ackD
2.3.0 - added wildcard "*" ability for group name match 2.3.0 - added wildcard "*" ability for group name match
- added ackASnd .. ackDSnd sounds as options - added ackASnd .. ackDSnd sounds as options
3.0.0 - new radioMainMenu and attachTo: mechanics
cascading radioMainMenu support
detect cyclic references
--]]-- --]]--
function radioMenu.addRadioMenu(theZone) function radioMenu.addRadioMenu(theZone)
table.insert(radioMenu.menus, theZone) table.insert(radioMenu.menus, theZone)
end end
function radioMenu.addRadioMainMenu(theZone)
radioMenu.mainMenus[theZone.name] = theZone
end
function radioMenu.getRadioMenuByName(aName) function radioMenu.getRadioMenuByName(aName)
for idx, aZone in pairs(radioMenu.menus) do for idx, aZone in pairs(radioMenu.menus) do
if aName == aZone.name then return aZone end if aName == aZone.name then return aZone end
@ -38,6 +46,10 @@ function radioMenu.getRadioMenuByName(aName)
return nil return nil
end end
function radioMenu.getRadioMainMenuByName(theName)
return radioMenu.mainMenus[theName]
end
-- --
-- read zone -- read zone
-- --
@ -169,6 +181,9 @@ function radioMenu.installMenu(theZone)
end end
theZone.rootMenu = {} theZone.rootMenu = {}
theZone.mainRoot = nil -- can be altered with attachTo
-- see if this menu has an attachTo attribute
theZone.mcdA = {} theZone.mcdA = {}
theZone.mcdB = {} theZone.mcdB = {}
theZone.mcdC = {} theZone.mcdC = {}
@ -180,7 +195,11 @@ function radioMenu.installMenu(theZone)
if theZone.menuGroup or theZone.menuTypes then if theZone.menuGroup or theZone.menuTypes then
for idx, grp in pairs(gID) do for idx, grp in pairs(gID) do
local aRoot = missionCommands.addSubMenuForGroup(grp, theZone.rootName, nil) if theZone.attachTo then
local mainMenu = theZone.attachTo
theZone.mainRoot = radioMenu.getMainMenuFor(mainMenu, theZone, grp)
end
local aRoot = missionCommands.addSubMenuForGroup(grp, theZone.rootName, theZone.mainRoot)
theZone.rootMenu[grp] = aRoot theZone.rootMenu[grp] = aRoot
theZone.mcdA[grp] = 0 theZone.mcdA[grp] = 0
theZone.mcdB[grp] = 0 theZone.mcdB[grp] = 0
@ -188,9 +207,17 @@ function radioMenu.installMenu(theZone)
theZone.mcdD[grp] = 0 theZone.mcdD[grp] = 0
end end
elseif theZone.coalition == 0 then elseif theZone.coalition == 0 then
theZone.rootMenu[0] = missionCommands.addSubMenu(theZone.rootName, nil) if theZone.attachTo then
local mainMenu = theZone.attachTo
theZone.mainRoot = radioMenu.getMainMenuFor(mainMenu, theZone, 0)
end
theZone.rootMenu[0] = missionCommands.addSubMenu(theZone.rootName, theZone.mainRoot)
else else
theZone.rootMenu[0] = missionCommands.addSubMenuForCoalition(theZone.coalition, theZone.rootName, nil) if theZone.attachTo then
local mainMenu = theZone.attachTo
theZone.mainRoot = radioMenu.getMainMenuFor(mainMenu, theZone, 0)
end
theZone.rootMenu[0] = missionCommands.addSubMenuForCoalition(theZone.coalition, theZone.rootName, theZone.mainRoot)
end end
if theZone:hasProperty("itemA") then if theZone:hasProperty("itemA") then
@ -253,6 +280,18 @@ end
function radioMenu.createRadioMenuWithZone(theZone) function radioMenu.createRadioMenuWithZone(theZone)
theZone.rootName = theZone:getStringFromZoneProperty("radioMenu", "<No Name>") theZone.rootName = theZone:getStringFromZoneProperty("radioMenu", "<No Name>")
if theZone:hasProperty("attachTo:") then
local attachTo = theZone:getStringFromZoneProperty("attachTo:", "<none>")
if radioMenu.verbose or theZone.verbose then
trigger.action.outText("Menu <" .. theZone.name .. "> will attach to <" .. attachTo .. ">", 30)
end
if not radioMenu.mainMenus[attachTo] then
trigger.action.outText("+++rdoM: menu <" .. theZone.name .. "> tries to attachTo unknown radioMainMenu <" .. attachTo .. "> - cancelled.", 30)
else
theZone.attachTo = radioMenu.mainMenus[attachTo]
end
end
theZone.coalition = theZone:getCoalitionFromZoneProperty("coalition", 0) theZone.coalition = theZone:getCoalitionFromZoneProperty("coalition", 0)
-- groups / types -- groups / types
if theZone:hasProperty("group") then if theZone:hasProperty("group") then
@ -350,6 +389,116 @@ function radioMenu.createRadioMenuWithZone(theZone)
end end
end end
function radioMenu.getMainMenuFor(mainMenu, theZone, idx)
if not idx then idx = 0 end
if not mainMenu.rootMenu[idx] then
-- trigger.action.outText("main <" .. mainMenu.name .. "> for zone <" .. theZone.name .. ">: forcing idx to 0", 30)
return mainMenu.rootMenu[0]
end
-- trigger.action.outText("good main <" .. mainMenu.name .. "> for zone <" .. theZone.name .. ">", 30)
return mainMenu.rootMenu[idx]
end
function radioMenu.installMainMenu(theZone)
local gID = nil -- set of all groups this menu applies to
if theZone.menuGroup then
if not cfxMX then
trigger.action.outText("WARNING: radioMenu's group attribute requires the 'cfxMX' module", 30)
return
end
-- access cfxMX player info for group ID
gID = radioMenu.filterPlayerIDForGroup(theZone)
elseif theZone.menuTypes then
if not cfxMX then
trigger.action.outText("WARNING: radioMenu's type attribute requires the 'cfxMX' module", 30)
return
end
-- access cxfMX player infor with type match for ID
gID = radioMenu.filterPlayerIDForType(theZone)
end
theZone.rootMenu = {} -- roots by many different things
local mainRoot = nil
if theZone.menuGroup or theZone.menuTypes then
for idx, grp in pairs(gID) do
if theZone.attachTo then
local mainMenu = theZone.attachTo
mainRoot = radioMenu.getMainMenuFor(mainMenu, theZone, grp)
end
local aRoot = missionCommands.addSubMenuForGroup(grp, theZone.rootName, mainRoot)
theZone.rootMenu[grp] = aRoot
end
elseif theZone.coalition == 0 then
if theZone.attachTo then
local mainMenu = theZone.attachTo
mainRoot = radioMenu.getMainMenuFor(mainMenu, theZone, grp)
end
theZone.rootMenu[0] = missionCommands.addSubMenu(theZone.rootName, mainRoot)
else
if theZone.attachTo then
local mainMenu = theZone.attachTo
mainRoot = radioMenu.getMainMenuFor(mainMenu, theZone, grp)
end
theZone.rootMenu[0] = missionCommands.addSubMenuForCoalition(theZone.coalition, theZone.rootName, mainRoot)
end
end
function radioMenu.createRadioMainMenuWithZone(theZone)
theZone.rootName = theZone:getStringFromZoneProperty("radioMainMenu", "<No Name>")
if theZone:hasProperty("radioMenu") then
trigger.action.outText("+++radM: ERROR: main menu <" .. theZone.name .. "> also has conflicting 'radioMenu' entry", 30)
end
-- CASCADING SUPPORT. LOOP DETECTION
if theZone:hasProperty("attachTo:") then
local attachTo = theZone:getStringFromZoneProperty("attachTo:", "<none>")
if radioMenu.verbose or theZone.verbose then
trigger.action.outText("MAIN Menu <" .. theZone.name .. "> wants to attach to <" .. attachTo .. ">", 30)
end
if not radioMenu.mainMenus[attachTo] then
trigger.action.outText("+++radioMM: MAIN menu <" .. theZone.name .. "> tries to 'attachTo:' unknown radioMainMenu <" .. attachTo .. "> - cancelled.", 30)
else
-- make sure that this zone has been processed
local super = radioMenu.mainMenus[attachTo]
if super.mainDone then
theZone.attachTo = super
else
-- we need other zones to be processed before
if radioMenu.verbose or theZone.verbose then
trigger.action.outText("Main menu <" .. theZone.name .. "> refers to unprocessed zone <" .. attachTo .. ">, deferring.", 30)
end
return false
end
end
end
-- we now create the ROOT menu that all other menu
-- items attach to that have this as main menu
theZone.coalition = theZone:getCoalitionFromZoneProperty("coalition", 0)
-- groups / types
if theZone:hasProperty("group") then
theZone.menuGroup = theZone:getStringFromZoneProperty("group", "<none>")
theZone.menuGroup = dcsCommon.trim(theZone.menuGroup)
elseif theZone:hasProperty("groups") then
theZone.menuGroup = theZone:getStringFromZoneProperty("groups", "<none>")
theZone.menuGroup = dcsCommon.trim(theZone.menuGroup)
elseif theZone:hasProperty("type") then
theZone.menuTypes = theZone:getStringFromZoneProperty("type", "none")
elseif theZone:hasProperty("types") then
theZone.menuTypes = theZone:getStringFromZoneProperty("types", "none")
end
-- always install this one
radioMenu.installMainMenu(theZone)
theZone.mainDone = true
if radioMenu.verbose or theZone.verbose then
trigger.action.outText("Main menu <" .. theZone.name .. "> processed", 30)
end
return true
end
-- --
-- Output processing -- Output processing
@ -546,17 +695,9 @@ end
function radioMenu.readConfigZone() function radioMenu.readConfigZone()
local theZone = cfxZones.getZoneByName("radioMenuConfig") local theZone = cfxZones.getZoneByName("radioMenuConfig")
if not theZone then if not theZone then
if radioMenu.verbose then
trigger.action.outText("+++radioMenu: NO config zone!", 30)
end
theZone = cfxZones.createSimpleZone("radioMenuConfig") theZone = cfxZones.createSimpleZone("radioMenuConfig")
end end
radioMenu.verbose = theZone:getBoolFromZoneProperty("verbose", false) radioMenu.verbose = theZone:getBoolFromZoneProperty("verbose", false)
if radioMenu.verbose then
trigger.action.outText("+++radioMenu: read config", 30)
end
end end
function radioMenu.start() function radioMenu.start()
@ -572,10 +713,45 @@ function radioMenu.start()
-- read config -- read config
radioMenu.readConfigZone() radioMenu.readConfigZone()
-- process radioMainMenu top-level zones
--local filtered = {}
local tries = 0
local attrZones = cfxZones.getZonesWithAttributeNamed("radioMainMenu")
-- set up all zones so they can 'reach up' even if not yet procced
for k, aZone in pairs (attrZones) do
radioMenu.addRadioMainMenu(aZone)
end
-- now process, and detect/break cyclic references
repeat
local filtered = {}
for k, aZone in pairs(attrZones) do
radioMenu.createRadioMainMenuWithZone(aZone) -- process attributes
if aZone.mainDone then
-- all good
else
-- wait for next round
table.insert(filtered, aZone)
end
end
done = dcsCommon.getSizeOfTable(filtered) < 1
tries = tries + 1
attrZones = filtered
until done or tries > 20
if tries > 20 then
local msg = "+++radioMenu: ERROR: Cyclic references in menu structure, can't fully process main menus. Unresolved main menus are:\n "
local c = 0
for idx, theZone in pairs(attrZones) do
if c > 0 then msg = msg .. ", " else c = 1 end
msg = msg .. theZone.name
end
trigger.action.outText(msg .. ".", 30)
end
-- process radioMenu Zones -- process radioMenu Zones
-- old style -- old style
local attrZones = cfxZones.getZonesWithAttributeNamed("radioMenu") local rmZones = cfxZones.getZonesWithAttributeNamed("radioMenu")
for k, aZone in pairs(attrZones) do for k, aZone in pairs(rmZones) do
radioMenu.createRadioMenuWithZone(aZone) -- process attributes radioMenu.createRadioMenuWithZone(aZone) -- process attributes
radioMenu.addRadioMenu(aZone) -- add to list radioMenu.addRadioMenu(aZone) -- add to list
end end
@ -595,4 +771,8 @@ end
--[[-- --[[--
check CD/standby code for multiple groups check CD/standby code for multiple groups
add/remove for mainmenu
visible/invisible for main menus
--]]-- --]]--

708
modules/reaper.lua Normal file
View File

@ -0,0 +1,708 @@
reaper = {}
reaper.version = "1.0.0"
reaper.requiredLibs = {
"dcsCommon",
"cfxZones",
}
--[[--
VERSION HISTORY
1.0.0 - Initial Version
--]]--
reaper.zones = {}-- all zones
reaper.scanning = {} -- zones that are scanning (looking for tgt). by zone name
reaper.tracking = {} -- zones that are tracking tgt. by zone name
reaper.scanInterval = 10 -- seconds
reaper.trackInterval = 0.3 -- seconds
-- reading reaper zones
function reaper.readReaperZone(theZone)
theZone.myType = string.lower(theZone:getStringFromZoneProperty("reaper", "reaper"))
if dcsCommon.stringStartsWith(theZone.myType, "pre") then theZone.myType = "RQ-1A Predator" else theZone.myType = "MQ-9 Reaper" end
if theZone.myType == "MQ-9 Reaper" then
theZone.alt = 9500
else theZone.alt = 7500 end theZone.alt = theZone:getNumberFromZoneProperty("alt", theZone.alt)
theZone.coa = theZone:getCoalitionFromZoneProperty("coalition", 2)
if theZone.coa == 0 then
trigger.action.outText("+++Reap: Zone <" .. theZone.name .. "> is of coalition NEUTRAL. Switched to BLUE", 30)
theZone.coa = 2
end
theZone.enemy = dcsCommon.getEnemyCoalitionFor(theZone.coa)
theZone.onStart = theZone:getBoolFromZoneProperty("onStart", true)
theZone.code = theZone:getNumberFromZoneProperty("code", 1688)
theZone.theTarget = nil
theZone.theSpot = nil
theZone.theUav = nil
theZone.theGroup = nil
theZone.doSmoke = theZone:getBoolFromZoneProperty("doSmoke", false)
theZone.smokeColor = theZone:getSmokeColorStringFromZoneProperty("smokeColor", "red")
theZone.smokeColor = dcsCommon.smokeColor2Num(theZone.smokeColor)
theZone.cost = theZone:getNumberFromZoneProperty("cost", 700) -- for bank integration
theZone.autoRespawn = theZone:getBoolFromZoneProperty("autoRespawn", false)
theZone.launchUI = theZone:getBoolFromZoneProperty("launchUI", true)
theZone.statusUI = theZone:getBoolFromZoneProperty("statusUI", true)
if theZone:hasProperty("launch?") then
theZone.launch = theZone:getStringFromZoneProperty("launch?", "<none>")
theZone.launchVal = theZone:getFlagValue(theZone.launch)
end
if theZone:hasProperty("status?") then
theZone.status = theZone:getStringFromZoneProperty("status?", "<none>")
theZone.statusVal = theZone:getFlagValue(theZone.status)
end
theZone.hasSpawned = false
if theZone.onStart then
reaper.spawnForZone(theZone)
end
end
-- spawn a drone from a zone
function reaper.spawnForZone(theZone, ack)
-- create spawn data
local gdata = dcsCommon.createEmptyGroundGroupData (dcsCommon.uuid(theZone.name))
gdata.task = "Reconnaissance"
gdata.route = {}
-- calculate left and right
local p = theZone:getPoint()
local left, right
-- use dml zone bounds to get upper left and lower right
if theZone.isPoly then
left = dcsCommon.clone(theZone.bounds.ll) -- tried ll
right = dcsCommon.clone(theZone.bounds.ur) --
else
left = dcsCommon.clone(theZone.bounds.ul)
right = dcsCommon.clone(theZone.bounds.lr)
end
gdata.x = left.x
gdata.y = left.z
-- build the unit data
local unit = {}
unit.name = dcsCommon.uuid(theZone.name)
unit.x = left.x
unit.y = left.z
unit.type = theZone.myType
unit.skill = "High"
if theZone.myType == "MQ-9 Reaper" then
unit.speed = 55
-- unit.alt = 9500
else
-- unit.alt = 7500
unit.speed = 33
end
unit.alt = theZone.alt
-- add to group
gdata.units[1] = unit
-- now create and add waypoints to route
gdata.route.points = {}
local wp1 = reaper.createInitialWP(left, unit.alt, unit.speed)
gdata.route.points[1] = wp1
local wp2 = dcsCommon.createSimpleRoutePointData(right, unit.alt, unit.speed)
gdata.route.points[2] = wp2
-- spawn the group
local cty = dcsCommon.getACountryForCoalition(theZone.coa)
local theGroup = coalition.addGroup(cty, 0, gdata)
if not theGroup then
trigger.action.outText("+++Reap: failed to spawn for zone <" .. theZone.name .. ">", 30)
return
end
if theZone.verbose or reaper.verbose then
trigger.action.outText("+++reap: Spawned <" .. theGroup:getName() .. "> reaper", 30)
end
if ack then
trigger.action.outTextForCoalition(theZone.coa, "Drone <" .. theZone.name .. "> on station", 30)
trigger.action.outSoundForCoalition(theZone.coa, reaper.actionSound)
end
reaper.cleanUp(theZone) -- dealloc anything still there
theZone.theGroup = theGroup
local uavs = theGroup:getUnits()
theZone.theUav = uavs[1]
theZone.theTarget = nil
theZone.theSpot = nil
theZone.hasSpawned = true
reaper.scanning[theZone.name] = theZone
end
function reaper.cleanUp(theZone)
if theZone.theUav and Unit.isExist(theZone.theUav) then
Unit.destroy(theZone.theUav)
end
theZone.theUav = nil
theZone.theTarget = nil
theZone.theGroup = nil
if theZone.theSpot then
Spot.destroy(theZone.theSpot)
end
theZone.theSpot = nil
end
function reaper.createInitialWP(p, alt, speed)
local wp = {
["alt"] = alt,
["action"] = "Turning Point",
["alt_type"] = "BARO",
["properties"] = {
["addopt"] = {}, -- end of ["addopt"]
}, -- end of ["properties"]
["speed"] = speed,
["task"] = {
["id"] = "ComboTask",
["params"] = {
["tasks"] = {
[1] = {
["enabled"] = true,
["auto"] = true,
["id"] = "WrappedAction",
["number"] = 1,
["params"] = {
["action"] = {
["id"] = "EPLRS",
["params"] = {
["value"] = true,
["groupId"] = 1,
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [1]
[2] = {
["enabled"] = true,
["auto"] = false,
["id"] = "Orbit",
["number"] = 2,
["params"] = {
["altitude"] = alt,
["pattern"] = "Race-Track",
["speed"] = speed,
}, -- end of ["params"]
}, -- end of [2]
}, -- end of ["tasks"]
}, -- end of ["params"]
}, -- end of ["task"]
["type"] = "Turning Point",
["ETA"] = 0,
["ETA_locked"] = true,
["y"] = p.z,
["x"] = p.x,
["speed_locked"] = true,
["formation_template"] = "",
} -- end of wp
return wp
end
-- scanning & tracking
-- scanning looks for vehicles to track, and exectues much less often
-- tracking tracks a single vehicle and places a pointer on it
function reaper.findFirstEnemyUnitVisible(enemies, theZone)
local p = theZone.theUav:getPoint()
-- we assume a flat altitude of 7000m
local visRange = theZone.alt * 1 -- based on tan(45) = 1 --> range = alt
for idx, aGroup in pairs(enemies) do
local theUnits = aGroup:getUnits()
-- optimization: only scan the first vehicle in group if it's in range local theUnit = theUnits[1]
local theUnit = theUnits[1]
if theUnit and Unit.isExist(theUnit) then
up = theUnit:getPoint()
d = dcsCommon.distFlat(up, p)
if d < visRange then
-- try each unit if it is visible from drone
for idy, aUnit in pairs(theUnits) do
local up = aUnit:getPoint()
up.y = up.y + 2
if land.isVisible(p, up) then return aUnit end
end
end
end
end
end
function reaper.scan()
-- how far can the drone see? we calculate with a 120 degree opening
-- camera lens, making half angle = 45 --> tan(45) = 1
-- so the radius of the visible circle on the ground is 1 * altidude
timer.scheduleFunction(reaper.scan, {}, timer.getTime() + reaper.scanInterval)
filtered = {}
local redEnemies = coalition.getGroups(2, 2) -- blue ground vehicles
local blueEnemeis = coalition.getGroups(1, 2) -- get ground vehicles
for name, theZone in pairs(reaper.scanning) do
local enemies = redEnemies
if theZone.coa == 2 then enemies = blueEnemeis end
if Unit.isExist(theZone.theUav) then
local theTarget = reaper.findFirstEnemyUnitVisible(enemies, theZone)
if theTarget then
-- add a laser tracker to this unit
local lp = theTarget:getPoint()
local lat, lon, alt = coord.LOtoLL(lp)
lat, lon = dcsCommon.latLon2Text(lat, lon)
local theSpot = Spot.createLaser(theZone.theUav, {0, 2, 0}, lp, theZone.code)
if theZone.doSmoke then
trigger.action.smoke(lp , theZone.smokeColor )
end
trigger.action.outTextForCoalition(theZone.coa, "Drone <" .. theZone.name .. "> is tracking a <" .. theTarget:getTypeName() .. "> at " .. lat .. " " .. lon .. ", code " .. theZone.code, 30)
trigger.action.outSoundForCoalition(theZone.coa, reaper.actionSound)
theZone.theTarget = theTarget
if theZone.theSpot then
theZone.theSpot:destroy()
end
theZone.theSpot = theSpot
-- put me in track mode
reaper.tracking[name] = theZone
else
-- will scan again
filtered[name] = theZone
end
else
-- does not remain
if theZone.verbose or reaper.verbose then
trigger.action.outText("+++reap: drone from <" .. theZone.name .. "> no longer exists", 30)
end
trigger.action.outTextForCoalition(theZone.coa, "Drone <" .. theZone.name .. "> lost.", 30)
trigger.action.outSoundForCoalition(theZone.coa, reaper.actionSound)
theZone.theUav = nil
theZone.theSpot = nil
theZone.theTarget = nil
theZone.theGroup = nil
end
end
reaper.scanning = filtered
end
function reaper.track()
local filtered = {}
for name, theZone in pairs(reaper.tracking) do
-- check if uav still alive
if Unit.isExist(theZone.theUav) then
if Unit.isExist(theZone.theTarget) then
-- update stop
local d = theZone.theTarget:getPoint()
theZone.theSpot:setPoint(d)
filtered[name] = theZone
else
trigger.action.outTextForCoalition(theZone.coa, "Drone <" .. theZone.name .. "> searching for new targets", 30)
trigger.action.outSoundForCoalition(theZone.coa, reaper.actionSound)
if theZone.theSpot then
theZone.theSpot:destroy()
end
theZone.theSpot = nil
reaper.scanning[name] = theZone -- back to scanning
end
else
trigger.action.outTextForCoalition(theZone.coa, "Drone <" .. theZone.name .. "> lost", 30)
trigger.action.outSoundForCoalition(theZone.coa, reaper.actionSound)
if theZone.theSpot then
theZone.theSpot:destroy()
end
theZone.theSpot = nil
theZone.theUav = nil
theZone.theGroup = nil
end
end
reaper.tracking = filtered
timer.scheduleFunction(reaper.track, {}, timer.getTime() + reaper.trackInterval)
end
function reaper.update()
timer.scheduleFunction(reaper.update, {}, timer.getTime() + 1)
-- go through all my zones, and respawn those that have no
-- uav but have autoRespawn active
for name, theZone in pairs(reaper.zones) do
if theZone.autoRespawn and not theZone.theUav and theZone.hasSpawned then
-- auto-respawn needs to kick in
reaper.scanning[name] = nil
reaper.tracking[name] = nil
if reaper.verbose or theZone.verbose then
trigger.action.outText("+++reap: respawning for <" .. name .. ">", 30)
end
reaper.spawnForZone(theZone)
end
if theZone.status and theZone:testZoneFlag(theZone.status, "change", "statusVal") then
if theZone.verbose then
trigger.action.outText("+++reap: Triggered status for zone <" .. name .. "> on <" .. theZone.status .. ">", 30)
end
reaper.doSingleDroneStatus(theZone)
end
if theZone.launch and theZone:testZoneFlag(theZone.launch, "change", "launchVal") then
args = {}
args[1] = theZone.coa -- = args[1]
args[2] = name -- = args[2]
reaper.doLaunch(args)
end
end
-- now poll my (global) status flags
if reaper.blueStatus and cfxZones.testZoneFlag(reaper, reaper.blueStatus, "change", "blueStatusVal") then
reaper.doDroneStatusBlue()
end
if reaper.redStatus and cfxZones.testZoneFlag(reaper, reaper.redStatus, "change", "redStatusVal") then
reaper.doDroneStatusRed()
end
end
--
-- UI
--
function reaper.installFullUIForCoa(coa)
-- install "Drone Control" as root for red and blue
local mainMenu = nil
if reaper.mainMenu then
mainMenu = radioMenu.getMainMenuFor(reaper.mainMenu) -- nilling both next params will return menus[0]
end
local root = missionCommands.addSubMenuForCoalition(coa, reaper.menuName, mainMenu)
-- now install submenus
local c1 = missionCommands.addCommandForCoalition(coa, "Drone Status", root, reaper.redirectDroneStatus, {coa,})
local r2 = missionCommands.addSubMenuForCoalition(coa, "Launch Drones", root)
reaper.installLaunchersForCoa(coa, r2)
end
function reaper.installLaunchersForCoa(coa, root)
-- WARNING: we currently install commands, may overflow!
-- trigger.action.outText("enter launchers builder", 30)
local filtered = {}
for name, theZone in pairs(reaper.zones) do
if theZone.coa == coa and theZone.launchUI then
filtered[name] = theZone
end
end
local n = dcsCommon.getSizeOfTable(filtered)
if n > 10 then
trigger.action.outText("+++reap: WARNING too many (" .. n .. ") launchers for coa <" .. coa .. ">", 30)
return
end
for name, theZone in pairs(filtered) do
-- trigger.action.outText("proccing " .. name, 30)
mnu = theZone.name .. ": " .. theZone.myType
if bank and reaper.useCost then
-- requires bank module
mnu = mnu .. "" .. theZone.cost .. ")"
end
local args = {coa, name, }
local r3 = missionCommands.addCommandForCoalition(coa, mnu, root, reaper.redirectLaunch, args)
end
end
function reaper.redirectDroneStatus(args)
timer.scheduleFunction(reaper.doDroneStatus, args, timer.getTime() + 0.1)
end
function reaper.redirectLaunch(args)
timer.scheduleFunction(reaper.doLaunch, args, timer.getTime() + 0.1)
end
--
-- DML API for UI
--
function reaper.doDroneStatusRed()
reaper.doDroneStatus({1,})
end
function reaper.doDroneStatusBlue()
reaper.doDroneStatus({2,})
end
function reaper.doDroneStatus(args)
local coa = args[1]
-- trigger.action.outText("enter do drone status for coa " .. coa, 30)
local done = {}
local msg = ""
local filtered = {}
for name, theZone in pairs(reaper.tracking) do
if theZone.coa == coa and theZone.statusUI then
filtered[name] = theZone
end
end
local n = dcsCommon.getSizeOfTable(filtered)
-- collect tracking drones
if n > 0 then
msg = msg .. "\nThe following drones are tracking targets:"
for name, theZone in pairs(filtered) do
msg = msg .. "\n <" .. name .. ">: "
local theTarget = theZone.theTarget
if theTarget and Unit.isExist(theTarget) then
local lp = theTarget:getPoint()
local lat, lon, alt = coord.LOtoLL(lp)
lat, lon = dcsCommon.latLon2Text(lat, lon)
local ut = theTarget:getTypeName()
msg = msg .. ut .. " at " .. lat .. ", " .. lon .. " code " .. theZone.code
else
msg = msg .. "<signal failure, please try later>"
end
done[name] = true
end
else
msg = msg .. "\n(No drones are tracking a target)\n"
end
-- collect loitering drones
filtered = {}
for name, theZone in pairs(reaper.scanning) do
if theZone.coa == coa and theZone.statusUI then filtered[name] = theZone end
end
n = dcsCommon.getSizeOfTable(filtered)
if n > 0 then
msg = msg .. "\n\nThe following drones are loitering on-station"
for name, theZone in pairs(filtered) do
msg = msg .. "\n <" .. name .. ">: (" .. theZone.myType .. ")"
done[name] = true
end
else
msg = msg .. "\n\n(No drones are loitering)\n"
end
filtered = {}
for name, theZone in pairs(reaper.zones) do
if theZone.coa == coa and theZone.statusUI and not done[name] then
filtered[name] = theZone
end
end
n = dcsCommon.getSizeOfTable(filtered)
if n > 0 then
msg = msg .. "\n\nThe following drones are ready to launch"
for name, theZone in pairs(filtered) do
msg = msg .. "\n <" .. name .. ">: " .. theZone.myType .. " "
if bank and reaper.useCost then
msg = msg .. "" .. theZone.cost .. ")"
end
end
msg = msg .. "\n"
else
msg = msg .. "\n\n(All drones have launched)\n"
end
trigger.action.outTextForCoalition(coa, msg, 30)
trigger.action.outSoundForCoalition(coa, reaper.actionSound)
end
function reaper.doSingleDroneStatus(theZone)
local coa = theZone.coa
-- trigger.action.outText("enter SINGLE drone status for coa " .. coa, 30)
local msg = ""
local name = theZone.name
-- see if drone is tracking
if reaper.tracking[name] and theZone.theTarget then
msg = "<" .. name .. ">: "
local theTarget = theZone.theTarget
if theTarget and Unit.isExist(theTarget) then
local lp = theTarget:getPoint()
local lat, lon, alt = coord.LOtoLL(lp)
lat, lon = dcsCommon.latLon2Text(lat, lon)
local ut = theTarget:getTypeName()
msg = msg .. ut .. " at " .. lat .. ", " .. lon .. " code " .. theZone.code
else
msg = msg .. "[signal failure, please try later]"
end
trigger.action.outTextForCoalition(coa, msg, 30)
trigger.action.outSoundForCoalition(coa, reaper.actionSound)
return
end
-- see if drone is loitering
if reaper.scanning[name] then
msg = "<" .. name .. ">: (" .. theZone.myType .. ") loitering, scanning for targets"
trigger.action.outTextForCoalition(coa, msg, 30)
trigger.action.outSoundForCoalition(coa, reaper.actionSound)
return
end
msg = "<" .. name .. ">: " .. theZone.myType .. " "
if bank and reaper.useCost then
msg = msg .. "" .. theZone.cost .. ") "
end
msg = msg .. "ready to launch"
trigger.action.outTextForCoalition(coa, msg, 30)
trigger.action.outSoundForCoalition(coa, reaper.actionSound)
end
function reaper.doLaunch(args)
coa = args[1]
name = args[2]
-- check if we can launch
local theZone = reaper.zones[name]
if not theZone then
trigger.action.outText("+++reap: something strange happened with launcher <" .. name .. ">", 30)
return
end
if theZone.theUav and Unit.isExist(theZone.theUav) then
trigger.action.outTextForCoalition(coa, "Drone <" .. name .. "> is already on-station", 30)
trigger.action.outSoundForCoalition(coa, reaper.actionSound)
return
end
local hasBalance, amount = 0, 0
-- money check if enabled
if bank and reaper.useCost then
hasBalance, amount = bank.getBalance(coa)
if not hasBalance then
amount = 0
end
if amount < theZone.cost then
trigger.action.outTextForCoalition(coa, "Insufficient funds (§" .. theZone.cost .. " required, you have §" .. amount, 30)
trigger.action.outSoundForCoalition(coa, reaper.actionSound)
return
end
end
-- ok, go for launch
reaper.spawnForZone(theZone)
-- subtract funds
if bank and reaper.useCost then
trigger.action.outTextForCoalition(coa, "Launching <" .. theZone.myType .. "> drone for §" .. theZone.cost .. ", §" .. amount - theZone.cost .. " remaining.", 30)
bank.withdawFunds(coa, theZone.cost)
else
trigger.action.outTextForCoalition(coa, "Launching <" .. theZone.myType .. "> drone.", 30)
end
trigger.action.outSoundForCoalition(coa, reaper.actionSound)
end
--
-- config
--
function reaper.readConfigZone()
local theZone = cfxZones.getZoneByName("reaperConfig")
if not theZone then
theZone = cfxZones.createSimpleZone("reaperConfig")
end
reaper.name = "reaperConfig" -- zones comaptibility
reaper.actionSound = theZone:getStringFromZoneProperty("actionSound", "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav")
reaper.UI = theZone:getBoolFromZoneProperty("UI", true)
reaper.menuName = theZone:getStringFromZoneProperty("menuName", "Drone Command")
reaper.useCost = theZone:getBoolFromZoneProperty("useCost", true)
if theZone:hasProperty("blueStatus?") then
reaper.blueStatus = theZone:getStringFromZoneProperty("blueStatus?", "<none>")
reaper.blueStatusVal = theZone:getFlagValue(reaper.blueStatus) -- save last value
end
if theZone:hasProperty("redStatus?") then
reaper.redStatus = theZone:getStringFromZoneProperty("redStatus?", "<none>")
reaper.redStatusVal = theZone:getFlagValue(reaper.redStatus) -- save last value
end
if theZone:hasProperty("attachTo:") then
local attachTo = theZone:getStringFromZoneProperty("attachTo:", "<none>")
if radioMenu then
local mainMenu = radioMenu.mainMenus[attachTo]
if mainMenu then
reaper.mainMenu = mainMenu
else
trigger.action.outText("+++reaper: cannot find super menu <" .. attachTo .. ">", 30)
end
else
trigger.action.outText("+++reaper: REQUIRES radioMenu to run before reaper. 'AttachTo:' ignored.", 30)
end
end
reaper.verbose = theZone.verbose
end
-- persistence
function reaper.saveData()
local theData = {}
-- save all non-self-starting, yet running reapers
local running = {}
for name, theZone in pairs (reaper.zones) do
if (not theZone.onStart) and (theZone.theUav)
and Unit.isExist(theZone.theUav) then
running[name] = true
end
end
theData.running = running
return theData, reaper.sharedData
end
function reaper.loadData()
if not persistence then return end
local theData = persistence.getSavedDataForModule("reaper", reaper.sharedData)
if not theData then
if reaper.verbose then
trigger.action.outText("+++reaper: no save data received, skipping.", 30)
end
return
end
local running = theData.running
if theData.running then
for name, ignore in pairs (running) do
local theZone = reaper.zones[name]
if theZone then
reaper.spawnForZone(theZone)
else
trigger.action.outText("+++reaper - persistence: zone <" .. name .. "> does not exist", 30)
end
end
end
end
-- go go go
function reaper.start()
if not dcsCommon.libCheck then
trigger.action.outText("cfx reaper requires dcsCommon", 30)
return false
end
if not dcsCommon.libCheck("cfx reaper", reaper.requiredLibs) then
return false
end
-- read config
reaper.readConfigZone()
-- read reaper zones
local rZones = cfxZones.zonesWithProperty("reaper")
for k, aZone in pairs(rZones) do
reaper.readReaperZone(aZone)
reaper.zones[aZone.name] = aZone
end
-- install UI if desired
if reaper.UI then
local coas = {1, 2}
for idx, coa in pairs(coas) do
reaper.installFullUIForCoa(coa)
end
end
-- load data if persisted
if persistence then
-- sign up for persistence
callbacks = {}
callbacks.persistData = reaper.saveData
persistence.registerModule("reaper", callbacks)
-- now load my data
reaper.loadData()
end
-- schedule first update
timer.scheduleFunction(reaper.update, {}, timer.getTime() + 1)
-- schedule scan and track loops
timer.scheduleFunction(reaper.scan, {}, timer.getTime() + 1)
timer.scheduleFunction(reaper.track, {}, timer.getTime() + 1)
trigger.action.outText("reaper v " .. reaper.version .. " running.", 30)
return true
end
if not reaper.start() then
trigger.action.outText("Reaper failed to start", 30)
end
--[[--
Idea: mobile launch vehicle, zone follows apc around. Can even be hauled along with hook
idea: prioritizing targets in a group
fix quad zone waypoints
filter targets for lasing by list?
--]]--

View File

@ -1,5 +1,5 @@
scribe = {} scribe = {}
scribe.version = "1.1.0" scribe.version = "2.0.0"
scribe.requiredLibs = { scribe.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
"cfxZones", -- Zones, of course "cfxZones", -- Zones, of course
@ -12,6 +12,7 @@ VERSION HISTORY
1.0.0 Initial Version 1.0.0 Initial Version
1.0.1 postponed land, postponed takeoff, unit_lost 1.0.1 postponed land, postponed takeoff, unit_lost
1.1.0 supports persistence's SHARED ability to share data across missions 1.1.0 supports persistence's SHARED ability to share data across missions
2.0.0 support for main menu
--]]-- --]]--
scribe.verbose = true scribe.verbose = true
scribe.db = {} -- indexed by player name scribe.db = {} -- indexed by player name
@ -488,6 +489,12 @@ function scribe.startPlayerGUI()
-- note: currently assumes single-player groups -- note: currently assumes single-player groups
-- in preparation of single-player 'commandForUnit' -- in preparation of single-player 'commandForUnit'
-- ASSUMES SINGLE-UNIT PLAYER GROUPS! -- ASSUMES SINGLE-UNIT PLAYER GROUPS!
local mainMenu = nil
if scribe.mainMenu then
mainMenu = radioMenu.getMainMenuFor(scribe.mainMenu) -- nilling both next params will return menus[0]
end
for uName, uData in pairs(cfxMX.playerUnitByName) do for uName, uData in pairs(cfxMX.playerUnitByName) do
local unitInfo = {} local unitInfo = {}
-- try and access each unit even if we know that the -- try and access each unit even if we know that the
@ -508,7 +515,7 @@ function scribe.startPlayerGUI()
unitInfo.uID = uData.unitId unitInfo.uID = uData.unitId
unitInfo.theType = theType unitInfo.theType = theType
unitInfo.cat = cfxMX.groupTypeByName[gName] unitInfo.cat = cfxMX.groupTypeByName[gName]
unitInfo.root = missionCommands.addSubMenuForGroup(unitInfo.gID, scribe.uiMenu) unitInfo.root = missionCommands.addSubMenuForGroup(unitInfo.gID, scribe.uiMenu, mainMenu)
unitInfo.checkData = missionCommands.addCommandForGroup(unitInfo.gID, "Get Pilot's Statistics", unitInfo.root, scribe.redirectCheckData, unitInfo) unitInfo.checkData = missionCommands.addCommandForGroup(unitInfo.gID, "Get Pilot's Statistics", unitInfo.root, scribe.redirectCheckData, unitInfo)
end end
end end
@ -524,6 +531,24 @@ function scribe.readConfigZone()
scribe.verbose = theZone.verbose scribe.verbose = theZone.verbose
scribe.hasGUI = theZone:getBoolFromZoneProperty("hasGUI", true) scribe.hasGUI = theZone:getBoolFromZoneProperty("hasGUI", true)
scribe.uiMenu = theZone:getStringFromZoneProperty("uiMenu", "Mission Logbook") scribe.uiMenu = theZone:getStringFromZoneProperty("uiMenu", "Mission Logbook")
scribe.name = "scribeConfig" -- zones comaptibility
if theZone:hasProperty("attachTo:") then
local attachTo = theZone:getStringFromZoneProperty("attachTo:", "<none>")
if radioMenu then
local mainMenu = radioMenu.mainMenus[attachTo]
if mainMenu then
scribe.mainMenu = mainMenu
else
trigger.action.outText("+++scribe: cannot find super menu <" .. attachTo .. ">", 30)
end
else
trigger.action.outText("+++scribe: REQUIRES radioMenu to run before scribe. 'AttachTo:' ignored.", 30)
end
end
scribe.greetPlayer = theZone:getBoolFromZoneProperty("greetPlayer", true) scribe.greetPlayer = theZone:getBoolFromZoneProperty("greetPlayer", true)
scribe.byePlayer = theZone:getBoolFromZoneProperty("byebyePlayer", true) scribe.byePlayer = theZone:getBoolFromZoneProperty("byebyePlayer", true)
scribe.landings = theZone:getBoolFromZoneProperty("landings", true) scribe.landings = theZone:getBoolFromZoneProperty("landings", true)

View File

@ -467,7 +467,7 @@ function unitPersistence.loadMission()
end end
if mismatchWarning then if mismatchWarning then
trigger.action.outText("\n+++WARNING: \nSaved data does not match mission. You should re-start from scratch\n", 30) trigger.action.outText("\n+++WARNING: \nSaved unit data does not match mission. You should re-start from scratch\n", 30)
end end
-- set mission according to data received from last save -- set mission according to data received from last save
if unitPersistence.verbose then if unitPersistence.verbose then

View File

@ -1,5 +1,5 @@
williePete = {} williePete = {}
williePete.version = "2.0.2" williePete.version = "2.0.3"
williePete.ups = 10 -- we update at 10 fps, so accuracy of a williePete.ups = 10 -- we update at 10 fps, so accuracy of a
-- missile moving at Mach 2 is within 33 meters, -- missile moving at Mach 2 is within 33 meters,
-- with interpolation even at 3 meters -- with interpolation even at 3 meters
@ -19,6 +19,7 @@ williePete.requiredLibs = {
- getFirstLivingPlayerInGroupNamed() - getFirstLivingPlayerInGroupNamed()
2.0.1 - added Harrier's FFAR M156 WP 2.0.1 - added Harrier's FFAR M156 WP
2.0.2 - hardened playerUpdate() 2.0.2 - hardened playerUpdate()
2.0.3 - further hardened playerUpdate()
--]]-- --]]--
williePete.willies = {} williePete.willies = {}
@ -635,7 +636,7 @@ function williePete.playerUpdate()
end end
end end
else else
trigger.action.outText("+++wp: strange issues with group <" .. gName .. ">, does not exist. Skipped in playerUpdate()", 30) trigger.action.outText("+++wp: strange issues with group <" .. unitInfo.gName .. ">, does not exist. Skipped in playerUpdate()", 30)
end end
if dropUnit then if dropUnit then
-- all outside, remove from zone check-in -- all outside, remove from zone check-in

View File

@ -1,5 +1,5 @@
wiper = {} wiper = {}
wiper.version = "1.2.0" wiper.version = "1.3.0"
wiper.verbose = false wiper.verbose = false
wiper.ups = 1 wiper.ups = 1
wiper.requiredLibs = { wiper.requiredLibs = {
@ -15,6 +15,9 @@ wiper.wipers = {}
- categories can now be a list - categories can now be a list
- declutter opetion - declutter opetion
- if first category is 'none', zone will not wipe at all but may declutter - if first category is 'none', zone will not wipe at all but may declutter
1.3.0 - now warns when wiper zone is polygonal
- enhanced verbosity for dictionary setups
- now warns of possible incompatibility with cloners
--]]-- --]]--
@ -39,6 +42,11 @@ end
function wiper.createWiperWithZone(theZone) function wiper.createWiperWithZone(theZone)
theZone.triggerWiperFlag = theZone:getStringFromZoneProperty("wipe?", "*<none>") theZone.triggerWiperFlag = theZone:getStringFromZoneProperty("wipe?", "*<none>")
-- see if the zone also has a 'cloner' attribute, and warn
if theZone:hasProperty("cloner") then
trigger.action.outText("+++Wpr: Zone <" .. theZone.name .. "> is also a CLONER - check usage of 'wipe?' attribute.", 30)
end
-- triggerWiperMethod -- triggerWiperMethod
theZone.triggerWiperMethod = theZone:getStringFromZoneProperty("triggerMethod", "change") theZone.triggerWiperMethod = theZone:getStringFromZoneProperty("triggerMethod", "change")
if theZone:hasProperty("triggerWiperMethod") then if theZone:hasProperty("triggerWiperMethod") then
@ -98,7 +106,7 @@ function wiper.createWiperWithZone(theZone)
end end
theDict[shortName] = ew theDict[shortName] = ew
if wiper.verbose or theZone.verbose then if wiper.verbose or theZone.verbose then
trigger.action.outText("+++wpr: dict [".. shortName .."], '*' = " .. dcsCommon.bool2Text(ew) .. " for <" .. theZone:getName() .. ">",30) trigger.action.outText("+++wpr: dict [".. shortName .."], '*' = " .. dcsCommon.bool2Text(ew) .. " successful for <" .. theZone:getName() .. ">",30)
end end
end end
theZone.wipeNamed = theDict theZone.wipeNamed = theDict
@ -106,6 +114,10 @@ function wiper.createWiperWithZone(theZone)
theZone.wipeInventory = theZone:getBoolFromZoneProperty("wipeInventory", false) theZone.wipeInventory = theZone:getBoolFromZoneProperty("wipeInventory", false)
if theZone.isPoly then
trigger.action.outText("+++wpr: WARNING: wiper zone <" .. theZone.name .. "> is NOT CIRCULAR, but quad-based. Expect erratic behavior!", 30)
end
if wiper.verbose or theZone.verbose then if wiper.verbose or theZone.verbose then
trigger.action.outText("+++wpr: new wiper zone <".. theZone.name ..">", 30) trigger.action.outText("+++wpr: new wiper zone <".. theZone.name ..">", 30)
end end

Binary file not shown.