mirror of
https://github.com/weyne85/DML.git
synced 2025-10-29 16:57:49 +00:00
Version 1.2.3
New ASW modules Lots of maintenance fixes
This commit is contained in:
parent
654b782894
commit
dc81decee6
Binary file not shown.
Binary file not shown.
983
modules/asw.lua
Normal file
983
modules/asw.lua
Normal file
@ -0,0 +1,983 @@
|
|||||||
|
asw = {}
|
||||||
|
asw.version = "1.0.0"
|
||||||
|
asw.verbose = false
|
||||||
|
asw.requiredLibs = {
|
||||||
|
"dcsCommon", -- always
|
||||||
|
"cfxZones", -- Zones, of course
|
||||||
|
}
|
||||||
|
asw.ups = 0.1 -- = once every 10 seconds
|
||||||
|
asw.buoys = {} -- all buoys, by name
|
||||||
|
asw.torpedoes = {} -- all torpedoes in the water.
|
||||||
|
asw.thumpers = {} -- all current sonar amplifiers/booms that are active
|
||||||
|
asw.fixes = {} -- all subs that we have a fix on. indexed by sub name
|
||||||
|
-- fixname encodes the coalition of the fix in "/<coanum>"
|
||||||
|
|
||||||
|
--[[--
|
||||||
|
Version History
|
||||||
|
1.0.0 - initial version
|
||||||
|
|
||||||
|
--]]--
|
||||||
|
|
||||||
|
--
|
||||||
|
-- :::WARNING:::
|
||||||
|
-- CURRENTLY NOT CHECKING FOR COALITIONS
|
||||||
|
--
|
||||||
|
|
||||||
|
function asw.createTorpedo()
|
||||||
|
local t = {}
|
||||||
|
t.lifeTimer = timer.getTime() + asw.torpedoLife
|
||||||
|
t.speed = asw.torpedoSpeed
|
||||||
|
t.state = 0; -- not yet released. FSM
|
||||||
|
t.name = dcsCommon.uuid("asw.t")
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
function asw.createTorpedoForUnit(theUnit)
|
||||||
|
local t = asw.createTorpedo()
|
||||||
|
t.coalition = theUnit:getCoalition()
|
||||||
|
t.point = theUnit:getPoint()
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
function asw.createTorpedoForZone(theZone)
|
||||||
|
local t = asw.createTorpedo()
|
||||||
|
t.coalition = theZone.coalition
|
||||||
|
t.point = cfxZones.getPoint(theZone)
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
function asw.createBuoy()
|
||||||
|
local b = {}
|
||||||
|
b.markID = dcsCommon.numberUUID() -- buoy mark
|
||||||
|
b.coalition = 0
|
||||||
|
b.point = nil
|
||||||
|
b.smokeTimer = timer.getTime() + 5 * 60 -- for refresh
|
||||||
|
b.smokeColor = nil --
|
||||||
|
b.lifeTimer = timer.getTime() + asw.buoyLife
|
||||||
|
b.contacts = {} -- detected contacts in range. by unit name
|
||||||
|
b.timeStamps = {}
|
||||||
|
b.bearing = {} -- bearing to contact
|
||||||
|
b.lines = {} -- line art for contact (wedges)
|
||||||
|
b.lastContactNum = 0
|
||||||
|
b.lastReportedIn = 0 -- time of last report
|
||||||
|
return b
|
||||||
|
end
|
||||||
|
|
||||||
|
function asw.createBuoyForUnit(theUnit)
|
||||||
|
-- theUnit drops buoy, making it belong to the same coalition
|
||||||
|
-- as the dropping unit
|
||||||
|
local b = asw.createBuoy()
|
||||||
|
b.point = theUnit:getPoint()
|
||||||
|
b.point.y = 0
|
||||||
|
b.coalition = theUnit:getCoalition()
|
||||||
|
b.smokeColor = asw.smokeColor -- needs to be done later
|
||||||
|
b.name = dcsCommon.uuid("asw-b." .. theUnit:getName())
|
||||||
|
return b
|
||||||
|
end
|
||||||
|
|
||||||
|
function asw.createBuoyForZone(theZone)
|
||||||
|
-- theZone drops buoy (if zone isn't linked to unit)
|
||||||
|
-- making it belong to the same coalition
|
||||||
|
-- as the dropping unit
|
||||||
|
local theUnit = cfxZones.getLinkedUnit(theZone)
|
||||||
|
if theUnit then
|
||||||
|
b = asw.createBuoyForUnit(theUnit)
|
||||||
|
return b
|
||||||
|
end
|
||||||
|
|
||||||
|
local b = asw.createBuoy()
|
||||||
|
b.point = cfxZones.getPoint(theZone)
|
||||||
|
b.point.y = 0
|
||||||
|
b.coalition = theZone.coalition
|
||||||
|
b.smokeColor = asw.smokeColor -- needs to be done later
|
||||||
|
b.name = dcsCommon.uuid("asw-b." .. theZone.name)
|
||||||
|
return b
|
||||||
|
end
|
||||||
|
|
||||||
|
-- uid generation for this module.
|
||||||
|
asw.ccounter = 0 -- init to preferred value
|
||||||
|
asw.ccinc = 1 -- init to preferred increment
|
||||||
|
function asw.contactCount()
|
||||||
|
asw.ccounter = asw.ccounter + asw.ccinc
|
||||||
|
return asw.ccounter
|
||||||
|
end
|
||||||
|
|
||||||
|
function asw.createFixForSub(theUnit, theCoalition)
|
||||||
|
if not theCoalition then
|
||||||
|
trigger.action.outText("+++ASW: createFix without coalition, assuming BLUE", 30)
|
||||||
|
theCoalition = 2
|
||||||
|
end
|
||||||
|
|
||||||
|
local now = timer.getTime()
|
||||||
|
local f = {}
|
||||||
|
f.coalition = theCoalition
|
||||||
|
f.theUnit = theUnit
|
||||||
|
if theCoalition == theUnit:getCoalition() then
|
||||||
|
trigger.action.outText("+++ASW: createFix - theUnit <" .. theUnit:getName() .. "> has same coalition than detection side (" .. theCoalition .. ")", 30)
|
||||||
|
end
|
||||||
|
|
||||||
|
f.name = theUnit:getName()
|
||||||
|
f.typeName = theUnit:getTypeName()
|
||||||
|
f.desig = "SC-" .. asw.contactCount()
|
||||||
|
f.lifeTimer = now + asw.fixLife -- will be renewed whenever we hit enough signal strength
|
||||||
|
f.lines = 0
|
||||||
|
return f
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- dropping buoys, torpedos and thumpers
|
||||||
|
--
|
||||||
|
|
||||||
|
function asw.dropBuoyFrom(theUnit)
|
||||||
|
if not theUnit or not Unit.isExist(theUnit) then return end
|
||||||
|
-- make sure we do not drop over land
|
||||||
|
local p3 = theUnit:getPoint()
|
||||||
|
local p2 = {x=p3.x, y=p3.z}
|
||||||
|
local lType = land.getSurfaceType(p2)
|
||||||
|
if lType ~= 3 then
|
||||||
|
if asw.verbose then
|
||||||
|
trigger.action.outText("+++aswZ: ASW counter-measures must be dropped over open water, not <" .. lType .. ">. Aborting deployment for <" .. theUnit:getName() .. "> failed, counter-measure lost", 30)
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local now = timer.getTime()
|
||||||
|
-- create buoy
|
||||||
|
local theBuoy = asw.createBuoyForUnit(theUnit)
|
||||||
|
|
||||||
|
-- mark point
|
||||||
|
dcsCommon.markPointWithSmoke(theBuoy.point, theBuoy.smokeColor)
|
||||||
|
theBuoy.smokeTimer = now + 5 * 60
|
||||||
|
|
||||||
|
-- add buoy to my inventory
|
||||||
|
asw.buoys[theBuoy.name] = theBuoy
|
||||||
|
|
||||||
|
-- mark on map
|
||||||
|
local info = "Buoy dropped by " .. theUnit:getName() .. " at " .. dcsCommon.nowString()
|
||||||
|
trigger.action.markToCoalition(theBuoy.markID, info, theUnit:getPoint(), theBuoy.coalition, true, "")
|
||||||
|
if asw.verbose then
|
||||||
|
trigger.action.outText("Dropping buoy " .. theBuoy.name, 30)
|
||||||
|
end
|
||||||
|
return theBuoy
|
||||||
|
end
|
||||||
|
|
||||||
|
function asw.dropBuoyFromZone(theZone)
|
||||||
|
-- trigger.action.outText("enter asw.dropBuoyFromZone <" .. theZone.name .. ">", 30)
|
||||||
|
local theUnit = cfxZones.getLinkedUnit(theZone)
|
||||||
|
if theUnit and Unit.isExist(theUnit)then
|
||||||
|
return asw.dropBuoyFrom(theUnit)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- try and set the zone's coalition by the unit that
|
||||||
|
-- it is following
|
||||||
|
local coa = cfxZones.getLinkedUnit(theZone)
|
||||||
|
if coa then
|
||||||
|
theZone.coalition = coa
|
||||||
|
end
|
||||||
|
|
||||||
|
if not theZone.coalition or theZone.coalition == 0 then
|
||||||
|
trigger.action.outText("+++aswZ: 0 coalition for aswZone <" .. theZone.name .. ">, aborting buoy drop.", 30)
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- make sure we do not drop over land
|
||||||
|
local p3 = cfxZones.getPoint(theZone)
|
||||||
|
local p2 = {x=p3.x, y=p3.z}
|
||||||
|
local lType = land.getSurfaceType(p2)
|
||||||
|
if lType ~= 3 then
|
||||||
|
if asw.verbose then
|
||||||
|
trigger.action.outText("+++aswZ: asw measures must be dropped over open water, not <" .. lType .. ">. Aborting deployment for <" .. theZone.name .. ">", 30)
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local now = timer.getTime()
|
||||||
|
-- create buoy
|
||||||
|
local theBuoy = asw.createBuoyForZone(theZone)
|
||||||
|
|
||||||
|
-- mark point
|
||||||
|
dcsCommon.markPointWithSmoke(theBuoy.point, theBuoy.smokeColor)
|
||||||
|
theBuoy.smokeTimer = now + 5 * 60
|
||||||
|
|
||||||
|
-- add buoy to my inventory
|
||||||
|
asw.buoys[theBuoy.name] = theBuoy
|
||||||
|
|
||||||
|
-- mark on map
|
||||||
|
local info = "Buoy dropped by " .. theZone.name .. " at " .. dcsCommon.nowString()
|
||||||
|
local pos = cfxZones.getPoint(theZone)
|
||||||
|
trigger.action.markToCoalition(theBuoy.markID, info, pos, theBuoy.coalition, true, "")
|
||||||
|
if asw.verbose then
|
||||||
|
trigger.action.outText("Dropping buoy " .. theBuoy.name, 30)
|
||||||
|
end
|
||||||
|
return theBuoy
|
||||||
|
end
|
||||||
|
|
||||||
|
function asw.dropTorpedoFrom(theUnit)
|
||||||
|
if not theUnit or not Unit.isExist(theUnit) then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local p3 = theUnit:getPoint()
|
||||||
|
local p2 = {x=p3.x, y=p3.z}
|
||||||
|
local lType = land.getSurfaceType(p2)
|
||||||
|
if lType ~= 3 then
|
||||||
|
if asw.verbose then
|
||||||
|
trigger.action.outText("+++aswZ: sub counter-measures must be dropped over open water, not <" .. lType .. ">. Aborting deployment for <" .. theUnit:getName() .. "> failed, counter-measure lost", 30)
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local t = asw.createTorpedoForUnit(theUnit)
|
||||||
|
-- add to inventory
|
||||||
|
asw.torpedoes[t.name] = t
|
||||||
|
if asw.verbose then
|
||||||
|
trigger.action.outText("Launching torpedo " .. t.name, 30)
|
||||||
|
end
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
function asw.dropTorpedoFromZone(theZone)
|
||||||
|
local theUnit = cfxZones.getLinkedUnit(theZone)
|
||||||
|
if theUnit then
|
||||||
|
return asw.dropTorpedoFrom(theUnit)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- try and set the zone's coalition by the unit that
|
||||||
|
-- it is following
|
||||||
|
local coa = cfxZones.getLinkedUnit(theZone)
|
||||||
|
if coa then
|
||||||
|
theZone.coalition = coa
|
||||||
|
end
|
||||||
|
|
||||||
|
if not theZone.coalition or theZone.coalition == 0 then
|
||||||
|
trigger.action.outText("+++aswZ: 0 coalition for aswZone <" .. theZone.name .. ">, aborting torpedo drop.", 30)
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- make sure we do not drop over land
|
||||||
|
local p3 = cfxZones.getPoint(theZone)
|
||||||
|
local p2 = {x=p3.x, y=p3.z}
|
||||||
|
local lType = land.getSurfaceType(p2)
|
||||||
|
if lType ~= 3 then
|
||||||
|
if asw.verbose then
|
||||||
|
trigger.action.outText("+++aswZ: asw measures must be dropped over open water, not <" .. lType .. ">. Aborting deployment for <" .. theZone.name .. ">", 30)
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local t = asw.createTorpedoForZone(theZone)
|
||||||
|
-- add to inventory
|
||||||
|
asw.torpedoes[t.name] = t
|
||||||
|
if asw.verbose then
|
||||||
|
trigger.action.outText("Launching torpedo for zone", 30)
|
||||||
|
end
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- UPDATE
|
||||||
|
--
|
||||||
|
function asw.getClosestFixTo(loc, coalition)
|
||||||
|
local dist = math.huge
|
||||||
|
local closestFix = nil
|
||||||
|
for fixName, theFix in pairs(asw.fixes) do
|
||||||
|
if theFix.coalition == coalition then
|
||||||
|
local theUnit = theFix.theUnit
|
||||||
|
if Unit.isExist(theUnit) then
|
||||||
|
pos = theUnit:getPoint()
|
||||||
|
d = dcsCommon.distFlat(loc, pos)
|
||||||
|
if d < dist then
|
||||||
|
dist = d
|
||||||
|
closestFix = theFix
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return closestFix, dist
|
||||||
|
end
|
||||||
|
|
||||||
|
function asw.getClosestSubToLoc(loc, allSubs)
|
||||||
|
local dist = math.huge
|
||||||
|
local closestSub = nil
|
||||||
|
for cName, contact in pairs(allSubs) do
|
||||||
|
if Unit.isExist(contact.theUnit) then
|
||||||
|
d = dcsCommon.distFlat(loc, contact.theUnit:getPoint())
|
||||||
|
if d < dist then
|
||||||
|
closestSub = contact.theUnit
|
||||||
|
dist = d
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return closestSub, dist
|
||||||
|
end
|
||||||
|
|
||||||
|
function asw.wedgeForBuoyAndContact(theBuoy, aName, p)
|
||||||
|
--env.info(" >enter wedge for buoy/contact: <" .. theBuoy.name .. ">/< .. aName .. >, p= " .. p)
|
||||||
|
if p > 1 then p = 1 end
|
||||||
|
theBuoy.lines[aName] = dcsCommon.numberUUID()
|
||||||
|
local shape = theBuoy.lines[aName]
|
||||||
|
local p1 = theBuoy.point
|
||||||
|
local deviant = asw.maxDeviation * (1-p) -- get percentage of max dev
|
||||||
|
local minDev = math.floor(5 + (deviant * 0.2)) -- one fifth + 5 is fixed
|
||||||
|
local varDev = math.floor(deviant * 0.8) -- four fifth is variable
|
||||||
|
--env.info(" |will now calculate leftD and rightD")
|
||||||
|
local leftD = math.floor(minDev + varDev * math.random()) -- dcsCommon.smallRandom(varDev) -- varDev * math.random()
|
||||||
|
local rightD = math.floor(minDev + varDev * math.random()) -- dcsCommon.smallRandom(varDev) -- varDev * math.random()
|
||||||
|
--env.info(" |will now calculate p2 and p3")
|
||||||
|
local p2 = dcsCommon.newPointAtDegreesRange(p1, theBuoy.bearing[aName] - leftD, asw.maxDetectionRange)
|
||||||
|
local p3 = dcsCommon.newPointAtDegreesRange(p1, theBuoy.bearing[aName] + rightD, asw.maxDetectionRange)
|
||||||
|
--env.info(" |will now create wedge <" .. shape .. "> ")
|
||||||
|
trigger.action.markupToAll(7, theBuoy.coalition, shape, p1, p2, p3, p1, {1, 0, 0, 0.25}, {1, 0, 0, 0.05}, 4, true, "Contact " .. tonumber(shape))
|
||||||
|
--env.info(" <complete, leaving wedge for buoy/contact: <" .. theBuoy.name .. ">/< .. aName .. >")
|
||||||
|
end
|
||||||
|
|
||||||
|
function asw.updateBuoy(theBuoy, allSubs)
|
||||||
|
--env.info(" >>enter update buoy for " .. theBuoy.name)
|
||||||
|
-- note: buoys never see subs of their own side since it is
|
||||||
|
-- assumed that their location is known and filtered
|
||||||
|
if not theBuoy then return false end
|
||||||
|
|
||||||
|
-- allSubs are all possible contacts
|
||||||
|
local now = timer.getTime()
|
||||||
|
if now > theBuoy.lifeTimer then
|
||||||
|
--env.info(" lifetime ran out")
|
||||||
|
-- buoy timed out: remove mark
|
||||||
|
if asw.verbose then
|
||||||
|
trigger.action.outText("+++ASW: removing mark <" .. theBuoy.markID .. "> for buoy <" .. theBuoy.name .. ">", 30)
|
||||||
|
end
|
||||||
|
--env.info(" - will remove mark " .. theBuoy.markID)
|
||||||
|
trigger.action.removeMark(theBuoy.markID)
|
||||||
|
--env.info(" - removed mark")
|
||||||
|
-- now also remove all wedges
|
||||||
|
for name, wedge in pairs(theBuoy.lines) do
|
||||||
|
if asw.verbose then
|
||||||
|
trigger.action.outText("+++ASW: removing wedge mark <" .. wedge .. "> for sub <" .. name .. ">", 30)
|
||||||
|
end
|
||||||
|
--env.info(" - will remove wedge " .. wedge)
|
||||||
|
trigger.action.removeMark(wedge)
|
||||||
|
end
|
||||||
|
--env.info(" <<updateBuoy, returning false")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- buoy is alive!
|
||||||
|
-- see if we need to resmoke
|
||||||
|
if now > theBuoy.smokeTimer then
|
||||||
|
--env.info(" resmoking buoy, continue")
|
||||||
|
dcsCommon.markPointWithSmoke(theBuoy.point, theBuoy.smokeColor)
|
||||||
|
theBuoy.smokeTimer = now + 5 * 60
|
||||||
|
--env.info(" resmoke done, continue")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- check all contacts, skip own coalition subs
|
||||||
|
-- check signal strength to all subs
|
||||||
|
local newContacts = {} -- as opposed to already in theBuoy.contacts
|
||||||
|
--env.info(" :iterating allSubs for contacts")
|
||||||
|
for contactName, contact in pairs (allSubs) do
|
||||||
|
if contact.coalition ~= theBuoy.coalition then -- not on our side
|
||||||
|
local theSub = contact.theUnit
|
||||||
|
local theSubLoc = theSub:getPoint()
|
||||||
|
local theSubName = contact.name
|
||||||
|
local p = 0 -- detection probability
|
||||||
|
local canDetect = false
|
||||||
|
local sureDetect = false
|
||||||
|
local depth = -dcsCommon.getUnitAGL(theSub) -- NOTE: INVERTED!!
|
||||||
|
if depth > 5 and depth < asw.maxDetectionDepth then
|
||||||
|
-- distance. probability recedes by square of distance
|
||||||
|
local dist = dcsCommon.distFlat(theBuoy.point, theSubLoc)
|
||||||
|
if dist > asw.maxDetectionRange then
|
||||||
|
-- will not detect
|
||||||
|
elseif dist < asw.sureDetectionRange then
|
||||||
|
canDetect = true
|
||||||
|
sureDetect = true
|
||||||
|
p = 1
|
||||||
|
theBuoy.bearing[theSubName] = dcsCommon.bearingInDegreesFromAtoB(theBuoy.point, theSubLoc)
|
||||||
|
else
|
||||||
|
canDetect = true
|
||||||
|
p = 1 - (dist - asw.sureDetectionRange) / asw.maxDetectionRange -- percentage
|
||||||
|
p = p * p * p -- cubed, in 3D
|
||||||
|
theBuoy.bearing[theSubName] = dcsCommon.bearingInDegreesFromAtoB(theBuoy.point, theSubLoc)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if canDetect then
|
||||||
|
if sureDetect or math.random() < p then
|
||||||
|
-- we have detected sub this round!
|
||||||
|
newContacts[theSubName] = p -- remember for buoy
|
||||||
|
contact.trackedBy[theBuoy.name] = p -- remember for sub
|
||||||
|
else
|
||||||
|
-- didn't detect, do nothing
|
||||||
|
-- contact.trackedBy[theBuoy.name] = nil -- probably not required, contact is new each pass
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- contact.trackedBy[theBuoy.name] = nil -- probably not required
|
||||||
|
end
|
||||||
|
end -- if not the same coalition
|
||||||
|
end -- for all contacts
|
||||||
|
--env.info(" :iterating allSubs done")
|
||||||
|
-- now compare old contacts with new contacts
|
||||||
|
-- if contact lost, remove wedge
|
||||||
|
--env.info(" >start iterating buoy.contacts to find which contacts we lost")
|
||||||
|
for aName, aP in pairs(theBuoy.contacts) do
|
||||||
|
if newContacts[aName] then
|
||||||
|
-- exists, therefore old contact. Keep it
|
||||||
|
--[[-- code to update wedge removed
|
||||||
|
if theBuoy.timeStamps[aName] + 60 * 2 < now then
|
||||||
|
-- update map: remove wedge
|
||||||
|
local shape = theBuoy.lines[aName]
|
||||||
|
trigger.action.removeMark(shape)
|
||||||
|
-- draw a new one
|
||||||
|
local pc = newContacts[aName] -- new probability
|
||||||
|
asw.wedgeForBuoyAndContact(theBuoy, aName, pc)
|
||||||
|
end
|
||||||
|
--]]--
|
||||||
|
else
|
||||||
|
-- contact lost. remove wedge
|
||||||
|
local shape = theBuoy.lines[aName]
|
||||||
|
if asw.verbose then
|
||||||
|
trigger.action.outText("+++ASW: will remove wedge <" .. shape .. ">", 30)
|
||||||
|
end
|
||||||
|
--env.info(" >removing wedge #" .. shape)
|
||||||
|
trigger.action.removeMark(shape)
|
||||||
|
--env.info(" >done removing wedge")
|
||||||
|
-- delete this line entry
|
||||||
|
theBuoy.lines[aName] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
--env.info(" <iterating buoy.contacts for lost contact done")
|
||||||
|
-- check if contact is new and add wedge if so
|
||||||
|
--env.info(" >start iterating newContacts for new contacts")
|
||||||
|
for aName, aP in pairs(newContacts) do
|
||||||
|
if theBuoy.contacts[aName] then
|
||||||
|
-- exists, is old contact, do nothing
|
||||||
|
else
|
||||||
|
-- new contact, draw wedge
|
||||||
|
theBuoy.timeStamps[aName] = now
|
||||||
|
theBuoy.lines[aName] = dcsCommon.numberUUID() -- new shape ID
|
||||||
|
asw.wedgeForBuoyAndContact(theBuoy, aName, aP)
|
||||||
|
-- sound, but suppress ping if we have a fix for that sub
|
||||||
|
-- fixes are indexed by <subname>"/"<coalition>
|
||||||
|
if theBuoy.coalition == 1 then -- and (not asw.fixes[aName .. "/" .. "1"])then
|
||||||
|
asw.newRedBuoyContact = true
|
||||||
|
elseif theBuoy.coalition == 2 then --and (not asw.fixes[aName .. "/" .. "2"]) then
|
||||||
|
asw.newBlueBuoyContact = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
--env.info(" >iterating newContacts for new contacts done")
|
||||||
|
-- we may want to suppress beep if the sub is already in a fix
|
||||||
|
|
||||||
|
-- now save the new contacts and overwrite old
|
||||||
|
theBuoy.contacts = newContacts
|
||||||
|
--env.info(" <<done update buoy for " .. theBuoy.name .. ", returning true")
|
||||||
|
return true -- true = keep uoy alive
|
||||||
|
end
|
||||||
|
|
||||||
|
function asw.hasFix(contact)
|
||||||
|
-- determine if this sub can be fixed by the buoys
|
||||||
|
-- run down all buoys that currently see me
|
||||||
|
-- sub is only seen by opposing buoys.
|
||||||
|
|
||||||
|
local bNum = 0
|
||||||
|
local pTotal = 0
|
||||||
|
local deltaB = 0
|
||||||
|
local bearings = {}
|
||||||
|
local subName = contact.name
|
||||||
|
for bName, p in pairs(contact.trackedBy) do
|
||||||
|
local theBuoy = asw.buoys[bName]
|
||||||
|
-- CHECK FOR COALITION
|
||||||
|
-- make bnum to bnumred and bnumblue
|
||||||
|
if theBuoy.coalition == contact.coalition then
|
||||||
|
trigger.action.outText("+++Warning: same coa for buoy <" .. theBuoy.name .. "> and sub contact <" .. contact.name .. "> ", 30)
|
||||||
|
end
|
||||||
|
bNum = bNum + 1 -- count number of tracking buoys
|
||||||
|
pTotal = pTotal + p
|
||||||
|
bearings[bName] = theBuoy.bearing[subName] - 180
|
||||||
|
if bearings[bName] < 0 then bearings[bName] = bearings[bName] + 360 end
|
||||||
|
end
|
||||||
|
|
||||||
|
local best90 = 0
|
||||||
|
local above30 = 0
|
||||||
|
for bName, aBearing in pairs (bearings) do
|
||||||
|
for bbName, bBearing in pairs(bearings) do
|
||||||
|
local a = aBearing
|
||||||
|
if a > 180 then a = a - 180 end
|
||||||
|
local b = bBearing
|
||||||
|
if b > 180 then b = b - 180 end
|
||||||
|
local d = math.abs(a - b) -- 0..180
|
||||||
|
if d > 90 then d = 90 - (d-90) end -- d = 0..90
|
||||||
|
local this90 = d
|
||||||
|
if this90 > 30 then above30 = above30 + 1 end
|
||||||
|
if this90 > best90 then best90 = this90 end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
above30 = above30 / 2 -- number of buoys that have more than 30° angle to contact, by 2 because each counts twice.
|
||||||
|
local solver = above30 * best90/90 * pTotal
|
||||||
|
if solver >= 2.0 then -- we have a fix
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function asw.updateFixes(allSubs)
|
||||||
|
-- in order to create or maintain a fix, we need at least x
|
||||||
|
-- buoys with a confidence level of xx for that sub
|
||||||
|
-- and their azimuth must make at least 45 degrees so we
|
||||||
|
-- can make a fix
|
||||||
|
-- remember that buoys can only see subs of *opposing* side
|
||||||
|
local now = timer.getTime()
|
||||||
|
|
||||||
|
for subName, contact in pairs(allSubs) do
|
||||||
|
-- calculate if we have a fix on this sub
|
||||||
|
local coa = dcsCommon.getEnemyCoalitionFor(contact.coalition)
|
||||||
|
-- if coa is nil, it's a neutral sub, and we skip
|
||||||
|
if coa and asw.hasFix(contact) then
|
||||||
|
-- if new fix? Access existing ones via fix name scheme
|
||||||
|
-- fix naming scheme is to allow (later) detection of
|
||||||
|
-- same-side subs with buoys and not create a fix name
|
||||||
|
-- collision. Currently overkill
|
||||||
|
local theFix = asw.fixes[subName .. "/" .. tonumber(coa)]
|
||||||
|
if theFix then
|
||||||
|
-- exists, nothing to do
|
||||||
|
else
|
||||||
|
-- create a new fix
|
||||||
|
theFix = asw.createFixForSub(contact.theUnit, coa)
|
||||||
|
local theUnit = theFix.theUnit
|
||||||
|
local pos = theUnit:getPoint()
|
||||||
|
local lat, lon, dep = coord.LOtoLL(pos)
|
||||||
|
local lla, llb = dcsCommon.latLon2Text(lat, lon)
|
||||||
|
trigger.action.outTextForCoalition(coa, "NEW FIX " .. theFix.desig .. ": submerged contact, class <" .. theFix.typeName .. ">, location " .. lla .. ", " .. llb .. ", tracking.", 30)
|
||||||
|
if coa == 1 then asw.newRedFix = true
|
||||||
|
elseif coa == 2 then asw.newBlueFix = true
|
||||||
|
end
|
||||||
|
-- add fix to list of fixes
|
||||||
|
asw.fixes[subName .. "/" .. tonumber(coa)] = theFix
|
||||||
|
end
|
||||||
|
-- update life timer for all fixes
|
||||||
|
theFix.lifeTimer = now + asw.fixLife
|
||||||
|
trigger.action.outTextForCoalition(coa, "contact fix " .. theFix.desig .. " confirmed.", 30)
|
||||||
|
if asw.verbose then
|
||||||
|
trigger.action.outText("renewed lease for fix " .. subName .. "/" .. tonumber(coa), 30)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- no new fix,
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- now iterate all fixes and update them, or time out
|
||||||
|
local filtered = {}
|
||||||
|
for fixName, theFix in pairs(asw.fixes) do
|
||||||
|
if now < theFix.lifeTimer and Unit.isExist(theFix.theUnit) then
|
||||||
|
-- update the location
|
||||||
|
if theFix.lines and theFix.lines > 0 then
|
||||||
|
-- remove old
|
||||||
|
trigger.action.removeMark(theFix.lines)
|
||||||
|
end
|
||||||
|
-- allocate new fix id. we always need new fix id
|
||||||
|
theFix.lines = dcsCommon.numberUUID()
|
||||||
|
-- mark on map for coalition
|
||||||
|
local theUnit = theFix.theUnit
|
||||||
|
local pos = theUnit:getPoint()
|
||||||
|
-- assemble sub info
|
||||||
|
local vel = math.floor(1.94384 * dcsCommon.getUnitSpeed(theUnit))
|
||||||
|
local heading = math.floor(dcsCommon.getUnitHeadingDegrees(theUnit))
|
||||||
|
local delta = asw.fixLife - (theFix.lifeTimer - now)
|
||||||
|
local timeAgo = dcsCommon.processHMS("<m>:<:s>", delta)
|
||||||
|
local info = "Submerged contact, identified as '" .. theFix.theUnit:getTypeName() .. "' class, moving at " .. vel .. " kts, heading " .. heading .. ", last fix " .. timeAgo .. " minutes ago."
|
||||||
|
-- note: neet to change to markToCoalition!
|
||||||
|
trigger.action.markToCoalition(theFix.lines, info, pos, theFix.coalition, true, "")
|
||||||
|
|
||||||
|
-- add to filtered
|
||||||
|
filtered[fixName] = theFix
|
||||||
|
else
|
||||||
|
-- do not add to filtered, timed out or unit destroyed
|
||||||
|
trigger.action.outTextForCoalition(theFix.coalition, "Lost fix for contact", 30)
|
||||||
|
-- remove mark
|
||||||
|
if theFix.lines and theFix.lines > 0 then
|
||||||
|
trigger.action.removeMark(theFix.lines)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
asw.fixes = filtered
|
||||||
|
end
|
||||||
|
|
||||||
|
function markTorpedo(theTorpedo)
|
||||||
|
theTorpedo.markID = dcsCommon.numberUUID()
|
||||||
|
trigger.action.markToCoalition(theTorpedo.markID, "Torpedo " .. theTorpedo.name, theTorpedo.point, theTorpedo.coalition, true, "")
|
||||||
|
end
|
||||||
|
|
||||||
|
function asw.updateTorpedo(theTorpedo, allSubs)
|
||||||
|
-- homes in on closest torpedo, but only if it can detect it
|
||||||
|
-- else it simply runs in a random direction
|
||||||
|
|
||||||
|
-- remove old mark
|
||||||
|
if theTorpedo.markID then
|
||||||
|
trigger.action.removeMark(theTorpedo.markID)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- outside of lethal range, torp can randomly fail and never
|
||||||
|
-- re-aquire (lostTrack is true) unless it accidentally
|
||||||
|
-- gets into lethal range
|
||||||
|
|
||||||
|
-- see if it timed out
|
||||||
|
local now = timer.getTime()
|
||||||
|
if now > theTorpedo.lifeTimer then
|
||||||
|
trigger.action.outTextForCoalition(theTorpedo.coalition, "Torpedo " .. theTorpedo.name .. " ran out", 30)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- redraw mark for torpedo. give it a new
|
||||||
|
-- uuid every time
|
||||||
|
-- during update, it gets near and if it can get close
|
||||||
|
-- enough, it will set them up the bomb and create an explosion
|
||||||
|
-- near the sub it detected.
|
||||||
|
-- uses FSM
|
||||||
|
-- state 0 = dropped into water
|
||||||
|
if theTorpedo.state == 0 then
|
||||||
|
-- state 0: dropping in the water
|
||||||
|
trigger.action.outTextForCoalition(theTorpedo.coalition, "Torpedo " .. theTorpedo.name .. " in the water!", 30)
|
||||||
|
theTorpedo.state = 1
|
||||||
|
markTorpedo(theTorpedo)
|
||||||
|
return true
|
||||||
|
|
||||||
|
elseif theTorpedo.state == 1 then
|
||||||
|
-- seeking. get closest fix. if we have a fix in range
|
||||||
|
-- we go to stage homing, and it's a race between time and
|
||||||
|
-- and sub
|
||||||
|
trigger.action.outTextForCoalition(theTorpedo.coalition, "Torpedo " .. theTorpedo.name .. " is seeking contact...", 30)
|
||||||
|
|
||||||
|
-- select closest fix from same side as torpedo
|
||||||
|
local theFix, dist = asw.getClosestFixTo(theTorpedo.point, theTorpedo.coalition)
|
||||||
|
|
||||||
|
if theFix and dist > asw.maxDetectionRange / 2 then
|
||||||
|
-- too far, forget it existed
|
||||||
|
theFix = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if not theFix then
|
||||||
|
if asw.verbose then
|
||||||
|
trigger.action.outText("stage1: No fix/distance found for " .. theTorpedo.name, 30)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if asw.verbose then
|
||||||
|
trigger.action.outText("stage1: found fix <" .. theFix.name .. "> at dist <" .. dist .. "> for " .. theTorpedo.name, 30)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if theFix and dist < 1700 then
|
||||||
|
-- have seeker, go to homing mode
|
||||||
|
theTorpedo.target = theFix.theUnit
|
||||||
|
if asw.verbose then
|
||||||
|
trigger.action.outText("+++asw: target found: <" .. theTorpedo.target:getName() .. ">", 30)
|
||||||
|
end
|
||||||
|
theTorpedo.state = 20 -- homing
|
||||||
|
|
||||||
|
elseif theFix then
|
||||||
|
local B = theFix.theUnit:getPoint()
|
||||||
|
theTorpedo.course = dcsCommon.bearingFromAtoB(theTorpedo.point, B)
|
||||||
|
if asw.verbose then
|
||||||
|
trigger.action.outText("+++asw: unguided heading for <" .. theFix.theUnit:getName() .. ">", 30)
|
||||||
|
end
|
||||||
|
theTorpedo.state = 10 -- directed run
|
||||||
|
else
|
||||||
|
-- no fix anywhere in range,
|
||||||
|
-- simply pick a course and run
|
||||||
|
-- maybe we get lucky
|
||||||
|
theTorpedo.course = 2 * 3.1415 * math.random()
|
||||||
|
if asw.verbose then
|
||||||
|
trigger.action.outText("+++asw: random heading", 30)
|
||||||
|
end
|
||||||
|
theTorpedo.state = 10 -- random run
|
||||||
|
end
|
||||||
|
|
||||||
|
markTorpedo(theTorpedo)
|
||||||
|
return true
|
||||||
|
|
||||||
|
elseif theTorpedo.state == 10 then -- moving, not homing
|
||||||
|
-- move torpedo and see if it's close enough to a sub
|
||||||
|
-- to track or blow up
|
||||||
|
local displacement = asw.torpedoSpeed * 1/asw.ups -- meters travelled
|
||||||
|
if not theTorpedo.course then
|
||||||
|
theTorpedo.course = 0
|
||||||
|
trigger.action.outText("+++ASW: Torpedo <" .. theTorpedo.name .. "> stage (10) with undefined course, setting 0", 30)
|
||||||
|
end
|
||||||
|
|
||||||
|
theTorpedo.point.x = theTorpedo.point.x + displacement * math.cos(theTorpedo.course)
|
||||||
|
theTorpedo.point.z = theTorpedo.point.z + displacement * math.sin(theTorpedo.course)
|
||||||
|
|
||||||
|
-- seeking ANY sub now.
|
||||||
|
-- warning: may go after our own subs as well, torpedo don't care!
|
||||||
|
local theSub, dist = asw.getClosestSubToLoc(theTorpedo.point, allSubs)
|
||||||
|
if dist < 1200 then
|
||||||
|
-- we lock on to this sub
|
||||||
|
theTorpedo.target = theSub
|
||||||
|
theTorpedo.state = 20 -- switch to homing
|
||||||
|
trigger.action.outTextForCoalition(theTorpedo.coalition, "Torpedo " .. theTorpedo.name .. " is going active!", 30)
|
||||||
|
end
|
||||||
|
|
||||||
|
if dist < 1.2 * displacement then
|
||||||
|
theTorpedo.state = 99 -- go boom
|
||||||
|
end
|
||||||
|
markTorpedo(theTorpedo)
|
||||||
|
return true
|
||||||
|
|
||||||
|
elseif theTorpedo.state == 20 then -- HOMING!
|
||||||
|
if not Unit.isExist(theTorpedo.target) then
|
||||||
|
-- target was destroyed?
|
||||||
|
if asw.verbose then
|
||||||
|
trigger.action.outText("+++asw: target lost", 30)
|
||||||
|
end
|
||||||
|
theTorpedo.course = 2 * 3.1415 * math.random()
|
||||||
|
theTorpedo.state = 10 -- switch to run free
|
||||||
|
theTorpedo.target = nil
|
||||||
|
trigger.action.outTextForCoalition(theTorpedo.coalition, "Torpedo " .. theTorpedo.name .. " lost track, searching...", 30)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if not theTorpedo.target then
|
||||||
|
-- sanity check
|
||||||
|
theTorpedo.course = 2 * 3.1415 * math.random()
|
||||||
|
theTorpedo.state = 10 -- switch to run free
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- we know that isExist(target)
|
||||||
|
local B = theTorpedo.target:getPoint()
|
||||||
|
theTorpedo.course = dcsCommon.bearingFromAtoB(theTorpedo.point, B)
|
||||||
|
local displacement = asw.torpedoSpeed * 1/asw.ups -- meters travelled
|
||||||
|
theTorpedo.point.x = theTorpedo.point.x + displacement * math.cos(theTorpedo.course)
|
||||||
|
theTorpedo.point.z = theTorpedo.point.z + displacement * math.sin(theTorpedo.course)
|
||||||
|
local dist = dcsCommon.distFlat(theTorpedo.point, B)
|
||||||
|
if dist < displacement then
|
||||||
|
theTorpedo.state = 99 -- boom, babe!
|
||||||
|
else
|
||||||
|
local hdg = math.floor(57.2958 * theTorpedo.course)
|
||||||
|
if hdg < 0 then hdg = hdg + 360 end
|
||||||
|
trigger.action.outTextForCoalition(theTorpedo.coalition, "Torpedo " .. theTorpedo.name .. " is homing, course " .. hdg .. ", " .. math.floor(dist) .. "m to impact", 30)
|
||||||
|
end
|
||||||
|
-- move to this torpedo and blow up
|
||||||
|
-- when close enough
|
||||||
|
markTorpedo(theTorpedo)
|
||||||
|
|
||||||
|
return true
|
||||||
|
elseif theTorpedo.state == 99 then -- go boom
|
||||||
|
if Unit.isExist(theTorpedo.target) then
|
||||||
|
Unit.destroy(theTorpedo.target)
|
||||||
|
end
|
||||||
|
-- impact!
|
||||||
|
trigger.action.outTextForCoalition(theTorpedo.coalition, "Impact for " .. theTorpedo.name .. "! We have confirmed hit on submerged contact!", 30)
|
||||||
|
if theTorpedo.coalition == 1 then
|
||||||
|
if asw.redKill then
|
||||||
|
cfxZones.pollFlag(asw.redKill, asw.method, asw)
|
||||||
|
end
|
||||||
|
elseif theTorpedo.coalition == 2 then
|
||||||
|
if asw.blueKill then
|
||||||
|
cfxZones.pollFlag(asw.blueKill, asw.method, asw)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- make surface explosion
|
||||||
|
-- choose point 1m under water
|
||||||
|
local loc = theTorpedo.point
|
||||||
|
local alt = land.getHeight({x = loc.x, y = loc.z})
|
||||||
|
loc.y = alt-1
|
||||||
|
trigger.action.explosion(loc, 3000)
|
||||||
|
|
||||||
|
-- we are done
|
||||||
|
return false
|
||||||
|
|
||||||
|
else
|
||||||
|
-- we somehow ran into an unknown state
|
||||||
|
trigger.action.outText("unknown torpedo state <" .. theTorpedo.state .. "> for <" .. theTorpedo.name .. ">", 20)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- return true if it should be kept in array
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- MAIN UPDATE
|
||||||
|
--
|
||||||
|
-- does not find subs that have surfaced
|
||||||
|
-- returns a list of 'contacts' - ready made tables
|
||||||
|
-- to track the sub: who sees them (trackedBy) and misc
|
||||||
|
-- info.
|
||||||
|
-- contacts is indexed by unit name
|
||||||
|
function asw.gatherSubs()
|
||||||
|
local allCoas = {0, 1, 2}
|
||||||
|
local subs = {}
|
||||||
|
for idx, coa in pairs(allCoas) do
|
||||||
|
local allGroups = coalition.getGroups(coa, 3) -- ships only
|
||||||
|
for idy, aGroup in pairs(allGroups) do
|
||||||
|
allUnits = aGroup:getUnits()
|
||||||
|
for idz, aUnit in pairs(allUnits) do
|
||||||
|
-- see if this unit is a sub
|
||||||
|
if aUnit and Unit.isExist(aUnit) and
|
||||||
|
(dcsCommon.getUnitAGL(aUnit) < -5) then -- yes, submerged contact.
|
||||||
|
local contact = {}
|
||||||
|
contact.theUnit = aUnit
|
||||||
|
contact.trackedBy = {} -- buoys that have a ping
|
||||||
|
contact.name = aUnit:getName()
|
||||||
|
contact.coalition = aUnit:getCoalition()
|
||||||
|
subs[contact.name] = contact
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return subs
|
||||||
|
end
|
||||||
|
|
||||||
|
function asw.update()
|
||||||
|
--env.info("-->Enter asw update")
|
||||||
|
-- first, schedule next invocation
|
||||||
|
timer.scheduleFunction(asw.update, {}, timer.getTime() + 1/asw.ups)
|
||||||
|
|
||||||
|
local subs = asw.gatherSubs() -- ALL contacts/subs
|
||||||
|
|
||||||
|
asw.newRedBuoyContact = false
|
||||||
|
asw.newBlueBuoyContact = false
|
||||||
|
|
||||||
|
-- refresh all buoy detections
|
||||||
|
-- if #asw.buoys > 0 then
|
||||||
|
--env.info("Before buoy proc")
|
||||||
|
local filtered = {}
|
||||||
|
for bName, theBuoy in pairs(asw.buoys) do
|
||||||
|
if asw.updateBuoy(theBuoy, subs) then
|
||||||
|
filtered[bName] = theBuoy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
asw.buoys = filtered
|
||||||
|
--env.info("Complete buoy proc")
|
||||||
|
|
||||||
|
if asw.newRedBuoyContact then
|
||||||
|
trigger.action.outSoundForCoalition(1, asw.sonarSound)
|
||||||
|
end
|
||||||
|
if asw.newBlueBuoyContact then
|
||||||
|
trigger.action.outSoundForCoalition(2, asw.sonarSound)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- update fixes: create if they don't exist
|
||||||
|
asw.newBlueFix = false
|
||||||
|
asw.newRedFix = false
|
||||||
|
|
||||||
|
--env.info("Before fixes")
|
||||||
|
asw.updateFixes(subs)
|
||||||
|
--env.info("Complete fixes")
|
||||||
|
|
||||||
|
if asw.newBlueFix then
|
||||||
|
trigger.action.outSoundForCoalition(2, asw.fixSound)
|
||||||
|
end
|
||||||
|
|
||||||
|
if asw.newRedFix then
|
||||||
|
trigger.action.outSoundForCoalition(1, asw.fixSound)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- see if there are any torpedoes in the water
|
||||||
|
--if #asw.torpedoes > 0 then
|
||||||
|
--env.info("Before torpedoes")
|
||||||
|
local filtered = {}
|
||||||
|
for tName, theTorpedo in pairs(asw.torpedoes) do
|
||||||
|
if asw.updateTorpedo(theTorpedo, subs) then
|
||||||
|
filtered[tName] = theTorpedo
|
||||||
|
end
|
||||||
|
end
|
||||||
|
asw.torpedoes = filtered
|
||||||
|
|
||||||
|
--env.info("Complete torpedoes")
|
||||||
|
|
||||||
|
--end
|
||||||
|
--env.info("<--Leave asw update")
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- CONFIG & START
|
||||||
|
--
|
||||||
|
function asw.readConfigZone()
|
||||||
|
local theZone = cfxZones.getZoneByName("aswConfig")
|
||||||
|
if not theZone then
|
||||||
|
if asw.verbose then
|
||||||
|
trigger.action.outText("+++asw: no config zone!", 30)
|
||||||
|
end
|
||||||
|
theZone = cfxZones.createSimpleZone("aswConfig")
|
||||||
|
end
|
||||||
|
asw.verbose = theZone.verbose
|
||||||
|
asw.name = "aswConfig" -- make compatible with cfxZones
|
||||||
|
|
||||||
|
-- set defaults, later do the reading
|
||||||
|
asw.buoyLife = 30 * 60 -- 30 minutes life time
|
||||||
|
asw.buoyLife = cfxZones.getNumberFromZoneProperty(theZone, "buoyLife", asw.buoyLife)
|
||||||
|
if asw.buoyLife < 1 then asw.buoyLife = 999999 end -- very, very long time
|
||||||
|
|
||||||
|
asw.maxDetectionRange = 12000 -- 12 km
|
||||||
|
asw.maxDetectionRange = cfxZones.getNumberFromZoneProperty(theZone, "detectionRange", 12000)
|
||||||
|
asw.sureDetectionRange = 1000 -- inside 1 km will always detect sub
|
||||||
|
asw.sureDetectionRange = cfxZones.getNumberFromZoneProperty(theZone, "sureDetect", 1000)
|
||||||
|
asw.torpedoLife = 7 * 60 + 30 -- 7.5 minutes, will reach max range in that time
|
||||||
|
asw.torpedoSpeed = 28.3 -- speed in m/s -- 55 knots
|
||||||
|
asw.maxDetectionDepth = 500 -- in meters. deeper than that, no detection.
|
||||||
|
asw.maxDetectionDepth = cfxZones.getNumberFromZoneProperty(theZone, "detectionDepth", 500)
|
||||||
|
asw.fixLife = 3 * 60 -- a sub "fix" lives 3 minutes past last renew
|
||||||
|
asw.fixLife = cfxZones.getNumberFromZoneProperty(theZone, "fixLife", asw.fixLife)
|
||||||
|
if asw.fixLife < 1 then asw.fixLife = 999999 end -- a long time
|
||||||
|
|
||||||
|
asw.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
|
||||||
|
|
||||||
|
asw.maxDeviation = 40 -- 40 degrees + 5 = 45 degrees left and right max deviation makes a worst-case 90 degree left/right wedge
|
||||||
|
asw.fixSound = "submarine ping.ogg"
|
||||||
|
asw.fixSound = cfxZones.getStringFromZoneProperty(theZone, "fixSound", asw.fixSound)
|
||||||
|
asw.sonarSound = "beacon beep-beep.ogg"
|
||||||
|
asw.sonarSound = cfxZones.getStringFromZoneProperty(theZone, "sonarSound", asw.sonarSound)
|
||||||
|
if cfxZones.hasProperty(theZone, "redKill!") then
|
||||||
|
asw.redKill = cfxZones.getStringFromZoneProperty(theZone, "redKill!", "none")
|
||||||
|
end
|
||||||
|
if cfxZones.hasProperty(theZone, "blueKill!") then
|
||||||
|
asw.blueKill = cfxZones.getStringFromZoneProperty(theZone, "blueKill!", "none")
|
||||||
|
end
|
||||||
|
|
||||||
|
asw.method = cfxZones.getStringFromZoneProperty(theZone, "method", "inc")
|
||||||
|
|
||||||
|
asw.smokeColor = cfxZones.getSmokeColorStringFromZoneProperty(theZone, "smokeColor", "red")
|
||||||
|
asw.smokeColor = dcsCommon.smokeColor2Num(asw.smokeColor)
|
||||||
|
|
||||||
|
if asw.verbose then
|
||||||
|
trigger.action.outText("+++asw: read config", 30)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function asw.start()
|
||||||
|
if not dcsCommon.libCheck then
|
||||||
|
trigger.action.outText("cfx asw requires dcsCommon", 30)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if not dcsCommon.libCheck("cfx asw", asw.requiredLibs) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- read config
|
||||||
|
asw.readConfigZone()
|
||||||
|
|
||||||
|
-- start update
|
||||||
|
asw.update()
|
||||||
|
|
||||||
|
trigger.action.outText("cfx ASW v" .. asw.version .. " started.", 30)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- start up asw
|
||||||
|
--
|
||||||
|
if not asw.start() then
|
||||||
|
trigger.action.outText("cfx asw aborted: missing libraries", 30)
|
||||||
|
asw = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[--
|
||||||
|
Ideas/to do
|
||||||
|
- false positives for detections
|
||||||
|
- triangle mark for fixes, color red
|
||||||
|
- squares for torps, color yellow
|
||||||
|
- remove torpedoes when they run aground
|
||||||
|
|
||||||
|
--]]--
|
||||||
597
modules/aswGUI.lua
Normal file
597
modules/aswGUI.lua
Normal file
@ -0,0 +1,597 @@
|
|||||||
|
aswGUI = {}
|
||||||
|
aswGUI.version = "1.0.0"
|
||||||
|
aswGUI.verbose = false
|
||||||
|
aswGUI.requiredLibs = {
|
||||||
|
"dcsCommon", -- always
|
||||||
|
"cfxZones", -- Zones, of course
|
||||||
|
"asw", -- needs asw module
|
||||||
|
"aswZones", -- also needs the asw zones
|
||||||
|
}
|
||||||
|
|
||||||
|
--[[--
|
||||||
|
Version History
|
||||||
|
1.0.0 - initial version
|
||||||
|
|
||||||
|
--]]--
|
||||||
|
|
||||||
|
aswGUI.ups = 1 -- = once every second
|
||||||
|
aswGUI.aswCraft = {}
|
||||||
|
|
||||||
|
--[[--
|
||||||
|
::::::::::::::::: ASSUMES SINGLE_UNIT GROUPS ::::::::::::::::::
|
||||||
|
--]]--
|
||||||
|
|
||||||
|
|
||||||
|
function aswGUI.resetConf(asc)
|
||||||
|
if asc.rootMenu then
|
||||||
|
missionCommands.removeItemForGroup(asc.groupID, asc.rootMenu)
|
||||||
|
end
|
||||||
|
asc.rootMenu = missionCommands.addSubMenuForGroup(asc.groupID, "ASW")
|
||||||
|
asc.buoyNum = 0
|
||||||
|
asc.torpedoNum = 0
|
||||||
|
asc.coolDown = 0 -- used when waiting, currently not used
|
||||||
|
end
|
||||||
|
|
||||||
|
-- we use lazy init whenever player enters
|
||||||
|
function aswGUI.initUnit(unitName) -- now this unit exists
|
||||||
|
local theUnit = Unit.getByName(unitName)
|
||||||
|
if not theUnit then
|
||||||
|
trigger.action.outText("+++aswGUI: <" .. unitName .. "> not a unit, aborting initUnit", 30)
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local theGroup = theUnit:getGroup()
|
||||||
|
local asc = {} -- set up player craft config block
|
||||||
|
--local groupData = cfxMX.playerUnit2Group[unitName]
|
||||||
|
asc.groupName = theGroup:getName() -- groupData.name
|
||||||
|
asc.name = unitName
|
||||||
|
asc.groupID = theGroup:getID() -- groupData.groupId
|
||||||
|
aswGUI.resetConf(asc)
|
||||||
|
return asc
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function aswGUI.processWeightFor(conf)
|
||||||
|
-- make total weight and handle all
|
||||||
|
-- cargo for this unit
|
||||||
|
|
||||||
|
-- hand off to DML cargo manager if implemented
|
||||||
|
if cargosuper then
|
||||||
|
trigger.action.outText("CargoSuper handling regquired, using none", 30)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local totalWeight = conf.buoyNum * aswGUI.buoyWeight
|
||||||
|
totalWeight = totalWeight + conf.torpedoNum * aswGUI.torpedoWeight
|
||||||
|
|
||||||
|
-- set cargo weight
|
||||||
|
trigger.action.setUnitInternalCargo(conf.name, totalWeight)
|
||||||
|
local theUnit = Unit.getByName(conf.name)
|
||||||
|
trigger.action.outTextForGroup(conf.groupID, "Total asw weight: " .. totalWeight .. "kg (" .. math.floor(totalWeight * 2.20462) .. "lbs)", 30)
|
||||||
|
return totalWeight
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- build unit menu
|
||||||
|
--
|
||||||
|
function aswGUI.getBuoyCapa(conf) -- returns capa per slot
|
||||||
|
-- warning: assumes two "slots" maximum
|
||||||
|
if conf.torpedoNum > aswGUI.torpedoesPerSlot then return 0 end -- both slots are filled with torpedoes
|
||||||
|
if conf.torpedoNum > 0 then -- one slot is taken up by torpedoes
|
||||||
|
return aswGUI.buoysPerSlot - conf.buoyNum
|
||||||
|
end
|
||||||
|
if conf.buoyNum >= aswGUI.buoysPerSlot then
|
||||||
|
return 2 * aswGUI.buoysPerSlot - conf.buoyNum
|
||||||
|
end
|
||||||
|
return aswGUI.buoysPerSlot - conf.buoyNum
|
||||||
|
end
|
||||||
|
|
||||||
|
function aswGUI.getTorpedoCapa(conf)
|
||||||
|
if conf.buoyNum > aswGUI.buoysPerSlot then return 0 end -- both slots are filled with buoys
|
||||||
|
if conf.buoyNum > 0 then -- one slot is taken up by torpedoes
|
||||||
|
return aswGUI.torpedoesPerSlot - conf.torpedoNum
|
||||||
|
end
|
||||||
|
if conf.torpedoNum >= aswGUI.torpedoesPerSlot then
|
||||||
|
return 2 * aswGUI.torpedoesPerSlot - conf.torpedoNum
|
||||||
|
end
|
||||||
|
return aswGUI.torpedoesPerSlot - conf.torpedoNum
|
||||||
|
end
|
||||||
|
|
||||||
|
function aswGUI.setGroundMenu(conf, theUnit)
|
||||||
|
-- build menu for load stores
|
||||||
|
local loc = theUnit:getPoint()
|
||||||
|
local closestAswZone = aswZones.getClosestASWZoneTo(loc)
|
||||||
|
local inZone = cfxZones.pointInZone(loc, closestAswZone)
|
||||||
|
local bStore = 0 -- available buoys
|
||||||
|
local tStore = 0 -- available torpedoes
|
||||||
|
-- ... but only if we are in an asw zone
|
||||||
|
-- calculate how much is available
|
||||||
|
if inZone then
|
||||||
|
bStore = closestAswZone.buoyNum
|
||||||
|
if bStore < 0 then bStore = aswGUI.buoysPerSlot end
|
||||||
|
tStore = closestAswZone.torpedoNum
|
||||||
|
if tStore < 0 then tStore = aswGUI.torpedoesPerSlot end
|
||||||
|
end
|
||||||
|
|
||||||
|
if bStore > 0 then
|
||||||
|
local bCapa = aswGUI.getBuoyCapa(conf)
|
||||||
|
if bCapa > 0 then
|
||||||
|
missionCommands.addCommandForGroup(conf.groupID, "Load <" .. bCapa .."> ASW Buoys", conf.rootMenu, aswGUI.xHandleLoadBuoys, conf)
|
||||||
|
else
|
||||||
|
missionCommands.addCommandForGroup(conf.groupID, "(No free Buoy stores)", conf.rootMenu, aswGUI.xHandleGeneric, conf)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
missionCommands.addCommandForGroup(conf.groupID, "(Can't load ASW Buoys, no supplies in range)", conf.rootMenu, aswGUI.xHandleGeneric, conf)
|
||||||
|
end
|
||||||
|
|
||||||
|
if conf.buoyNum > 0 then
|
||||||
|
local toUnload = conf.buoyNum
|
||||||
|
if toUnload > aswGUI.buoysPerSlot then toUnload = aswGUI.buoysPerSlot end
|
||||||
|
missionCommands.addCommandForGroup(conf.groupID, "Unload <" .. toUnload .. "> ASW Buoys (" .. conf.buoyNum .. " on board)", conf.rootMenu, aswGUI.xHandleUnloadBuoys, conf)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- torpedo proccing
|
||||||
|
|
||||||
|
if tStore > 0 then
|
||||||
|
local tCapa = aswGUI.getTorpedoCapa(conf)
|
||||||
|
if tCapa > 0 then
|
||||||
|
tCapa = 1 -- one at a time
|
||||||
|
missionCommands.addCommandForGroup(conf.groupID, "Load <" .. tCapa .."> ASW Torpedoes", conf.rootMenu, aswGUI.xHandleLoadTorpedoes, conf)
|
||||||
|
else
|
||||||
|
missionCommands.addCommandForGroup(conf.groupID, "All stores filled to capacity", conf.rootMenu, aswGUI.xHandleGeneric, conf)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
missionCommands.addCommandForGroup(conf.groupID, "(Can't load ASW Torpedoes, no supplies in range)", conf.rootMenu, aswGUI.xHandleGeneric, conf)
|
||||||
|
end
|
||||||
|
|
||||||
|
if conf.torpedoNum > 0 then
|
||||||
|
local toUnload = conf.torpedoNum
|
||||||
|
if toUnload > aswGUI.torpedoesPerSlot then toUnload = aswGUI.buoysPerSlot end
|
||||||
|
missionCommands.addCommandForGroup(conf.groupID, "Unload <" .. toUnload .. "> ASW Torpedoes (" .. conf.torpedoNum .. " on board)", conf.rootMenu, aswGUI.xHandleUnloadTorpedoes, conf)
|
||||||
|
end
|
||||||
|
missionCommands.addCommandForGroup(conf.groupID, "[Stores: <" .. conf.buoyNum .. "> Buoys | <" .. conf.torpedoNum .. "> Torpedoes]", conf.rootMenu, aswGUI.xHandleGeneric, conf)
|
||||||
|
end
|
||||||
|
|
||||||
|
function aswGUI.setAirMenu(conf, theUnit)
|
||||||
|
-- build menu for load stores
|
||||||
|
local bStore = conf.buoyNum -- available buoys
|
||||||
|
local tStore = conf.torpedoNum -- available torpedoes
|
||||||
|
|
||||||
|
if bStore < 1 and tStore < 1 then
|
||||||
|
missionCommands.addCommandForGroup(conf.groupID, "No ASW munitions on board", conf.rootMenu, aswGUI.xHandleGeneric, conf)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if bStore > 0 then
|
||||||
|
missionCommands.addCommandForGroup(conf.groupID, "BUOY - Drop an ASW Buoy", conf.rootMenu, aswGUI.xHandleBuoyDropoff, conf)
|
||||||
|
else
|
||||||
|
missionCommands.addCommandForGroup(conf.groupID, "No ASW Buoys on board", conf.rootMenu, aswGUI.xHandleGeneric, conf)
|
||||||
|
end
|
||||||
|
|
||||||
|
if tStore > 0 then
|
||||||
|
missionCommands.addCommandForGroup(conf.groupID, "TORP - Drop an ASW Torpedo", conf.rootMenu, aswGUI.xHandleTorpedoDropoff, conf)
|
||||||
|
else
|
||||||
|
missionCommands.addCommandForGroup(conf.groupID, "No ASW Torpedoes on board", conf.rootMenu, aswGUI.xHandleGeneric, conf)
|
||||||
|
end
|
||||||
|
|
||||||
|
missionCommands.addCommandForGroup(conf.groupID, "[Stores: <" .. conf.buoyNum .. "> Buoys | <" .. conf.torpedoNum .. "> Torpedoes]", conf.rootMenu, aswGUI.xHandleGeneric, conf)
|
||||||
|
end
|
||||||
|
|
||||||
|
function aswGUI.setMenuForUnit(theUnit)
|
||||||
|
if not theUnit then return end
|
||||||
|
if not Unit.isExist(theUnit) then return end
|
||||||
|
local uName = theUnit:getName()
|
||||||
|
|
||||||
|
-- if we get here, the unit exists. fetch unit config
|
||||||
|
local conf = aswGUI.aswCraft[uName]
|
||||||
|
-- delete old, and create new root menu
|
||||||
|
missionCommands.removeItemForGroup(conf.groupID, conf.rootMenu)
|
||||||
|
conf.rootMenu = missionCommands.addSubMenuForGroup(conf.groupID, "ASW")
|
||||||
|
|
||||||
|
-- if we are in the air, we add menus to drop buoys or torpedoes
|
||||||
|
if theUnit:inAir() then
|
||||||
|
aswGUI.setAirMenu(conf, theUnit)
|
||||||
|
else
|
||||||
|
aswGUI.setGroundMenu(conf, theUnit)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- comms callback handling
|
||||||
|
--
|
||||||
|
--
|
||||||
|
-- LOADING / UNLOADING
|
||||||
|
--
|
||||||
|
function aswGUI.xHandleGeneric(args)
|
||||||
|
timer.scheduleFunction(aswGUI.handleGeneric, args, timer.getTime() + 0.1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function aswGUI.handleGeneric(args)
|
||||||
|
if not args then args = "*EMPTY*" end
|
||||||
|
-- do nothing
|
||||||
|
end
|
||||||
|
|
||||||
|
function aswGUI.xHandleLoadBuoys(args)
|
||||||
|
timer.scheduleFunction(aswGUI.handleLoadBuoys, args, timer.getTime() + 0.1)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function aswGUI.handleLoadBuoys(args)
|
||||||
|
local conf = args
|
||||||
|
local theUnit = Unit.getByName(conf.name)
|
||||||
|
if not theUnit then
|
||||||
|
trigger.action.outText("+++aswG: (load buoys) can't find unit <" .. conf.name .. ">", 30)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local loc = theUnit:getPoint()
|
||||||
|
local theZone = aswZones.getClosestASWZoneTo(loc)
|
||||||
|
local inZone = cfxZones.pointInZone(loc, theZone)
|
||||||
|
local bStore = 0 -- available buoys
|
||||||
|
if inZone then
|
||||||
|
bStore = theZone.buoyNum
|
||||||
|
if bStore < 0 then bStore = aswGUI.buoysPerSlot end
|
||||||
|
else
|
||||||
|
trigger.action.outTextForGroup(conf.groupID, "Nothing loaded. Return to ASW loading zone.", 30)
|
||||||
|
aswGUI.setMenuForUnit(theUnit)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if bStore < 1 then
|
||||||
|
trigger.action.outTextForGroup(conf.groupID, "ASW Buoy stock has run out. Sorry.", 30)
|
||||||
|
aswGUI.setMenuForUnit(theUnit)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local capa = aswGUI.getBuoyCapa(conf)
|
||||||
|
conf.buoyNum=conf.buoyNum + capa
|
||||||
|
|
||||||
|
if theZone.buoyNum >= 0 then
|
||||||
|
theZone.buoyNum = theZone.buoyNum - capa
|
||||||
|
if theZone.buoyNum < 0 then theZone.buoyNum = 0 end
|
||||||
|
-- proc new weight
|
||||||
|
end
|
||||||
|
|
||||||
|
aswGUI.processWeightFor(conf)
|
||||||
|
trigger.action.outTextForGroup(conf.groupID, "Loaded <" .. capa .. "> ASW Buoys.", 30)
|
||||||
|
aswGUI.setMenuForUnit(theUnit)
|
||||||
|
end
|
||||||
|
|
||||||
|
function aswGUI.xHandleUnloadBuoys(args)
|
||||||
|
timer.scheduleFunction(aswGUI.handleUnloadBuoys, args, timer.getTime() + 0.1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function aswGUI.handleUnloadBuoys(args)
|
||||||
|
local conf = args
|
||||||
|
local theUnit = Unit.getByName(conf.name)
|
||||||
|
if not theUnit then
|
||||||
|
trigger.action.outText("+++aswG: (unload buoys) can't find unit <" .. conf.name .. ">", 30)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local loc = theUnit:getPoint()
|
||||||
|
local theZone = aswZones.getClosestASWZoneTo(loc)
|
||||||
|
local inZone = cfxZones.pointInZone(loc, theZone)
|
||||||
|
|
||||||
|
local amount = conf.buoyNum
|
||||||
|
while amount > aswGUI.buoysPerSlot do -- future proof, any # of slots
|
||||||
|
amount = amount - aswGUI.buoysPerSlot
|
||||||
|
end
|
||||||
|
conf.buoyNum = conf.buoyNum - amount
|
||||||
|
|
||||||
|
if inZone then
|
||||||
|
if theZone.buoyNum >= 0 then theZone.buoyNum = theZone.buoyNum + amount end
|
||||||
|
trigger.action.outTextForGroup(conf.groupID, "Returned <" .. amount .. "> ASW Buoys to storage.", 30)
|
||||||
|
else
|
||||||
|
-- simply drop them, irrecoverable
|
||||||
|
trigger.action.outTextForGroup(conf.groupID, "Discarded <" .. amount .. "> ASW Buoys.", 30)
|
||||||
|
end
|
||||||
|
aswGUI.processWeightFor(conf)
|
||||||
|
aswGUI.setMenuForUnit(theUnit)
|
||||||
|
end
|
||||||
|
|
||||||
|
function aswGUI.xHandleLoadTorpedoes(args)
|
||||||
|
timer.scheduleFunction(aswGUI.handleLoadTorpedoes, args, timer.getTime() + 0.1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function aswGUI.handleLoadTorpedoes(args)
|
||||||
|
local conf = args
|
||||||
|
local theUnit = Unit.getByName(conf.name)
|
||||||
|
if not theUnit then
|
||||||
|
trigger.action.outText("+++aswG: (load torps) can't find unit <" .. conf.name .. ">", 30)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local loc = theUnit:getPoint()
|
||||||
|
local theZone = aswZones.getClosestASWZoneTo(loc)
|
||||||
|
local inZone = cfxZones.pointInZone(loc, theZone)
|
||||||
|
local tStore = 0 -- available torpedoes
|
||||||
|
if inZone then
|
||||||
|
tStore = theZone.torpedoNum
|
||||||
|
if tStore < 0 then tStore = aswGUI.torpedoesPerSlot end
|
||||||
|
else
|
||||||
|
trigger.action.outTextForGroup(conf.groupID, "Nothing loaded. Return to ASW loading zone.", 30)
|
||||||
|
aswGUI.setMenuForUnit(theUnit)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if tStore < 1 then
|
||||||
|
trigger.action.outTextForGroup(conf.groupID, "ASW Torpedo stock has run out. Sorry.", 30)
|
||||||
|
aswGUI.setMenuForUnit(theUnit)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local capa = aswGUI.getTorpedoCapa(conf)
|
||||||
|
capa = 1 -- load one at a time
|
||||||
|
conf.torpedoNum=conf.torpedoNum + capa
|
||||||
|
if theZone.torpedoNum >= 0 then
|
||||||
|
theZone.torpedoNum = theZone.torpedoNum - capa
|
||||||
|
if theZone.torpedoNum < 0 then theZone.torpedoNum = 0 end
|
||||||
|
end
|
||||||
|
|
||||||
|
aswGUI.processWeightFor(conf)
|
||||||
|
|
||||||
|
trigger.action.outTextForGroup(conf.groupID, "Loaded <" .. capa .. "> asw Torpedoes.", 30)
|
||||||
|
aswGUI.setMenuForUnit(theUnit)
|
||||||
|
end
|
||||||
|
|
||||||
|
function aswGUI.xHandleUnloadTorpedoes(args)
|
||||||
|
timer.scheduleFunction(aswGUI.handleUnloadTorpedoes, args, timer.getTime() + 0.1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function aswGUI.handleUnloadTorpedoes(args)
|
||||||
|
local conf = args
|
||||||
|
local theUnit = Unit.getByName(conf.name)
|
||||||
|
if not theUnit then
|
||||||
|
trigger.action.outText("+++aswG: (unload torpedoes) can't find unit <" .. conf.name .. ">", 30)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local loc = theUnit:getPoint()
|
||||||
|
local theZone = aswZones.getClosestASWZoneTo(loc)
|
||||||
|
local inZone = cfxZones.pointInZone(loc, theZone)
|
||||||
|
|
||||||
|
local amount = conf.torpedoNum
|
||||||
|
while amount > aswGUI.torpedoesPerSlot do -- future proof, any # of slots
|
||||||
|
amount = amount - aswGUI.torpedoesPerSlot
|
||||||
|
end
|
||||||
|
conf.torpedoNum = conf.torpedoNum - amount
|
||||||
|
|
||||||
|
if inZone then
|
||||||
|
if theZone.torpedoNum >= 0 then theZone.torpedoNum = theZone.torpedoNum + amount end
|
||||||
|
trigger.action.outTextForGroup(conf.groupID, "Returned <" .. amount .. "> ASW Torpedoes to storage.", 30)
|
||||||
|
else
|
||||||
|
-- simply drop them, irrecoverable
|
||||||
|
trigger.action.outTextForGroup(conf.groupID, "Discarded <" .. amount .. "> ASW Torpedoes.", 30)
|
||||||
|
end
|
||||||
|
aswGUI.processWeightFor(conf)
|
||||||
|
aswGUI.setMenuForUnit(theUnit)
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- LIVE DROP
|
||||||
|
--
|
||||||
|
function aswGUI.xHandleBuoyDropoff(args)
|
||||||
|
timer.scheduleFunction(aswGUI.handleBuoyDropoff, args, timer.getTime() + 0.1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function aswGUI.hasDropoffParams(conf)
|
||||||
|
-- to be added later, can be curtailed for units
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function aswGUI.handleBuoyDropoff(args)
|
||||||
|
local conf = args
|
||||||
|
local theUnit = Unit.getByName(conf.name)
|
||||||
|
if not theUnit or not Unit.isExist(theUnit) then
|
||||||
|
trigger.action.outText("+++aswG: (drop buoy) unit <" .. conf.name .. "> does not exits", 30)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- we could now make height and speed checks, but dont really do
|
||||||
|
if not aswGUI.hasDropoffParams(conf) then
|
||||||
|
trigger.action.outTextForGroup(conf.groupID, "You need to be below xxx knots and yyy ft AGL to drop ASW munitions", 30)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- check that we really have some buoys left
|
||||||
|
if conf.buoyNum < 1 then
|
||||||
|
trigger.action.outText("+++aswG: no buoys for <" .. conf.name .. ">.", 30)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
conf.buoyNum = conf.buoyNum - 1
|
||||||
|
|
||||||
|
-- do the deed
|
||||||
|
asw.dropBuoyFrom(theUnit)
|
||||||
|
trigger.action.outTextForGroup(conf.groupID, "Dropping ASW Buoy...", 30)
|
||||||
|
|
||||||
|
-- wrap up
|
||||||
|
aswGUI.processWeightFor(conf)
|
||||||
|
aswGUI.setMenuForUnit(theUnit)
|
||||||
|
end
|
||||||
|
|
||||||
|
function aswGUI.xHandleTorpedoDropoff(args)
|
||||||
|
timer.scheduleFunction(aswGUI.handleTorpedoDropoff, args, timer.getTime() + 0.1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function aswGUI.handleTorpedoDropoff(args)
|
||||||
|
local conf = args
|
||||||
|
local theUnit = Unit.getByName(conf.name)
|
||||||
|
if not theUnit or not Unit.isExist(theUnit) then
|
||||||
|
trigger.action.outText("+++aswG: (drop torpedo) unit <" .. conf.name .. "> does not exits", 30)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- we could now make height and speed checks, but dont really do
|
||||||
|
if not aswGUI.hasDropoffParams(conf) then
|
||||||
|
trigger.action.outTextForGroup(conf.groupID, "You need to be below xxx knots and yyy ft AGL to drop ASW munitions", 30)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- check that we really have some buoys left
|
||||||
|
if conf.torpedoNum < 1 then
|
||||||
|
trigger.action.outText("+++aswG: no torpedoes for <" .. conf.name .. ">.", 30)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
conf.torpedoNum = conf.torpedoNum - 1
|
||||||
|
|
||||||
|
-- do the deed
|
||||||
|
asw.dropTorpedoFrom(theUnit)
|
||||||
|
trigger.action.outTextForGroup(conf.groupID, "Dropping ASW Torpedo...", 30)
|
||||||
|
|
||||||
|
-- wrap up
|
||||||
|
aswGUI.processWeightFor(conf)
|
||||||
|
aswGUI.setMenuForUnit(theUnit)
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Event handling
|
||||||
|
--
|
||||||
|
function aswGUI:onEvent(theEvent)
|
||||||
|
--env.info("> >ENTER aswGUI:onEvent")
|
||||||
|
if not theEvent then
|
||||||
|
trigger.action.outText("+++aswGUI: nil theEvent", 30)
|
||||||
|
--env.info("< <ABEND aswGUI:onEvent: nil event")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local theID = theEvent.id
|
||||||
|
if not theID then
|
||||||
|
trigger.action.outText("+++aswGUI: nil event.ID", 30)
|
||||||
|
--env.info("< <ABEND aswGUI:onEvent: nil event ID")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local initiator = theEvent.initiator
|
||||||
|
if not initiator then
|
||||||
|
--env.info("< <ABEND aswGUI:onEvent: nil initiator")
|
||||||
|
return
|
||||||
|
end -- not interested
|
||||||
|
local theUnit = initiator
|
||||||
|
if not Unit.isExist(theUnit) then
|
||||||
|
trigger.action.outText("+++aswGUI: non-unit event filtred.", 30)
|
||||||
|
--env.info("< <ABEND aswGUI:onEvent: theUnit does not exist")
|
||||||
|
end
|
||||||
|
local name = theUnit:getName()
|
||||||
|
if not name then
|
||||||
|
trigger.action.outText("+++aswGUI: unable to access unit name in onEvent, aborting", 30)
|
||||||
|
--env.info("< <ABEND aswGUI:onEvent: theUnit not a unit/no name")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
-- see if this is a player aircraft
|
||||||
|
if not theUnit.getPlayerName then
|
||||||
|
--env.info("< <LEAVE aswGUI:onEvent: not player unit A")
|
||||||
|
return
|
||||||
|
end -- not a player
|
||||||
|
if not theUnit:getPlayerName() then
|
||||||
|
--env.info("< <LEAVE aswGUI:onEvent: not player unit B")
|
||||||
|
return
|
||||||
|
end -- not a player
|
||||||
|
-- this is a player unit. Is it ASW carrier?
|
||||||
|
local uType = theUnit:getTypeName()
|
||||||
|
if not dcsCommon.isTroopCarrierType(uType, aswGUI.aswCarriers) then
|
||||||
|
if aswGUI.verbose then
|
||||||
|
trigger.action.outText("+++aswGUI: Player <" .. theUnit:getPlayerName() .. ">'s unit <" .. name .. "> of type <" .. uType .. "> is not ASW-capable. ASW Types are:", 30)
|
||||||
|
for idx, aType in pairs(aswGUI.aswCarriers) do
|
||||||
|
trigger.action.outText(aType,30)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
--env.info("< <LEAVE aswGUI:onEvent: not troop carrier")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
--env.info("> >Proccing aswGUI:onEvent event <" .. theID .. "")
|
||||||
|
|
||||||
|
-- now let's access it if it was
|
||||||
|
-- used before
|
||||||
|
local conf = aswGUI.aswCraft[name]
|
||||||
|
if not conf then
|
||||||
|
-- let's init it
|
||||||
|
conf = aswGUI.initUnit(name)
|
||||||
|
if not conf then
|
||||||
|
-- something went wrong, abort
|
||||||
|
return
|
||||||
|
end
|
||||||
|
aswGUI.aswCraft[name] = conf
|
||||||
|
end
|
||||||
|
|
||||||
|
-- if we get here, theUnit is an asw craft
|
||||||
|
if theID == 4 or -- land
|
||||||
|
theID == 3 then -- take off
|
||||||
|
aswGUI.setMenuForUnit(theUnit)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if theID == 20 or -- player enter
|
||||||
|
theID == 15 then -- birth (server player enter)
|
||||||
|
|
||||||
|
-- reset
|
||||||
|
aswGUI.resetConf(conf)
|
||||||
|
-- set menus
|
||||||
|
aswGUI.setMenuForUnit(theUnit)
|
||||||
|
end
|
||||||
|
|
||||||
|
if theID == 21 then -- player leave
|
||||||
|
aswGUI.resetConf(conf)
|
||||||
|
end
|
||||||
|
--env.info("< <Proccing complete asw event <" .. theID .. "")
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Config & start
|
||||||
|
--
|
||||||
|
function aswGUI.readConfigZone()
|
||||||
|
local theZone = cfxZones.getZoneByName("aswGUIConfig")
|
||||||
|
|
||||||
|
if not theZone then
|
||||||
|
if aswGUI.verbose then
|
||||||
|
trigger.action.outText("+++aswGUI: no config zone!", 30)
|
||||||
|
end
|
||||||
|
theZone = cfxZones.createSimpleZone("aswGUIConfig")
|
||||||
|
end
|
||||||
|
aswGUI.verbose = theZone.verbose
|
||||||
|
|
||||||
|
-- read & set defaults
|
||||||
|
if cfxZones.hasProperty(theZone, "aswCarriers") then
|
||||||
|
local carr = cfxZones.getStringFromZoneProperty(theZone, "aswCarriers", "")
|
||||||
|
carr = dcsCommon.splitString(carr, ",")
|
||||||
|
aswGUI.aswCarriers = dcsCommon.trimArray(carr)
|
||||||
|
end
|
||||||
|
|
||||||
|
aswGUI.buoysPerSlot = 10
|
||||||
|
aswGUI.torpedoesPerSlot = 2
|
||||||
|
aswGUI.buoyWeight = 50 -- kg, 10x = 500, 20x = 1000
|
||||||
|
aswGUI.buoyWeight = cfxZones.getNumberFromZoneProperty(theZone, "buoyWeight", aswGUI.buoyWeight)
|
||||||
|
aswGUI.torpedoWeight = 700 -- kg
|
||||||
|
aswGUI.torpedoWeight = cfxZones.getNumberFromZoneProperty(theZone, "torpedoWeight", aswGUI.torpedoWeight)
|
||||||
|
|
||||||
|
if aswGUI.verbose then
|
||||||
|
trigger.action.outText("+++aswGUI: read config", 30)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function aswGUI.start()
|
||||||
|
--env.info(">>>ENTER asw GUI start")
|
||||||
|
if not dcsCommon.libCheck then
|
||||||
|
trigger.action.outText("cfx aswGUI requires dcsCommon", 30)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if not dcsCommon.libCheck("cfx aswGUI", aswGUI.requiredLibs) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- read config
|
||||||
|
aswGUI.readConfigZone()
|
||||||
|
|
||||||
|
-- subscribe to world events
|
||||||
|
world.addEventHandler(aswGUI)
|
||||||
|
|
||||||
|
-- say Hi
|
||||||
|
trigger.action.outText("cfx ASW GUI v" .. aswGUI.version .. " started.", 30)
|
||||||
|
--env.info("<<<asw GUI started")
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- start up aswZones
|
||||||
|
--
|
||||||
|
if not aswGUI.start() then
|
||||||
|
trigger.action.outText("cfx aswGUI aborted: missing libraries", 30)
|
||||||
|
aswGUI = nil
|
||||||
|
end
|
||||||
192
modules/aswSubs.lua
Normal file
192
modules/aswSubs.lua
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
aswSubs = {}
|
||||||
|
aswSubs.version = "1.0.0"
|
||||||
|
aswSubs.verbose = false
|
||||||
|
aswSubs.requiredLibs = {
|
||||||
|
"dcsCommon", -- always
|
||||||
|
"cfxZones", -- Zones, of course
|
||||||
|
}
|
||||||
|
|
||||||
|
--[[--
|
||||||
|
Version History
|
||||||
|
1.0.0 - initial version
|
||||||
|
|
||||||
|
--]]--
|
||||||
|
|
||||||
|
aswSubs.groupsToWatch = {} -- subs attack any group in here if they are of a different coalition and not neutral
|
||||||
|
aswSubs.unitsHit = {} -- the goners
|
||||||
|
|
||||||
|
function aswSubs.addWatchgroup(name)
|
||||||
|
if Group.getByName(name) then
|
||||||
|
aswSubs.groupsToWatch[name] = name
|
||||||
|
else
|
||||||
|
trigger.action.outText("+++aswSubs: no group named <" .. name .. "> to watch over", 30)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function aswSubs.gatherSubs()
|
||||||
|
local allCoas = {0, 1, 2}
|
||||||
|
local subs = {}
|
||||||
|
for idx, coa in pairs(allCoas) do
|
||||||
|
local allGroups = coalition.getGroups(coa, 3) -- ships only
|
||||||
|
for idy, aGroup in pairs(allGroups) do
|
||||||
|
allUnits = aGroup:getUnits()
|
||||||
|
for idz, aUnit in pairs(allUnits) do
|
||||||
|
-- see if this unit is a sub
|
||||||
|
if aUnit and Unit.isExist(aUnit) then
|
||||||
|
if (dcsCommon.getUnitAGL(aUnit) < -5) then -- submerged contact.
|
||||||
|
local contact = {}
|
||||||
|
contact.theUnit = aUnit
|
||||||
|
contact.coalition = coa
|
||||||
|
contact.name = aUnit:getName()
|
||||||
|
contact.loc = aUnit:getPoint()
|
||||||
|
subs[contact.name] = contact
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return subs
|
||||||
|
end
|
||||||
|
|
||||||
|
function aswSubs.boom(args)
|
||||||
|
|
||||||
|
local uName = args.name
|
||||||
|
local loc = args.loc
|
||||||
|
local theUnit = Unit.getByName(uName)
|
||||||
|
if theUnit and theUnit.isExist(theUnit) then
|
||||||
|
loc = theUnit:getPoint()
|
||||||
|
end
|
||||||
|
|
||||||
|
trigger.action.explosion(loc, aswSubs.explosionDamage)
|
||||||
|
end
|
||||||
|
|
||||||
|
function aswSubs.alert(theUnit, theContact)
|
||||||
|
-- note: we dont need theContact right now
|
||||||
|
if not theUnit or not Unit.isExist(theUnit) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- see if this was hit before
|
||||||
|
local uName = theUnit:getName()
|
||||||
|
if aswSubs.unitsHit[uName] then return end
|
||||||
|
|
||||||
|
-- mark it as hit
|
||||||
|
aswSubs.unitsHit[uName] = theContact.name
|
||||||
|
|
||||||
|
-- schedule a few explosions
|
||||||
|
local args = {}
|
||||||
|
args.name = uName
|
||||||
|
args.loc = theUnit:getPoint()
|
||||||
|
local salvoSize = tonumber(aswSubs.salvoMin)
|
||||||
|
local varPart = tonumber(aswSubs.salvoMax) - tonumber(aswSubs.salvoMin)
|
||||||
|
if varPart > 0 then
|
||||||
|
varPart = dcsCommon.smallRandom(varPart)
|
||||||
|
salvoSize = salvoSize + varPart
|
||||||
|
end
|
||||||
|
|
||||||
|
for i=1, tonumber(salvoSize) do
|
||||||
|
timer.scheduleFunction(aswSubs.boom, args, timer.getTime() + i*2 + 4)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- theContact has come within crit dist of theUnit
|
||||||
|
local coa = theUnit:getCoalition()
|
||||||
|
trigger.action.outTextForCoalition(coa, theUnit:getName() .. " reports " .. salvoSize .. " incoming torpedoes!", 30)
|
||||||
|
end
|
||||||
|
|
||||||
|
function aswSubs.update()
|
||||||
|
--env.info("-->Enter asw Subs update")
|
||||||
|
timer.scheduleFunction(aswSubs.update, {}, timer.getTime() + 1)
|
||||||
|
|
||||||
|
-- get all current subs
|
||||||
|
local allSubs = aswSubs.gatherSubs()
|
||||||
|
|
||||||
|
-- now iterate all watch groups
|
||||||
|
for idx, name in pairs(aswSubs.groupsToWatch) do
|
||||||
|
local theGroup = Group.getByName(name)
|
||||||
|
if theGroup and Group.isExist(theGroup) then
|
||||||
|
local groupCoa = theGroup:getCoalition()
|
||||||
|
if theGroup and Group.isExist(theGroup) then
|
||||||
|
allUnits = theGroup:getUnits()
|
||||||
|
for idx, aUnit in pairs(allUnits) do
|
||||||
|
-- check against all subs
|
||||||
|
if aUnit and Unit.isExist(aUnit) then
|
||||||
|
local loc = aUnit:getPoint()
|
||||||
|
for cName, contact in pairs(allSubs) do
|
||||||
|
-- attack other side but not neutral
|
||||||
|
if groupCoa ~= contact.coalition and groupCoa ~= 0 then
|
||||||
|
-- ok, go check
|
||||||
|
local dist = dcsCommon.dist(loc, contact.loc)
|
||||||
|
if dist < aswSubs.critDist then
|
||||||
|
aswSubs.alert(aUnit, contact)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
--env.info("<--Levae asw Subs update")
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Config & start
|
||||||
|
--
|
||||||
|
function aswSubs.readConfigZone()
|
||||||
|
local theZone = cfxZones.getZoneByName("aswSubsConfig")
|
||||||
|
if not theZone then
|
||||||
|
if aswSubs.verbose then
|
||||||
|
trigger.action.outText("+++aswSubs: no config zone!", 30)
|
||||||
|
end
|
||||||
|
theZone = cfxZones.createSimpleZone("aswSubsConfig")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- read & set defaults
|
||||||
|
aswSubs.critDist = 4000
|
||||||
|
aswSubs.critDist = cfxZones.getNumberFromZoneProperty(theZone, "critDist", aswSubs.critDist)
|
||||||
|
aswSubs.explosionDamage = 1000
|
||||||
|
aswSubs.explosionDamage = cfxZones.getNumberFromZoneProperty(theZone, "explosionDamage", aswSubs.explosionDamage)
|
||||||
|
|
||||||
|
aswSubs.salvoMin, aswSubs.salvoMax = cfxZones.getPositiveRangeFromZoneProperty(theZone, "salvoSize", 4, 4)
|
||||||
|
--trigger.action.outText("salvo: min <" .. aswSubs.salvoMin .. ">, max <" .. aswSubs.salvoMax .. ">", 30)
|
||||||
|
local targets = cfxZones.getStringFromZoneProperty(theZone, "targets", "")
|
||||||
|
local t2 = dcsCommon.string2Array(targets, ",")
|
||||||
|
for idx, targetName in pairs (t2) do
|
||||||
|
aswSubs.addWatchgroup(targetName)
|
||||||
|
end
|
||||||
|
|
||||||
|
if aswSubs.verbose then
|
||||||
|
trigger.action.outText("+++aswSubs: read config", 30)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function aswSubs.start()
|
||||||
|
if not dcsCommon.libCheck then
|
||||||
|
trigger.action.outText("cfx aswSubs requires dcsCommon", 30)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if not dcsCommon.libCheck("cfx aswSubs", aswSubs.requiredLibs) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- read config
|
||||||
|
aswSubs.readConfigZone()
|
||||||
|
|
||||||
|
-- start the script
|
||||||
|
aswSubs.update()
|
||||||
|
|
||||||
|
-- all is good
|
||||||
|
trigger.action.outText("cfx ASW Subs v" .. aswGUI.version .. " started.", 30)
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- start up aswSubs
|
||||||
|
--
|
||||||
|
if not aswSubs.start() then
|
||||||
|
trigger.action.outText("cfx aswSubs aborted: missing libraries", 30)
|
||||||
|
aswSubs = nil
|
||||||
|
end
|
||||||
|
|
||||||
193
modules/aswZones.lua
Normal file
193
modules/aswZones.lua
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
aswZones = {}
|
||||||
|
aswZones.version = "1.0.0"
|
||||||
|
aswZones.verbose = false
|
||||||
|
aswZones.requiredLibs = {
|
||||||
|
"dcsCommon", -- always
|
||||||
|
"cfxZones", -- Zones, of course
|
||||||
|
"asw", -- needs asw module
|
||||||
|
}
|
||||||
|
--[[--
|
||||||
|
Version History
|
||||||
|
1.0.0 - initial version
|
||||||
|
|
||||||
|
--]]--
|
||||||
|
|
||||||
|
aswZones.ups = 1 -- = once every second
|
||||||
|
aswZones.zones = {} -- all zones, by name
|
||||||
|
|
||||||
|
function aswZones.addZone(theZone)
|
||||||
|
if not theZone then
|
||||||
|
trigger.action.outText("aswZ: nil zone in addZone", 30)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
aswZones.zones[theZone.name] = theZone
|
||||||
|
end
|
||||||
|
|
||||||
|
function aswZones.getZoneNamed(theName)
|
||||||
|
if not theName then return nil end
|
||||||
|
return aswZones[theName]
|
||||||
|
end
|
||||||
|
|
||||||
|
function aswZones.getClosestASWZoneTo(loc)
|
||||||
|
local closestZone = nil
|
||||||
|
local loDist = math.huge
|
||||||
|
for name, theZone in pairs(aswZones.zones) do
|
||||||
|
local zp = cfxZones.getPoint(theZone)
|
||||||
|
local d = dcsCommon.distFlat(zp, loc)
|
||||||
|
if d < loDist then
|
||||||
|
loDist = d
|
||||||
|
closestZone = theZone
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return closestZone, loDist
|
||||||
|
end
|
||||||
|
|
||||||
|
function aswZones.createASWZone(theZone)
|
||||||
|
-- get inventory of buoys
|
||||||
|
theZone.buoyNum = cfxZones.getNumberFromZoneProperty(theZone, "buoyS", -1) -- also used as supply for helos if they land in zone
|
||||||
|
theZone.torpedoNum = cfxZones.getNumberFromZoneProperty(theZone, "torpedoes", -1) -- also used as supply for helos if they land in zone
|
||||||
|
|
||||||
|
theZone.coalition = cfxZones.getCoalitionFromZoneProperty(theZone, "coalition", 0)
|
||||||
|
|
||||||
|
-- trigger method
|
||||||
|
theZone.aswTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "triggerMethod", "change")
|
||||||
|
if cfxZones.hasProperty(theZone, "aswTriggerMethod") then
|
||||||
|
theZone.aswTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "aswTriggerMethod", "change")
|
||||||
|
end
|
||||||
|
|
||||||
|
if cfxZones.hasProperty(theZone, "buoy?") then
|
||||||
|
theZone.buoyFlag = cfxZones.getStringFromZoneProperty(theZone, "buoy?", "none")
|
||||||
|
theZone.lastBuoyValue = cfxZones.getFlagValue(theZone.buoyFlag, theZone)
|
||||||
|
end
|
||||||
|
|
||||||
|
if cfxZones.hasProperty(theZone, "torpedo?") then
|
||||||
|
theZone.torpedoFlag = cfxZones.getStringFromZoneProperty(theZone, "torpedo?", "none")
|
||||||
|
theZone.lastTorpedoValue = cfxZones.getFlagValue(theZone.torpedoFlag, theZone)
|
||||||
|
end
|
||||||
|
|
||||||
|
if theZone.verbose or aswZones.verbose then
|
||||||
|
trigger.action.outText("+++aswZ: new asw zone <" .. theZone.name .. ">", 30)
|
||||||
|
trigger.action.outText("has coalition " .. theZone.coalition, 30)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- responding to triggers
|
||||||
|
--
|
||||||
|
function aswZones.dropBuoy(theZone)
|
||||||
|
if theZone.buoyNum == 0 then
|
||||||
|
-- we are fresh out. no launch
|
||||||
|
if theZone.verbose or aswZones.verbose then
|
||||||
|
trigger.action.outText("+++aswZ: zone <" .. theZone.name .. "> is out of buoys, can't drop", 30)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local theBuoy = asw.dropBuoyFromZone(theZone)
|
||||||
|
if theZone.buoyNum > 0 then
|
||||||
|
theZone.buoyNum = theZone.buoyNum - 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function aswZones.dropTorpedo(theZone)
|
||||||
|
if theZone.torpedoNum == 0 then
|
||||||
|
-- we are fresh out. no launch
|
||||||
|
if theZone.verbose or aswZones.verbose then
|
||||||
|
trigger.action.outText("+++aswZ: zone <" .. theZone.name .. "> is out of torpedoes, can't drop", 30)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local theTorpedo = asw.dropTorpedoFromZone(theZone)
|
||||||
|
if theZone.torpedoNum > 0 then
|
||||||
|
theZone.torpedoNum = theZone.torpedoNum - 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
--
|
||||||
|
-- Update
|
||||||
|
--
|
||||||
|
function aswZones.update()
|
||||||
|
--env.info("-->Enter asw ZONES update")
|
||||||
|
-- first, schedule next invocation
|
||||||
|
timer.scheduleFunction(aswZones.update, {}, timer.getTime() + 1/aswZones.ups)
|
||||||
|
|
||||||
|
for zName, theZone in pairs(aswZones.zones) do
|
||||||
|
if theZone.buoyFlag and cfxZones.testZoneFlag(theZone, theZone.buoyFlag, theZone.aswTriggerMethod, "lastBuoyValue") then
|
||||||
|
trigger.action.outText("zone <" .. theZone.name .. "> will now drop a buoy", 30)
|
||||||
|
aswZones.dropBuoy(theZone)
|
||||||
|
end
|
||||||
|
|
||||||
|
if theZone.torpedoFlag and cfxZones.testZoneFlag(theZone, theZone.torpedoFlag, theZone.aswTriggerMethod, "lastTorpedoValue") then
|
||||||
|
trigger.action.outText("zone <" .. theZone.name .. "> will now drop a TORPEDO", 30)
|
||||||
|
aswZones.dropTorpedo(theZone)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--env.info("<--Leave asw ZONES update")
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Config & start
|
||||||
|
--
|
||||||
|
function aswZones.readConfigZone()
|
||||||
|
local theZone = cfxZones.getZoneByName("aswZonesConfig")
|
||||||
|
if not theZone then
|
||||||
|
if aswZones.verbose then
|
||||||
|
trigger.action.outText("+++aswZ: no config zone!", 30)
|
||||||
|
end
|
||||||
|
theZone = cfxZones.createSimpleZone("aswZonesConfig")
|
||||||
|
end
|
||||||
|
aswZones.verbose = theZone.verbose
|
||||||
|
|
||||||
|
-- set defaults, later do the reading
|
||||||
|
|
||||||
|
|
||||||
|
if aswZones.verbose then
|
||||||
|
trigger.action.outText("+++aswZ: read config", 30)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function aswZones.start()
|
||||||
|
if not dcsCommon.libCheck then
|
||||||
|
trigger.action.outText("cfx aswZones requires dcsCommon", 30)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if not dcsCommon.libCheck("cfx aswZones", aswZones.requiredLibs) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- read config
|
||||||
|
aswZones.readConfigZone()
|
||||||
|
|
||||||
|
-- read zones
|
||||||
|
local attrZones = cfxZones.getZonesWithAttributeNamed("asw")
|
||||||
|
|
||||||
|
-- collect my zones
|
||||||
|
for k, aZone in pairs(attrZones) do
|
||||||
|
aswZones.createASWZone(aZone) -- process attributes
|
||||||
|
aswZones.addZone(aZone) -- add to inventory
|
||||||
|
end
|
||||||
|
|
||||||
|
-- start update
|
||||||
|
aswZones.update()
|
||||||
|
|
||||||
|
-- say hi
|
||||||
|
trigger.action.outText("cfx aswZones v" .. aswZones.version .. " started.", 30)
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- start up aswZones
|
||||||
|
--
|
||||||
|
if not aswZones.start() then
|
||||||
|
trigger.action.outText("cfx aswZones aborted: missing libraries", 30)
|
||||||
|
aswZones = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- add asw.helper with zones that can
|
||||||
|
-- drop torps
|
||||||
|
-- have inventory per zone or -1 as infinite
|
||||||
|
-- have an event when a buoy finds something
|
||||||
|
-- hav an event when a buoy times out
|
||||||
|
-- have buoyOut! and torpedoOut! events
|
||||||
@ -4,7 +4,7 @@
|
|||||||
-- *** EXTENDS ZONES: 'pathing' attribute
|
-- *** EXTENDS ZONES: 'pathing' attribute
|
||||||
--
|
--
|
||||||
cfxCommander = {}
|
cfxCommander = {}
|
||||||
cfxCommander.version = "1.1.2"
|
cfxCommander.version = "1.1.3"
|
||||||
--[[-- VERSION HISTORY
|
--[[-- VERSION HISTORY
|
||||||
- 1.0.5 - createWPListForGroupToPointViaRoads: detect no road found
|
- 1.0.5 - createWPListForGroupToPointViaRoads: detect no road found
|
||||||
- 1.0.6 - build in more group checks in assign wp list
|
- 1.0.6 - build in more group checks in assign wp list
|
||||||
@ -27,6 +27,9 @@ cfxCommander.version = "1.1.2"
|
|||||||
- makeGroupStopTransmitting
|
- makeGroupStopTransmitting
|
||||||
- verbose check before path warning
|
- verbose check before path warning
|
||||||
- added delay defaulting for most scheduling functions
|
- added delay defaulting for most scheduling functions
|
||||||
|
- 1.1.3 - isExist() guard improvements for multiple methods
|
||||||
|
- cleaned up comments
|
||||||
|
|
||||||
--]]--
|
--]]--
|
||||||
|
|
||||||
cfxCommander.requiredLibs = {
|
cfxCommander.requiredLibs = {
|
||||||
@ -199,7 +202,8 @@ function cfxCommander.doScheduledTask(data)
|
|||||||
end
|
end
|
||||||
local theGroup = data.group
|
local theGroup = data.group
|
||||||
if not theGroup then return end
|
if not theGroup then return end
|
||||||
if not theGroup.isExist then return end
|
if not Group.isExist(theGroup) then return end
|
||||||
|
-- if not theGroup.isExist then return end
|
||||||
|
|
||||||
local theController = theGroup:getController()
|
local theController = theGroup:getController()
|
||||||
theController:pushTask(data.task)
|
theController:pushTask(data.task)
|
||||||
@ -290,7 +294,7 @@ function cfxCommander.assignWPListToGroup(group, wpList, delay)
|
|||||||
group = Group.getByName(group)
|
group = Group.getByName(group)
|
||||||
end
|
end
|
||||||
if not group then return end
|
if not group then return end
|
||||||
if not group:isExist() then return end
|
if not Group.isExist(group) then return end
|
||||||
|
|
||||||
local theTask = cfxCommander.buildTaskFromWPList(wpList)
|
local theTask = cfxCommander.buildTaskFromWPList(wpList)
|
||||||
local ctrl = group:getController()
|
local ctrl = group:getController()
|
||||||
@ -427,7 +431,6 @@ function cfxCommander.makeGroupGoTherePreferringRoads(group, there, speed, delay
|
|||||||
if oRide and oRide.pathing == "offroad" then
|
if oRide and oRide.pathing == "offroad" then
|
||||||
-- yup, override road preference
|
-- yup, override road preference
|
||||||
cfxCommander.makeGroupGoThere(group, there, speed, "Off Road", delay)
|
cfxCommander.makeGroupGoThere(group, there, speed, "Off Road", delay)
|
||||||
--trigger.action.outText("pathing: override offroad")
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -441,7 +444,7 @@ end
|
|||||||
|
|
||||||
function cfxCommander.makeGroupHalt(group, delay)
|
function cfxCommander.makeGroupHalt(group, delay)
|
||||||
if not group then return end
|
if not group then return end
|
||||||
if not group:isExist() then return end
|
if not Group.isExist(group) then return end
|
||||||
if not delay then delay = 0 end
|
if not delay then delay = 0 end
|
||||||
local theTask = {id = 'Hold', params = {}}
|
local theTask = {id = 'Hold', params = {}}
|
||||||
cfxCommander.scheduleTaskForGroup(group, theTask, delay)
|
cfxCommander.scheduleTaskForGroup(group, theTask, delay)
|
||||||
|
|||||||
@ -21,84 +21,90 @@ cfxGroundTroops.requiredLibs = {
|
|||||||
-- module and addTroopsToPool to have them then managed by this
|
-- module and addTroopsToPool to have them then managed by this
|
||||||
-- module
|
-- module
|
||||||
|
|
||||||
cfxGroundTroops.deployedTroops = {}
|
cfxGroundTroops.deployedTroops = {} -- indexed by group name
|
||||||
|
|
||||||
-- version history
|
--[[--
|
||||||
-- 1.3.0 - added "wait-" prefix to have toops do nothing
|
version history
|
||||||
-- - added lazing
|
1.3.0 - added "wait-" prefix to have toops do nothing
|
||||||
-- 1.3.1 - sound for lazing msg is "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav"
|
- added lazing
|
||||||
-- - lazing --> lasing in text
|
1.3.1 - sound for lazing msg is "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav"
|
||||||
-- 1.3.2 - set ups to 2
|
- lazing --> lasing in text
|
||||||
-- 1.4.0 - queued updates except for lazers
|
1.3.2 - set ups to 2
|
||||||
-- 1.4.1 - makeTroopsEngageZone now issues hold before moving on 5 seconds later
|
1.4.0 - queued updates except for lazers
|
||||||
-- - getTroopReport
|
1.4.1 - makeTroopsEngageZone now issues hold before moving on 5 seconds later
|
||||||
-- - include size of group
|
- getTroopReport
|
||||||
-- 1.4.2 - uses unitIsInfantry from dcsCommon
|
- include size of group
|
||||||
-- 1.5.0 - new scheduled updates per troop to reduce processor load
|
1.4.2 - uses unitIsInfantry from dcsCommon
|
||||||
-- - tiebreak code
|
1.5.0 - new scheduled updates per troop to reduce processor load
|
||||||
-- 1.5.1 - small bugfix in scheduled code
|
- tiebreak code
|
||||||
-- 1.5.2 - checkSchedule
|
1.5.1 - small bugfix in scheduled code
|
||||||
-- - speed warning in scheduler
|
1.5.2 - checkSchedule
|
||||||
-- - go off road when speed warning too much
|
- speed warning in scheduler
|
||||||
-- 1.5.3 - monitor troops
|
- go off road when speed warning too much
|
||||||
-- - managed queue for ground troops
|
1.5.3 - monitor troops
|
||||||
-- - on second switch to offroad now removed from MQ
|
- managed queue for ground troops
|
||||||
-- 1.5.4 - removed debugging messages
|
- on second switch to offroad now removed from MQ
|
||||||
-- 1.5.5 - removed bug in troop report reading nil destination
|
1.5.4 - removed debugging messages
|
||||||
-- 1.6.0 - check modules
|
1.5.5 - removed bug in troop report reading nil destination
|
||||||
-- 1.6.1 - troopsCallback management so you can be informed if a
|
1.6.0 - check modules
|
||||||
-- troop you have added to the pool is dead or has achieved a goal.
|
1.6.1 - troopsCallback management so you can be informed if a
|
||||||
-- callback will list reasons "dead" and "arrived"
|
troop you have added to the pool is dead or has achieved a goal.
|
||||||
-- updateAttackers
|
callback will list reasons "dead" and "arrived"
|
||||||
-- 1.6.2 - also accept 'lase' as 'laze', translate directly
|
updateAttackers
|
||||||
-- 1.7.0 - now can use groundTroopsConfig zone
|
1.6.2 - also accept 'lase' as 'laze', translate directly
|
||||||
-- 1.7.1 - addTroopsDeadCallback() renamed to addTroopsCallback()
|
1.7.0 - now can use groundTroopsConfig zone
|
||||||
-- - invokeCallbacksFor also accepts and passes on data block
|
1.7.1 - addTroopsDeadCallback() renamed to addTroopsCallback()
|
||||||
-- - troops is always passed in data block as .troops
|
- invokeCallbacksFor also accepts and passes on data block
|
||||||
-- 1.7.2 - callback when group is neutralized on guard orders
|
- troops is always passed in data block as .troops
|
||||||
-- - callback when group is being engaged under guard orders
|
1.7.2 - callback when group is neutralized on guard orders
|
||||||
-- 1.7.3 - callbacks for lase:tracking and lase:stop
|
- callback when group is being engaged under guard orders
|
||||||
-- 1.7.4 - verbose flag, warnings suppressed
|
1.7.3 - callbacks for lase:tracking and lase:stop
|
||||||
-- 1.7.5 - some troop.group hardening with isExist()
|
1.7.4 - verbose flag, warnings suppressed
|
||||||
-- 1.7.6 - fixed switchToOffroad
|
1.7.5 - some troop.group hardening with isExist()
|
||||||
-- 1.7.7 - no longer case sensitive for orders
|
1.7.6 - fixed switchToOffroad
|
||||||
|
1.7.7 - no longer case sensitive for orders
|
||||||
|
1.7.7 - updateAttackers() now inspects 'moving' status and invokes makeTroopsEngageZone
|
||||||
|
- makeTroopsEngageZone() sets 'moving' status to true
|
||||||
|
- createGroundTroops() sets moving status to false
|
||||||
|
- updateZoneAttackers() uses moving
|
||||||
|
|
||||||
|
|
||||||
-- an entry into the deployed troop has the following attributes
|
an entry into the deployed troop table has the following attributes
|
||||||
-- - group - the group
|
- group - the group
|
||||||
-- - orders: "guard" - will guard the spot and look for enemies in range
|
- orders: "guard" - will guard the spot and look for enemies in range
|
||||||
-- "patrol" - will walk between way points back and forth
|
"patrol" - will walk between way points back and forth
|
||||||
-- "laze" - will stay in place and try to laze visible vehicles in range
|
"laze" - will stay in place and try to laze visible vehicles in range
|
||||||
-- "attackOwnedZone" - interface to cfxOwnedZones module, seeks out
|
"attackOwnedZone" - interface to cfxOwnedZones module, seeks out
|
||||||
-- enemy zones to attack and capture them
|
enemy zones to attack and capture them
|
||||||
-- "wait-<some other orders>" do nothing. the "wait" prefix will be removed some time and <some other order> then revealed. Used at least by heloTroops
|
"wait-<some other orders>" do nothing. the "wait" prefix will be removed some time and <some other order> then revealed. Used at least by heloTroops
|
||||||
-- "train" - target dummies. ROE=HOLD, no ground loop
|
"train" - target dummies. ROE=HOLD, no ground loop
|
||||||
-- "attack" - transition to destination, once there, stop and
|
"attack" - transition to destination, once there, stop and
|
||||||
-- switch to guard. requires destination zone be sez to a valid cfxZone
|
switch to guard. requires destination zone be set to a valid cfxZone
|
||||||
-- - coalition - the coalition from the group
|
- coalition - the coalition from the group
|
||||||
-- - enemy - if set, the group this group it is engaging. this means the group is fighting and not idle
|
- enemy - if set, the group this group it is engaging. this means the group is fighting and not idle
|
||||||
-- - name - name of group, dan be freely changed
|
- name - name of group, dan be freely changed
|
||||||
-- - signature - "cfx" to tell apart from dcs groups
|
- signature - "cfx" to tell apart from dcs groups
|
||||||
-- - range = range to look for enemies. default is 300m. In "laze" orders, range to laze
|
- range = range to look for enemies. default is 300m. In "laze" orders, range to laze
|
||||||
-- - lazeTarget - target currently lazing
|
- lazeTarget - target currently lazing
|
||||||
-- - lazeCode - laser code. default is 1688
|
- lazeCode - laser code. default is 1688
|
||||||
|
- moving - has been given orders to move somewhere already. used for first movement order with attack orders
|
||||||
|
|
||||||
--
|
|
||||||
-- usage:
|
usage:
|
||||||
-- take a dcs group of ground troops and create a cfx ground troop record with
|
take a dcs group of ground troops and create a cfx ground troop record with
|
||||||
-- createGroundTroops()
|
createGroundTroops()
|
||||||
-- then add this to the manager with
|
then add this to the manager with
|
||||||
-- addGroundTroopsToPool()
|
addGroundTroopsToPool()
|
||||||
--
|
|
||||||
-- you can control what the group is to do by changing the cfx troop attribute orders
|
you can control what the group is to do by changing the cfx troop attribute orders
|
||||||
-- you can install a callback that will notify you if a troop reached a goal or
|
you can install a callback that will notify you if a troop reached a goal or
|
||||||
-- was killed with addTroopsCallback() which will also give a reason
|
was killed with addTroopsCallback() which will also give a reason
|
||||||
-- callback pattern is myCallback(reason, theGroup, orders, data) with troop being the
|
callback pattern is myCallback(reason, theGroup, orders, data) with troop being the
|
||||||
-- group, and orders the original orders, and reason a string containing why the
|
group, and orders the original orders, and reason a string containing why the
|
||||||
-- callback was invoked. Currently defined reasons are
|
callback was invoked. Currently defined reasons are
|
||||||
-- - "dead" - entire group was killed
|
- "dead" - entire group was killed
|
||||||
-- - "arrived" - at least a part of group arrived at destination (only with some orders)
|
- "arrived" - at least a part of group arrived at destination (only with some orders)
|
||||||
--
|
--]]--
|
||||||
|
|
||||||
--
|
--
|
||||||
-- UPDATE MODELS
|
-- UPDATE MODELS
|
||||||
@ -129,7 +135,7 @@ function cfxGroundTroops.readConfigZone()
|
|||||||
if cfxGroundTroops.verbose then
|
if cfxGroundTroops.verbose then
|
||||||
trigger.action.outText("***gndT: NO config zone!", 30)
|
trigger.action.outText("***gndT: NO config zone!", 30)
|
||||||
end
|
end
|
||||||
return
|
theZone = cfxZones.createSimpleZone("groundTroopsConfig")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- ok, for each property, load it if it exists
|
-- ok, for each property, load it if it exists
|
||||||
@ -198,9 +204,10 @@ end
|
|||||||
|
|
||||||
-- create controller commands to attack a group "enemies"
|
-- create controller commands to attack a group "enemies"
|
||||||
-- enemies are an attribute of the troop structure
|
-- enemies are an attribute of the troop structure
|
||||||
|
-- usually called from a group on guard when idling
|
||||||
function cfxGroundTroops.makeTroopsEngageEnemies(troop)
|
function cfxGroundTroops.makeTroopsEngageEnemies(troop)
|
||||||
local group = troop.group
|
local group = troop.group
|
||||||
if not group:isExist() then
|
if not Group.isExist(group) then
|
||||||
trigger.action.outText("+++gndT: troup don't exist, dropping", 30)
|
trigger.action.outText("+++gndT: troup don't exist, dropping", 30)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@ -214,10 +221,11 @@ function cfxGroundTroops.makeTroopsEngageEnemies(troop)
|
|||||||
-- we lerp to 2/3 of enemy location
|
-- we lerp to 2/3 of enemy location
|
||||||
there = dcsCommon.vLerp(from, there, 0.66)
|
there = dcsCommon.vLerp(from, there, 0.66)
|
||||||
|
|
||||||
local speed = 10 -- m/s = 10 km/h
|
local speed = 10 -- m/s = 10 km/h -- wait. 10 m/s is 36 km/h
|
||||||
cfxCommander.makeGroupGoThere(group, there, speed)
|
cfxCommander.makeGroupGoThere(group, there, speed)
|
||||||
local attask = cfxCommander.createAttackGroupCommand(enemies)
|
local attask = cfxCommander.createAttackGroupCommand(enemies)
|
||||||
cfxCommander.scheduleTaskForGroup(group, attask, 0.5)
|
cfxCommander.scheduleTaskForGroup(group, attask, 0.5)
|
||||||
|
troop.moving = true
|
||||||
end
|
end
|
||||||
|
|
||||||
-- make the troops engage a cfxZone passed in the destination
|
-- make the troops engage a cfxZone passed in the destination
|
||||||
@ -225,7 +233,7 @@ end
|
|||||||
function cfxGroundTroops.makeTroopsEngageZone(troop)
|
function cfxGroundTroops.makeTroopsEngageZone(troop)
|
||||||
local group = troop.group
|
local group = troop.group
|
||||||
if not group:isExist() then
|
if not group:isExist() then
|
||||||
trigger.action.outText("+++gndT: make engage zone: troops do not exist, exiting", 30)
|
trigger.action.outText("+++gndT: make troops engage zone: troops do not exist, exiting", 30)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -235,22 +243,14 @@ function cfxGroundTroops.makeTroopsEngageZone(troop)
|
|||||||
local there = enemyZone.point -- access zone position
|
local there = enemyZone.point -- access zone position
|
||||||
if not there then return end
|
if not there then return end
|
||||||
|
|
||||||
-- we lerp to 102% of enemy location to force overshoot and engagement
|
|
||||||
--there = dcsCommon.vLerp(from, there, 1.02)
|
|
||||||
|
|
||||||
local speed = 14 -- m/s; 10 m/s = 36 km/h
|
local speed = 14 -- m/s; 10 m/s = 36 km/h
|
||||||
-- we prefer going over roads since we don't know
|
|
||||||
-- what is there
|
|
||||||
|
|
||||||
-- make troops stop in 1 second, then start in 5 seconds to give AI respite
|
-- make troops stop in 1 second, then start in 5 seconds to give AI respite
|
||||||
cfxCommander.makeGroupHalt(group, 1) -- 1 second delay
|
cfxCommander.makeGroupHalt(group, 1) -- 1 second delay
|
||||||
cfxCommander.makeGroupGoTherePreferringRoads(group, there, speed, 5)
|
cfxCommander.makeGroupGoTherePreferringRoads(group, there, speed, 5)
|
||||||
-- no attack command since we don't know what is there
|
|
||||||
-- but mayhaps we should issue weapons free?
|
|
||||||
-- we'll soon test that by sticking in a troop on the way
|
|
||||||
|
|
||||||
-- local attask = cfxCommander.createAttackGroupCommand(enemies)
|
-- remember that we have issued a move order
|
||||||
-- cfxCommander.scheduleTaskForGroup(group, attask, 0.5)
|
troop.moving = true
|
||||||
end
|
end
|
||||||
|
|
||||||
function cfxGroundTroops.switchToOffroad(troops)
|
function cfxGroundTroops.switchToOffroad(troops)
|
||||||
@ -301,13 +301,11 @@ function cfxGroundTroops.updateZoneAttackers(troop)
|
|||||||
local newTargetZone = cfxGroundTroops.getClosestEnemyZone(troop)
|
local newTargetZone = cfxGroundTroops.getClosestEnemyZone(troop)
|
||||||
if not newTargetZone then
|
if not newTargetZone then
|
||||||
-- all target zones are friendly, go to guard mode
|
-- all target zones are friendly, go to guard mode
|
||||||
-- trigger.action.outTextForCoalition(troop.side, troop.name .. " holding position", 30)
|
|
||||||
troop.orders = "guard"
|
troop.orders = "guard"
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if newTargetZone ~= troop.destination then
|
if newTargetZone ~= troop.destination then
|
||||||
-- trigger.action.outTextForCoalition(troop.side, troop.name .. " enroute to " .. newTargetZone.name, 30)
|
|
||||||
troop.destination = newTargetZone
|
troop.destination = newTargetZone
|
||||||
cfxGroundTroops.makeTroopsEngageZone(troop)
|
cfxGroundTroops.makeTroopsEngageZone(troop)
|
||||||
troop.lastOrderDate = timer.getTime()
|
troop.lastOrderDate = timer.getTime()
|
||||||
@ -315,23 +313,37 @@ function cfxGroundTroops.updateZoneAttackers(troop)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- if we get here, we should be under way to our nearest enemy zone
|
||||||
|
if not troop.moving then
|
||||||
|
cfxGroundTroops.makeTroopsEngageZone(troop)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
-- if we get here, we are under way to troop.destination
|
-- if we get here, we are under way to troop.destination
|
||||||
-- check if we are inside the zone, and if so, set variable to true
|
-- check if we are inside the zone, and if so, set variable to true
|
||||||
local p = dcsCommon.getGroupLocation(troop.group)
|
local p = dcsCommon.getGroupLocation(troop.group)
|
||||||
troop.insideDestination = cfxZones.isPointInsideZone(p, troop.destination)
|
troop.insideDestination = cfxZones.isPointInsideZone(p, troop.destination)
|
||||||
|
|
||||||
-- if we get here, we need no change
|
-- if we get here, we need no change
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- attackers simply travel to their destination, and then switch to
|
-- attackers simply travel to their destination (zone), and then switch to
|
||||||
-- guard orders once they arrive
|
-- guard orders once they arrive
|
||||||
function cfxGroundTroops.updateAttackers(troop)
|
function cfxGroundTroops.updateAttackers(troop)
|
||||||
if not troop then return end
|
if not troop then return end
|
||||||
if not troop.destination then return end
|
if not troop.destination then return end
|
||||||
if not troop.group:isExist() then return end
|
if not troop.group:isExist() then return end
|
||||||
|
|
||||||
|
-- if we are not moving, we need to issue move oders now
|
||||||
|
-- this can happen if previously, there was a 'wait' command
|
||||||
|
-- and this now was removed so we end up in the method
|
||||||
|
if not troop.moving then
|
||||||
|
cfxGroundTroops.makeTroopsEngageZone(troop)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
if cfxZones.isGroupPartiallyInZone(troop.group, troop.destination) then
|
if cfxZones.isGroupPartiallyInZone(troop.group, troop.destination) then
|
||||||
-- we have arrived
|
-- we have arrived
|
||||||
-- we could now also initiate a general callback with reason
|
-- we could now also initiate a general callback with reason
|
||||||
@ -613,21 +625,18 @@ function cfxGroundTroops.update()
|
|||||||
cfxGroundTroops.updateSchedule = timer.scheduleFunction(cfxGroundTroops.update, {}, timer.getTime() + 1/cfxGroundTroops.ups)
|
cfxGroundTroops.updateSchedule = timer.scheduleFunction(cfxGroundTroops.update, {}, timer.getTime() + 1/cfxGroundTroops.ups)
|
||||||
-- iterate all my troops and build next
|
-- iterate all my troops and build next
|
||||||
-- versions pool
|
-- versions pool
|
||||||
local liveTroops = {}
|
local liveTroops = {} -- filtered table, indexed by name
|
||||||
for idx, troop in pairs(cfxGroundTroops.deployedTroops) do
|
for idx, troop in pairs(cfxGroundTroops.deployedTroops) do
|
||||||
local group = troop.group
|
local group = troop.group
|
||||||
if not dcsCommon.isGroupAlive(group) then
|
if not dcsCommon.isGroupAlive(group) then
|
||||||
-- group dead. remove from pool
|
-- group dead. remove from pool
|
||||||
-- this happens by not copying it into the poos
|
-- this happens by not copying it into the poos
|
||||||
-- trigger.action.outText("+++ removing ground troops " .. troop.name, 30)
|
|
||||||
cfxGroundTroops.invokeCallbacksFor("dead", troop) -- notify anyone who is interested that we are no longer proccing these
|
cfxGroundTroops.invokeCallbacksFor("dead", troop) -- notify anyone who is interested that we are no longer proccing these
|
||||||
else
|
else
|
||||||
-- work with this groop according to its orders
|
-- work with this groop according to its orders
|
||||||
cfxGroundTroops.updateTroops(troop)
|
cfxGroundTroops.updateTroops(troop)
|
||||||
-- trigger.action.outText("+++ updated troops " .. troop.name, 30)
|
|
||||||
-- since group is alive remember it for next loop
|
-- since group is alive remember it for next loop
|
||||||
--table.insert(liveTroops, troop)
|
liveTroops[idx] = troop -- do NOT use insert as we have indexed table by name
|
||||||
liveTroops[idx] = troop -- do NOT use insert as we have indexed table
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
-- liveTroops holds all troops that are still alive and will
|
-- liveTroops holds all troops that are still alive and will
|
||||||
@ -958,6 +967,7 @@ function cfxGroundTroops.createGroundTroops(inGroup, range, orders)
|
|||||||
newTroops.coalition = inGroup:getCoalition()
|
newTroops.coalition = inGroup:getCoalition()
|
||||||
newTroops.side = newTroops.coalition -- because we'e been using both.
|
newTroops.side = newTroops.coalition -- because we'e been using both.
|
||||||
newTroops.name = inGroup:getName()
|
newTroops.name = inGroup:getName()
|
||||||
|
newTroops.moving = false -- set to not have received move orders yet
|
||||||
newTroops.signature = "cfx" -- to verify this is groundTroop group, not dcs groups
|
newTroops.signature = "cfx" -- to verify this is groundTroop group, not dcs groups
|
||||||
if not range then range = 300 end
|
if not range then range = 300 end
|
||||||
newTroops.range = range
|
newTroops.range = range
|
||||||
@ -985,7 +995,6 @@ function cfxGroundTroops.addGroundTroopsToPool(troops) -- troops MUST be a table
|
|||||||
if cfxGroundTroops.maxManagedTroops > 0 and dcsCommon.getSizeOfTable(cfxGroundTroops.deployedTroops) >= cfxGroundTroops.maxManagedTroops then
|
if cfxGroundTroops.maxManagedTroops > 0 and dcsCommon.getSizeOfTable(cfxGroundTroops.deployedTroops) >= cfxGroundTroops.maxManagedTroops then
|
||||||
-- we need to queue
|
-- we need to queue
|
||||||
table.insert(cfxGroundTroops.troopQueue, troops)
|
table.insert(cfxGroundTroops.troopQueue, troops)
|
||||||
-- trigger.action.outText("enqued " .. troops.group:getName() .. " at pos ".. #cfxGroundTroops.troopQueue ..", manage cap surpassed.", 30)
|
|
||||||
else
|
else
|
||||||
-- add to deployed set
|
-- add to deployed set
|
||||||
cfxGroundTroops.deployedTroops[troops.group:getName()] = troops
|
cfxGroundTroops.deployedTroops[troops.group:getName()] = troops
|
||||||
|
|||||||
@ -1,37 +1,43 @@
|
|||||||
cfxHeloTroops = {}
|
cfxHeloTroops = {}
|
||||||
cfxHeloTroops.version = "2.3.0"
|
cfxHeloTroops.version = "2.4.0"
|
||||||
cfxHeloTroops.verbose = false
|
cfxHeloTroops.verbose = false
|
||||||
cfxHeloTroops.autoDrop = true
|
cfxHeloTroops.autoDrop = true
|
||||||
cfxHeloTroops.autoPickup = false
|
cfxHeloTroops.autoPickup = false
|
||||||
cfxHeloTroops.pickupRange = 100 -- meters
|
cfxHeloTroops.pickupRange = 100 -- meters
|
||||||
--
|
--
|
||||||
|
--[[--
|
||||||
|
VERSION HISTORY
|
||||||
|
1.1.3 - repaired forgetting 'wait-' when loading/disembarking
|
||||||
|
1.1.4 - corrected coalition bug in deployTroopsFromHelicopter
|
||||||
|
2.0.0 - added weight change when troops enter and leave the helicopter
|
||||||
|
- idividual troop capa max per helicopter
|
||||||
|
2.0.1 - lib loader verification
|
||||||
|
- uses dcsCommon.isTroopCarrier(theUnit)
|
||||||
|
2.0.2 - can now deploy from spawners with "requestable" attribute
|
||||||
|
2.1.0 - supports config zones
|
||||||
|
- check spawner legality by types
|
||||||
|
- updated types to include 2.7.6 additions to infantry
|
||||||
|
- updated types to include stinger/manpads
|
||||||
|
2.2.0 - minor maintenance (dcsCommon)
|
||||||
|
- (re?) connected readConfigZone (wtf?)
|
||||||
|
- persistence support
|
||||||
|
- made legalTroops entrirely optional and defer to dcsComon else
|
||||||
|
2.3.0 - interface with owned zones and playerScore when
|
||||||
|
- combat-dropping troops into non-owned owned zone.
|
||||||
|
- prevent auto-load from pre-empting loading csar troops
|
||||||
|
2.3.1 - added ability to self-define troopCarriers via config
|
||||||
|
2.4.0 - added missing support for attackZone orders (destination)
|
||||||
|
- eliminated cfxPlayer module import and all dependencies
|
||||||
|
- added support for groupTracker / limbo
|
||||||
|
- removed restriction to only apply to helicopters in anticipation of the C-130 Hercules appearing in the game
|
||||||
|
|
||||||
|
--]]--
|
||||||
--
|
--
|
||||||
-- VERSION HISTORY
|
-- cfxHeloTroops -- a module to pick up and drop infantry.
|
||||||
-- 1.1.3 -- repaired forgetting 'wait-' when loading/disembarking
|
-- Can be used with ANY aircraft, configured by default to be
|
||||||
-- 1.1.4 -- corrected coalition bug in deployTroopsFromHelicopter
|
-- restricted to troop-carrying helicopters.
|
||||||
-- 2.0.0 -- added weight change when troops enter and leave the helicopter
|
-- might be configure to apply to any type you want using the
|
||||||
-- idividual troop capa max per helicopter
|
-- configuration zone.
|
||||||
-- 2.0.1 -- lib loader verification
|
|
||||||
-- -- uses dcsCommon.isTroopCarrier(theUnit)
|
|
||||||
-- 2.0.2 -- can now deploy from spawners with "requestable" attribute
|
|
||||||
-- 2.1.0 -- supports config zones
|
|
||||||
-- -- check spawner legality by types
|
|
||||||
-- -- updated types to include 2.7.6 additions to infantry
|
|
||||||
-- -- updated types to include stinger/manpads
|
|
||||||
-- 2.2.0 -- minor maintenance (dcsCommon)
|
|
||||||
-- -- (re?) connected readConfigZone (wtf?)
|
|
||||||
-- -- persistence support
|
|
||||||
-- -- made legalTroops entrirely optional and defer to dcsComon else
|
|
||||||
-- 2.3.0 -- interface with owned zones and playerScore when
|
|
||||||
-- -- combat-dropping troops into non-owned owned zone.
|
|
||||||
-- -- prevent auto-load from pre-empting loading csar troops
|
|
||||||
--
|
|
||||||
-- cfxHeloTroops -- a module to pick up and drop infantry. Can be used with any helo,
|
|
||||||
-- might be used to configure to only certain
|
|
||||||
-- currently only supports a single helicopter per group
|
|
||||||
-- only helicopters that can transport troops will have this feature
|
|
||||||
-- Copyright (c) 2021, 2022 by Christian Franz and cf/x AG
|
|
||||||
--
|
|
||||||
|
|
||||||
|
|
||||||
cfxHeloTroops.requiredLibs = {
|
cfxHeloTroops.requiredLibs = {
|
||||||
@ -39,17 +45,11 @@ cfxHeloTroops.requiredLibs = {
|
|||||||
-- pretty stupid to check for this since we
|
-- pretty stupid to check for this since we
|
||||||
-- need common to invoke the check, but anyway
|
-- need common to invoke the check, but anyway
|
||||||
"cfxZones", -- Zones, of course
|
"cfxZones", -- Zones, of course
|
||||||
"cfxPlayer", -- player events
|
|
||||||
"cfxCommander", -- to make troops do stuff
|
"cfxCommander", -- to make troops do stuff
|
||||||
"cfxGroundTroops", -- generic when dropping troops
|
"cfxGroundTroops", -- generic when dropping troops
|
||||||
}
|
}
|
||||||
|
|
||||||
cfxHeloTroops.unitConfigs = {} -- all configs are stored by unit's name
|
cfxHeloTroops.unitConfigs = {} -- all configs are stored by unit's name
|
||||||
cfxHeloTroops.myEvents = {3, 4, 5} -- 3- takeoff, 4 - land, 5 - crash
|
|
||||||
|
|
||||||
-- legalTroops now optional, else check against dcsCommon.typeIsInfantry
|
|
||||||
--cfxHeloTroops.legalTroops = {"Soldier AK", "Infantry AK", "Infantry AK ver2", "Infantry AK ver3", "Infantry AK Ins", "Soldier M249", "Soldier M4 GRG", "Soldier M4", "Soldier RPG", "Paratrooper AKS-74", "Paratrooper RPG-16", "Stinger comm dsr", "Stinger comm", "Soldier stinger", "SA-18 Igla-S comm", "SA-18 Igla-S manpad", "Igla manpad INS", "SA-18 Igla comm", "SA-18 Igla manpad",}
|
|
||||||
|
|
||||||
cfxHeloTroops.troopWeight = 100 -- kg average weight per trooper
|
cfxHeloTroops.troopWeight = 100 -- kg average weight per trooper
|
||||||
|
|
||||||
-- persistence support
|
-- persistence support
|
||||||
@ -59,13 +59,12 @@ function cfxHeloTroops.resetConfig(conf)
|
|||||||
conf.autoDrop = cfxHeloTroops.autoDrop --if true, will drop troops on-board upon touchdown
|
conf.autoDrop = cfxHeloTroops.autoDrop --if true, will drop troops on-board upon touchdown
|
||||||
conf.autoPickup = cfxHeloTroops.autoPickup -- if true will load nearest troops upon touchdown
|
conf.autoPickup = cfxHeloTroops.autoPickup -- if true will load nearest troops upon touchdown
|
||||||
conf.pickupRange = cfxHeloTroops.pickupRange --meters, maybe make per helo?
|
conf.pickupRange = cfxHeloTroops.pickupRange --meters, maybe make per helo?
|
||||||
-- maybe set up max seats by type
|
|
||||||
conf.currentState = -1 -- 0 = landed, 1 = airborne, -1 undetermined
|
conf.currentState = -1 -- 0 = landed, 1 = airborne, -1 undetermined
|
||||||
conf.troopsOnBoardNum = 0 -- if not 0, we have troops and can spawnm/drop
|
conf.troopsOnBoardNum = 0 -- if not 0, we have troops and can spawnm/drop
|
||||||
conf.troopCapacity = 8 -- should be depending on airframe
|
conf.troopCapacity = 8 -- should be depending on airframe
|
||||||
|
-- troopsOnBoard.name contains name of group
|
||||||
|
-- the other fields info for troops picked up
|
||||||
conf.troopsOnBoard = {} -- table with the following
|
conf.troopsOnBoard = {} -- table with the following
|
||||||
|
|
||||||
|
|
||||||
conf.troopsOnBoard.name = "***reset***"
|
conf.troopsOnBoard.name = "***reset***"
|
||||||
conf.dropFormation = "circle_out" -- may be chosen later?
|
conf.dropFormation = "circle_out" -- may be chosen later?
|
||||||
end
|
end
|
||||||
@ -80,7 +79,6 @@ function cfxHeloTroops.createDefaultConfig(theUnit)
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function cfxHeloTroops.getUnitConfig(theUnit) -- will create new config if not existing
|
function cfxHeloTroops.getUnitConfig(theUnit) -- will create new config if not existing
|
||||||
if not theUnit then
|
if not theUnit then
|
||||||
trigger.action.outText("+++WARNING: nil unit in get config!", 30)
|
trigger.action.outText("+++WARNING: nil unit in get config!", 30)
|
||||||
@ -98,70 +96,6 @@ function cfxHeloTroops.getConfigForUnitNamed(aName)
|
|||||||
return cfxHeloTroops.unitConfigs[aName]
|
return cfxHeloTroops.unitConfigs[aName]
|
||||||
end
|
end
|
||||||
|
|
||||||
function cfxHeloTroops.removeConfigForUnitNamed(aName)
|
|
||||||
if cfxHeloTroops.unitConfigs[aName] then cfxHeloTroops.unitConfigs[aName] = nil end
|
|
||||||
end
|
|
||||||
|
|
||||||
function cfxHeloTroops.setState(theUnit, isLanded)
|
|
||||||
-- called to set the current state of the helicopter (group)
|
|
||||||
-- currently one helicopter per group max
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--
|
|
||||||
-- E V E N T H A N D L I N G
|
|
||||||
--
|
|
||||||
function cfxHeloTroops.isInteresting(eventID)
|
|
||||||
-- return true if we are interested in this event, false else
|
|
||||||
for key, evType in pairs(cfxHeloTroops.myEvents) do
|
|
||||||
if evType == eventID then return true end
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function cfxHeloTroops.preProcessor(event)
|
|
||||||
-- make sure it has an initiator
|
|
||||||
if not event.initiator then return false end -- no initiator
|
|
||||||
local theUnit = event.initiator
|
|
||||||
if not dcsCommon.isPlayerUnit(theUnit) then return false end -- not a player unit
|
|
||||||
local cat = theUnit:getCategory()
|
|
||||||
if cat ~= Group.Category.HELICOPTER then return false end
|
|
||||||
|
|
||||||
return cfxHeloTroops.isInteresting(event.id)
|
|
||||||
end
|
|
||||||
|
|
||||||
function cfxHeloTroops.postProcessor(event)
|
|
||||||
-- don't do anything
|
|
||||||
end
|
|
||||||
|
|
||||||
function cfxHeloTroops.somethingHappened(event)
|
|
||||||
-- when this is invoked, the preprocessor guarantees that
|
|
||||||
-- it's an interesting event
|
|
||||||
-- unit is valid and player
|
|
||||||
-- airframe category is helicopter
|
|
||||||
|
|
||||||
local theUnit = event.initiator
|
|
||||||
local ID = event.id
|
|
||||||
|
|
||||||
|
|
||||||
local myType = theUnit:getTypeName()
|
|
||||||
|
|
||||||
if ID == 4 then
|
|
||||||
cfxHeloTroops.heloLanded(theUnit)
|
|
||||||
end
|
|
||||||
|
|
||||||
if ID == 3 then
|
|
||||||
cfxHeloTroops.heloDeparted(theUnit)
|
|
||||||
end
|
|
||||||
|
|
||||||
if ID == 5 then
|
|
||||||
cfxHeloTroops.heloCrashed(theUnit)
|
|
||||||
end
|
|
||||||
|
|
||||||
cfxHeloTroops.setCommsMenu(theUnit)
|
|
||||||
end
|
|
||||||
|
|
||||||
--
|
--
|
||||||
--
|
--
|
||||||
-- LANDED
|
-- LANDED
|
||||||
@ -172,9 +106,8 @@ function cfxHeloTroops.loadClosestGroup(conf)
|
|||||||
local cat = Group.Category.GROUND
|
local cat = Group.Category.GROUND
|
||||||
local unitsToLoad = dcsCommon.getLivingGroupsAndDistInRangeToPoint(p, conf.pickupRange, conf.unit:getCoalition(), cat)
|
local unitsToLoad = dcsCommon.getLivingGroupsAndDistInRangeToPoint(p, conf.pickupRange, conf.unit:getCoalition(), cat)
|
||||||
|
|
||||||
-- now, the groups may contain units that are not for transport.
|
-- groups may contain units that are not for transport.
|
||||||
-- later we can filter this by weight, or other cool stuff
|
-- for now we only load troops with legal type strings
|
||||||
-- for now we simply only troopy with legal type strings
|
|
||||||
unitsToLoad = cfxHeloTroops.filterTroopsByType(unitsToLoad)
|
unitsToLoad = cfxHeloTroops.filterTroopsByType(unitsToLoad)
|
||||||
|
|
||||||
-- now limit the options to the five closest legal groups
|
-- now limit the options to the five closest legal groups
|
||||||
@ -190,7 +123,7 @@ end
|
|||||||
|
|
||||||
function cfxHeloTroops.heloLanded(theUnit)
|
function cfxHeloTroops.heloLanded(theUnit)
|
||||||
-- when we have landed,
|
-- when we have landed,
|
||||||
if not dcsCommon.isTroopCarrier(theUnit) then return end
|
if not dcsCommon.isTroopCarrier(theUnit, cfxHeloTroops.troopCarriers) then return end
|
||||||
|
|
||||||
local conf = cfxHeloTroops.getUnitConfig(theUnit)
|
local conf = cfxHeloTroops.getUnitConfig(theUnit)
|
||||||
conf.unit = theUnit
|
conf.unit = theUnit
|
||||||
@ -231,7 +164,7 @@ end
|
|||||||
--
|
--
|
||||||
|
|
||||||
function cfxHeloTroops.heloDeparted(theUnit)
|
function cfxHeloTroops.heloDeparted(theUnit)
|
||||||
if not dcsCommon.isTroopCarrier(theUnit) then return end
|
if not dcsCommon.isTroopCarrier(theUnit, cfxHeloTroops.troopCarriers) then return end
|
||||||
|
|
||||||
-- when we take off, all that needs to be done is to change the state
|
-- when we take off, all that needs to be done is to change the state
|
||||||
-- to airborne, and then set the status flag
|
-- to airborne, and then set the status flag
|
||||||
@ -248,17 +181,40 @@ end
|
|||||||
-- Helo Crashed
|
-- Helo Crashed
|
||||||
--
|
--
|
||||||
--
|
--
|
||||||
function cfxHeloTroops.heloCrashed(theUnit)
|
function cfxHeloTroops.cleanHelo(theUnit)
|
||||||
if not dcsCommon.isTroopCarrier(theUnit) then return end
|
|
||||||
|
|
||||||
-- clean up
|
-- clean up
|
||||||
local conf = cfxHeloTroops.getUnitConfig(theUnit)
|
local conf = cfxHeloTroops.getUnitConfig(theUnit)
|
||||||
conf.unit = theUnit
|
conf.unit = theUnit
|
||||||
conf.troopsOnBoardNum = 0 -- all dead
|
conf.troopsOnBoardNum = 0 -- all dead
|
||||||
conf.currentState = -1 -- (we don't know)
|
conf.currentState = -1 -- (we don't know)
|
||||||
-- conf.troopsOnBoardTypes = "" -- no troops, remember?
|
|
||||||
|
-- check if we need to interface with groupTracker
|
||||||
|
if conf.troopsOnBoard.name and groupTracker then
|
||||||
|
local theName = conf.troopsOnBoard.name
|
||||||
|
-- there was (possibly) a group on board. see if it was tracked
|
||||||
|
local isTracking, numTracking, trackers = groupTracker.groupNameTrackedBy(theName)
|
||||||
|
|
||||||
|
-- if so, remove it from limbo
|
||||||
|
if isTracking then
|
||||||
|
for idx, theTracker in pairs(trackers) do
|
||||||
|
groupTracker.removeGroupNamedFromTracker(theName, theTracker)
|
||||||
|
if cfxHeloTroops.verbose then
|
||||||
|
trigger.action.outText("+++Helo: removed group <" .. theName .. "> from tracker <" .. theTracker.name .. ">", 30)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
conf.troopsOnBoard = {}
|
conf.troopsOnBoard = {}
|
||||||
cfxHeloTroops.removeComms(conf.unit)
|
end
|
||||||
|
|
||||||
|
function cfxHeloTroops.heloCrashed(theUnit)
|
||||||
|
if not dcsCommon.isTroopCarrier(theUnit, cfxHeloTroops.troopCarriers) then return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- clean up
|
||||||
|
cfxHeloTroops.cleanHelo(theUnit)
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--
|
--
|
||||||
@ -332,15 +288,19 @@ function cfxHeloTroops.setCommsMenu(theUnit)
|
|||||||
if not theUnit:isExist() then return end
|
if not theUnit:isExist() then return end
|
||||||
|
|
||||||
-- we only add this menu to troop carriers
|
-- we only add this menu to troop carriers
|
||||||
if not dcsCommon.isTroopCarrier(theUnit) then return end
|
if not dcsCommon.isTroopCarrier(theUnit, cfxHeloTroops.troopCarriers) then
|
||||||
|
if cfxHeloTroops.verbose then
|
||||||
|
trigger.action.outText("+++heloT - player unit <" .. theUnit:getName() .. "> type <" .. theUnit:getTypeName() .. "> is not legal troop carrier.", 30)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local group = theUnit:getGroup()
|
local group = theUnit:getGroup()
|
||||||
local id = group:getID()
|
local id = group:getID()
|
||||||
local conf = cfxHeloTroops.getUnitConfig(theUnit) --cfxHeloTroops.unitConfigs[theUnit:getName()]
|
local conf = cfxHeloTroops.getUnitConfig(theUnit)
|
||||||
conf.id = id; -- we do this ALWAYS to it is current even after a crash
|
conf.id = id; -- we do this ALWAYS to it is current even after a crash
|
||||||
conf.unit = theUnit -- link back
|
conf.unit = theUnit -- link back
|
||||||
|
|
||||||
--local conf = cfxHeloTroops.getUnitConfig(theUnit)
|
|
||||||
-- 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(id, 'Airlift Troops')
|
conf.myMainMenu = missionCommands.addSubMenuForGroup(id, 'Airlift Troops')
|
||||||
@ -548,6 +508,7 @@ function cfxHeloTroops.filterTroopsByType(unitsToLoad)
|
|||||||
end
|
end
|
||||||
return filteredGroups
|
return filteredGroups
|
||||||
end
|
end
|
||||||
|
|
||||||
--
|
--
|
||||||
-- T O G G L E S
|
-- T O G G L E S
|
||||||
--
|
--
|
||||||
@ -580,7 +541,6 @@ function cfxHeloTroops.doToggleConfig(args)
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Deploying Troops
|
-- Deploying Troops
|
||||||
--
|
--
|
||||||
@ -629,7 +589,6 @@ function cfxHeloTroops.doDeployTroops(args)
|
|||||||
-- set own troops to 0 and erase type string
|
-- set own troops to 0 and erase type string
|
||||||
conf.troopsOnBoardNum = 0
|
conf.troopsOnBoardNum = 0
|
||||||
conf.troopsOnBoard = {}
|
conf.troopsOnBoard = {}
|
||||||
-- conf.troopsOnBoardTypes = ""
|
|
||||||
conf.troopsOnBoard.name = "***wasdeployed***"
|
conf.troopsOnBoard.name = "***wasdeployed***"
|
||||||
|
|
||||||
-- reset menu
|
-- reset menu
|
||||||
@ -644,10 +603,6 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
|
|||||||
local theUnit = conf.unit
|
local theUnit = conf.unit
|
||||||
local p = theUnit:getPoint()
|
local p = theUnit:getPoint()
|
||||||
|
|
||||||
--for i=1, scenario.troopSize[theUnit:getName()] do
|
|
||||||
-- table.insert(unitTypes, "Soldier M4")
|
|
||||||
--end
|
|
||||||
|
|
||||||
-- split the conf.troopsOnBoardTypes into an array of types
|
-- split the conf.troopsOnBoardTypes into an array of types
|
||||||
unitTypes = dcsCommon.splitString(conf.troopsOnBoard.types, ",")
|
unitTypes = dcsCommon.splitString(conf.troopsOnBoard.types, ",")
|
||||||
if #unitTypes < 1 then
|
if #unitTypes < 1 then
|
||||||
@ -656,6 +611,9 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
|
|||||||
|
|
||||||
local range = conf.troopsOnBoard.range
|
local range = conf.troopsOnBoard.range
|
||||||
local orders = conf.troopsOnBoard.orders
|
local orders = conf.troopsOnBoard.orders
|
||||||
|
local dest = conf.troopsOnBoard.destination
|
||||||
|
local theName = conf.troopsOnBoard.name
|
||||||
|
|
||||||
if not orders then orders = "guard" end
|
if not orders then orders = "guard" end
|
||||||
|
|
||||||
-- order processing: if the orders were pre-pended with "wait-"
|
-- order processing: if the orders were pre-pended with "wait-"
|
||||||
@ -671,7 +629,7 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
|
|||||||
local theCoalition = theUnit:getGroup():getCoalition() -- make it choppers COALITION
|
local theCoalition = theUnit:getGroup():getCoalition() -- make it choppers COALITION
|
||||||
local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
|
local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
|
||||||
theCoalition,
|
theCoalition,
|
||||||
conf.troopsOnBoard.name, -- dcsCommon.uuid("Assault"), -- maybe use config name as loaded from the group
|
theName, -- group name, may be tracked
|
||||||
chopperZone,
|
chopperZone,
|
||||||
unitTypes,
|
unitTypes,
|
||||||
conf.dropFormation,
|
conf.dropFormation,
|
||||||
@ -682,14 +640,27 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
|
|||||||
troopData.orders = orders -- always set
|
troopData.orders = orders -- always set
|
||||||
troopData.side = theCoalition
|
troopData.side = theCoalition
|
||||||
troopData.range = range
|
troopData.range = range
|
||||||
|
troopData.destination = dest -- only for attackzone orders
|
||||||
cfxHeloTroops.deployedTroops[theData.name] = troopData
|
cfxHeloTroops.deployedTroops[theData.name] = troopData
|
||||||
|
|
||||||
local troop = cfxGroundTroops.createGroundTroops(theGroup, range, orders) -- use default range and orders
|
local troop = cfxGroundTroops.createGroundTroops(theGroup, range, orders)
|
||||||
-- instead of scheduling tasking in one second, we add to
|
troop.destination = dest -- transfer target zone for attackzone oders
|
||||||
-- ground troops pool, and the troop pool manager will assign some enemies
|
cfxGroundTroops.addGroundTroopsToPool(troop) -- will schedule move orders
|
||||||
cfxGroundTroops.addGroundTroopsToPool(troop)
|
|
||||||
trigger.action.outTextForGroup(conf.id, "<" .. theGroup:getName() .. "> have deployed to the ground with orders " .. orders .. "!", 30)
|
trigger.action.outTextForGroup(conf.id, "<" .. theGroup:getName() .. "> have deployed to the ground with orders " .. orders .. "!", 30)
|
||||||
|
|
||||||
|
-- see if this is tracked by a tracker, and pass them back so
|
||||||
|
-- they can un-limbo
|
||||||
|
if groupTracker then
|
||||||
|
local isTracking, numTracking, trackers = groupTracker.groupNameTrackedBy(theName)
|
||||||
|
if isTracking then
|
||||||
|
for idx, theTracker in pairs (trackers) do
|
||||||
|
groupTracker.addGroupToTracker(theGroup, theTracker)
|
||||||
|
if cfxHeloTroops.verbose then
|
||||||
|
trigger.action.outText("+++Helo: un-limbo and tracking group <" .. theName .. "> with tracker <" .. theTracker.name .. ">", 30)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@ -709,21 +680,38 @@ function cfxHeloTroops.doLoadGroup(args)
|
|||||||
-- get the size
|
-- get the size
|
||||||
conf.troopsOnBoardNum = group:getSize()
|
conf.troopsOnBoardNum = group:getSize()
|
||||||
-- and name
|
-- and name
|
||||||
conf.troopsOnBoard.name = group:getName()
|
local gName = group:getName()
|
||||||
|
conf.troopsOnBoard.name = gName
|
||||||
-- and put it all into the helicopter config
|
-- and put it all into the helicopter config
|
||||||
|
|
||||||
-- now we need to destroy the group. First, remove it from the pool
|
-- now we need to destroy the group. Let's prepare:
|
||||||
|
-- if it was tracked, tell tracker to move it to limbo
|
||||||
|
-- to remember it even if it's destroyed
|
||||||
|
if groupTracker then
|
||||||
|
-- only if groupTracker is active
|
||||||
|
local isTracking, numTracking, trackers = groupTracker.groupTrackedBy(group)
|
||||||
|
if isTracking then
|
||||||
|
-- we need to put them in limbo for every tracker
|
||||||
|
for idx, aTracker in pairs(trackers) do
|
||||||
|
if cfxHeloTroops.verbose then
|
||||||
|
trigger.action.outText("+++Helo: moving group <" .. gName .. "> to limbo for tracker <" .. aTracker.name .. ">", 30)
|
||||||
|
end
|
||||||
|
groupTracker.moveGroupToLimboForTracker(group, aTracker)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- then, remove it from the pool
|
||||||
local pooledGroup = cfxGroundTroops.getGroundTroopsForGroup(group)
|
local pooledGroup = cfxGroundTroops.getGroundTroopsForGroup(group)
|
||||||
if pooledGroup then
|
if pooledGroup then
|
||||||
-- copy some important info from the troops
|
-- copy some important info from the troops
|
||||||
-- if they are set
|
-- if they are set
|
||||||
conf.troopsOnBoard.orders = pooledGroup.orders
|
conf.troopsOnBoard.orders = pooledGroup.orders
|
||||||
conf.troopsOnBoard.range = pooledGroup.range
|
conf.troopsOnBoard.range = pooledGroup.range
|
||||||
|
conf.troopsOnBoard.destination = pooledGroup.destination -- may be nil
|
||||||
cfxGroundTroops.removeTroopsFromPool(pooledGroup)
|
cfxGroundTroops.removeTroopsFromPool(pooledGroup)
|
||||||
trigger.action.outTextForGroup(conf.id, "Team '".. conf.troopsOnBoard.name .."' loaded and has orders <" .. conf.troopsOnBoard.orders .. ">", 30)
|
trigger.action.outTextForGroup(conf.id, "Team '".. conf.troopsOnBoard.name .."' loaded and has orders <" .. conf.troopsOnBoard.orders .. ">", 30)
|
||||||
else
|
else
|
||||||
--trigger.action.outTextForGroup(conf.id, "Team '".. conf.troopsOnBoard.name .."' loaded!", 30)
|
|
||||||
if cfxHeloTroops.verbose then
|
if cfxHeloTroops.verbose then
|
||||||
trigger.action.outText("+++heloT: ".. conf.troopsOnBoard.name .." was not committed to ground troops", 30)
|
trigger.action.outText("+++heloT: ".. conf.troopsOnBoard.name .." was not committed to ground troops", 30)
|
||||||
end
|
end
|
||||||
@ -731,7 +719,6 @@ function cfxHeloTroops.doLoadGroup(args)
|
|||||||
|
|
||||||
-- now simply destroy the group
|
-- now simply destroy the group
|
||||||
-- we'll re-assemble it when we deploy it
|
-- we'll re-assemble it when we deploy it
|
||||||
-- we currently can't change the weight of the helicopter
|
|
||||||
-- TODO: add weight changing code
|
-- TODO: add weight changing code
|
||||||
-- TODO: ensure compatibility with CSAR module
|
-- TODO: ensure compatibility with CSAR module
|
||||||
group:destroy()
|
group:destroy()
|
||||||
@ -783,47 +770,55 @@ function cfxHeloTroops.doSpawnGroup(args)
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Player event callbacks
|
-- handle events
|
||||||
--
|
--
|
||||||
function cfxHeloTroops.playerChangeEvent(evType, description, player, data)
|
function cfxHeloTroops:onEvent(theEvent)
|
||||||
if evType == "newGroup" then
|
local theID = theEvent.id
|
||||||
theUnit = data.primeUnit
|
local initiator = theEvent.initiator
|
||||||
cfxHeloTroops.setCommsMenu(theUnit)
|
if not initiator then return end -- not interested
|
||||||
|
local theUnit = initiator
|
||||||
|
local name = theUnit:getName()
|
||||||
|
-- see if this is a player aircraft
|
||||||
|
if not theUnit.getPlayerName then return end -- not a player
|
||||||
|
if not theUnit:getPlayerName() then return end -- not a player
|
||||||
|
|
||||||
|
-- only for helicopters -- overridedden by troop carriers
|
||||||
|
-- we don't check for cat any more, so any airframe
|
||||||
|
-- can be used as long as it's ok with isTroopCarrier()
|
||||||
|
|
||||||
|
-- only for troop carriers
|
||||||
|
if not dcsCommon.isTroopCarrier(theUnit, cfxHeloTroops.troopCarriers) then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if evType == "removeGroup" then
|
if theID == 4 then -- land
|
||||||
-- trigger.action.outText("+++Helo Troops: a group disappeared", 30)
|
cfxHeloTroops.heloLanded(theUnit)
|
||||||
-- data.name contains the name of the group. nil the entry in config list, so all
|
end
|
||||||
-- troops that group was carrying are gone
|
|
||||||
-- we must remove the comms menu for this group else we try to add another one to this group later
|
if theID == 3 then -- take off
|
||||||
-- we assume a one-unit group structure, else the following may fail
|
cfxHeloTroops.heloDeparted(theUnit)
|
||||||
local conf = cfxHeloTroops.getConfigForUnitNamed(data.primeUnitName)
|
end
|
||||||
|
|
||||||
|
if theID == 5 then -- crash
|
||||||
|
cfxHeloTroops.heloCrashed(theUnit)
|
||||||
|
end
|
||||||
|
|
||||||
|
if theID == 20 or -- player enter
|
||||||
|
theID == 15 then -- birth
|
||||||
|
cfxHeloTroops.cleanHelo(theUnit)
|
||||||
|
end
|
||||||
|
|
||||||
|
if theID == 21 then -- player leave
|
||||||
|
cfxHeloTroops.cleanHelo(theUnit)
|
||||||
|
local conf = cfxHeloTroops.getConfigForUnitNamed(name)
|
||||||
if conf then
|
if conf then
|
||||||
cfxHeloTroops.removeCommsFromConfig(conf)
|
cfxHeloTroops.removeCommsFromConfig(conf)
|
||||||
end
|
end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if evType == "leave" then
|
cfxHeloTroops.setCommsMenu(theUnit)
|
||||||
local conf = cfxHeloTroops.getConfigForUnitNamed(player.unitName)
|
|
||||||
if conf then
|
|
||||||
cfxHeloTroops.resetConfig(conf)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if evType == "unit" then
|
|
||||||
-- player changed units. almost never in MP, but possible in solo
|
|
||||||
-- we need to reset the conf so no troops are carried any longer
|
|
||||||
local conf = cfxHeloTroops.getConfigForUnitNamed(data.oldUnitName)
|
|
||||||
if conf then
|
|
||||||
cfxHeloTroops.resetConfig(conf)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--
|
--
|
||||||
@ -883,6 +878,13 @@ function cfxHeloTroops.readConfigZone()
|
|||||||
cfxHeloTroops.autoPickup = cfxZones.getBoolFromZoneProperty(theZone, "autoPickup", false)
|
cfxHeloTroops.autoPickup = cfxZones.getBoolFromZoneProperty(theZone, "autoPickup", false)
|
||||||
cfxHeloTroops.pickupRange = cfxZones.getNumberFromZoneProperty(theZone, "pickupRange", 100)
|
cfxHeloTroops.pickupRange = cfxZones.getNumberFromZoneProperty(theZone, "pickupRange", 100)
|
||||||
cfxHeloTroops.combatDropScore = cfxZones.getNumberFromZoneProperty(theZone, "combatDropScore", 200)
|
cfxHeloTroops.combatDropScore = cfxZones.getNumberFromZoneProperty(theZone, "combatDropScore", 200)
|
||||||
|
|
||||||
|
-- add own troop carriers
|
||||||
|
if cfxZones.hasProperty(theZone, "troopCarriers") then
|
||||||
|
local tc = cfxZones.getStringFromZoneProperty(theZone, "troopCarriers", "UH-1D")
|
||||||
|
tc = dcsCommon.splitString(tc, ",")
|
||||||
|
cfxHeloTroops.troopCarriers = dcsCommon.trimArray(tc)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--
|
--
|
||||||
@ -955,23 +957,12 @@ function cfxHeloTroops.start()
|
|||||||
-- start housekeeping
|
-- start housekeeping
|
||||||
cfxHeloTroops.houseKeeping()
|
cfxHeloTroops.houseKeeping()
|
||||||
|
|
||||||
-- install callbacks for helo-relevant events
|
world.addEventHandler(cfxHeloTroops)
|
||||||
dcsCommon.addEventHandler(cfxHeloTroops.somethingHappened, cfxHeloTroops.preProcessor, cfxHeloTroops.postProcessor)
|
|
||||||
|
|
||||||
-- now iterate through all player groups and install the Assault Troop Menu
|
|
||||||
allPlayerGroups = cfxPlayerGroups -- cfxPlayerGroups is a global, don't fuck with it!
|
|
||||||
-- contains per group a player record, use prime unit to access player's unit
|
|
||||||
for gname, pgroup in pairs(allPlayerGroups) do
|
|
||||||
local aUnit = pgroup.primeUnit -- get any unit of that group
|
|
||||||
cfxHeloTroops.setCommsMenu(aUnit)
|
|
||||||
end
|
|
||||||
-- now install the new group notifier to install Assault Troops menu
|
|
||||||
|
|
||||||
cfxPlayer.addMonitor(cfxHeloTroops.playerChangeEvent)
|
|
||||||
trigger.action.outText("cf/x Helo Troops v" .. cfxHeloTroops.version .. " started", 30)
|
trigger.action.outText("cf/x Helo Troops v" .. cfxHeloTroops.version .. " started", 30)
|
||||||
|
|
||||||
-- now load all save data and populate map with troops that
|
-- persistence:
|
||||||
-- we deployed last save.
|
-- load all save data and populate map with troops that
|
||||||
|
-- we deployed when we last saved.
|
||||||
if persistence then
|
if persistence then
|
||||||
-- sign up for persistence
|
-- sign up for persistence
|
||||||
callbacks = {}
|
callbacks = {}
|
||||||
@ -990,11 +981,5 @@ if not cfxHeloTroops.start() then
|
|||||||
cfxHeloTroops = nil
|
cfxHeloTroops = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[--
|
|
||||||
- interface with spawnable: request troops via comms menu if
|
|
||||||
- spawnZones defined
|
|
||||||
- spawners in range and
|
|
||||||
- spawner auf 'paused' und 'requestable'
|
|
||||||
|
|
||||||
--]]--
|
|
||||||
-- TODO: weight when loading troops
|
-- TODO: weight when loading troops
|
||||||
@ -1,5 +1,5 @@
|
|||||||
cfxOwnedZones = {}
|
cfxOwnedZones = {}
|
||||||
cfxOwnedZones.version = "1.2.3"
|
cfxOwnedZones.version = "1.2.4"
|
||||||
cfxOwnedZones.verbose = false
|
cfxOwnedZones.verbose = false
|
||||||
cfxOwnedZones.announcer = true
|
cfxOwnedZones.announcer = true
|
||||||
cfxOwnedZones.name = "cfxOwnedZones"
|
cfxOwnedZones.name = "cfxOwnedZones"
|
||||||
@ -48,6 +48,7 @@ cfxOwnedZones.name = "cfxOwnedZones"
|
|||||||
1.2.1 - fix in load to correctly re-establish all attackers for subsequent save
|
1.2.1 - fix in load to correctly re-establish all attackers for subsequent save
|
||||||
1.2.2 - redCap! and blueCap!
|
1.2.2 - redCap! and blueCap!
|
||||||
1.2.3 - fix for persistence bug when not using conquered flag
|
1.2.3 - fix for persistence bug when not using conquered flag
|
||||||
|
1.2.4 - pause? and activate? inputs
|
||||||
|
|
||||||
|
|
||||||
--]]--
|
--]]--
|
||||||
@ -235,6 +236,23 @@ function cfxOwnedZones.addOwnedZone(aZone)
|
|||||||
aZone.blueCap = cfxZones.getStringFromZoneProperty(aZone, "blueCap!", "none")
|
aZone.blueCap = cfxZones.getStringFromZoneProperty(aZone, "blueCap!", "none")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- pause? and activate?
|
||||||
|
if cfxZones.hasProperty(aZone, "pause?") then
|
||||||
|
aZone.pauseFlag = cfxZones.getStringFromZoneProperty(aZone, "pause?", "none")
|
||||||
|
aZone.lastPauseValue = trigger.misc.getUserFlag(aZone.pauseFlag)
|
||||||
|
end
|
||||||
|
|
||||||
|
if cfxZones.hasProperty(aZone, "activate?") then
|
||||||
|
aZone.activateFlag = cfxZones.getStringFromZoneProperty(aZone, "activate?", "none")
|
||||||
|
aZone.lastActivateValue = trigger.misc.getUserFlag(aZone.activateFlag)
|
||||||
|
end
|
||||||
|
|
||||||
|
aZone.ownedTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "triggerMethod", "change")
|
||||||
|
if cfxZones.hasProperty(aZone, "ownedTriggerMethod") then
|
||||||
|
aZone.ownedTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "ownedTriggerMethod", "change")
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
aZone.unbeatable = cfxZones.getBoolFromZoneProperty(aZone, "unbeatable", false)
|
aZone.unbeatable = cfxZones.getBoolFromZoneProperty(aZone, "unbeatable", false)
|
||||||
aZone.untargetable = cfxZones.getBoolFromZoneProperty(aZone, "untargetable", false)
|
aZone.untargetable = cfxZones.getBoolFromZoneProperty(aZone, "untargetable", false)
|
||||||
aZone.hidden = cfxZones.getBoolFromZoneProperty(aZone, "hidden", false)
|
aZone.hidden = cfxZones.getBoolFromZoneProperty(aZone, "hidden", false)
|
||||||
@ -856,6 +874,16 @@ function cfxOwnedZones.update()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- see if pause/unpause was issued
|
||||||
|
-- note that capping a zone will not change pause status
|
||||||
|
if aZone.pauseFlag and cfxZones.testZoneFlag(aZone, aZone.pauseFlag, aZone.ownedTriggerMethod, "lastPauseValue") then
|
||||||
|
aZone.paused = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if aZone.activateFlag and cfxZones.testZoneFlag(aZone, aZone.activateFlag, aZone.ownedTriggerMethod, "lastActivateValue") then
|
||||||
|
aZone.paused = false
|
||||||
|
end
|
||||||
|
|
||||||
-- now, perhaps with their new owner call updateZone()
|
-- now, perhaps with their new owner call updateZone()
|
||||||
cfxOwnedZones.updateZone(aZone)
|
cfxOwnedZones.updateZone(aZone)
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
cfxSpawnZones = {}
|
cfxSpawnZones = {}
|
||||||
cfxSpawnZones.version = "1.7.2"
|
cfxSpawnZones.version = "1.7.4"
|
||||||
cfxSpawnZones.requiredLibs = {
|
cfxSpawnZones.requiredLibs = {
|
||||||
"dcsCommon", -- common is of course needed for everything
|
"dcsCommon", -- common is of course needed for everything
|
||||||
-- pretty stupid to check for this since we
|
-- pretty stupid to check for this since we
|
||||||
@ -20,112 +20,78 @@ cfxSpawnZones.spawnedGroups = {}
|
|||||||
-- Zones that conform with this requirements spawn toops automatically
|
-- Zones that conform with this requirements spawn toops automatically
|
||||||
-- *** DOES NOT EXTEND ZONES *** LINKED OWNER via masterOwner ***
|
-- *** DOES NOT EXTEND ZONES *** LINKED OWNER via masterOwner ***
|
||||||
--
|
--
|
||||||
|
--[[--
|
||||||
-- version history
|
-- version history
|
||||||
-- 1.3.0
|
1.3.0
|
||||||
-- - maxSpawn
|
- maxSpawn
|
||||||
-- - orders
|
- orders
|
||||||
-- - range
|
- range
|
||||||
-- 1.3.1 - spawnWithSpawner correct translation of country to coalition
|
1.3.1 - spawnWithSpawner correct translation of country to coalition
|
||||||
-- - createSpawner - corrected reading from properties
|
- createSpawner - corrected reading from properties
|
||||||
-- 1.3.2 - createSpawner - correct reading 'owner' from properties, now
|
1.3.2 - createSpawner - correct reading 'owner' from properties, now
|
||||||
-- directly reads coalition
|
directly reads coalition
|
||||||
-- 1.4.0 - checks modules
|
1.4.0 - checks modules
|
||||||
-- - orders 'train' or 'training' - will make the
|
- orders 'train' or 'training' - will make the
|
||||||
-- ground troops be issued HOLD WEAPS and
|
ground troops be issued HOLD WEAPS and
|
||||||
-- not added to any queue. 'Training' troops
|
not added to any queue. 'Training' troops
|
||||||
-- are target dummies.
|
are target dummies.
|
||||||
-- - optional heading attribute
|
- optional heading attribute
|
||||||
-- - typeMult: repeate type this many time (can produce army in one call)
|
- typeMult: repeate type this many time (can produce army in one call)
|
||||||
-- 1.4.1 - 'requestable' attribute. will automatically set zone to
|
1.4.1 - 'requestable' attribute. will automatically set zone to
|
||||||
-- - paused, so troops can be produced on call
|
- paused, so troops can be produced on call
|
||||||
-- - getRequestableSpawnersInRange
|
- getRequestableSpawnersInRange
|
||||||
-- 1.4.2 - target attribute. used for
|
1.4.2 - target attribute. used for
|
||||||
-- - orders: attackZone
|
- orders: attackZone
|
||||||
-- - spawner internally copies name from cfxZone used for spawning (convenience only)
|
- spawner internally copies name from cfxZone used for spawning (convenience only)
|
||||||
-- 1.4.3 - can subscribe to callbacks. currently called when spawnForSpawner is invoked, reason is "spawned"
|
1.4.3 - can subscribe to callbacks. currently called when spawnForSpawner is invoked, reason is "spawned"
|
||||||
-- - masterOwner to link ownership to other zone
|
- masterOwner to link ownership to other zone
|
||||||
-- 1.4.4 - autoRemove flag to instantly start CD and respawn
|
1.4.4 - autoRemove flag to instantly start CD and respawn
|
||||||
-- 1.4.5 - verify that maxSpawns ~= 0 on initial spawn on start-up
|
1.4.5 - verify that maxSpawns ~= 0 on initial spawn on start-up
|
||||||
-- 1.4.6 - getSpawnerForZoneNamed(aName)
|
1.4.6 - getSpawnerForZoneNamed(aName)
|
||||||
-- - nil-trapping orders before testing for 'training'
|
- nil-trapping orders before testing for 'training'
|
||||||
-- 1.4.7 - defaulting orders to 'guard'
|
1.4.7 - defaulting orders to 'guard'
|
||||||
-- - also accept 'dummy' and 'dummies' as substitute for training
|
- also accept 'dummy' and 'dummies' as substitute for training
|
||||||
-- 1.4.8 - spawnWithSpawner uses getPoint to support linked spawn zones
|
1.4.8 - spawnWithSpawner uses getPoint to support linked spawn zones
|
||||||
-- - update spawn count on initial spawn
|
- update spawn count on initial spawn
|
||||||
-- 1.5.0 - f? support to trigger spawn
|
1.5.0 - f? support to trigger spawn
|
||||||
-- - spawnWithSpawner made string compatible
|
- spawnWithSpawner made string compatible
|
||||||
-- 1.5.1 - relaxed baseName and default to dcsCommon.uuid()
|
1.5.1 - relaxed baseName and default to dcsCommon.uuid()
|
||||||
-- - verbose
|
- verbose
|
||||||
-- 1.5.2 - activate?, pause? flag
|
1.5.2 - activate?, pause? flag
|
||||||
-- 1.5.3 - spawn?, spawnUnits? flags
|
1.5.3 - spawn?, spawnUnits? flags
|
||||||
-- 1.6.0 - trackwith interface for group tracker
|
1.6.0 - trackwith interface for group tracker
|
||||||
-- 1.7.0 - persistence support
|
1.7.0 - persistence support
|
||||||
-- 1.7.1 - improved verbosity
|
1.7.1 - improved verbosity
|
||||||
-- - spelling check
|
- spelling check
|
||||||
-- 1.7.2 - baseName now can can be set to zone name by issuing "*"
|
1.7.2 - baseName now can can be set to zone name by issuing "*"
|
||||||
-- 1.7.3 - ability to hand off to delicates, useDelicates attribute
|
1.7.3 - ability to hand off to delicates, useDelicates attribute
|
||||||
--
|
1.7.4 - wait-attackZone fixes
|
||||||
-- new version requires cfxGroundTroops, where they are
|
|
||||||
--
|
|
||||||
-- How do we recognize a spawn zone?
|
- types - type strings, comma separated
|
||||||
-- contains a "spawner" attribute
|
see here: https://github.com/mrSkortch/DCS-miscScripts/tree/master/ObjectDB
|
||||||
-- a spawner must also have the following attributes
|
|
||||||
-- - spawner - anything, must be present to signal. put in 'ground' to be able to expand to other types
|
- country - defaults to 2 (usa) -- see here https://wiki.hoggitworld.com/view/DCS_enum_country
|
||||||
-- - types - type strings, comma separated
|
some important: 0 = Russia, 2 = US, 82 = UN neutral
|
||||||
-- see here: https://github.com/mrSkortch/DCS-miscScripts/tree/master/ObjectDB
|
country is converted to coalition and then assigned to
|
||||||
-- - typeMult - repeat types n times to create really LOT of troops. optional, defaults to 1
|
Joint Task Force <side> upon spawn
|
||||||
-- - country - defaults to 2 (usa) -- see here https://wiki.hoggitworld.com/view/DCS_enum_country
|
|
||||||
-- some important: 0 = Russia, 2 = US, 82 = UN neutral
|
- formation - default is circle_out; other formations are
|
||||||
-- country is converted to coalition and then assigned to
|
- line - left lo right (west-east) facing north
|
||||||
-- Joint Task Force <side> upon spawn
|
- line_V - vertical line, facing north
|
||||||
-- - masterOwner - optional name of master cfxZone used to determine whom the surrounding
|
- chevron - west-east, point growing to north
|
||||||
-- territory belongs to. Spwaner will only spawn if the owner coalition is the
|
- scattered, random
|
||||||
-- the same as the coalition my own county belongs to.
|
- circle, circle_forward (all fact north)
|
||||||
-- if not given, spawner spawns even if inside a zone owned by opposing force
|
- circle-in (all face in)
|
||||||
-- - baseName - for naming spawned groups - MUST BE UNIQUE!!!!
|
- circle-out (all face out)
|
||||||
--
|
- grid, square, rect arrayed in optimal grid
|
||||||
-- the following attributes are optional
|
- 2deep, 2cols two columns, deep
|
||||||
-- - cooldown, defaults to 60 (seconds) after troops are removed from zone,
|
- 2wide 2 columns wide (2 deep)
|
||||||
-- then the next group spawns. This means troops will only spawn after
|
--]]--
|
||||||
-- troops are removed and cooldown timed out
|
|
||||||
-- - autoRemove - instantly removes spwaned troops, will spawn again
|
|
||||||
-- again after colldown
|
|
||||||
-- - formation - default is circle_out; other formations are
|
|
||||||
-- - line - left lo right (west-east) facing north
|
|
||||||
-- - line_V - vertical line, facing north
|
|
||||||
-- - chevron - west-east, point growing to north
|
|
||||||
-- - scattered, random
|
|
||||||
-- - circle, circle_forward (all fact north)
|
|
||||||
-- - circle-in (all face in)
|
|
||||||
-- - circle-out (all face out)
|
|
||||||
-- - grid, square, rect arrayed in optimal grid
|
|
||||||
-- - 2deep, 2cols two columns, deep
|
|
||||||
-- - 2wide 2 columns wide (2 deep)
|
|
||||||
-- - heading in DEGREES (deafult 0 = north ) direction entire group is facing
|
|
||||||
-- - destination - zone name to go to, no destination = stay where you are
|
|
||||||
-- - paused - defaults to false. If present true, spawning will not happen
|
|
||||||
-- you can then manually invoke cfxSpawnZones.spawnWithSpawner(spawner) to
|
|
||||||
-- spawn the troops as they are described in the spawner
|
|
||||||
-- - orders - tell them what to do. "train" makes them dummies, "guard"
|
|
||||||
-- "laze", "wait-laze" etc
|
|
||||||
-- other orders are as defined by cfxGroundTroops, at least
|
|
||||||
-- guard - hold and defend (default)
|
|
||||||
-- laze - laze targets
|
|
||||||
-- wait-xxx for helo troops, stand by until dropped from helo
|
|
||||||
-- attackOwnedZone - seek nearest owned zone and attack
|
|
||||||
-- attackZone - move towards the named cfxZone. will generate error if zone not found
|
|
||||||
-- name of zone to attack is in 'target' attribute
|
|
||||||
-- - target - names a target cfxZone, used for orders. Troops will immediately
|
|
||||||
-- start moving towards that zone if defined and such a zone exists
|
|
||||||
-- - maxSpawns - limit number of spawn cycles. omit or -1 is unlimited
|
|
||||||
-- - requestable - used with heloTroops to determine if spawning can be ordered by
|
|
||||||
-- comms when in range
|
|
||||||
-- respawn currently happens after theSpawn is deleted and cooldown seconds have passed
|
|
||||||
cfxSpawnZones.allSpawners = {}
|
cfxSpawnZones.allSpawners = {}
|
||||||
cfxSpawnZones.callbacks = {} -- signature: cb(reason, group, spawner)
|
cfxSpawnZones.callbacks = {} -- signature: cb(reason, group, spawner)
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- C A L L B A C K S
|
-- C A L L B A C K S
|
||||||
--
|
--
|
||||||
@ -225,24 +191,25 @@ function cfxSpawnZones.createSpawner(inZone)
|
|||||||
theSpawner.formation = "circle_out"
|
theSpawner.formation = "circle_out"
|
||||||
theSpawner.formation = cfxZones.getStringFromZoneProperty(inZone, "formation", "circle_out")
|
theSpawner.formation = cfxZones.getStringFromZoneProperty(inZone, "formation", "circle_out")
|
||||||
theSpawner.paused = cfxZones.getBoolFromZoneProperty(inZone, "paused", false)
|
theSpawner.paused = cfxZones.getBoolFromZoneProperty(inZone, "paused", false)
|
||||||
|
-- orders are always converted to all lower case
|
||||||
theSpawner.orders = cfxZones.getStringFromZoneProperty(inZone, "orders", "guard"):lower()
|
theSpawner.orders = cfxZones.getStringFromZoneProperty(inZone, "orders", "guard"):lower()
|
||||||
--theSpawner.orders = cfxZones.getZoneProperty(inZone, "orders")
|
-- used to assign orders, default is 'guard', use "laze" to make them laze targets. can be 'wait-' which may auto-convert to 'guard' after pick-up by helo, to be handled outside.
|
||||||
-- used to assign special orders, default is 'guard', use "laze" to make them laze targets. can be 'wait-' which may auto-convert to 'guard' after pick-up by helo, to be handled outside.
|
|
||||||
-- use "train" to tell them to HOLD WEAPONS, don't move and don't participate in loop, so we have in effect target dummies
|
-- use "train" to tell them to HOLD WEAPONS, don't move and don't participate in loop, so we have in effect target dummies
|
||||||
-- can also use order 'dummy' or 'dummies' to switch to train
|
-- can also use order 'dummy' or 'dummies' to switch to train
|
||||||
if theSpawner.orders:lower() == "dummy" or theSpawner.orders:lower() == "dummies" then theSpawner.orders = "train" end
|
if theSpawner.orders:lower() == "dummy" or theSpawner.orders:lower() == "dummies" then theSpawner.orders = "train" end
|
||||||
if theSpawner.orders:lower() == "training" then theSpawner.orders = "train" end
|
if theSpawner.orders:lower() == "training" then theSpawner.orders = "train" end
|
||||||
|
|
||||||
|
|
||||||
theSpawner.range = cfxZones.getNumberFromZoneProperty(inZone, "range", 300) -- if we have a range, for example enemy detection for Lasing or engage range
|
theSpawner.range = cfxZones.getNumberFromZoneProperty(inZone, "range", 300) -- if we have a range, for example enemy detection for Lasing or engage range
|
||||||
theSpawner.maxSpawns = cfxZones.getNumberFromZoneProperty(inZone, "maxSpawns", -1) -- if there is a limit on how many troops can spawn. -1 = endless spawns
|
theSpawner.maxSpawns = cfxZones.getNumberFromZoneProperty(inZone, "maxSpawns", -1) -- if there is a limit on how many troops can spawn. -1 = endless spawns
|
||||||
theSpawner.requestable = cfxZones.getBoolFromZoneProperty(inZone, "requestable", false)
|
theSpawner.requestable = cfxZones.getBoolFromZoneProperty(inZone, "requestable", false)
|
||||||
if theSpawner.requestable then
|
if theSpawner.requestable then
|
||||||
theSpawner.paused = true
|
theSpawner.paused = true
|
||||||
end
|
end
|
||||||
theSpawner.target = cfxZones.getStringFromZoneProperty(inZone, "target", "")
|
if cfxZones.hasProperty(inZone, "target") then
|
||||||
if theSpawner.target == "" then -- this is the defaut case
|
theSpawner.target = cfxZones.getStringFromZoneProperty(inZone, "target", "")
|
||||||
theSpawner.target = nil
|
if theSpawner.target == "" then -- this is the defaut case
|
||||||
|
theSpawner.target = nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if cfxSpawnZones.verbose or inZone.verbose then
|
if cfxSpawnZones.verbose or inZone.verbose then
|
||||||
@ -331,10 +298,9 @@ function cfxSpawnZones.verifySpawnOwnership(spawner)
|
|||||||
|
|
||||||
if (myCoalition ~= masterZone.owner) then
|
if (myCoalition ~= masterZone.owner) then
|
||||||
-- can't spawn, surrounding area owned by enemy
|
-- can't spawn, surrounding area owned by enemy
|
||||||
--trigger.action.outText("spawner " .. spawner.name .. " - spawn suppressed: area not owned: " .. " master owner is " .. masterZone.owner .. ", we are " .. myCoalition, 30)
|
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
--trigger.action.outText("spawner " .. spawner.name .. " good to go: ", 30)
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -362,7 +328,6 @@ function cfxSpawnZones.spawnWithSpawner(aSpawner)
|
|||||||
|
|
||||||
local theCountry = aSpawner.country
|
local theCountry = aSpawner.country
|
||||||
local theCoalition = coalition.getCountryCoalition(theCountry)
|
local theCoalition = coalition.getCountryCoalition(theCountry)
|
||||||
-- trigger.action.outText("+++ spawn: coal <" .. theCoalition .. "> from country <" .. theCountry .. ">", 30)
|
|
||||||
|
|
||||||
local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
|
local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
|
||||||
theCoalition,
|
theCoalition,
|
||||||
@ -374,7 +339,7 @@ function cfxSpawnZones.spawnWithSpawner(aSpawner)
|
|||||||
aSpawner.theSpawn = theGroup
|
aSpawner.theSpawn = theGroup
|
||||||
aSpawner.count = aSpawner.count + 1
|
aSpawner.count = aSpawner.count + 1
|
||||||
|
|
||||||
-- isnert into collector for persistence
|
-- insert into collector for persistence
|
||||||
local troopData = {}
|
local troopData = {}
|
||||||
troopData.groupData = theData
|
troopData.groupData = theData
|
||||||
troopData.orders = aSpawner.orders -- always set
|
troopData.orders = aSpawner.orders -- always set
|
||||||
@ -384,6 +349,7 @@ function cfxSpawnZones.spawnWithSpawner(aSpawner)
|
|||||||
troopData.range = aSpawner.range
|
troopData.range = aSpawner.range
|
||||||
cfxSpawnZones.spawnedGroups[theData.name] = troopData
|
cfxSpawnZones.spawnedGroups[theData.name] = troopData
|
||||||
|
|
||||||
|
-- remember: orders are always lower case only
|
||||||
if aSpawner.orders and (
|
if aSpawner.orders and (
|
||||||
aSpawner.orders:lower() == "training" or
|
aSpawner.orders:lower() == "training" or
|
||||||
aSpawner.orders:lower() == "train" )
|
aSpawner.orders:lower() == "train" )
|
||||||
@ -403,16 +369,17 @@ function cfxSpawnZones.spawnWithSpawner(aSpawner)
|
|||||||
cfxGroundTroops.addGroundTroopsToPool(newTroops)
|
cfxGroundTroops.addGroundTroopsToPool(newTroops)
|
||||||
|
|
||||||
-- see if we have defined a target zone as destination
|
-- see if we have defined a target zone as destination
|
||||||
|
-- and set it accordingly
|
||||||
if aSpawner.target then
|
if aSpawner.target then
|
||||||
local destZone = cfxZones.getZoneByName(aSpawner.target)
|
local destZone = cfxZones.getZoneByName(aSpawner.target)
|
||||||
if destZone then
|
if destZone then
|
||||||
newTroops.destination = destZone
|
newTroops.destination = destZone
|
||||||
cfxGroundTroops.makeTroopsEngageZone(newTroops)
|
|
||||||
else
|
else
|
||||||
trigger.action.outText("+++ spawner " .. aSpawner.name .. " has illegal target " .. aSpawner.target .. ". Pausing.", 30)
|
trigger.action.outText("+++ spawner " .. aSpawner.name .. " has illegal (unknown) target zone <" .. aSpawner.target .. ">. Pausing.", 30)
|
||||||
aSpawner.paused = true
|
aSpawner.paused = true
|
||||||
end
|
end
|
||||||
elseif aSpawner.orders == "attackZone" then
|
elseif aSpawner.orders == "attackzone" then
|
||||||
|
-- attackZone command but no zone given
|
||||||
trigger.action.outText("+++ spawner " .. aSpawner.name .. " has no target but attackZone command. Pausing.", 30)
|
trigger.action.outText("+++ spawner " .. aSpawner.name .. " has no target but attackZone command. Pausing.", 30)
|
||||||
aSpawner.paused = true
|
aSpawner.paused = true
|
||||||
end
|
end
|
||||||
@ -459,7 +426,7 @@ function cfxSpawnZones.handoffTracking(theGroup, theZone)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
local trackerName = theZone.trackWith
|
local trackerName = theZone.trackWith
|
||||||
--if trackerName == "*" then trackerName = theZone.name end
|
|
||||||
-- now assemble a list of all trackers
|
-- now assemble a list of all trackers
|
||||||
if cfxSpawnZones.verbose or theZone.verbose then
|
if cfxSpawnZones.verbose or theZone.verbose then
|
||||||
trigger.action.outText("+++spawner: spawn pass-off: " .. trackerName, 30)
|
trigger.action.outText("+++spawner: spawn pass-off: " .. trackerName, 30)
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
cfxZones = {}
|
cfxZones = {}
|
||||||
cfxZones.version = "3.0.2"
|
cfxZones.version = "3.0.3"
|
||||||
|
|
||||||
-- 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
|
||||||
@ -119,6 +119,9 @@ cfxZones.version = "3.0.2"
|
|||||||
- 3.0.2 - maxRadius for all zones, only differs from radius in polyZones
|
- 3.0.2 - maxRadius for all zones, only differs from radius in polyZones
|
||||||
- re-factoring zone-base string processing from messenger module
|
- re-factoring zone-base string processing from messenger module
|
||||||
- new processStringWildcards() that does almost all that messenger can
|
- new processStringWildcards() that does almost all that messenger can
|
||||||
|
- 3.0.3 - new getLinkedUnit()
|
||||||
|
- 3.0.4 - new createRandomPointOnZoneBoundary()
|
||||||
|
- 3.0.5 - getPositiveRangeFromZoneProperty() now also supports upper bound (optional)
|
||||||
|
|
||||||
|
|
||||||
--]]--
|
--]]--
|
||||||
@ -392,6 +395,17 @@ function cfxZones.createRandomPointInsideBounds(bounds)
|
|||||||
return cfxZones.createPoint(x, 0, z)
|
return cfxZones.createPoint(x, 0, z)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function cfxZones.createRandomPointOnZoneBoundary(theZone)
|
||||||
|
if not theZone then return nil end
|
||||||
|
if theZone.isPoly then
|
||||||
|
local loc, dx, dy = cfxZones.createRandomPointInPolyZone(theZone, true)
|
||||||
|
return loc, dx, dy
|
||||||
|
else
|
||||||
|
local loc, dx, dy = cfxZones.createRandomPointInCircleZone(theZone, true)
|
||||||
|
return loc, dx, dy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function cfxZones.createRandomPointInZone(theZone)
|
function cfxZones.createRandomPointInZone(theZone)
|
||||||
if not theZone then return nil end
|
if not theZone then return nil end
|
||||||
if theZone.isPoly then
|
if theZone.isPoly then
|
||||||
@ -408,7 +422,7 @@ function cfxZones.randomPointInZone(theZone)
|
|||||||
return loc, dx, dy
|
return loc, dx, dy
|
||||||
end
|
end
|
||||||
|
|
||||||
function cfxZones.createRandomPointInCircleZone(theZone)
|
function cfxZones.createRandomPointInCircleZone(theZone, onEdge)
|
||||||
if not theZone.isCircle then
|
if not theZone.isCircle then
|
||||||
trigger.action.outText("+++Zones: warning - createRandomPointInCircleZone called for non-circle zone <" .. theZone.name .. ">", 30)
|
trigger.action.outText("+++Zones: warning - createRandomPointInCircleZone called for non-circle zone <" .. theZone.name .. ">", 30)
|
||||||
return {x=theZone.point.x, y=0, z=theZone.point.z}
|
return {x=theZone.point.x, y=0, z=theZone.point.z}
|
||||||
@ -417,7 +431,10 @@ function cfxZones.createRandomPointInCircleZone(theZone)
|
|||||||
-- ok, let's first create a random percentage value for the new radius
|
-- ok, let's first create a random percentage value for the new radius
|
||||||
-- now lets get a random degree
|
-- now lets get a random degree
|
||||||
local degrees = math.random() * 2 * 3.14152 -- radiants.
|
local degrees = math.random() * 2 * 3.14152 -- radiants.
|
||||||
local r = theZone.radius * math.random()
|
local r = theZone.radius
|
||||||
|
if not onEdge then
|
||||||
|
r = r * math.random()
|
||||||
|
end
|
||||||
local p = cfxZones.getPoint(theZone) -- force update of zone if linked
|
local p = cfxZones.getPoint(theZone) -- force update of zone if linked
|
||||||
local dx = r * math.cos(degrees)
|
local dx = r * math.cos(degrees)
|
||||||
local dz = r * math.sin(degrees)
|
local dz = r * math.sin(degrees)
|
||||||
@ -426,7 +443,7 @@ function cfxZones.createRandomPointInCircleZone(theZone)
|
|||||||
return {x=px, y=0, z = pz}, dx, dz -- returns loc and offsets to theZone.point
|
return {x=px, y=0, z = pz}, dx, dz -- returns loc and offsets to theZone.point
|
||||||
end
|
end
|
||||||
|
|
||||||
function cfxZones.createRandomPointInPolyZone(theZone)
|
function cfxZones.createRandomPointInPolyZone(theZone, onEdge)
|
||||||
if not theZone.isPoly then
|
if not theZone.isPoly then
|
||||||
trigger.action.outText("+++Zones: warning - createRandomPointInPolyZone called for non-poly zone <" .. theZone.name .. ">", 30)
|
trigger.action.outText("+++Zones: warning - createRandomPointInPolyZone called for non-poly zone <" .. theZone.name .. ">", 30)
|
||||||
return cfxZones.createPoint(theZone.point.x, 0, theZone.point.z)
|
return cfxZones.createPoint(theZone.point.x, 0, theZone.point.z)
|
||||||
@ -446,6 +463,11 @@ function cfxZones.createRandomPointInPolyZone(theZone)
|
|||||||
local b = theZone.poly[lineIdxA]
|
local b = theZone.poly[lineIdxA]
|
||||||
local randompercent = math.random()
|
local randompercent = math.random()
|
||||||
local sourceA = dcsCommon.vLerp (a, b, randompercent)
|
local sourceA = dcsCommon.vLerp (a, b, randompercent)
|
||||||
|
-- if all we want is a point on an edge, we are done
|
||||||
|
if onEdge then
|
||||||
|
local polyPoint = sourceA
|
||||||
|
return polyPoint, polyPoint.x - p.x, polyPoint.z - p.z -- return loc, dx, dz
|
||||||
|
end
|
||||||
|
|
||||||
-- now get point on second line
|
-- now get point on second line
|
||||||
a = theZone.poly[lineIdxB]
|
a = theZone.poly[lineIdxB]
|
||||||
@ -1962,13 +1984,15 @@ function cfxZones.randomDelayFromPositiveRange(minVal, maxVal)
|
|||||||
return delay
|
return delay
|
||||||
end
|
end
|
||||||
|
|
||||||
function cfxZones.getPositiveRangeFromZoneProperty(theZone, theProperty, default)
|
function cfxZones.getPositiveRangeFromZoneProperty(theZone, theProperty, default, defaultmax)
|
||||||
-- reads property as string, and interprets as range 'a-b'.
|
-- reads property as string, and interprets as range 'a-b'.
|
||||||
-- if not a range but single number, returns both for upper and lower
|
-- if not a range but single number, returns both for upper and lower
|
||||||
--trigger.action.outText("***Zne: enter with <" .. theZone.name .. ">: range for property <" .. theProperty .. ">!", 30)
|
--trigger.action.outText("***Zne: enter with <" .. theZone.name .. ">: range for property <" .. theProperty .. ">!", 30)
|
||||||
if not default then default = 0 end
|
if not default then default = 0 end
|
||||||
|
if not defaultmax then defaultmax = default end
|
||||||
|
|
||||||
local lowerBound = default
|
local lowerBound = default
|
||||||
local upperBound = default
|
local upperBound = defaultmax
|
||||||
|
|
||||||
local rangeString = cfxZones.getStringFromZoneProperty(theZone, theProperty, "")
|
local rangeString = cfxZones.getStringFromZoneProperty(theZone, theProperty, "")
|
||||||
if dcsCommon.containsString(rangeString, "-") then
|
if dcsCommon.containsString(rangeString, "-") then
|
||||||
@ -1987,12 +2011,12 @@ function cfxZones.getPositiveRangeFromZoneProperty(theZone, theProperty, default
|
|||||||
|
|
||||||
else
|
else
|
||||||
-- bounds illegal
|
-- bounds illegal
|
||||||
trigger.action.outText("+++Zne: illegal range <" .. rangeString .. ">, using " .. default .. "-" .. default, 30)
|
trigger.action.outText("+++Zne: illegal range <" .. rangeString .. ">, using " .. default .. "-" .. defaultmax, 30)
|
||||||
lowerBound = default
|
lowerBound = default
|
||||||
upperBound = default
|
upperBound = defaultmax
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
upperBound = cfxZones.getNumberFromZoneProperty(theZone, theProperty, default) -- between pulses
|
upperBound = cfxZones.getNumberFromZoneProperty(theZone, theProperty, defaultmax) -- between pulses
|
||||||
lowerBound = upperBound
|
lowerBound = upperBound
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -2455,11 +2479,18 @@ function cfxZones.getDCSOrigin(aZone)
|
|||||||
return o
|
return o
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function cfxZones.getLinkedUnit(theZone)
|
||||||
|
if not theZone then return nil end
|
||||||
|
if not theZone.linkedUnit then return nil end
|
||||||
|
if not Unit.isExist(theZone.linkedUnit) then return nil end
|
||||||
|
return theZone.linkedUnit
|
||||||
|
end
|
||||||
|
|
||||||
function cfxZones.getPoint(aZone) -- always works, even linked, returned point can be reused
|
function cfxZones.getPoint(aZone) -- always works, even linked, returned point can be reused
|
||||||
if aZone.linkedUnit then
|
if aZone.linkedUnit then
|
||||||
local theUnit = aZone.linkedUnit
|
local theUnit = aZone.linkedUnit
|
||||||
-- has a link. is link existing?
|
-- has a link. is link existing?
|
||||||
if theUnit:isExist() then
|
if Unit.isExist(theUnit) then
|
||||||
-- updates zone position
|
-- updates zone position
|
||||||
cfxZones.centerZoneOnUnit(aZone, theUnit)
|
cfxZones.centerZoneOnUnit(aZone, theUnit)
|
||||||
local dx = aZone.dx
|
local dx = aZone.dx
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
changer = {}
|
changer = {}
|
||||||
changer.version = "1.0.4"
|
changer.version = "1.0.5"
|
||||||
changer.verbose = false
|
changer.verbose = false
|
||||||
changer.ups = 1
|
changer.ups = 1
|
||||||
changer.requiredLibs = {
|
changer.requiredLibs = {
|
||||||
@ -14,6 +14,7 @@ changer.changers = {}
|
|||||||
1.0.2 - on/off: verbosity
|
1.0.2 - on/off: verbosity
|
||||||
1.0.3 - NOT on/off
|
1.0.3 - NOT on/off
|
||||||
1.0.4 - a little bit more conversation
|
1.0.4 - a little bit more conversation
|
||||||
|
1.0.5 - fixed a bug in verbosity
|
||||||
|
|
||||||
Transmogrify an incoming signal to an output signal
|
Transmogrify an incoming signal to an output signal
|
||||||
- not
|
- not
|
||||||
@ -241,7 +242,7 @@ function changer.update()
|
|||||||
end
|
end
|
||||||
else
|
else
|
||||||
if aZone.verbose then
|
if aZone.verbose then
|
||||||
trigger.action.outText("+++chgr: <" .. aZone.name .. "> is paused.")
|
trigger.action.outText("+++chgr: <" .. aZone.name .. "> is paused.", 30)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
civAir = {}
|
civAir = {}
|
||||||
civAir.version = "1.5.1"
|
civAir.version = "1.5.2"
|
||||||
--[[--
|
--[[--
|
||||||
1.0.0 initial version
|
1.0.0 initial version
|
||||||
1.1.0 exclude list for airfields
|
1.1.0 exclude list for airfields
|
||||||
@ -22,6 +22,7 @@ civAir.version = "1.5.1"
|
|||||||
massive simplifications: always between zoned airfieds
|
massive simplifications: always between zoned airfieds
|
||||||
exclude list and include list
|
exclude list and include list
|
||||||
1.5.1 added depart only and arrive only options for airfields
|
1.5.1 added depart only and arrive only options for airfields
|
||||||
|
1.5.2 fixed bugs inb verbosity
|
||||||
|
|
||||||
|
|
||||||
--]]--
|
--]]--
|
||||||
@ -181,7 +182,7 @@ function civAir.getTwoAirbases()
|
|||||||
local filteredAB = civAir.filterAirfields(departAB, civAir.excludeAirfields)
|
local filteredAB = civAir.filterAirfields(departAB, civAir.excludeAirfields)
|
||||||
-- if none left, error
|
-- if none left, error
|
||||||
if #filteredAB < 1 then
|
if #filteredAB < 1 then
|
||||||
trigger.action.outText("+++civA: too few departure airfields")
|
trigger.action.outText("+++civA: too few departure airfields", 30)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -195,7 +196,7 @@ function civAir.getTwoAirbases()
|
|||||||
|
|
||||||
-- if one left use it twice, boring flight.
|
-- if one left use it twice, boring flight.
|
||||||
if #filteredAB < 1 then
|
if #filteredAB < 1 then
|
||||||
trigger.action.outText("+++civA: too few arrival airfields")
|
trigger.action.outText("+++civA: too few arrival airfields", 30)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -93,6 +93,7 @@ cloneZones.respawnOnGroupID = true
|
|||||||
- masterOwner "*" convenience shortcut
|
- masterOwner "*" convenience shortcut
|
||||||
1.7.1 - useDelicates handOff for delicates
|
1.7.1 - useDelicates handOff for delicates
|
||||||
- forcedRespawn passes zone instead of verbose
|
- forcedRespawn passes zone instead of verbose
|
||||||
|
1.7.2 - onPerimeter attribute
|
||||||
|
|
||||||
--]]--
|
--]]--
|
||||||
|
|
||||||
@ -362,6 +363,8 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
|
|||||||
|
|
||||||
theZone.onRoad = cfxZones.getBoolFromZoneProperty(theZone, "onRoad", false)
|
theZone.onRoad = cfxZones.getBoolFromZoneProperty(theZone, "onRoad", false)
|
||||||
|
|
||||||
|
theZone.onPerimeter = cfxZones.getBoolFromZoneProperty(theZone, "onPerimeter", false)
|
||||||
|
|
||||||
-- check for name scheme and / or identical
|
-- check for name scheme and / or identical
|
||||||
if cfxZones.hasProperty(theZone, "identical") then
|
if cfxZones.hasProperty(theZone, "identical") then
|
||||||
theZone.identical = cfxZones.getBoolFromZoneProperty(theZone, "identical", false)
|
theZone.identical = cfxZones.getBoolFromZoneProperty(theZone, "identical", false)
|
||||||
@ -1119,12 +1122,21 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
|
|||||||
-- calculate the entire group's displacement
|
-- calculate the entire group's displacement
|
||||||
local units = rawData.units
|
local units = rawData.units
|
||||||
|
|
||||||
local loc, dx, dy = cfxZones.createRandomPointInZone(spawnZone) -- also supports polygonal zones
|
local loc, dx, dy
|
||||||
|
if spawnZone.onPerimeter then
|
||||||
|
loc, dx, dy = cfxZones.createRandomPointOnZoneBoundary(spawnZone)
|
||||||
|
else
|
||||||
|
loc, dx, dy = cfxZones.createRandomPointInZone(spawnZone) -- also supports polygonal zones
|
||||||
|
end
|
||||||
|
|
||||||
for idx, aUnit in pairs(units) do
|
for idx, aUnit in pairs(units) do
|
||||||
if not spawnZone.centerOnly then
|
if not spawnZone.centerOnly then
|
||||||
-- *every unit's displacement is randomized
|
-- *every unit's displacement is randomized
|
||||||
loc, dx, dy = cfxZones.createRandomPointInZone(spawnZone)
|
if spawnZone.onPerimeter then
|
||||||
|
loc, dx, dy = cfxZones.createRandomPointOnZoneBoundary(spawnZone)
|
||||||
|
else
|
||||||
|
loc, dx, dy = cfxZones.createRandomPointInZone(spawnZone)
|
||||||
|
end
|
||||||
aUnit.x = loc.x
|
aUnit.x = loc.x
|
||||||
aUnit.y = loc.z
|
aUnit.y = loc.z
|
||||||
else
|
else
|
||||||
@ -1363,9 +1375,14 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
|
|||||||
-- randomize if enabled
|
-- randomize if enabled
|
||||||
if spawnZone.rndLoc then
|
if spawnZone.rndLoc then
|
||||||
|
|
||||||
local loc, dx, dy = cfxZones.createRandomPointInZone(spawnZone) -- also supports polygonal zones
|
local loc, dx, dy
|
||||||
rawData.x = rawData.x + dx
|
if spawnZone.onPerimeter then
|
||||||
rawData.y = rawData.y + dy
|
loc, dx, dy = cfxZones.createRandomPointOnZoneBoundary(spawnZone)
|
||||||
|
else
|
||||||
|
loc, dx, dy = cfxZones.createRandomPointInZone(spawnZone) -- also supports polygonal zones
|
||||||
|
end
|
||||||
|
rawData.x = rawData.x + dx -- might want to use loc
|
||||||
|
rawData.y = rawData.y + dy -- directly
|
||||||
end
|
end
|
||||||
|
|
||||||
if spawnZone.rndHeading then
|
if spawnZone.rndHeading then
|
||||||
@ -1963,7 +1980,7 @@ function cloneZones.start()
|
|||||||
-- to our watchlist
|
-- to our watchlist
|
||||||
for k, aZone in pairs(attrZones) do
|
for k, aZone in pairs(attrZones) do
|
||||||
cloneZones.createClonerWithZone(aZone) -- process attribute and add to zone
|
cloneZones.createClonerWithZone(aZone) -- process attribute and add to zone
|
||||||
cloneZones.addCloneZone(aZone) -- remember it so we can smoke it
|
cloneZones.addCloneZone(aZone)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- update all cloners and spawned clones from file
|
-- update all cloners and spawned clones from file
|
||||||
|
|||||||
@ -49,13 +49,15 @@ csarManager.ups = 1
|
|||||||
- integration with playerScore
|
- integration with playerScore
|
||||||
- score global and per-mission
|
- score global and per-mission
|
||||||
- isCSARTarget API
|
- isCSARTarget API
|
||||||
|
- 2.2.1 - added troopCarriers attribute to config
|
||||||
|
- passes own troop carriers to dcsCommin.isTroopCarrier()
|
||||||
|
|
||||||
|
|
||||||
--]]--
|
--]]--
|
||||||
-- modules that need to be loaded BEFORE I run
|
-- modules that need to be loaded BEFORE I run
|
||||||
csarManager.requiredLibs = {
|
csarManager.requiredLibs = {
|
||||||
"dcsCommon", -- common is of course needed for everything
|
"dcsCommon", -- common is of course needed for everything
|
||||||
"cfxZones", -- zones management foc CSAR and CSAR Mission zones
|
"cfxZones", -- zones management for CSAR and CSAR Mission zones
|
||||||
"cfxPlayer", -- player monitoring and group monitoring
|
"cfxPlayer", -- player monitoring and group monitoring
|
||||||
"nameStats", -- generic data module for weight
|
"nameStats", -- generic data module for weight
|
||||||
"cargoSuper",
|
"cargoSuper",
|
||||||
@ -385,7 +387,7 @@ end
|
|||||||
|
|
||||||
function csarManager.heloLanded(theUnit)
|
function csarManager.heloLanded(theUnit)
|
||||||
-- when we have landed,
|
-- when we have landed,
|
||||||
if not dcsCommon.isTroopCarrier(theUnit) then return end
|
if not dcsCommon.isTroopCarrier(theUnit, csarManager.troopCarriers) then return end
|
||||||
local conf = csarManager.getUnitConfig(theUnit)
|
local conf = csarManager.getUnitConfig(theUnit)
|
||||||
conf.unit = theUnit
|
conf.unit = theUnit
|
||||||
local theGroup = theUnit:getGroup()
|
local theGroup = theUnit:getGroup()
|
||||||
@ -532,7 +534,7 @@ end
|
|||||||
--
|
--
|
||||||
--
|
--
|
||||||
function csarManager.heloDeparted(theUnit)
|
function csarManager.heloDeparted(theUnit)
|
||||||
if not dcsCommon.isTroopCarrier(theUnit) then return end
|
if not dcsCommon.isTroopCarrier(theUnit, csarManager.troopCarriers) then return end
|
||||||
-- if we have timed extractions (i.e. not instantaneous),
|
-- if we have timed extractions (i.e. not instantaneous),
|
||||||
-- then we need to check if we take off after the timer runs out
|
-- then we need to check if we take off after the timer runs out
|
||||||
|
|
||||||
@ -555,7 +557,7 @@ end
|
|||||||
--
|
--
|
||||||
|
|
||||||
function csarManager.heloCrashed(theUnit)
|
function csarManager.heloCrashed(theUnit)
|
||||||
if not dcsCommon.isTroopCarrier(theUnit) then return end
|
if not dcsCommon.isTroopCarrier(theUnit, csarManager.troopCarriers) then return end
|
||||||
-- problem: this isn't called on network games.
|
-- problem: this isn't called on network games.
|
||||||
|
|
||||||
-- clean up
|
-- clean up
|
||||||
@ -573,7 +575,7 @@ end
|
|||||||
|
|
||||||
function csarManager.airframeCrashed(theUnit)
|
function csarManager.airframeCrashed(theUnit)
|
||||||
-- called from airframe manager
|
-- called from airframe manager
|
||||||
if not dcsCommon.isTroopCarrier(theUnit) then return end
|
if not dcsCommon.isTroopCarrier(theUnit, csarManager.troopCarriers) then return end
|
||||||
local conf = csarManager.getUnitConfig(theUnit)
|
local conf = csarManager.getUnitConfig(theUnit)
|
||||||
conf.unit = theUnit
|
conf.unit = theUnit
|
||||||
local theGroup = theUnit:getGroup()
|
local theGroup = theUnit:getGroup()
|
||||||
@ -584,7 +586,7 @@ end
|
|||||||
|
|
||||||
function csarManager.airframeDitched(theUnit)
|
function csarManager.airframeDitched(theUnit)
|
||||||
-- called from airframe manager
|
-- called from airframe manager
|
||||||
if not dcsCommon.isTroopCarrier(theUnit) then return end
|
if not dcsCommon.isTroopCarrier(theUnit, csarManager.troopCarriers) then return end
|
||||||
|
|
||||||
local conf = csarManager.getUnitConfig(theUnit)
|
local conf = csarManager.getUnitConfig(theUnit)
|
||||||
conf.unit = theUnit
|
conf.unit = theUnit
|
||||||
@ -652,7 +654,7 @@ function csarManager.setCommsMenu(theUnit)
|
|||||||
|
|
||||||
-- we only add this menu to helicopter troop carriers
|
-- we only add this menu to helicopter troop carriers
|
||||||
-- will also filter out all non-helicopters as nice side effect
|
-- will also filter out all non-helicopters as nice side effect
|
||||||
if not dcsCommon.isTroopCarrier(theUnit) then return end
|
if not dcsCommon.isTroopCarrier(theUnit, csarManager.troopCarriers) then return end
|
||||||
|
|
||||||
local group = theUnit:getGroup()
|
local group = theUnit:getGroup()
|
||||||
local id = group:getID()
|
local id = group:getID()
|
||||||
@ -822,7 +824,7 @@ end
|
|||||||
function csarManager.playerChangeEvent(evType, description, player, data)
|
function csarManager.playerChangeEvent(evType, description, player, data)
|
||||||
if evType == "newGroup" then
|
if evType == "newGroup" then
|
||||||
local theUnit = data.primeUnit
|
local theUnit = data.primeUnit
|
||||||
if not dcsCommon.isTroopCarrier(theUnit) then return end
|
if not dcsCommon.isTroopCarrier(theUnit, csarManager.troopCarriers) then return end
|
||||||
|
|
||||||
csarManager.setCommsMenu(theUnit) -- allocates new config
|
csarManager.setCommsMenu(theUnit) -- allocates new config
|
||||||
-- trigger.action.outText("+++csar: added " .. theUnit:getName() .. " to comms menu", 30)
|
-- trigger.action.outText("+++csar: added " .. theUnit:getName() .. " to comms menu", 30)
|
||||||
@ -939,7 +941,7 @@ function csarManager.update() -- every second
|
|||||||
local uID = uGroup:getID()
|
local uID = uGroup:getID()
|
||||||
local uSide = aUnit:getCoalition()
|
local uSide = aUnit:getCoalition()
|
||||||
local agl = dcsCommon.getUnitAGL(aUnit)
|
local agl = dcsCommon.getUnitAGL(aUnit)
|
||||||
if dcsCommon.isTroopCarrier(aUnit) then
|
if dcsCommon.isTroopCarrier(aUnit, csarManager.troopCarriers) then
|
||||||
-- scan through all available csar missions to see if we are close
|
-- scan through all available csar missions to see if we are close
|
||||||
-- enough to trigger comms
|
-- enough to trigger comms
|
||||||
for idx, csarMission in pairs (csarManager.openMissions) do
|
for idx, csarMission in pairs (csarManager.openMissions) do
|
||||||
@ -1277,7 +1279,7 @@ function csarManager.readConfigZone()
|
|||||||
|
|
||||||
if cfxZones.hasProperty(theZone, "csarDelivered!") then
|
if cfxZones.hasProperty(theZone, "csarDelivered!") then
|
||||||
csarManager.csarDelivered = cfxZones.getStringFromZoneProperty(theZone, "csarDelivered!", "*<none>")
|
csarManager.csarDelivered = cfxZones.getStringFromZoneProperty(theZone, "csarDelivered!", "*<none>")
|
||||||
--trigger.action.outText("+++csar: will bang csarDelivered: <" .. csarManager.csarDelivered .. ">", 30)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
csarManager.rescueRadius = cfxZones.getNumberFromZoneProperty(theZone, "rescueRadius", 70) --70 -- must land within 50m to rescue
|
csarManager.rescueRadius = cfxZones.getNumberFromZoneProperty(theZone, "rescueRadius", 70) --70 -- must land within 50m to rescue
|
||||||
@ -1293,6 +1295,19 @@ function csarManager.readConfigZone()
|
|||||||
csarManager.actionSound = cfxZones.getStringFromZoneProperty(theZone, "actionSound", "Quest Snare 3.wav")
|
csarManager.actionSound = cfxZones.getStringFromZoneProperty(theZone, "actionSound", "Quest Snare 3.wav")
|
||||||
csarManager.vectoring = cfxZones.getBoolFromZoneProperty(theZone, "vectoring", true)
|
csarManager.vectoring = cfxZones.getBoolFromZoneProperty(theZone, "vectoring", true)
|
||||||
|
|
||||||
|
-- add own troop carriers
|
||||||
|
if cfxZones.hasProperty(theZone, "troopCarriers") then
|
||||||
|
local tc = cfxZones.getStringFromZoneProperty(theZone, "troopCarriers", "UH-1D")
|
||||||
|
tc = dcsCommon.splitString(tc, ",")
|
||||||
|
csarManager.troopCarriers = dcsCommon.trimArray(tc)
|
||||||
|
if csarManager.verbose then
|
||||||
|
trigger.action.outText("+++casr: redefined troop carriers to types:", 30)
|
||||||
|
for idx, aType in pairs(csarManager.troopCarriers) do
|
||||||
|
trigger.action.outText(aType, 30)
|
||||||
|
end
|
||||||
|
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
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
dcsCommon = {}
|
dcsCommon = {}
|
||||||
dcsCommon.version = "2.8.1"
|
dcsCommon.version = "2.8.2"
|
||||||
--[[-- VERSION HISTORY
|
--[[-- VERSION HISTORY
|
||||||
2.2.6 - compassPositionOfARelativeToB
|
2.2.6 - compassPositionOfARelativeToB
|
||||||
- clockPositionOfARelativeToB
|
- clockPositionOfARelativeToB
|
||||||
@ -127,6 +127,18 @@ dcsCommon.version = "2.8.1"
|
|||||||
- processStringWildcards()
|
- processStringWildcards()
|
||||||
- new wildArrayContainsString()
|
- new wildArrayContainsString()
|
||||||
- fix for stringStartsWith oddity with aircraft types
|
- fix for stringStartsWith oddity with aircraft types
|
||||||
|
2.8.2 - better fixes for string.find() in stringStartsWith and containsString
|
||||||
|
- dcsCommon.isTroopCarrier(theUnit, carriers) new carriers optional param
|
||||||
|
- better guards for getUnitAlt and getUnitAGL
|
||||||
|
- new newPointAtDegreesRange()
|
||||||
|
- new newPointAtAngleRange()
|
||||||
|
- new isTroopCarrierType()
|
||||||
|
- stringStartsWith now supports case insensitive match
|
||||||
|
- isTroopCarrier() supports 'any' and 'all'
|
||||||
|
- made getEnemyCoalitionFor() more resilient
|
||||||
|
- fix to smallRandom for negative numbers
|
||||||
|
- isTroopCarrierType uses wildArrayContainsString
|
||||||
|
|
||||||
--]]--
|
--]]--
|
||||||
|
|
||||||
-- dcsCommon is a library of common lua functions
|
-- dcsCommon is a library of common lua functions
|
||||||
@ -139,7 +151,7 @@ dcsCommon.version = "2.8.1"
|
|||||||
|
|
||||||
-- 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 and Gazelle can't carry troops
|
dcsCommon.troopCarriers = {"Mi-8MT", "UH-1H", "Mi-24P"} -- Ka-50, Apache and Gazelle can't carry troops
|
||||||
dcsCommon.coalitionSides = {0, 1, 2}
|
dcsCommon.coalitionSides = {0, 1, 2}
|
||||||
|
|
||||||
-- lookup tables
|
-- lookup tables
|
||||||
@ -270,8 +282,12 @@ dcsCommon.version = "2.8.1"
|
|||||||
-- 50 items (usually some more), and only then one itemis picked from
|
-- 50 items (usually some more), and only then one itemis picked from
|
||||||
-- that array with a random number that is from a greater range (0..50+)
|
-- that array with a random number that is from a greater range (0..50+)
|
||||||
function dcsCommon.smallRandom(theNum) -- adapted from mist, only support ints
|
function dcsCommon.smallRandom(theNum) -- adapted from mist, only support ints
|
||||||
|
theNum = math.floor(theNum)
|
||||||
if theNum >= 50 then return math.random(theNum) end
|
if theNum >= 50 then return math.random(theNum) end
|
||||||
|
if theNum < 1 then
|
||||||
|
trigger.action.outText("smallRandom: invoke with argument < 1 (" .. theNum .. "), using 1", 30)
|
||||||
|
theNum = 1
|
||||||
|
end
|
||||||
-- for small randoms (<50)
|
-- for small randoms (<50)
|
||||||
local lowNum, highNum
|
local lowNum, highNum
|
||||||
highNum = theNum
|
highNum = theNum
|
||||||
@ -886,6 +902,20 @@ dcsCommon.version = "2.8.1"
|
|||||||
return thePoint, degrees
|
return thePoint, degrees
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function dcsCommon.newPointAtDegreesRange(p1, degrees, radius)
|
||||||
|
local rads = degrees * 3.14152 / 180
|
||||||
|
local p2 = dcsCommon.newPointAtAngleRange(p1, rads, radius)
|
||||||
|
return p2
|
||||||
|
end
|
||||||
|
|
||||||
|
function dcsCommon.newPointAtAngleRange(p1, angle, radius)
|
||||||
|
local p2 = {}
|
||||||
|
p2.x = p1.x + radius * math.cos(angle)
|
||||||
|
p2.y = p1.y
|
||||||
|
p2.z = p1.z + radius * math.sin(angle)
|
||||||
|
return p2
|
||||||
|
end
|
||||||
|
|
||||||
-- get group location: get the group's location by
|
-- get group location: get the group's location by
|
||||||
-- accessing the fist existing, alive member of the group that it finds
|
-- accessing the fist existing, alive member of the group that it finds
|
||||||
function dcsCommon.getGroupLocation(group)
|
function dcsCommon.getGroupLocation(group)
|
||||||
@ -1005,13 +1035,14 @@ dcsCommon.version = "2.8.1"
|
|||||||
end
|
end
|
||||||
|
|
||||||
function dcsCommon.getEnemyCoalitionFor(aCoalition)
|
function dcsCommon.getEnemyCoalitionFor(aCoalition)
|
||||||
if aCoalition == 1 then return 2 end
|
|
||||||
if aCoalition == 2 then return 1 end
|
|
||||||
if type(aCoalition) == "string" then
|
if type(aCoalition) == "string" then
|
||||||
aCoalition = aCoalition:lower()
|
aCoalition = aCoalition:lower()
|
||||||
if aCoalition == "red" then return 2 end
|
if aCoalition == "red" then return 2 end
|
||||||
if aCoalition == "blue" then return 1 end
|
if aCoalition == "blue" then return 1 end
|
||||||
|
return nil
|
||||||
end
|
end
|
||||||
|
if aCoalition == 1 then return 2 end
|
||||||
|
if aCoalition == 2 then return 1 end
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -1818,7 +1849,7 @@ dcsCommon.version = "2.8.1"
|
|||||||
theUnit.x = theUnit.x + cx -- MOVE BACK
|
theUnit.x = theUnit.x + cx -- MOVE BACK
|
||||||
theUnit.y = theUnit.y + cy
|
theUnit.y = theUnit.y + cy
|
||||||
|
|
||||||
-- may also want to increase heading by degreess
|
-- may also want to increase heading by degrees
|
||||||
theUnit.heading = theUnit.heading + rads
|
theUnit.heading = theUnit.heading + rads
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -1840,7 +1871,7 @@ dcsCommon.version = "2.8.1"
|
|||||||
theUnit.x = theUnit.x + cx -- MOVE BACK
|
theUnit.x = theUnit.x + cx -- MOVE BACK
|
||||||
theUnit.y = theUnit.y + cy
|
theUnit.y = theUnit.y + cy
|
||||||
|
|
||||||
-- may also want to increase heading by degreess
|
-- may also want to increase heading by degrees
|
||||||
theUnit.heading = theUnit.heading + rads
|
theUnit.heading = theUnit.heading + rads
|
||||||
-- now kill psi if it existed before
|
-- now kill psi if it existed before
|
||||||
-- theUnit.psi = nil
|
-- theUnit.psi = nil
|
||||||
@ -2011,29 +2042,30 @@ end
|
|||||||
|
|
||||||
--trigger.action.outText("wildACS: theString = <" .. theString .. ">, theArray contains <" .. #theArray .. "> elements", 30)
|
--trigger.action.outText("wildACS: theString = <" .. theString .. ">, theArray contains <" .. #theArray .. "> elements", 30)
|
||||||
local wildIn = dcsCommon.stringEndsWith(theString, "*")
|
local wildIn = dcsCommon.stringEndsWith(theString, "*")
|
||||||
if wildIn then dcsCommon.removeEnding(thestring, "*") end
|
if wildIn then dcsCommon.removeEnding(theString, "*") end
|
||||||
for i = 1, #theArray do
|
for idx, theElement in pairs(theArray) do -- i = 1, #theArray do
|
||||||
local theElement = theArray[i]
|
--local theElement = theArray[i]
|
||||||
if caseSensitive then theElement = string.upper(theElement) end
|
--trigger.action.outText("test e <" .. theElement .. "> against s <" .. theString .. ">", 30)
|
||||||
|
if not caseSensitive then theElement = string.upper(theElement) end
|
||||||
local wildEle = dcsCommon.stringEndsWith(theElement, "*")
|
local wildEle = dcsCommon.stringEndsWith(theElement, "*")
|
||||||
if wildEle then theElement = dcsCommon.removeEnding(theElement, "*") end
|
if wildEle then theElement = dcsCommon.removeEnding(theElement, "*") end
|
||||||
--trigger.action.outText("matching s=<" .. theString .. "> with e=<" .. theElement .. ">", 30)
|
--trigger.action.outText("matching s=<" .. theString .. "> with e=<" .. theElement .. ">", 30)
|
||||||
if wildEle and wildIn then
|
if wildEle and wildIn then
|
||||||
-- both end on wildcards, partial match for both
|
-- both end on wildcards, partial match for both
|
||||||
if dcsCommon.stringStartsWith(theElement. theString) then return true end
|
if dcsCommon.stringStartsWith(theElement, theString) then return true end
|
||||||
if dcsCommon.stringStartsWith(theString, theElement) then return true end
|
if dcsCommon.stringStartsWith(theString, theElement) then return true end
|
||||||
--trigger.action.outText("match e* with s* failed.", 30)
|
--trigger.action.outText("match e* with s* failed.", 30)
|
||||||
elseif wildEle then
|
elseif wildEle then
|
||||||
-- Element is a wildcard, partial match
|
-- Element is a wildcard, partial match
|
||||||
if dcsCommon.stringStartsWith(theString, theElement) then return true end
|
if dcsCommon.stringStartsWith(theString, theElement) then return true end
|
||||||
--trigger.action.outText("match e* with s failed.", 30)
|
--trigger.action.outText("startswith - match e* <" .. theElement .. "> with s <" .. theString .. "> failed.", 30)
|
||||||
elseif wildIn then
|
elseif wildIn then
|
||||||
-- theString is a wildcard. partial match
|
-- theString is a wildcard. partial match
|
||||||
if dcsCommon.stringStartsWith(theElement. theString) then return true end
|
if dcsCommon.stringStartsWith(theElement, theString) then return true end
|
||||||
--trigger.action.outText("match e with s* failed.", 30)
|
--trigger.action.outText("match e with s* failed.", 30)
|
||||||
else
|
else
|
||||||
-- standard: no wildcards, full match
|
-- standard: no wildcards, full match
|
||||||
if theArray[i] == theString then return true end
|
if theElement == theString then return true end
|
||||||
--trigger.action.outText("match e with s (straight) failed.", 30)
|
--trigger.action.outText("match e with s (straight) failed.", 30)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -2165,13 +2197,22 @@ end
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
function dcsCommon.stringStartsWith(theString, thePrefix)
|
function dcsCommon.stringStartsWith(theString, thePrefix, caseInsensitive)
|
||||||
if not theString then return false end
|
if not theString then return false end
|
||||||
if not thePrefix then return false end
|
if not thePrefix then return false end
|
||||||
|
if not caseInsensitive then caseInsensitive = false end
|
||||||
|
|
||||||
|
if caseInsensitive then
|
||||||
|
theString = string.upper(theString)
|
||||||
|
thePrefix = string.upper(theString)
|
||||||
|
end
|
||||||
-- new code because old 'string.find' had some really
|
-- new code because old 'string.find' had some really
|
||||||
-- strange results with aircraft types. Prefix "A-10" did not
|
-- strange results with aircraft types. Prefix "A-10" did not
|
||||||
-- match string "A-10A" etc.
|
-- match string "A-10A" etc.
|
||||||
|
|
||||||
|
-- superseded: string.find (s, pattern [, init [, plain]]) solves the problem
|
||||||
|
|
||||||
|
--[[
|
||||||
local pl = string.len(thePrefix)
|
local pl = string.len(thePrefix)
|
||||||
if pl > string.len(theString) then return false end
|
if pl > string.len(theString) then return false end
|
||||||
if pl < 1 then return false end
|
if pl < 1 then return false end
|
||||||
@ -2184,11 +2225,12 @@ end
|
|||||||
end
|
end
|
||||||
|
|
||||||
return true
|
return true
|
||||||
--[[-- trigger.action.outText("---- OK???", 30)
|
--]]-- trigger.action.outText("---- OK???", 30)
|
||||||
-- strange stuff happening with some strings, let's investigate
|
-- strange stuff happening with some strings, let's investigate
|
||||||
|
|
||||||
|
local i, j = string.find(theString, thePrefix, 1, true)
|
||||||
local res = string.find(theString, thePrefix) == 1
|
return (i == 1)
|
||||||
|
--[[--
|
||||||
if res then
|
if res then
|
||||||
trigger.action.outText("startswith: <" .. theString .. "> pre <" .. thePrefix .. "> --> YES", 30)
|
trigger.action.outText("startswith: <" .. theString .. "> pre <" .. thePrefix .. "> --> YES", 30)
|
||||||
else
|
else
|
||||||
@ -2222,7 +2264,7 @@ end
|
|||||||
what = string.upper(what)
|
what = string.upper(what)
|
||||||
end
|
end
|
||||||
if inString == what then return true end -- when entire match
|
if inString == what then return true end -- when entire match
|
||||||
return string.find(inString, what)
|
return string.find(inString, what, 1, true) -- 1, true means start at 1, plaintext
|
||||||
end
|
end
|
||||||
|
|
||||||
function dcsCommon.bool2Text(theBool)
|
function dcsCommon.bool2Text(theBool)
|
||||||
@ -2441,6 +2483,7 @@ end
|
|||||||
end
|
end
|
||||||
|
|
||||||
function dcsCommon.markPointWithSmoke(p, smokeColor)
|
function dcsCommon.markPointWithSmoke(p, smokeColor)
|
||||||
|
if not smokeColor then smokeColor = 0 end
|
||||||
local x = p.x
|
local x = p.x
|
||||||
local z = p.z -- do NOT change the point directly
|
local z = p.z -- do NOT change the point directly
|
||||||
-- height-correct
|
-- height-correct
|
||||||
@ -2628,17 +2671,35 @@ function dcsCommon.isSceneryObject(theUnit)
|
|||||||
return theUnit.getCoalition == nil -- scenery objects do not return a coalition
|
return theUnit.getCoalition == nil -- scenery objects do not return a coalition
|
||||||
end
|
end
|
||||||
|
|
||||||
function dcsCommon.isTroopCarrier(theUnit)
|
function dcsCommon.isTroopCarrierType(theType, carriers)
|
||||||
-- return true if conf can carry troups
|
if not theType then return false end
|
||||||
if not theUnit then return false end
|
if not carriers then carriers = dcsCommon.troopCarriers
|
||||||
local uType = theUnit:getTypeName()
|
end
|
||||||
if dcsCommon.arrayContainsString(dcsCommon.troopCarriers, uType) then
|
-- remember that arrayContainsString is case INsensitive by default
|
||||||
|
if dcsCommon.wildArrayContainsString(carriers, theType) then
|
||||||
-- may add additional tests before returning true
|
-- may add additional tests before returning true
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- see if user wanted 'any' or 'all' supported
|
||||||
|
if dcsCommon.arrayContainsString(carriers, "any") then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
if dcsCommon.arrayContainsString(carriers, "all") then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function dcsCommon.isTroopCarrier(theUnit, carriers)
|
||||||
|
-- return true if conf can carry troups
|
||||||
|
if not theUnit then return false end
|
||||||
|
local uType = theUnit:getTypeName()
|
||||||
|
return dcsCommon.isTroopCarrierType(uType, carriers)
|
||||||
|
end
|
||||||
|
|
||||||
function dcsCommon.isPlayerUnit(theUnit)
|
function dcsCommon.isPlayerUnit(theUnit)
|
||||||
-- new patch. simply check if getPlayerName returns something
|
-- new patch. simply check if getPlayerName returns something
|
||||||
if not theUnit then return false end
|
if not theUnit then return false end
|
||||||
@ -2664,14 +2725,14 @@ end
|
|||||||
|
|
||||||
function dcsCommon.getUnitAlt(theUnit)
|
function dcsCommon.getUnitAlt(theUnit)
|
||||||
if not theUnit then return 0 end
|
if not theUnit then return 0 end
|
||||||
if not theUnit:isExist() then return 0 end
|
if not Unit.isExist(theUnit) then return 0 end -- safer
|
||||||
local p = theUnit:getPoint()
|
local p = theUnit:getPoint()
|
||||||
return p.y
|
return p.y
|
||||||
end
|
end
|
||||||
|
|
||||||
function dcsCommon.getUnitAGL(theUnit)
|
function dcsCommon.getUnitAGL(theUnit)
|
||||||
if not theUnit then return 0 end
|
if not theUnit then return 0 end
|
||||||
if not theUnit:isExist() then return 0 end
|
if not Unit.isExist(theUnit) then return 0 end -- safe fix
|
||||||
local p = theUnit:getPoint()
|
local p = theUnit:getPoint()
|
||||||
local alt = p.y
|
local alt = p.y
|
||||||
local loc = {x = p.x, y = p.z}
|
local loc = {x = p.x, y = p.z}
|
||||||
|
|||||||
@ -29,9 +29,22 @@ groupTracker.trackers = {}
|
|||||||
- numUnits output
|
- numUnits output
|
||||||
- persistence
|
- persistence
|
||||||
1.2.1 - allGone! bug removed
|
1.2.1 - allGone! bug removed
|
||||||
|
1.2.2 - new groupTrackedBy() method
|
||||||
|
- limbo for storing a unit in limbo so it is
|
||||||
|
- not counted as missing when being transported
|
||||||
|
|
||||||
--]]--
|
--]]--
|
||||||
|
|
||||||
|
-- 'limbo'
|
||||||
|
-- is a special storage in tracker indexed by name that is used
|
||||||
|
-- to temporarily suspend a groups tracking while it's not in
|
||||||
|
-- the mission, e.g. because it's being transported by heloTroops
|
||||||
|
-- in limbo, only the number of units is preserved
|
||||||
|
-- addGroup will automatically move a group back from limbo
|
||||||
|
-- to move into limbo, you must use moveGroupToLimboForTracker
|
||||||
|
-- to remove a group in limbo, use removeGroupNamedFromTracker
|
||||||
|
--
|
||||||
|
|
||||||
function groupTracker.addTracker(theZone)
|
function groupTracker.addTracker(theZone)
|
||||||
table.insert(groupTracker.trackers, theZone)
|
table.insert(groupTracker.trackers, theZone)
|
||||||
end
|
end
|
||||||
@ -54,6 +67,8 @@ end
|
|||||||
--
|
--
|
||||||
-- adding a group to a tracker - called by other modules and API
|
-- adding a group to a tracker - called by other modules and API
|
||||||
--
|
--
|
||||||
|
-- addGroupToTracker will automatically also move a group from
|
||||||
|
-- limbo to tracker if it already existed in limbo
|
||||||
function groupTracker.addGroupToTracker(theGroup, theTracker)
|
function groupTracker.addGroupToTracker(theGroup, theTracker)
|
||||||
-- check if filtering is enabled for this tracker
|
-- check if filtering is enabled for this tracker
|
||||||
if theTracker.groupFilter then
|
if theTracker.groupFilter then
|
||||||
@ -84,18 +99,29 @@ function groupTracker.addGroupToTracker(theGroup, theTracker)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
if not exists then
|
if not exists then
|
||||||
table.insert(theTracker.trackedGroups, theGroup)
|
table.insert(theTracker.trackedGroups, theGroup)
|
||||||
|
|
||||||
-- now bang/invoke addGroup!
|
-- see if we merely transfer group back from limbo
|
||||||
if theTracker.tAddGroup then
|
-- to tracked
|
||||||
cfxZones.pollFlag(theTracker.tAddGroup, "inc", theTracker)
|
if theTracker.limbo[theName] then
|
||||||
|
-- group of that name is in limbo
|
||||||
|
if theTracker.verbose then
|
||||||
|
trigger.action.outText("+++gTrk: moving shelved group <" .. theName .. "> back to normal tracking for <" .. theTracker.name .. ">", 30)
|
||||||
|
end
|
||||||
|
theTracker.limbo[theName] = nil -- remove from limbo
|
||||||
|
else
|
||||||
|
-- now bang/invoke addGroup!
|
||||||
|
if theTracker.tAddGroup then
|
||||||
|
cfxZones.pollFlag(theTracker.tAddGroup, "inc", theTracker)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- now set numGroups
|
-- now set numGroups
|
||||||
if theTracker.tNumGroups then
|
if theTracker.tNumGroups then
|
||||||
cfxZones.setFlagValue(theTracker.tNumGroups, #theTracker.trackedGroups, theTracker)
|
cfxZones.setFlagValue(theTracker.tNumGroups, dcsCommon.getSizeOfTable(theTracker.limbo) + #theTracker.trackedGroups, theTracker)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- count all units
|
-- count all units
|
||||||
@ -105,6 +131,9 @@ function groupTracker.addGroupToTracker(theGroup, theTracker)
|
|||||||
totalUnits = totalUnits + aGroup:getSize()
|
totalUnits = totalUnits + aGroup:getSize()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
for idx, limboNum in pairs(theTracker.limbo) do
|
||||||
|
totalUnits = totalUnits + limboNum
|
||||||
|
end
|
||||||
|
|
||||||
-- update unit count
|
-- update unit count
|
||||||
if theTracker.tNumUnits then
|
if theTracker.tNumUnits then
|
||||||
@ -113,6 +142,7 @@ function groupTracker.addGroupToTracker(theGroup, theTracker)
|
|||||||
-- invoke callbacks
|
-- invoke callbacks
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function groupTracker.addGroupToTrackerNamed(theGroup, trackerName)
|
function groupTracker.addGroupToTrackerNamed(theGroup, trackerName)
|
||||||
if not trackerName then
|
if not trackerName then
|
||||||
trigger.action.outText("+++gTrk: nil tracker in addGroupToTrackerNamed", 30)
|
trigger.action.outText("+++gTrk: nil tracker in addGroupToTrackerNamed", 30)
|
||||||
@ -133,6 +163,93 @@ function groupTracker.addGroupToTrackerNamed(theGroup, trackerName)
|
|||||||
groupTracker.addGroupToTracker(theGroup, theTracker)
|
groupTracker.addGroupToTracker(theGroup, theTracker)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function groupTracker.moveGroupToLimboForTracker(theGroup, theTracker)
|
||||||
|
if not theGroup then return end
|
||||||
|
if not theTracker then return end
|
||||||
|
if not Group.isExist(theGroup) then return end
|
||||||
|
|
||||||
|
local gName = theGroup:getName()
|
||||||
|
local filtered = {}
|
||||||
|
if theTracker.trackedGroups then
|
||||||
|
for idx, aGroup in pairs(theTracker.trackedGroups) do
|
||||||
|
if Group.isExist(aGroup) and aGroup:getName() == gName then
|
||||||
|
-- move this to limbo
|
||||||
|
theTracker.limbo[gName] = aGroup:getSize()
|
||||||
|
if theTracker.verbose then
|
||||||
|
trigger.action.outText("+++gTrk: moved group <" .. gName .. "> to limbo for <" .. theTracker.name .. ">", 30)
|
||||||
|
end
|
||||||
|
-- filtered
|
||||||
|
else
|
||||||
|
table.insert(filtered, aGroup)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
theTracker.trackedGroups = filtered
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function groupTracker.removeGroupNamedFromTracker(gName, theTracker)
|
||||||
|
if not gName then return end
|
||||||
|
if not theTracker then return end
|
||||||
|
|
||||||
|
local filteredGroups = {}
|
||||||
|
local foundOne = false
|
||||||
|
local totalUnits = 0
|
||||||
|
if not theTracker.trackedGroups then theTracker.trackedGroups = {} end
|
||||||
|
for idx, aGroup in pairs(theTracker.trackedGroups) do
|
||||||
|
if Group.isExist(aGroup) and aGroup:getName() == gName then
|
||||||
|
-- skip and remember
|
||||||
|
foundOne = true
|
||||||
|
else
|
||||||
|
table.insert(filteredGroups, aGroup)
|
||||||
|
if Group.isExist(aGroup) then
|
||||||
|
totalUnits = totalUnits + aGroup:getSize()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- also check limbo
|
||||||
|
for limboName, limboNum in pairs (theTracker.limbo) do
|
||||||
|
if gName == limboName then
|
||||||
|
-- don't count, but remember that it existed
|
||||||
|
foundOne = true
|
||||||
|
if theTracker.verbose then
|
||||||
|
trigger.action.outText("+++gTrk: removed group <" .. gName .. "> from limbo for <" .. theTracker.name .. ">", 30)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
totalUnits = totalUnits + limboNum
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- remove from limbo
|
||||||
|
theTracker.limbo[gName] = nil
|
||||||
|
|
||||||
|
if (not foundOne) and (theTracker.verbose or groupTracker.verbose) then
|
||||||
|
trigger.action.outText("+++gTrk: Removal Request Note: group <" .. gName .. "> wasn't tracked by <" .. theTracker.name .. ">", 30)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- remember the new, cleanded set
|
||||||
|
theTracker.trackedGroups = filteredGroups
|
||||||
|
|
||||||
|
-- update number of tracked units. do it in any case
|
||||||
|
if theTracker.tNumUnits then
|
||||||
|
cfxZones.setFlagValue(theTracker.tNumUnits, totalUnits, theTracker)
|
||||||
|
end
|
||||||
|
|
||||||
|
if foundOne then
|
||||||
|
if theTracker.verbose or groupTracker.verbose then
|
||||||
|
trigger.action.outText("+++gTrk: removed group <" .. gName .. "> from tracker <" .. theTracker.name .. ">", 30)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- now bang/invoke removeGroup!
|
||||||
|
if theTracker.tRemoveGroup then
|
||||||
|
cfxZones.pollFlag(theTracker.tRemoveGroup, "inc", theTracker)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- now set numGroups
|
||||||
|
if theTracker.tNumGroups then
|
||||||
|
cfxZones.setFlagValue(theTracker.tNumGroups, dcsCommon.getSizeOfTable(theTracker.limbo) + #theTracker.trackedGroups, theTracker)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function groupTracker.removeGroupNamedFromTrackerNamed(gName, trackerName)
|
function groupTracker.removeGroupNamedFromTrackerNamed(gName, trackerName)
|
||||||
local theTracker = groupTracker.getTrackerByName(trackerName)
|
local theTracker = groupTracker.getTrackerByName(trackerName)
|
||||||
if not theTracker then return end
|
if not theTracker then return end
|
||||||
@ -141,6 +258,8 @@ function groupTracker.removeGroupNamedFromTrackerNamed(gName, trackerName)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
groupTracker.removeGroupNamedFromTracker(gName, theTracker)
|
||||||
|
--[[--
|
||||||
local filteredGroups = {}
|
local filteredGroups = {}
|
||||||
local foundOne = false
|
local foundOne = false
|
||||||
local totalUnits = 0
|
local totalUnits = 0
|
||||||
@ -156,6 +275,18 @@ function groupTracker.removeGroupNamedFromTrackerNamed(gName, trackerName)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
-- also check limbo
|
||||||
|
for limboName, limboNum in pairs (theTracker.limbo) do
|
||||||
|
if gName == limboName then
|
||||||
|
-- don't count, but remember that it existed
|
||||||
|
foundOne = true
|
||||||
|
else
|
||||||
|
totalUnits = totalUnits + limboNum
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- remove from limbo
|
||||||
|
theTracker.limbo[gName] = nil
|
||||||
|
|
||||||
if (not foundOne) and (theTracker.verbose or groupTracker.verbose) then
|
if (not foundOne) and (theTracker.verbose or groupTracker.verbose) then
|
||||||
trigger.action.outText("+++gTrk: Removal Request Note: group <" .. gName .. "> wasn't tracked by <" .. trackerName .. ">", 30)
|
trigger.action.outText("+++gTrk: Removal Request Note: group <" .. gName .. "> wasn't tracked by <" .. trackerName .. ">", 30)
|
||||||
end
|
end
|
||||||
@ -180,16 +311,59 @@ function groupTracker.removeGroupNamedFromTrackerNamed(gName, trackerName)
|
|||||||
|
|
||||||
-- now set numGroups
|
-- now set numGroups
|
||||||
if theTracker.tNumGroups then
|
if theTracker.tNumGroups then
|
||||||
cfxZones.setFlagValue(theTracker.tNumGroups, #theTracker.trackedGroups, theTracker)
|
cfxZones.setFlagValue(theTracker.tNumGroups, dcsCommon.getSizeOfTable(theTracker.limbo) + #theTracker.trackedGroups, theTracker)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
--]]--
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- groupTrackedBy - return trackers that track group theGroup
|
||||||
|
-- returns 3 values: true/false (is tracking), number of trackers, array of trackers
|
||||||
|
function groupTracker.groupNameTrackedBy(theName)
|
||||||
|
local isTracking = false
|
||||||
|
|
||||||
|
-- now iterate all trackers
|
||||||
|
local tracking = {}
|
||||||
|
for idx, aTracker in pairs(groupTracker.trackers) do
|
||||||
|
-- only look at tracked groups if that tracker has an
|
||||||
|
-- initialized tracker (lazy init)
|
||||||
|
if aTracker.trackedGroups then
|
||||||
|
for idy, aGroup in pairs (aTracker.trackedGroups) do
|
||||||
|
if Group.isExist(aGroup) and aGroup:getName() == theName then
|
||||||
|
table.insert(tracking, aTracker)
|
||||||
|
isTracking = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for aName, aNum in pairs(aTracker.limbo) do
|
||||||
|
if aName == theName then
|
||||||
|
table.insert(tracking, aTracker)
|
||||||
|
isTracking = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return isTracking, #tracking, tracking
|
||||||
|
end
|
||||||
|
|
||||||
|
function groupTracker.groupTrackedBy(theGroup)
|
||||||
|
if not theGroup then return false,0, nil end
|
||||||
|
if not Group.isExist(theGroup) then return false, 0, nil end
|
||||||
|
local theName = theGroup:getName()
|
||||||
|
local isTracking, numTracks, trackers = groupTracker.groupNameTrackedBy(theName)
|
||||||
|
return isTracking, numTracks, trackers
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
-- read zone
|
-- read zone
|
||||||
|
--
|
||||||
function groupTracker.createTrackerWithZone(theZone)
|
function groupTracker.createTrackerWithZone(theZone)
|
||||||
-- init group tracking set
|
-- init group tracking set
|
||||||
theZone.trackedGroups = {}
|
theZone.trackedGroups = {}
|
||||||
|
theZone.limbo = {} -- name based, for groups that are tracked
|
||||||
|
-- although technically off the map (helo etc)
|
||||||
|
|
||||||
theZone.trackerMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "inc")
|
theZone.trackerMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "inc")
|
||||||
if cfxZones.hasProperty(theZone, "trackerMethod") then
|
if cfxZones.hasProperty(theZone, "trackerMethod") then
|
||||||
@ -264,6 +438,10 @@ function groupTracker.destroyAllInZone(theZone)
|
|||||||
theGroup:destroy()
|
theGroup:destroy()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
for aName, aNum in pairs(theZone.limbo) do
|
||||||
|
theZone.limbo[aName] = 0 -- <1 is special for 'remove me and detect kill on next checkGroups'
|
||||||
|
end
|
||||||
|
|
||||||
-- we keep all groups in trackedGroups so we
|
-- we keep all groups in trackedGroups so we
|
||||||
-- generate a host of destroy events when we run through
|
-- generate a host of destroy events when we run through
|
||||||
-- checkGroups next
|
-- checkGroups next
|
||||||
@ -300,12 +478,26 @@ function groupTracker.checkGroups(theZone)
|
|||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local newLimbo = {}
|
||||||
|
for aName, aNum in pairs (theZone.limbo) do
|
||||||
|
if aNum < 1 then
|
||||||
|
if groupTracker.verbose or theZone.verbose then
|
||||||
|
trigger.action.outText("+++gTrk: dead group <" .. aName .. "> detected in LIMBO for " .. theZone.name .. ", removing.", 30)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
newLimbo[aName] = aNum
|
||||||
|
totalUnits = totalUnits + aNum
|
||||||
|
end
|
||||||
|
end
|
||||||
|
theZone.limbo = newLimbo
|
||||||
|
|
||||||
-- now exchange filtered for current
|
-- now exchange filtered for current
|
||||||
theZone.trackedGroups = filteredGroups
|
theZone.trackedGroups = filteredGroups
|
||||||
--set new group value
|
--set new group value
|
||||||
-- now set numGroups if defined
|
-- now set numGroups if defined
|
||||||
if theZone.tNumGroups then
|
if theZone.tNumGroups then
|
||||||
cfxZones.setFlagValue(theZone.tNumGroups, #filteredGroups, theZone)
|
cfxZones.setFlagValue(theZone.tNumGroups, dcsCommon.getSizeOfTable(theZone.limbo) + #filteredGroups, theZone)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- and update unit count if defined
|
-- and update unit count if defined
|
||||||
@ -361,7 +553,7 @@ function groupTracker.update()
|
|||||||
groupTracker.checkGroups(theZone)
|
groupTracker.checkGroups(theZone)
|
||||||
|
|
||||||
-- see if we need to bang on empty!
|
-- see if we need to bang on empty!
|
||||||
local currCount = #theZone.trackedGroups
|
local currCount = #theZone.trackedGroups + dcsCommon.getSizeOfTable(theZone.limbo)
|
||||||
if theZone.allGoneFlag and currCount == 0 and currCount ~= theZone.lastGroupCount then
|
if theZone.allGoneFlag and currCount == 0 and currCount ~= theZone.lastGroupCount then
|
||||||
cfxZones.pollFlag(theZone.allGoneFlag, theZone.trackerMethod, theZone)
|
cfxZones.pollFlag(theZone.allGoneFlag, theZone.trackerMethod, theZone)
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
messenger = {}
|
messenger = {}
|
||||||
messenger.version = "2.2.0"
|
messenger.version = "2.2.1"
|
||||||
messenger.verbose = false
|
messenger.verbose = false
|
||||||
messenger.requiredLibs = {
|
messenger.requiredLibs = {
|
||||||
"dcsCommon", -- always
|
"dcsCommon", -- always
|
||||||
@ -64,6 +64,9 @@ messenger.messengers = {}
|
|||||||
2.2.0 - <player: unit>
|
2.2.0 - <player: unit>
|
||||||
- made dynamic string gen more portable in prep for move to cfxZones
|
- made dynamic string gen more portable in prep for move to cfxZones
|
||||||
- refactoring wildcard processing: moved to cfxZones
|
- refactoring wildcard processing: moved to cfxZones
|
||||||
|
2.2.1 - when messenger is linked to a unit, it can use the linked
|
||||||
|
unit as reference point for relative wildcards. Always broadcasts to coalition. Can be used to broadcase 'eye in the sky' type information
|
||||||
|
- fixed verbosity bug
|
||||||
|
|
||||||
--]]--
|
--]]--
|
||||||
|
|
||||||
@ -318,7 +321,7 @@ function messenger.createMessengerWithZone(theZone)
|
|||||||
-- flag whose value can be read: to be deprecated
|
-- flag whose value can be read: to be deprecated
|
||||||
if cfxZones.hasProperty(theZone, "messageValue?") then
|
if cfxZones.hasProperty(theZone, "messageValue?") then
|
||||||
theZone.messageValue = cfxZones.getStringFromZoneProperty(theZone, "messageValue?", "<none>")
|
theZone.messageValue = cfxZones.getStringFromZoneProperty(theZone, "messageValue?", "<none>")
|
||||||
trigger.action.outText("+++Msg: Warning - zone <" .. theZone.name .. "> uses 'messageValue' attribute. Migrate to <v:<flag> now!")
|
trigger.action.outText("+++Msg: Warning - zone <" .. theZone.name .. "> uses 'messageValue' attribute. Migrate to <v:<flag> now!", 30)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- time format for new <t: flagname>
|
-- time format for new <t: flagname>
|
||||||
@ -418,6 +421,20 @@ function messenger.isTriggered(theZone)
|
|||||||
end
|
end
|
||||||
trigger.action.outSoundForUnit(ID, fileName)
|
trigger.action.outSoundForUnit(ID, fileName)
|
||||||
end
|
end
|
||||||
|
elseif cfxZones.getLinkedUnit(theZone) then
|
||||||
|
-- this only works if the zone is linked to a unit
|
||||||
|
-- and not using group or unit
|
||||||
|
-- the linked unit is then used as reference
|
||||||
|
-- outputs to all of same coalition as the linked
|
||||||
|
-- unit
|
||||||
|
local theUnit = cfxZones.getLinkedUnit(theZone)
|
||||||
|
local ID = theUnit:getID()
|
||||||
|
local coa = theUnit:getCoalition()
|
||||||
|
msg = messenger.dynamicUnitProcessing(msg, theZone, theUnit)
|
||||||
|
if #msg > 0 or theZone.clearScreen then
|
||||||
|
trigger.action.outTextForCoalition(coa, msg, theZone.duration, theZone.clearScreen)
|
||||||
|
end
|
||||||
|
trigger.action.outSoundForCoalition(coa, fileName)
|
||||||
else
|
else
|
||||||
-- out to all
|
-- out to all
|
||||||
if #msg > 0 or theZone.clearScreen then
|
if #msg > 0 or theZone.clearScreen then
|
||||||
@ -518,3 +535,8 @@ if not messenger.start() then
|
|||||||
messenger = nil
|
messenger = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
--[[--
|
||||||
|
Ideas:
|
||||||
|
- when messenger is ties to a unit, that unit can also be base for all relative references. Only checked if neither group nor unit is set
|
||||||
|
--]]--
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
persistence = {}
|
persistence = {}
|
||||||
persistence.version = "1.0.4"
|
persistence.version = "1.0.6"
|
||||||
persistence.ups = 1 -- once every 1 seconds
|
persistence.ups = 1 -- once every 1 seconds
|
||||||
persistence.verbose = false
|
persistence.verbose = false
|
||||||
persistence.active = false
|
persistence.active = false
|
||||||
@ -26,6 +26,7 @@ persistence.requiredLibs = {
|
|||||||
new 'saveNotification" can be off
|
new 'saveNotification" can be off
|
||||||
1.0.4 - new optional 'root' property
|
1.0.4 - new optional 'root' property
|
||||||
1.0.5 - desanitize check on readConfig to early-abort
|
1.0.5 - desanitize check on readConfig to early-abort
|
||||||
|
1.0.6 - removed potential verbosity bug
|
||||||
|
|
||||||
|
|
||||||
PROVIDES LOAD/SAVE ABILITY TO MODULES
|
PROVIDES LOAD/SAVE ABILITY TO MODULES
|
||||||
@ -132,7 +133,7 @@ end
|
|||||||
function persistence.saveText(theString, fileName, shared, append)
|
function persistence.saveText(theString, fileName, shared, append)
|
||||||
if not persistence.active then return false end
|
if not persistence.active then return false end
|
||||||
if not fileName then
|
if not fileName then
|
||||||
trigger.action.outText("+++persistence: saveText without fileName")
|
trigger.action.outText("+++persistence: saveText without fileName", 30)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
if not shared then shared = flase end
|
if not shared then shared = flase end
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
pulseFlags = {}
|
pulseFlags = {}
|
||||||
pulseFlags.version = "1.3.1"
|
pulseFlags.version = "1.3.2"
|
||||||
pulseFlags.verbose = false
|
pulseFlags.verbose = false
|
||||||
pulseFlags.requiredLibs = {
|
pulseFlags.requiredLibs = {
|
||||||
"dcsCommon", -- always
|
"dcsCommon", -- always
|
||||||
@ -37,6 +37,7 @@ pulseFlags.requiredLibs = {
|
|||||||
returned onStart, defaulting to true
|
returned onStart, defaulting to true
|
||||||
- 1.3.0 persistence
|
- 1.3.0 persistence
|
||||||
- 1.3.1 typos corrected
|
- 1.3.1 typos corrected
|
||||||
|
- 1.3.2 removed last pulse's timeID upon entry in doPulse
|
||||||
|
|
||||||
--]]--
|
--]]--
|
||||||
|
|
||||||
@ -165,6 +166,7 @@ end
|
|||||||
|
|
||||||
|
|
||||||
function pulseFlags.doPulse(args)
|
function pulseFlags.doPulse(args)
|
||||||
|
|
||||||
local theZone = args[1]
|
local theZone = args[1]
|
||||||
-- check if we have been paused. if so, simply
|
-- check if we have been paused. if so, simply
|
||||||
-- exit with no new schedule
|
-- exit with no new schedule
|
||||||
@ -172,6 +174,8 @@ function pulseFlags.doPulse(args)
|
|||||||
theZone.pulsing = false
|
theZone.pulsing = false
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
-- erase old timerID, since we completed that
|
||||||
|
theZone.timerID = nil
|
||||||
|
|
||||||
-- do a poll on flags
|
-- do a poll on flags
|
||||||
-- first, we only do an initial pulse if zeroPulse is set
|
-- first, we only do an initial pulse if zeroPulse is set
|
||||||
@ -268,7 +272,7 @@ function pulseFlags.update()
|
|||||||
-- pausePulseFlag
|
-- pausePulseFlag
|
||||||
if cfxZones.testZoneFlag(aZone, aZone.pausePulseFlag, aZone.pulseTriggerMethod, "lastPauseValue") then
|
if cfxZones.testZoneFlag(aZone, aZone.pausePulseFlag, aZone.pulseTriggerMethod, "lastPauseValue") then
|
||||||
if pulseFlags.verbose or aZone.verbose then
|
if pulseFlags.verbose or aZone.verbose then
|
||||||
trigger.action.outText("+++pulF: pausing <" .. aZone.name .. ">", 30)
|
trigger.action.outText("+++pulF: pausing <" .. aZone.name .. ">", 30)
|
||||||
end
|
end
|
||||||
aZone.pulsePaused = true -- prevents new start
|
aZone.pulsePaused = true -- prevents new start
|
||||||
if aZone.timerID then
|
if aZone.timerID then
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
valet = {}
|
valet = {}
|
||||||
valet.version = "1.0.0"
|
valet.version = "1.0.2"
|
||||||
valet.verbose = false
|
valet.verbose = false
|
||||||
valet.requiredLibs = {
|
valet.requiredLibs = {
|
||||||
"dcsCommon", -- always
|
"dcsCommon", -- always
|
||||||
@ -10,6 +10,9 @@ valet.valets = {}
|
|||||||
--[[--
|
--[[--
|
||||||
Version History
|
Version History
|
||||||
1.0.0 - initial version
|
1.0.0 - initial version
|
||||||
|
1.0.1 - typos in verbosity corrected
|
||||||
|
1.0.2 - also scan birth events
|
||||||
|
|
||||||
--]]--
|
--]]--
|
||||||
|
|
||||||
function valet.addValet(theZone)
|
function valet.addValet(theZone)
|
||||||
@ -374,16 +377,20 @@ function valet.checkPlayerSpawn(playerName, theUnit)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function valet:onEvent(event)
|
function valet:onEvent(event)
|
||||||
if event.id == 20 then
|
if (event.id == 20) or (event.id == 15) then
|
||||||
if not event.initiator then return end
|
if not event.initiator then return end
|
||||||
local theUnit = event.initiator
|
local theUnit = event.initiator
|
||||||
if not theUnit.getPlayerName then
|
if not theUnit.getPlayerName then
|
||||||
trigger.action.outText("+++valet: non player event 20(?)", 30)
|
if event.id == 20 then
|
||||||
|
trigger.action.outText("+++valet: non player event 20(?)", 30)
|
||||||
|
end -- 15 (birth can happen to all)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local pName = theUnit:getPlayerName()
|
local pName = theUnit:getPlayerName()
|
||||||
if not pName then
|
if not pName then
|
||||||
trigger.action.outText("+++valet: nil player name on event 20 (!)", 30)
|
if event.id == 20 then
|
||||||
|
trigger.action.outText("+++valet: nil player name on event 20 (!)", 30)
|
||||||
|
end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -398,7 +405,7 @@ function valet.readConfigZone()
|
|||||||
local theZone = cfxZones.getZoneByName("valetConfig")
|
local theZone = cfxZones.getZoneByName("valetConfig")
|
||||||
if not theZone then
|
if not theZone then
|
||||||
if valet.verbose then
|
if valet.verbose then
|
||||||
trigger.action.outText("+++msgr: NO config zone!", 30)
|
trigger.action.outText("+++valet: NO config zone!", 30)
|
||||||
end
|
end
|
||||||
theZone = cfxZones.createSimpleZone("valetConfig")
|
theZone = cfxZones.createSimpleZone("valetConfig")
|
||||||
end
|
end
|
||||||
@ -406,7 +413,7 @@ function valet.readConfigZone()
|
|||||||
valet.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
|
valet.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
|
||||||
|
|
||||||
if valet.verbose then
|
if valet.verbose then
|
||||||
trigger.action.outText("+++msgr: read config", 30)
|
trigger.action.outText("+++valet: read config", 30)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
BIN
sound FX/beacon beep-beep.ogg
Normal file
BIN
sound FX/beacon beep-beep.ogg
Normal file
Binary file not shown.
BIN
sound FX/submarine ping.ogg
Normal file
BIN
sound FX/submarine ping.ogg
Normal file
Binary file not shown.
Binary file not shown.
BIN
tutorial & demo missions/demo - Davy Jones' Rocker.miz
Normal file
BIN
tutorial & demo missions/demo - Davy Jones' Rocker.miz
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user