Bug fixes
autoCsar Beta
williePete Beta
This commit is contained in:
Christian Franz 2022-09-29 09:18:43 +02:00
parent 46712cba20
commit 4c0b0e8bed
20 changed files with 1271 additions and 189 deletions

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,5 @@
LZ = {}
LZ.version = "1.0.0"
LZ.version = "1.1.0"
LZ.verbose = false
LZ.ups = 1
LZ.requiredLibs = {
@ -14,6 +14,7 @@ LZ.LZs = {}
Version History
1.0.0 - initial version
1.1.0 - persistence
--]]--
@ -131,13 +132,12 @@ end
-- Misc Processing
--
function LZ.unitIsInterestingForZone(theUnit, theZone)
--trigger.action.outText("enter isInterestingB4pause for <" .. theUnit:getName() .. ">", 40)
-- see if zone is interested in this unit.
if theZone.isPaused then
return false
end
-- trigger.action.outText("enter isinteresting for <" .. theUnit:getName() .. ">", 40)
if theZone.lzPlayerOnly then
if not dcsCommon.isPlayerUnit(theUnit) then
if theZone.verbose or LZ.verbose then
@ -198,11 +198,7 @@ function LZ.unitIsInterestingForZone(theUnit, theZone)
else
-- we can return true since player and coa mismatch
-- have already been filtered
--[[-- -- neither type, unit, nor group
local theGroup = theUnit:getGroup()
local coa = theGroup:getCoalition()
--
--]]--
return true -- theZone.coalition == coa end
end
@ -224,18 +220,10 @@ function LZ:onEvent(event)
return
end
--if LZ.verbose or true then
-- trigger.action.outText("+++LZ: on event proccing", 30)
--end
local theUnit = event.initiator
if not Unit.isExist(theUnit) then return end
local p = theUnit:getPoint()
--if LZ.verbose or true then
-- trigger.action.outText("+++LZ: before iterating zones", 30)
--end
for idx, aZone in pairs(LZ.LZs) do
-- see if inside the zone
local inZone, percent, dist = cfxZones.pointInZone(p, aZone)
@ -258,8 +246,8 @@ function LZ:onEvent(event)
end
end -- if interesting
else
if LZ.verbose or true then
-- trigger.action.outText("+++LZ: unit <" .. theUnit:getName() .. "> not in zone <" .. aZone.name .. ">", 30)
if LZ.verbose or aZone.verbose then
--trigger.action.outText("+++LZ: unit <" .. theUnit:getName() .. "> not in zone <" .. aZone.name .. ">", 30)
end
end -- if in zone
@ -292,13 +280,58 @@ function LZ.update()
end
--
-- LOAD / SAVE
--
function LZ.saveData()
local theData = {}
local allLZ = {}
for idx, theLZ in pairs(LZ.LZs) do
local theName = theLZ.name
local LZData = {}
LZData.isPaused = theLZ.isPaused
allLZ[theName] = LZData
end
theData.allLZ = allLZ
return theData
end
function LZ.loadData()
if not persistence then return end
local theData = persistence.getSavedDataForModule("LZ")
if not theData then
if LZ.verbose then
trigger.action.outText("+++LZ persistence: no save data received, skipping.", 30)
end
return
end
local allLZ = theData.allLZ
if not allLZ then
if LZ.verbose then
trigger.action.outText("+++LZ persistence: no LZ data, skipping", 30)
end
return
end
for theName, theData in pairs(allLZ) do
local theLZ = LZ.getLZByName(theName)
if theLZ then
theLZ.isPaused = theData.isPaused
else
trigger.action.outText("+++LZ: persistence: cannot synch LZ <" .. theName .. ">, skipping", 40)
end
end
end
--
-- Config & Start
--
function LZ.readConfigZone()
local theZone = cfxZones.getZoneByName("LZConfig")
if not theZone then
theZone = cfxZones.createSimpleZone(LZConfig)
theZone = cfxZones.createSimpleZone("LZConfig")
if LZ.verbose then
trigger.action.outText("+++LZ: NO config zone!", 30)
end
@ -336,6 +369,16 @@ function LZ.start()
-- connect event handler
world.addEventHandler(LZ)
-- load any saved data
if persistence then
-- sign up for persistence
callbacks = {}
callbacks.persistData = LZ.saveData
persistence.registerModule("LZ", callbacks)
-- now load my data
LZ.loadData()
end
-- start update
LZ.update()

112
modules/autoCSAR.lua Normal file
View File

@ -0,0 +1,112 @@
autoCSAR = {}
autoCSAR.version = "1.0.0"
autoCSAR.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
}
autoCSAR.killDelay = 2 * 60
autoCSAR.counter = 31 -- any nuber is good, to kick-off counting
--[[--
VERSION HISTORY
1.0.0 - Initial Version
--]]--
function autoCSAR.removeGuy(args)
local theGuy = args.theGuy
if theGuy and theGuy:isExist() then
Unit.destroy(theGuy)
end
end
function autoCSAR.createNewCSAR(theUnit)
if not csarManager then
trigger.action.outText("+++aCSAR: CSAR Manager not loaded, aborting", 30)
-- return
end
-- enter with unit from landing_after_eject event
-- unit has no group
local coa = theUnit:getCoalition()
if coa == 0 then -- neutral
trigger.action.outText("Neutal Pilot made it safely to ground.", 30)
return
end
if coa == 1 and not autoCSAR.redCSAR then
return -- we don't do red
end
if coa == 2 and not autoCSAR.blueCSAR then
return -- no blue rescue
end
-- for later expansion
local theGroup = theUnit:getGroup()
if theGroup then
trigger.action.outText("We have a group for <" .. theUnit:getName() .. ">", 30)
end
-- create a CSAR mission now
csarManager.createCSARForParachutist(theUnit, "Xray-" .. autoCSAR.counter)
autoCSAR.counter = autoCSAR.counter + 1
-- schedule removal of pilot
local args = {}
args.theGuy = theUnit
timer.scheduleFunction(autoCSAR.removeGuy, args, timer.getTime() + autoCSAR.killDelay)
end
function autoCSAR:onEvent(event)
if event.id == 31 then -- landing_after_eject
if event.initiator then
autoCSAR.createNewCSAR(event.initiator)
end
end
end
function autoCSAR.readConfigZone()
local theZone = cfxZones.getZoneByName("autoCSARConfig")
if not theZone then
theZone = cfxZones.createSimpleZone("autoCSARConfig")
if autoCSAR.verbose then
trigger.action.outText("+++aCSAR: NO config zone!", 30)
end
end
autoCSAR.redCSAR = cfxZones.getBoolFromZoneProperty(theZone, "red", true)
if cfxZones.hasProperty(theZone, "redCSAR") then
autoCSAR.redCSAR = cfxZones.getBoolFromZoneProperty(theZone, "redCSAR", true)
end
autoCSAR.blueCSAR = cfxZones.getBoolFromZoneProperty(theZone, "blue", true)
if cfxZones.hasProperty(theZone, "blueCSAR") then
autoCSAR.blueCSAR = cfxZones.getBoolFromZoneProperty(theZone, "blueCSAR", true)
end
if autoCSAR.verbose then
trigger.action.outText("+++aCSAR: read config", 30)
end
end
function autoCSAR.start()
-- lib check
if not dcsCommon.libCheck then
trigger.action.outText("cfx autoCSAR requires dcsCommon", 30)
return false
end
if not dcsCommon.libCheck("cfx autoCSAR", autoCSAR.requiredLibs) then
return false
end
-- read config
autoCSAR.readConfigZone()
-- connect event handler
world.addEventHandler(autoCSAR)
trigger.action.outText("cfx autoCSAR v" .. autoCSAR.version .. " started.", 30)
return true
end
-- let's go!
if not autoCSAR.start() then
trigger.action.outText("cfx autoCSAR aborted: missing libraries", 30)
autoCSAR = nil
end

View File

@ -1,5 +1,5 @@
cfxArtilleryZones = {}
cfxArtilleryZones.version = "2.2.1"
cfxArtilleryZones.version = "2.2.2"
cfxArtilleryZones.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
@ -30,6 +30,7 @@ cfxArtilleryZones.verbose = false
- code cleanup
2.2.0 - DML Watchflag integration
2.2.1 - minor code clean-up
2.2.2 - new doParametricFireAt()
Artillery Target Zones *** EXTENDS ZONES ***
Target Zones for artillery. Can determine which zones are in range and visible and then handle artillery barrage to this zone
@ -258,6 +259,37 @@ function cfxArtilleryZones.doBoom(args)
cfxArtilleryZones.invokeCallbacksFor('impact', args.zone, data)
end
function cfxArtilleryZones.doParametricFireAt(aPoint, accuracy, shellNum, shellBaseStrength, shellVariance, transitionTime)
-- accuracy is meters from center
if not aPoint then return end
if not accuracy then accuracy = 100 end
if not shellNum then shellNum = 17 end
if not shellBaseStrength then shellBaseStrength = 500 end
if not shellVariance then shellVariance = 0.2 end
if not transitionTime then transitionTime = 17 end
local alt = land.getHeight({x=aPoint.x, y=aPoint.z})
local center = {x=aPoint.x, y=alt, z=aPoint.z}
for i=1, shellNum do
local thePoint = dcsCommon.randomPointInCircle(accuracy, 0, center.x, center.z)
thePoint.y = land.getHeight({x=thePoint.x, y=thePoint.z})
local boomArgs = {}
local strVar = shellBaseStrength * shellVariance
strVar = strVar * (2 * dcsCommon.randomPercent() - 1.0) -- go from -1 to 1
boomArgs.strength = shellBaseStrength + strVar
thePoint.y = land.getHeight({x = thePoint.x, y = thePoint.z}) + 1 -- elevate to ground height + 1
boomArgs.point = thePoint
boomArgs.zone = aZone
local timeVar = 5 * (2 * dcsCommon.randomPercent() - 1.0) -- +/- 1.5 seconds
if timeVar < 0 then timeVar = -timeVar end
timer.scheduleFunction(cfxArtilleryZones.doBoom, boomArgs, timer.getTime() + transitionTime + timeVar)
end
end
function cfxArtilleryZones.doFireAt(aZone, maxDistFromCenter)
if type(aZone) == "string" then
local mZone = cfxArtilleryZones.findArtilleryZoneNamed(aZone)

View File

@ -1,5 +1,5 @@
cfxHeloTroops = {}
cfxHeloTroops.version = "2.2.0"
cfxHeloTroops.version = "2.3.0"
cfxHeloTroops.verbose = false
cfxHeloTroops.autoDrop = true
cfxHeloTroops.autoPickup = false
@ -22,7 +22,9 @@ cfxHeloTroops.pickupRange = 100 -- meters
-- -- (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
@ -511,30 +513,39 @@ end
function cfxHeloTroops.filterTroopsByType(unitsToLoad)
local filteredGroups = {}
for idx, aTeam in pairs(unitsToLoad) do
local group = aTeam.group
local theTypes = dcsCommon.getGroupTypeString(group)
for idx, aTeam in pairs(unitsToLoad) do
local group = aTeam.group
local theTypes = dcsCommon.getGroupTypeString(group)
local aT = dcsCommon.splitString(theTypes, ",")
local pass = true
for iT, sT in pairs(aT) do
-- check if this is a valid type
if cfxHeloTroops.legalTroops then
if not dcsCommon.arrayContainsString(cfxHeloTroops.legalTroops, sT) then
pass = false
break
end
else
if not dcsCommon.typeIsInfantry(sT) then
pass = false
break
end
local aT = dcsCommon.splitString(theTypes, ",")
local pass = true
for iT, sT in pairs(aT) do
-- check if this is a valid type
if cfxHeloTroops.legalTroops then
if not dcsCommon.arrayContainsString(cfxHeloTroops.legalTroops, sT) then
pass = false
break
end
else
if not dcsCommon.typeIsInfantry(sT) then
pass = false
break
end
end
if pass then
table.insert(filteredGroups, aTeam)
end
-- check if we are about to pre-empt a CSAR mission
if csarManager then
if csarManager.isCSARTarget(group) then
-- this one is managed by csarManager,
-- don't load it for helo troops
pass = false
end
end
if pass then
table.insert(filteredGroups, aTeam)
end
end
return filteredGroups
end
--
@ -578,12 +589,43 @@ function cfxHeloTroops.redirectDeployTroops(args)
timer.scheduleFunction(cfxHeloTroops.doDeployTroops, args, timer.getTime() + 0.1)
end
function cfxHeloTroops.scoreWhenCapturing(theUnit)
if theUnit and Unit.isExist(theUnit) and theUnit.getPlayerName then
-- see if wer are inside a non-alinged zone
-- and this includes a neutral zone
local coa = theUnit:getCoalition()
local p = theUnit:getPoint()
local theGroup = theUnit:getGroup()
local ID = theGroup:getID()
local nearestZone, dist = cfxOwnedZones.getNearestOwnedZoneToPoint(p)
if nearestZone and dist < nearestZone.radius then
-- we are inside an owned zone!
if nearestZone.owner ~= coa then
-- yup, combat drop!
local theScore = cfxHeloTroops.combatDropScore
local pName = theUnit:getPlayerName()
if pName then
cfxPlayerScore.updateScoreForPlayer(pName, theScore)
cfxPlayerScore.logFeatForPlayer(pName, "Combat Troop Insertion at " .. nearestZone.name, coa)
end
end
end
end
end
function cfxHeloTroops.doDeployTroops(args)
local conf = args[1]
local what = args[2]
-- deploy the troops I have on board in formation
cfxHeloTroops.deployTroopsFromHelicopter(conf)
-- interface with playerscore if we dropped
-- inside an enemy-owned zone
if cfxPlayerScore and cfxOwnedZones then
local theUnit = conf.unit
cfxHeloTroops.scoreWhenCapturing(theUnit)
end
-- set own troops to 0 and erase type string
conf.troopsOnBoardNum = 0
conf.troopsOnBoard = {}
@ -840,7 +882,7 @@ function cfxHeloTroops.readConfigZone()
cfxHeloTroops.autoDrop = cfxZones.getBoolFromZoneProperty(theZone, "autoDrop", false)
cfxHeloTroops.autoPickup = cfxZones.getBoolFromZoneProperty(theZone, "autoPickup", false)
cfxHeloTroops.pickupRange = cfxZones.getNumberFromZoneProperty(theZone, "pickupRange", 100)
cfxHeloTroops.combatDropScore = cfxZones.getNumberFromZoneProperty(theZone, "combatDropScore", 200)
end
--

View File

@ -1,5 +1,5 @@
cfxMX = {}
cfxMX.version = "1.2.3"
cfxMX.version = "1.2.4"
cfxMX.verbose = false
--[[--
Mission data decoder. Access to ME-built mission structures
@ -23,7 +23,7 @@ cfxMX.verbose = false
- playerUnitByName
1.2.3 - groupTypeByName
- groupCoalitionByName
1.2.4 - playerUnit2Group cross index
--]]--
cfxMX.groupNamesByID = {}
cfxMX.groupIDbyName = {}
@ -40,6 +40,7 @@ cfxMX.allStaticByName = {}
cfxMX.playerGroupByName = {} -- returns data only if a player is in group
cfxMX.playerUnitByName = {} -- returns data only if this is a player unit
cfxMX.playerUnit2Group = {} -- returns a group data for player units.
function cfxMX.getGroupFromDCSbyName(aName, fetchOriginal)
if not fetchOriginal then fetchOriginal = false end
@ -240,6 +241,7 @@ function cfxMX.createCrossReferences()
-- player unit
cfxMX.playerUnitByName[unit_data.name] = unit_data
cfxMX.playerGroupByName[aName] = group_data -- inefficient, but works
cfxMX.playerUnit2Group[unit_data.name] = group_data
end -- if unit skill client
end -- if has skill
end -- for all units

View File

@ -1,5 +1,5 @@
cfxOwnedZones = {}
cfxOwnedZones.version = "1.2.1"
cfxOwnedZones.version = "1.2.2"
cfxOwnedZones.verbose = false
cfxOwnedZones.announcer = true
cfxOwnedZones.name = "cfxOwnedZones"
@ -46,6 +46,8 @@ cfxOwnedZones.name = "cfxOwnedZones"
- conq+1 --> conquered!
- no cfxGroundTroop bug (no delay)
1.2.1 - fix in load to correctly re-establish all attackers for subsequent save
1.2.2 - redCap! and blueCap!
--]]--
cfxOwnedZones.requiredLibs = {
@ -218,11 +220,20 @@ function cfxOwnedZones.addOwnedZone(aZone)
local paused = cfxZones.getBoolFromZoneProperty(aZone, "paused", false)
aZone.paused = paused
aZone.conqueredFlag = cfxZones.getStringFromZoneProperty(aZone, "conquered!", "*<cfxnone>")
if cfxZones.hasProperty(aZone, "conq+1") then
if cfxZones.hasProperty(aZone, "conquered!") then
aZone.conqueredFlag = cfxZones.getStringFromZoneProperty(aZone, "conquered!", "*<cfxnone>")
elseif cfxZones.hasProperty(aZone, "conq+1") then
aZone.conqueredFlag = cfxZones.getStringFromZoneProperty(aZone, "conq+1", "*<cfxnone>")
end
if cfxZones.hasProperty(aZone, "redCap!") then
aZone.redCap = cfxZones.getStringFromZoneProperty(aZone, "redCap!", "none")
end
if cfxZones.hasProperty(aZone, "blueCap!") then
aZone.blueCap = cfxZones.getStringFromZoneProperty(aZone, "blueCap!", "none")
end
aZone.unbeatable = cfxZones.getBoolFromZoneProperty(aZone, "unbeatable", false)
aZone.untargetable = cfxZones.getBoolFromZoneProperty(aZone, "untargetable", false)
aZone.hidden = cfxZones.getBoolFromZoneProperty(aZone, "hidden", false)
@ -517,12 +528,19 @@ function cfxOwnedZones.zoneConquered(aZone, theSide, formerOwner) -- 0 = neutral
trigger.action.outSoundForCoalition(1, "Death BRASS.wav")
end
end
-- increase conq flag
-- if aZone.conqueredFlag then
-- local lastVal = trigger.misc.getUserFlag(aZone.conqueredFlag)
-- trigger.action.setUserFlag(aZone.conqueredFlag, lastVal + 1)
cfxZones.pollFlag(aZone.conqueredFlag, "inc", aZone)
-- end
if aZone.conqueredFlag then
cfxZones.pollFlag(aZone.conqueredFlag, "inc", aZone)
end
if theSide == 1 and aZone.redCap then
cfxZones.pollFlag(aZone.redCap, "inc", aZone)
end
if theSide == 2 and aZone.blueCap then
cfxZones.pollFlag(aZone.blueCap, "inc", aZone)
end
-- invoke callbacks now
cfxOwnedZones.invokeConqueredCallbacks(aZone, theSide, formerOwner)

View File

@ -1,5 +1,5 @@
cfxPlayerScore = {}
cfxPlayerScore.version = "1.4.0"
cfxPlayerScore.version = "1.5.1"
cfxPlayerScore.badSound = "Death BRASS.wav"
cfxPlayerScore.scoreSound = "Quest Snare 3.wav"
cfxPlayerScore.announcer = true
@ -27,9 +27,13 @@ cfxPlayerScore.announcer = true
- removed dependency to cfxPlayer
1.4.0 - persistence support
- better unit-->static switch support for generic type kill
1.5.0 - added feats to score
- feats API
- logFeatForPlayer(playerName, theFeat, coa)
1.5.1 - init feats before reading
--]]--
cfxPlayerScore.requiredLibs = {
"dcsCommon", -- this is doing score keeping
"cfxZones", -- zones for config
@ -148,8 +152,10 @@ function cfxPlayerScore.getPlayerScore(playerName)
thePlayerScore = {}
thePlayerScore.name = playerName
thePlayerScore.score = 0 -- score
thePlayerScore.killTypes = {} -- the type strings killed
thePlayerScore.killTypes = {} -- the type strings killed, dict <typename> <numkilla>
thePlayerScore.totalKills = 0 -- number of kills total
thePlayerScore.featTypes = {} -- dict <featname> <number> of other things player did
thePlayerScore.totalFeats = 0
end
return thePlayerScore
end
@ -184,12 +190,51 @@ function cfxPlayerScore.logKillForPlayer(playerName, theUnit)
cfxPlayerScore.setPlayerScore(playerName, thePlayerScore)
end
function cfxPlayerScore.logFeatForPlayer(playerName, theFeat, coa)
if not theFeat then return end
if not playerName then return end
-- access player's record. will alloc if new by itself
local thePlayerScore = cfxPlayerScore.getPlayerScore(playerName)
if not thePlayerScore.featTypes then thePlayerScore.featTypes = {} end
local featCount = thePlayerScore.featTypes[theFeat]
if featCount == nil then
featCount = 0
end
featCount = featCount + 1
thePlayerScore.totalFeats = thePlayerScore.totalFeats + 1
thePlayerScore.featTypes[theFeat] = featCount
if coa then
trigger.action.outTextForCoalition(coa, playerName .. " achieved " .. theFeat, 30)
trigger.action.outSoundForCoalition(coa, cfxPlayerScore.scoreSound)
end
cfxPlayerScore.setPlayerScore(playerName, thePlayerScore)
end
function cfxPlayerScore.playerScore2text(thePlayerScore)
local desc = thePlayerScore.name .. " - score: ".. thePlayerScore.score .. " - kills: " .. thePlayerScore.totalKills .. "\n"
-- now go through all killSide
if dcsCommon.getSizeOfTable(thePlayerScore.killTypes) < 1 then
desc = desc .. " - NONE -\n"
end
for theType, quantity in pairs(thePlayerScore.killTypes) do
desc = desc .. " - " .. theType .. ": " .. quantity .. "\n"
end
-- now enumerate all feats
if not thePlayerScore.featTypes then thePlayerScore.featTypes = {} end
desc = desc .. "\nOther Accomplishments:\n"
if dcsCommon.getSizeOfTable(thePlayerScore.featTypes) < 1 then
desc = desc .. " - NONE -\n"
end
for theFeat, quantity in pairs(thePlayerScore.featTypes) do
desc = desc .. " - " .. theFeat
if quantity > 1 then
desc = desc .. " (x" .. quantity .. ")"
end
desc = desc .. "\n"
end
return desc
end

View File

@ -1,5 +1,5 @@
cfxReconMode = {}
cfxReconMode.version = "2.1.2"
cfxReconMode.version = "2.1.3"
cfxReconMode.verbose = false -- set to true for debug info
cfxReconMode.reconSound = "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav" -- to be played when somethiong discovered
@ -18,6 +18,7 @@ cfxReconMode.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
}
cfxReconMode.name = "cfxReconMode" -- to be compatible with test flags
--[[--
VERSION HISTORY
@ -82,6 +83,7 @@ VERSION HISTORY
2.1.2 - imperialUnits for elevation
- <ele> wildcard in message format
- fix for mgrs bug in message (zone coords, not unit)
2.1.3 - added cfxReconMode.name to allow direct acces with test zone flag
cfxReconMode is a script that allows units to perform reconnaissance
missions and, after detecting units, marks them on the map with

View File

@ -1,5 +1,5 @@
cfxZones = {}
cfxZones.version = "2.8.5"
cfxZones.version = "2.8.7"
-- cf/x zone management module
-- reads dcs zones and makes them accessible and mutable
@ -92,6 +92,9 @@ cfxZones.version = "2.8.5"
- 2.8.5 - createGroundUnitsInZoneForCoalition() now always passes back a copy of the group data
- data also contains cty = country and cat = category for easy spawn
- getFlagValue additional zone name guards
- 2.8.6 - fix in getFlagValue for missing delay
- 2.8.7 - update isPointInsideZone(thePoint, theZone, radiusIncrease) - new radiusIncrease
- isPointInsideZone() returns delta as well
--]]--
cfxZones.verbose = false
@ -507,17 +510,19 @@ function cfxZones.isPointInsidePoly(thePoint, poly)
return true
end;
function cfxZones.isPointInsideZone(thePoint, theZone)
function cfxZones.isPointInsideZone(thePoint, theZone, radiusIncrease)
-- radiusIncrease only works for circle zones
if not radiusIncrease then radiusIncrease = 0 end
local p = {x=thePoint.x, y = 0, z = thePoint.z} -- zones have no altitude
if (theZone.isCircle) then
local zp = cfxZones.getPoint(theZone)
local d = dcsCommon.dist(p, theZone.point)
return d < theZone.radius
return d < theZone.radius + radiusIncrease, d
end
if (theZone.isPoly) then
--trigger.action.outText("zne: isPointInside: " .. theZone.name .. " is Polyzone!", 30)
return (cfxZones.isPointInsidePoly(p, theZone.poly))
return (cfxZones.isPointInsidePoly(p, theZone.poly)), 0 -- always returns delta 0
end
trigger.action.outText("isPointInsideZone: Unknown zone type for " .. outerZone.name, 10)
@ -1324,7 +1329,7 @@ end
function cfxZones.getFlagValue(theFlag, theZone)
local zoneName = "<dummy>"
if not theZone or not theZone.name then
trigger.action.outText("+++Zne: no zone or zone name on getFlagValue")
trigger.action.outText("+++Zne: no zone or zone name on getFlagValue", 30)
else
zoneName = theZone.name -- for flag wildcards
end

View File

@ -1,5 +1,5 @@
csarManager = {}
csarManager.version = "2.1.3"
csarManager.version = "2.2.0"
csarManager.verbose = false
csarManager.ups = 1
@ -41,6 +41,14 @@ csarManager.ups = 1
- 2.1.3 - theMassObject now local
- winch pickup now also adds weight so they can be returned
- made some improvements to performance by making vars local
- 2.2.0 - interface for autoCSAR
createDownedPilot() - added existingUnit option
createCSARMissionData() - added existinUnit option
- when no config zone, runs through empty zone
- actionSound
- integration with playerScore
- score global and per-mission
- isCSARTarget API
--]]--
@ -53,79 +61,13 @@ csarManager.requiredLibs = {
"cargoSuper",
-- "cfxCommander", -- needed if you want to hand-create CSAR missions
}
-- *** DOES NOT EXTEND ZONES *** BUT USES OWN STRUCT
-- *** extends zones for csarMission zones though
--[[--
CSAR MANAGER
============
This module can create and manage CSAR missions, i.e.
create a unit on the ground, mark it on the map, handle
if the unit is killed, create enemies in the vicinity
It will install a menu in any troop helicopter as
determined by dcsCommon.isTroopCarrier() with the
option to list available csar mission. for each created mission
it will give range and frequency for ADF
When a helicopter is in range, it will set smoke to better
visually identify the location.
When the helicopter lands close enough to a downed pilot,
the pilot is picket up automatically. Their weight is added
to the unit, so it may overload!
When the helicopter than lands in a CSARBASE Zone, the mission is
a success and a success callback is invoked automatically for
all picked up groups. All zones that have the CSARBASE property are
CSAR Bases, but their coalition must be either neutral or match the
one of the unit that landed
On start, it scans all zones for a CSAR property, and creates
a CSAR mission with data taken from the properties in the
zone so you can easily create CSAR missions in ME
WARNING: ASSUMES SINGLE UNIT PLAYER GROUPS
==========================================
Main Interface
- createCSARMission(location, side, numCrew, mark, clearing, timeout)
creates a csar mission that can be tracked.
location is the position on the map
side is the side the unit is on (neutal is for any side)
numCrew the number of people (1-4)
mark true if marked on map
clearing will create a clearing
timeout - time in seconds until pilots die. timer stops on pickup
RETURNS true, "ok" -- false, "fail reason" (string)
- createCSARAdversaries(location, side, numEnemies, radius, maxRadius)
creates some random infantery randomized on a circle around the location
location - center, usually where the downed pilot is
side - side of the enemy red/blue
numEnemies - number of infantry
radius[, maxRadius] distance of the enemy troops
- in ME, create at least one zone with a property named "CSARBASE" for
each side that supports csar missions. This is where the players
can drop off pilots that they rescued. If you have no CSARBASE zone
defined, you'll receive a warning for that side when you attempt a
rescue
- in ME you can place zones with a CSAR attribute that will generate
a scar mission. Further attributes are "coalition" (red/blue), "name" (any name you like) and "freq" (for elt ADR, leave empty for random)
NOTE:
CSARBASE is compatible with the FARP Attribute of
FARP Zones
-- integrates automatically with playerScore if installed
--]]--
--
-- OPTIONS
--
csarManager.useSmoke = false -- smoke is a performance killer, so you can turn it off
csarManager.useSmoke = true -- smoke is a performance killer, so you can turn it off
csarManager.smokeColor = 4 -- when using smoke
@ -158,39 +100,49 @@ csarManager.csarCompleteCB = {}
--
-- CREATING A CSAR
--
function csarManager.createDownedPilot(theMission)
function csarManager.createDownedPilot(theMission, existingUnit)
if not cfxCommander then
trigger.action.outText("+++CSAR: can't create mission, module cfxCommander is missing.", 30)
return
end
local aLocation = {}
local aHeading = 0 -- in rads
local newTargetZone = theMission.zone
aLocation, aHeading = dcsCommon.randomPointOnPerimeter(newTargetZone.radius / 2 + 3, newTargetZone.point.x, newTargetZone.point.z)
if not existingUnit then
local aLocation = {}
local aHeading = 0 -- in rads
local newTargetZone = theMission.zone
if newTargetZone.radius > 1 then
aLocation, aHeading = dcsCommon.randomPointOnPerimeter(newTargetZone.radius / 2 + 3, newTargetZone.point.x, newTargetZone.point.z)
else
aLocation.x = newTargetZone.point.x
aLocation.z = newTargetZone.point.z
aHeading = math.random(360)/360 * 2 * 3.1415
end
local theBoyGroup = dcsCommon.createSingleUnitGroup(theMission.name,
"Soldier M4 GRG", -- "Soldier M4 GRG",
aLocation.x,
aLocation.z,
-aHeading + 1.5) -- + 1.5 to turn inwards
local theBoyGroup = dcsCommon.createSingleUnitGroup(theMission.name,
"Soldier M4 GRG", -- "Soldier M4 GRG",
aLocation.x,
aLocation.z,
-aHeading + 1.5) -- + 1.5 to turn inwards
-- WARNING:
-- coalition.addGroup takes the COUNTRY of the group, and derives the
-- coalition from that. So if mission.sie is 0, we use UN, if it is 1 (red) it
-- is joint red, if 2 it is joint blue
local theSideCJTF = dcsCommon.coalition2county(theMission.side) -- get the correct county CJTF
theMission.group = coalition.addGroup(theSideCJTF,
Group.Category.GROUND,
theBoyGroup)
-- WARNING:
-- coalition.addGroup takes the COUNTRY of the group, and derives the
-- coalition from that. So if mission.sie is 0, we use UN, if it is 1 (red) it
-- is joint red, if 2 it is joint blue
local theSideCJTF = dcsCommon.coalition2county(theMission.side) -- get the correct county CJTF
theMission.group = coalition.addGroup(theSideCJTF,
Group.Category.GROUND,
theBoyGroup)
if theBoyGroup then
if theBoyGroup then
else
trigger.action.outText("+++csar: FAILED to create csar!", 30)
end
else
trigger.action.outText("+++csar: FAILED to create csar!", 30)
theMission.group = existingUnit:getGroup()
end
-- we now use commands to send radio transmissions
local ADF = 20 + math.random(90)
if theMission.freq then ADF = theMission.freq else theMission.freq = ADF end
@ -202,7 +154,7 @@ function csarManager.createDownedPilot(theMission)
cfxCommander.scheduleCommands(theCommands, 2) -- in 2 seconds, so unit has time to percolate through DCS
end
function csarManager.createCSARMissionData(point, theSide, freq, name, numCrew, timeLimit, mapMarker, inRadius)
function csarManager.createCSARMissionData(point, theSide, freq, name, numCrew, timeLimit, mapMarker, inRadius, parashootUnit) -- if parashootUnit is set, will not allocate new
-- create a type
if not timeLimit then timeLimit = -1 end
if not point then return nil end
@ -228,7 +180,7 @@ function csarManager.createCSARMissionData(point, theSide, freq, name, numCrew,
newMission.freq = freq -- if nil will make random
-- allocate units
csarManager.createDownedPilot(newMission)
csarManager.createDownedPilot(newMission, parashootUnit)
-- update counter and return
csarManager.missionID = csarManager.missionID + 1
@ -262,6 +214,13 @@ function csarManager.removeMissionForGroup(theDownedGroup)
end
csarManager.openMissions = newMissions -- this is the new batch
end
function csarManager.isCSARTarget(theGroup)
for idx, theMission in pairs(csarManager.openMissions) do
if theMission.group == theGroup then return true end
end
return false
end
--
-- UNIT CONFIG
--
@ -308,7 +267,9 @@ end
function csarManager.removeConfigForUnitNamed(aName)
if not aName then return end
if csarManager.unitConfigs[aName] then csarManager.unitConfigs[aName] = nil end
if csarManager.unitConfigs[aName] then
csarManager.unitConfigs[aName] = nil
end
end
--
@ -376,6 +337,25 @@ end
--
function csarManager.successMission(who, where, theMission)
-- who is
-- where is
-- theMission is mission table
-- playerScore integration
if cfxPlayerScore then
local theScore = theMission.score
if not theScore then theScore = csarManager.rescueScore end
local theUnit = Unit.getByName(who)
if theUnit and theUnit.getPlayerName then
local pName = theUnit:getPlayerName()
if pName then
cfxPlayerScore.updateScoreForPlayer(pName, theScore)
cfxPlayerScore.logFeatForPlayer(pName, "Evacuated " .. theMission.name)
end
end
end
trigger.action.outTextForCoalition(theMission.side,
who .. " successfully evacuated " .. theMission.name .. " to " .. where .. "!",
30)
@ -385,7 +365,7 @@ function csarManager.successMission(who, where, theMission)
csarManager.invokeCallbacks(theMission.side, true, 1, "success")
trigger.action.outSoundForCoalition(theMission.side, "Quest Snare 3.wav")
trigger.action.outSoundForCoalition(theMission.side, csarManager.actionSound) -- "Quest Snare 3.wav")
if csarManager.csarRedDelivered and theMission.side == 1 then
cfxZones.pollFlag(csarManager.csarRedDelivered, "inc", csarManager.configZone)
@ -536,7 +516,7 @@ function csarManager.heloLanded(theUnit)
theMassObject)
end
if didPickup then
trigger.action.outSoundForCoalition(mySide, "Quest Snare 3.wav")
trigger.action.outSoundForCoalition(mySide, csarManager.actionSound) -- "Quest Snare 3.wav")
end
-- reset unit's weight based on people on board
local totalMass = cargoSuper.calculateTotalMassFor(myName)
@ -756,7 +736,7 @@ function csarManager.doListCSARRequests(args)
report = report .. "\n"
trigger.action.outTextForGroup(conf.id, report, 30)
trigger.action.outSoundForGroup(conf.id, "Quest Snare 3.wav")
trigger.action.outSoundForGroup(conf.id, csarManager.actionSound) -- "Quest Snare 3.wav")
end
function csarManager.redirectStatusCarrying(args)
@ -789,7 +769,7 @@ function csarManager.doStatusCarrying(args)
report = report .. "\n"
trigger.action.outTextForGroup(conf.id, report, 30)
trigger.action.outSoundForGroup(conf.id, "Quest Snare 3.wav")
trigger.action.outSoundForGroup(conf.id, csarManager.actionSound) -- "Quest Snare 3.wav")
end
function csarManager.redirectUnloadOne(args)
@ -807,14 +787,14 @@ function csarManager.unloadOne(args)
if theUnit:inAir() then
report = "STRONGLY recommend we land first, Sir!"
trigger.action.outTextForGroup(conf.id, report, 30)
trigger.action.outSoundForGroup(conf.id, "Quest Snare 3.wav")
trigger.action.outSoundForGroup(conf.id, csarManager.actionSound) -- "Quest Snare 3.wav")
return
end
if #conf.troopsOnBoard < 1 then
report = "No evacuees on board."
trigger.action.outTextForGroup(conf.id, report, 30)
trigger.action.outSoundForGroup(conf.id, "Quest Snare 3.wav")
trigger.action.outSoundForGroup(conf.id, csarManager.actionSound) -- "Quest Snare 3.wav")
else
-- simulate a crash but for one unit
@ -829,7 +809,7 @@ function csarManager.unloadOne(args)
--TODO: remove weight for this pilot!
trigger.action.outTextForCoalition(theSide, myName .. " has aborted evacuating " .. msn.name .. ". New CSAR available.", 30)
trigger.action.outSoundForCoalition(theSide, "Quest Snare 3.wav")
trigger.action.outSoundForCoalition(theSide, csarManager.actionSound) -- "Quest Snare 3.wav")
-- recalc weight
trigger.action.setUnitInternalCargo(myName, 10 + #conf.troopsOnBoard * csarManager.pilotWeight) -- 10 kg as empty + per-unit time people
@ -933,7 +913,7 @@ function csarManager.updateCSARMissions()
else
local msg = aMission.name .. " confirmed KIA, repeat KIA. Abort CSAR."
trigger.action.outTextForCoalition(aMission.side, msg, 30)
trigger.action.outSoundForCoalition(aMission.side, "Quest Snare 3.wav")
trigger.action.outSoundForCoalition(aMission.side, csarManager.actionSound) -- "Quest Snare 3.wav")
end
end
csarManager.openMissions = newMissions -- this is the new batch
@ -981,7 +961,7 @@ function csarManager.update() -- every second
end
msg = msg .. "\n"
trigger.action.outTextForGroup(uID, msg, 30)
trigger.action.outSoundForGroup(uID, "Quest Snare 3.wav")
trigger.action.outSoundForGroup(uID, csarManager.actionSound) -- "Quest Snare 3.wav")
table.insert(csarMission.messagedUnits, uName) -- remember that we messaged them so we don't do again
end
-- also pop smoke if not popped already, or more than 5 minutes ago
@ -1046,7 +1026,7 @@ function csarManager.update() -- every second
end
trigger.action.outTextForGroup(uID, hoverMsg, 30, true)
trigger.action.outSoundForGroup(uID, "Quest Snare 3.wav")
trigger.action.outSoundForGroup(uID, csarManager.actionSound) --"Quest Snare 3.wav")
return -- we only ever rescue one
end -- hovered long enough
@ -1104,7 +1084,7 @@ end
--
-- create a CSAR Mission for a unit
--
function csarManager.createCSARforUnit(theUnit, pilotName, radius, silent)
function csarManager.createCSARforUnit(theUnit, pilotName, radius, silent, score) -- invoked with aircraft as theUnit, usually still in air
if not silent then silent = false end
if not radius then radius = 1000 end
if not pilotName then pilotName = "Eddie" end
@ -1121,7 +1101,7 @@ function csarManager.createCSARforUnit(theUnit, pilotName, radius, silent)
if surf == 2 or surf == 3 then
if not silent then
trigger.action.outTextForCoalition(coal, "Bad chute! Bad chute! ".. pilotName .. " did not survive ejection out of their " .. theUnit:getTypeName(), 30)
trigger.action.outSoundForGroup(coal, "Quest Snare 3.wav")
trigger.action.outSoundForGroup(coal, csarManager.actionSound) -- "Quest Snare 3.wav")
end
return
end
@ -1137,13 +1117,26 @@ function csarManager.createCSARforUnit(theUnit, pilotName, radius, silent)
1,
nil,
nil)
theMission.score = score
csarManager.addMission(theMission)
if not silent then
trigger.action.outTextForCoalition(coal, "MAYDAY MAYDAY MAYDAY! ".. pilotName .. " in " .. theUnit:getTypeName() .. " ejected, report good chute. Prepare CSAR!", 30)
trigger.action.outSoundForGroup(coal, "Quest Snare 3.wav")
trigger.action.outSoundForGroup(coal, csarManager.actionSound) -- "Quest Snare 3.wav")
end
end
function csarManager.createCSARForParachutist(theUnit, name) -- invoked with parachute guy on ground as theUnit
local coa = theUnit:getCoalition()
local pos = theUnit:getPoint()
-- unit DOES NOT HAVE GROUP!!!
-- create a CSAR mission now
local theMission = csarManager.createCSARMissionData(pos, coa, nil, name, nil, nil, nil, 0.1, nil)
csarManager.addMission(theMission)
-- if not silent then
trigger.action.outTextForCoalition(coa, "MAYDAY MAYDAY MAYDAY! ".. name .. " requesting extraction after eject!", 30)
trigger.action.outSoundForGroup(coa, csarManager.actionSound) -- "Quest Snare 3.wav")
-- end
end
--
-- csar (mission) zones
@ -1199,6 +1192,9 @@ function csarManager.readCSARZone(theZone)
theZone.lastCSARVal = cfxZones.getFlagValue(theZone.startCSAR, theZone)
end
if cfxZones.hasProperty(theZone, "score") then
theZone.score = cfxZones.getNumberFromZoneProperty(theZone, "score", 100)
end
if (not deferred) then
local theMission = csarManager.createCSARMissionData(
@ -1240,9 +1236,6 @@ end
-- Init & Start
--
function csarManager.invokeCallbacks(theCoalition, success, numRescued, notes)
-- invoke anyone who wants to know that a group
-- of people was rescued.
@ -1261,7 +1254,7 @@ function csarManager.readConfigZone()
if csarManager.verbose then
trigger.action.outText("+++csar: NO config zone!", 30)
end
return
theZone = cfxZones.createSimpleZone("csarManagerConfig")
end
csarManager.configZone = theZone -- save for flag banging compatibility
@ -1284,7 +1277,7 @@ function csarManager.readConfigZone()
if cfxZones.hasProperty(theZone, "csarDelivered!") then
csarManager.csarDelivered = cfxZones.getStringFromZoneProperty(theZone, "csarDelivered!", "*<none>")
trigger.action.outText("+++csar: will bang csarDelivered: <" .. csarManager.csarDelivered .. ">", 30)
--trigger.action.outText("+++csar: will bang csarDelivered: <" .. csarManager.csarDelivered .. ">", 30)
end
csarManager.rescueRadius = cfxZones.getNumberFromZoneProperty(theZone, "rescueRadius", 70) --70 -- must land within 50m to rescue
@ -1295,6 +1288,9 @@ function csarManager.readConfigZone()
csarManager.beaconSound = cfxZones.getStringFromZoneProperty(theZone, "beaconSound", "Radio_beacon_of_distress_on_121.ogg") --"Radio_beacon_of_distress_on_121,5_MHz.ogg"
csarManager.pilotWeight = cfxZones.getNumberFromZoneProperty(theZone, "pilotWeight", 120) -- 120
csarManager.rescueScore = cfxZones.getNumberFromZoneProperty(theZone, "rescueScore", 100)
csarManager.actionSound = cfxZones.getStringFromZoneProperty(theZone, "actionSound", "Quest Snare 3.wav")
csarManager.vectoring = cfxZones.getBoolFromZoneProperty(theZone, "vectoring", true)
if csarManager.verbose then
@ -1373,4 +1369,5 @@ end
- when unloading one by menu, update weight!!!
-- allow neutral pick-up
--]]--

View File

@ -1,5 +1,5 @@
dcsCommon = {}
dcsCommon.version = "2.7.4"
dcsCommon.version = "2.7.5"
--[[-- VERSION HISTORY
2.2.6 - compassPositionOfARelativeToB
- clockPositionOfARelativeToB
@ -101,6 +101,9 @@ dcsCommon.version = "2.7.4"
2.7.3 - new string2Array()
- additional guard for isPlayerUnit
2.7.4 - new array2string()
2.7.5 - new bitAND32()
- new LSR()
- new num2bin()
--]]--
@ -2720,6 +2723,62 @@ function dcsCommon.getSceneryObjectInZoneByName(theName, theZone) -- DCS ZONE!!!
return nil
end
--
-- bitwise operators
--
function dcsCommon.bitAND32(a, b)
if not a then a = 0 end
if not b then b = 0 end
local z = 0
local e = 1
for i = 0, 31 do
local a1 = a % 2 -- 0 or 1
local b1 = b % 2 -- 0 or 1
if a1 == 1 and b1 == 1 then
a = a - 1 -- remove bit
b = b - 1
z = z + e
else
if a1 == 1 then a = a - 1 end -- remove bit
if b1 == 1 then b = b - 1 end
end
a = a / 2 -- shift right
b = b / 2
e = e * 2 -- raise e by 1
end
return z
end
function dcsCommon.num2bin(a)
if not a then a = 0 end
local z = ""
for i = 0, 31 do
local a1 = a % 2 -- 0 or 1
if a1 == 1 then
a = a - 1 -- remove bit
z = "1"..z
else
z = "0"..z
end
a = a / 2 -- shift right
end
return z
end
function dcsCommon.LSR(a, num)
if not a then a = 0 end
if not num then num = 16 end
for i = 1, num do
local a1 = a % 2 -- 0 or 1
if a1 == 1 then
a = a - 1 -- remove bit
end
a = a / 2 -- shift right
end
return a
end
--
--
-- INIT

View File

@ -1,5 +1,5 @@
fireFX = {}
fireFX.version = "1.0.0"
fireFX.version = "1.1.0"
fireFX.verbose = false
fireFX.ups = 1
fireFX.requiredLibs = {
@ -8,6 +8,13 @@ fireFX.requiredLibs = {
}
fireFX.fx = {}
--[[--
Version History
1.0.0 - Initial version
1.1.0 - persistence
--]]--
function fireFX.addFX(theZone)
table.insert(fireFX.fx, theZone)
end
@ -127,13 +134,62 @@ function fireFX.update()
end
--
-- LOAD / SAVE
--
function fireFX.saveData()
local theData = {}
local allFX = {}
for idx, theFX in pairs(fireFX.fx) do
local theName = theFX.name
local FXData = {}
FXData.burning = theFX.burning
allFX[theName] = FXData
end
theData.allFX = allFX
return theData
end
function fireFX.loadData()
if not persistence then return end
local theData = persistence.getSavedDataForModule("fireFX")
if not theData then
if fireFX.verbose then
trigger.action.outText("+++ffx persistence: no save data received, skipping.", 30)
end
return
end
local allFX = theData.allFX
if not allFX then
if fireFX.verbose then
trigger.action.outText("+++ffx persistence: no fire FX data, skipping", 30)
end
return
end
for theName, theData in pairs(allFX) do
local theFX = fireFX.getFXByName(theName)
if theFX then
if theData.burning then
fireFX.startTheFire(theFX)
end
theFX.inited = true -- ensure no onStart overwrite
else
trigger.action.outText("+++ffx: persistence: cannot synch fire FX <" .. theName .. ">, skipping", 40)
end
end
end
--
-- Config & Start
--
function fireFX.readConfigZone()
local theZone = cfxZones.getZoneByName("fireFXConfig")
if not theZone then
theZone = cfxZones.createSimpleZone(LZConfig)
theZone = cfxZones.createSimpleZone("fireFX")
if fireFX.verbose then
trigger.action.outText("+++ffx: NO config zone!", 30)
end
@ -165,9 +221,20 @@ function fireFX.start()
fireFX.addFX(aZone) -- add to list
end
-- load any saved data
if persistence then
-- sign up for persistence
callbacks = {}
callbacks.persistData = fireFX.saveData
persistence.registerModule("fireFX", callbacks)
-- now load my data
fireFX.loadData()
end
-- handle onStart
for idx, theZone in pairs(fireFX.fx) do
if theZone.fxOnStart then
if (not theZone.inited) and (theZone.fxOnStart) then
-- only if we did not init them with loaded data
fireFX.startTheFire(theZone)
end
end

View File

@ -52,6 +52,8 @@ limitedAirframes.requiredLibs = {
- 1.4.1 - removed dependency to cfxPlayer
- 1.5.0 - persistence support
- 1.5.1 - new "announcer" attribute
- 1.5.2 - integration with autoCSAR: prevent limitedAF from creating csar
when autoCSAR is active
--]]--
@ -665,6 +667,9 @@ end
function limitedAirframes.createCSAR(theUnit)
-- override if autoCSAR is installed
if autoCSAR then return end
-- only do this if we have installed CSAR Manager
if csarManager and csarManager.createCSARforUnit then
csarManager.createCSARforUnit(theUnit,

View File

@ -1,5 +1,5 @@
messenger = {}
messenger.version = "2.0.0"
messenger.version = "2.0.1"
messenger.verbose = false
messenger.requiredLibs = {
"dcsCommon", -- always
@ -42,6 +42,7 @@ messenger.messengers = {}
- messageError
- unit
- group
2.0.1 - config optimization
--]]--
@ -480,7 +481,7 @@ function messenger.readConfigZone()
if messenger.verbose then
trigger.action.outText("+++msgr: NO config zone!", 30)
end
return
theZone = cfxZones.createSimpleZone("messengerConfig")
end
messenger.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)

View File

@ -1,5 +1,5 @@
pulseFlags = {}
pulseFlags.version = "1.3.0"
pulseFlags.version = "1.3.1"
pulseFlags.verbose = false
pulseFlags.requiredLibs = {
"dcsCommon", -- always
@ -36,6 +36,7 @@ pulseFlags.requiredLibs = {
- 1.2.3 deprecated paused/pulsePaused
returned onStart, defaulting to true
- 1.3.0 persistence
- 1.3.1 typos corrected
--]]--
@ -326,7 +327,7 @@ function pulseFlags.loadData()
local theData = persistence.getSavedDataForModule("pulseFlags")
if not theData then
if pulseFlags.verbose then
trigger.action.outText("+++pulF Persistence: no save date received, skipping.", 30)
trigger.action.outText("+++pulF Persistence: no save data received, skipping.", 30)
end
return
end

View File

@ -1,5 +1,5 @@
radioTrigger = {}
radioTrigger.version = "1.0.0"
radioTrigger.version = "1.0.1"
radioTrigger.verbose = false
radioTrigger.ups = 1
radioTrigger.requiredLibs = {
@ -11,6 +11,7 @@ radioTrigger.radioTriggers = {}
--[[--
Version History
1.0.0 - initial version
1.0.1 - guarding rtOut to not bang on flags when not set
--]]--
@ -50,8 +51,9 @@ function radioTrigger.createRadioTriggerWithZone(theZone)
end
-- out flag
theZone.rtOutFlag = cfxZones.getStringFromZoneProperty(theZone, "out!", "*<none>")
if cfxZones.hasProperty(theZone, "rtOut!") then
if cfxZones.hasProperty(theZone, "out!") then
theZone.rtOutFlag = cfxZones.getStringFromZoneProperty(theZone, "out!", "*<none>")
elseif cfxZones.hasProperty(theZone, "rtOut!") then
theZone.rtOutFlag = cfxZones.getStringFromZoneProperty(theZone, "rtOut!", "*<none>")
end
@ -66,8 +68,9 @@ end
--
function radioTrigger.process(theZone)
-- we are triggered, simply poll the out flag
cfxZones.pollFlag(theZone.rtOutFlag, theZone.rtMethod, theZone)
if theZone.rtOutFlag then
cfxZones.pollFlag(theZone.rtOutFlag, theZone.rtMethod, theZone)
end
end
--

648
modules/williePete.lua Normal file
View File

@ -0,0 +1,648 @@
williePete = {}
williePete.version = "1.0.0"
williePete.ups = 10 -- we update at 10 fps, so accuracy of a
-- mach two missile is within 33 meters, with interpolation even less
williePete.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
"cfxMX",
}
williePete.willies = {}
williePete.wpZones = {}
williePete.playerGUIs = {} -- used for unit guis
williePete.smokeWeapons = {"HYDRA_70_M274","HYDRA_70_MK61","HYDRA_70_MK1","HYDRA_70_WTU1B","BDU_45B","BDU_33","BDU_45","BDU_45LGB","BDU_50HD","BDU_50LD","BDU_50LGB","C_8CM"}
function williePete.addWillie(theWillie)
table.insert(williePete.willies, theWillie)
end
function williePete.addWPZone(theZone)
table.insert(williePete.wpZones, theZone)
end
function williePete.closestCheckInTgtZoneForCoa(point, coa)
-- returns the closest zone that point is inside.
-- first tries directly, then, if none found,
-- with added check-in radius
local lPoint = {x=point.x, y=0, z=point.z}
local currDelta = math.huge
local closestZone = nil
-- first, we try if outright inside
for zName, zData in pairs(williePete.wpZones) do
if zData.coalition == coa then
-- local zPoint = cfxZones.getPoint(zData)
local inZone, delta = cfxZones.isPointInsideZone(lPoint, zData)
if inZone and (delta < currDelta) then
currDelta = delta
closestZone = zData
end
end
end
-- if we got one, we return that zone
if closestZone then return closestZone, currDelta end
for zName, zData in pairs(williePete.wpZones) do
if zData.coalition == coa then
-- local zPoint = cfxZones.getPoint(zData)
local inZone, delta = cfxZones.isPointInsideZone(lPoint, zData, zData.checkInRange)
if inZone and (delta < currDelta) then
currDelta = delta
closestZone = zData
end
end
end
if closestZone then return closestZone, currDelta end
return nil, -1
end
function williePete.getClosestZoneForCoa(point, coa)
local lPoint = {x=point.x, y=0, z=point.z}
local currDelta = math.huge
local closestZone = nil
for zName, zData in pairs(williePete.wpZones) do
if zData.coalition == coa then
local zPoint = cfxZones.getPoint(zData)
local delta = dcsCommon.dist(lPoint, zPoint) -- emulate flag compare
if (delta < currDelta) then
currDelta = delta
closestZone = zData
end
else
-- trigger.outText("Zone <" .. zData.name .. ">, coa <" .. zData.coalition .. "> does not match <" .. coa .. ">", 30)
end
end
return closestZone, currDelta
end
function williePete.createWPZone(aZone)
aZone.coalition = cfxZones.getCoalitionFromZoneProperty(aZone, "wpTarget", 0) -- side that marks it on map, and who fires arty
aZone.shellStrength = cfxZones.getNumberFromZoneProperty(aZone, "shellStrength", 500) -- power of shells (strength)
aZone.shellNum = cfxZones.getNumberFromZoneProperty(aZone, "shellNum", 17) -- number of shells in bombardment
aZone.transitionTime = cfxZones.getNumberFromZoneProperty(aZone, "transitionTime", 20) -- average time of travel for projectiles
aZone.coolDown = cfxZones.getNumberFromZoneProperty(aZone, "coolDown", 180) -- cooldown after arty fire, used to set readyTime
aZone.baseAccuracy = cfxZones.getNumberFromZoneProperty(aZone, "baseAccuracy", 50)
aZone.readyTime = 0 -- if readyTime > now we are not ready
aZone.trackingPlayer = nil -- name player's unit who is being tracked for wp. may not be neccessary
aZone.checkedIn = {} -- dict of all planes currently checked in
aZone.trackingEndsTime = 0 -- if now > trackingends, we remove player and send a message
aZone.wpTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "wpTriggerMethod", "change")
aZone.FACTypes = cfxZones.getStringFromZoneProperty(aZone, "facTypes", "all")
aZone.checkInRange = cfxZones.getNumberFromZoneProperty(aZone, "checkInRange", williePete.checkInRange) -- default to my default
aZone.ackSound = cfxZones.getStringFromZoneProperty(aZone, "ackSound", williePete.ackSound)
aZone.guiSound = cfxZones.getStringFromZoneProperty(aZone, "guiSound", williePete.guiSound)
if cfxZones.hasProperty(aZone, "triggerMethod") then
aZone.wpTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "triggerMethod", "change")
end
if aZone.verbose then
trigger.action.outText("Added wpTarget zone <" .. aZone.name .. ">", 30)
end
end
--
-- player management
--
function williePete.startPlayerGUI()
-- scan all mx players
-- note: currently assumes single-player groups
-- in preparation of single-player 'commandForUnit'
for uName, uData in pairs(cfxMX.playerUnitByName) do
local unitInfo = {}
-- try and access each unit even if we know that the
-- unit does not exist in-game right now
local gData = cfxMX.playerUnit2Group[uName]
local gName = gData.name
local coa = cfxMX.groupCoalitionByName[gName]
local theType = uData.type
if williePete.verbose then
trigger.action.outText("unit <" .. uName .. ">: type <" .. theType .. "> coa <" .. coa .. ">, group <" .. gName .. ">", 30)
end
unitInfo.name = uName -- needed for reverse-lookup
unitInfo.coa = coa
unitInfo.gID = gData.groupId
unitInfo.uID = uData.unitId
unitInfo.theType = theType
unitInfo.cat = cfxMX.groupTypeByName[gName]
-- now check type against willie pete config for allowable types
local pass = false
for idx, aType in pairs(williePete.facTypes) do
if aType == "ALL" then pass = true end
if aType == "ANY" then pass = true end
if aType == theType then pass = true end
if dcsCommon.stringStartsWith(aType, "HEL") and unitInfo.cat == "helicopter" then pass = true end
if dcsCommon.stringStartsWith(aType, "PLAN") and unitInfo.cat == "plane" then pass = true end
end
if pass then -- we install a menu for this group
-- we may not want check in stuff, but it could be cool
unitInfo.root = missionCommands.addSubMenuForGroup(unitInfo.gID, "FAC")
unitInfo.checkIn = missionCommands.addCommandForGroup(unitInfo.gID, "Check In", unitInfo.root, williePete.redirectCheckIn, unitInfo)
end
-- store it
williePete.playerGUIs[uName] = unitInfo
end
end
--
-- BOOM
--
--
-- BOOM command
--
function williePete.doBoom(args)
trigger.action.explosion(args.point, args.strength)
data = {}
data.point = args.point
data.strength = args.strength
-- cfxArtilleryZones.invokeCallbacksFor('impact', args.zone, data)
end
function williePete.doParametricFireAt(aPoint, accuracy, shellNum, shellBaseStrength, shellVariance, transitionTime)
if williePete.verbose then
trigger.action.outText("fire with accuracy <" .. accuracy .. "> shellNum <" .. shellNum .. "> baseStren <" .. shellBaseStrength .. "> variance <" .. shellVariance .. ">, ttime <" .. transitionTime .. ">", 30)
end
-- accuracy is meters from center
if not aPoint then return end
if not accuracy then accuracy = 100 end
if not shellNum then shellNum = 17 end
if not shellBaseStrength then shellBaseStrength = 500 end
if not shellVariance then shellVariance = 0.2 end
if not transitionTime then transitionTime = 17 end
local alt = land.getHeight({x=aPoint.x, y=aPoint.z})
local center = {x=aPoint.x, y=alt, z=aPoint.z}
for i=1, shellNum do
local thePoint = dcsCommon.randomPointInCircle(accuracy, 0, center.x, center.z)
thePoint.y = land.getHeight({x=thePoint.x, y=thePoint.z})
local boomArgs = {}
local strVar = shellBaseStrength * shellVariance
strVar = strVar * (2 * dcsCommon.randomPercent() - 1.0) -- go from -1 to 1
boomArgs.strength = shellBaseStrength + strVar
thePoint.y = land.getHeight({x = thePoint.x, y = thePoint.z}) + 1 -- elevate to ground height + 1
boomArgs.point = thePoint
boomArgs.zone = aZone
local timeVar = 5 * (2 * dcsCommon.randomPercent() - 1.0) -- +/- 1.5 seconds
if timeVar < 0 then timeVar = -timeVar end
timer.scheduleFunction(williePete.doBoom, boomArgs, timer.getTime() + transitionTime + timeVar)
end
end
--
-- COMMS
--
function williePete.redirectCheckIn(unitInfo)
timer.scheduleFunction(williePete.doCheckIn, unitInfo, timer.getTime() + 0.1)
end
function williePete.doCheckIn(unitInfo)
--trigger.action.outText("check-in received", 30)
local theUnit = Unit.getByName(unitInfo.name)
local p = theUnit:getPoint()
local theZone, dist = williePete.closestCheckInTgtZoneForCoa(p, unitInfo.coa)
if not theZone then
theZone, dist = williePete.getClosestZoneForCoa(p, unitInfo.coa)
if not theZone then
trigger.action.outTextForGroup(unitInfo.gID, "No target zone in range.", 30)
trigger.action.outSoundForGroup(unitInfo.gID, williePete.guiSound)
return
end
trigger.action.outTextForGroup(unitInfo.gID, "Too far from target zone, closest target zone is " .. theZone.name, 30)
trigger.action.outSoundForGroup(unitInfo.gID, theZone.guiSound)
return
end
-- we are now checked in to zone -- unless we are already checked in
if theZone.checkedIn[unitInfo.name] then
trigger.action.outTextForGroup(unitInfo.gID, unitInfo.name .. ", " .. theZone.name .. ", we heard you the first time, proceed.", 30)
trigger.action.outSoundForGroup(unitInfo.gID, theZone.guiSound)
return
end
-- we now check in
theZone.checkedIn[unitInfo.name] = unitInfo
-- add the 'Target marked' menu
unitInfo.targetMarked = missionCommands.addCommandForGroup(unitInfo.gID, "Target Marked", unitInfo.root, williePete.redirectTargetMarked, unitInfo)
-- remove 'check in'
missionCommands.removeItemForGroup(unitInfo.gID, unitInfo.checkIn)
unitInfo.checkIn = nil
-- add 'check out'
unitInfo.checkOut = missionCommands.addCommandForGroup(unitInfo.gID, "Check Out", unitInfo.root, williePete.redirectCheckOut, unitInfo)
trigger.action.outTextForGroup(unitInfo.gID, "Roger " .. unitInfo.name .. ", " .. theZone.name .. " tracks you, standing by for target data.", 30)
trigger.action.outSoundForGroup(unitInfo.gID, theZone.guiSound)
end
function williePete.redirectCheckOut(unitInfo)
timer.scheduleFunction(williePete.doCheckOut, unitInfo, timer.getTime() + 0.1)
end
function williePete.doCheckOut(unitInfo)
--trigger.action.outText("check-out received", 30)
-- check out of all zones
local wasCheckedIn = false
local fromZone = ""
for idx, theZone in pairs(williePete.wpZones) do
if theZone.checkedIn[unitInfo.name] then
wasCheckedIn = true
fromZone = theZone.name
end
theZone.checkedIn[unitInfo.name] = nil
end
if not wasCheckedIn then
trigger.action.outTextForGroup(unitInfo.gID, unitInfo.name .. ", roger cecked-out. Good hunting!", 30)
trigger.action.outSoundForGroup(unitInfo.gID, williePete.guiSound)
else
trigger.action.outTextForGroup(unitInfo.gID, unitInfo.name .. "has checked out of " .. fromZone ..".", 30)
trigger.action.outSoundForGroup(unitInfo.gID, williePete.guiSound)
end
-- remove checkOut and targetMarked
missionCommands.removeItemForGroup(unitInfo.gID, unitInfo.checkOut)
unitInfo.checkOut = nil
missionCommands.removeItemForGroup(unitInfo.gID, unitInfo.targetMarked)
unitInfo.targetMarked = nil
-- add check in
unitInfo.checkIn = missionCommands.addCommandForGroup(unitInfo.gID, "Check In", unitInfo.root, williePete.redirectCheckIn, unitInfo)
end
function williePete.redirectTargetMarked(unitInfo)
timer.scheduleFunction(williePete.doTargetMarked, unitInfo, timer.getTime() + 0.1)
end
function williePete.rogerDodger(args)
local unitInfo = args[1]
local theZone = args[2]
trigger.action.outTextForCoalition(unitInfo.coa, "Roger " .. unitInfo.name .. ", good copy, firing.", 30)
trigger.action.outSoundForCoalition(unitInfo.coa, theZone.ackSound)
end
function williePete.doTargetMarked(unitInfo)
--trigger.action.outText("mark received", 30)
-- first, check if we are past the time-out
local now = timer.getTime()
-- now check if zone matches check-in
if not unitInfo.expiryTime or unitInfo.expiryTime < now then
trigger.action.outTextForGroup(unitInfo.gID, "Target mark stale or ambiguous, set fresh mark", 30)
trigger.action.outSoundForGroup(unitInfo.gID, williePete.guiSound)
return
end
-- now, check if the zone is ready to receive
if not unitInfo.wpInZone or not unitInfo.pos then
-- should not happen, but better safe than sorry
trigger.action.outTextForGroup(unitInfo.gID, "Lost sight of target location, set new mark", 30)
trigger.action.outSoundForGroup(unitInfo.gID, williePete.guiSound)
return
end
local tgtZone = unitInfo.wpInZone
-- see if we are checked into that zone
if not tgtZone.checkedIn[unitInfo.name] then
-- zones don't match
trigger.action.outTextForGroup(unitInfo.gID, "Say again " .. unitInfo.name .. ", we have crosstalk. Try and reset coms", 30)
trigger.action.outSoundForGroup(unitInfo.gID, williePete.guiSound)
return
end
-- see if zone is ready to receive
local timeRemaining = math.floor(tgtZone.readyTime - now)
if timeRemaining > 0 then
-- zone not ready
trigger.action.outTextForGroup(unitInfo.gID, "Stand by " .. unitInfo.name .. ", artillery not ready. Expect " .. timeRemaining + math.random(1, 5) .. " seconds.", 30)
trigger.action.outSoundForGroup(unitInfo.gID, tgtZone.guiSound)
return
end
-- if we get here, we are fire at mark
local alt = math.floor(land.getHeight({x = unitInfo.pos.x, y = unitInfo.pos.z}))
local grid = coord.LLtoMGRS(coord.LOtoLL(unitInfo.pos))
local mgrs = grid.UTMZone .. ' ' .. grid.MGRSDigraph .. ' ' .. grid.Easting .. ' ' .. grid.Northing
local theLoc = mgrs
trigger.action.outTextForCoalition(unitInfo.coa, tgtZone.name ..", " .. unitInfo.name .." is transmitting target location. Fire at " .. theLoc .. ", elevation " .. alt .. " meters, target marked.", 30)
trigger.action.outSoundForCoalition(unitInfo.coa, tgtZone.guiSound)
timer.scheduleFunction(williePete.rogerDodger, {unitInfo, tgtZone},timer.getTime() + math.random(2, 5))
-- collect zone's fire params & fire
local shellStrength = tgtZone.shellStrength
local shellNum = tgtZone.shellNum
local transitionTime = tgtZone.transitionTime
local accuracy = tgtZone.baseAccuracy
williePete.doParametricFireAt(unitInfo.pos, accuracy, shellNum, shellStrength, 0.2, transitionTime)
-- set zone's cooldown
tgtZone.readyTime = now + tgtZone.coolDown
-- erase player's wp mark
unitInfo.wpInZone = nil
unitInfo.pos = nil
end
-- return true if a zone is actively tracking theUnit to place
-- a wp
function williePete.zoneIsTracking(theUnit)
local uName = theUnit:getName()
for idx, theZone in pairs(williePete.wpZones) do
if theZone.checkedIn[uName] then return true end
end
return false
end
function williePete.isWP(theWeapon)
local theDesc = theWeapon:getTypeName()
for idx, wpw in pairs(williePete.smokeWeapons) do
if theDesc == wpw then return true end
end
return false
end
function williePete:onEvent(event)
if not event.initiator then
--trigger.action.outText("onEvent - " .. event.id .. ": no initiator",30)
return
end
if not event.weapon then
--trigger.action.outText("onEvent - " .. event.id .. ": no WEAPON",30)
return
end
local theUnit = event.initiator
local pType = "(AI)"
if theUnit.getPlayerName then pType = "(" .. theUnit:getName() .. ")" end
if event.id == 1 then -- S_EVENT_SHOT
-- initiator is who fired. maybe want to test if player
--trigger.action.outText(theUnit:getName() .. " " .. pType .. " fired " .. event.weapon:getTypeName() .. ".", 30)
if not williePete.isWP(event.weapon) then
--trigger.action.outText("<" .. event.weapon:getTypeName() .. "> not a smoke weapon", 30)
return
end
-- make sure that whoever fired it is being tracked by
-- a zone
if not williePete.zoneIsTracking(theUnit) then
--trigger.action.outText("<" .. event.weapon:getTypeName() .. "> fired while not being tracked by zone", 30)
return
end
-- assuming it's a willie, let's track it
local theWillie = {}
theWillie.firedBy = theUnit:getName()
theWillie.theUnit = theUnit
theWillie.weapon = event.weapon
theWillie.wt = theWillie.weapon:getTypeName()
theWillie.pos = theWillie.weapon:getPoint()
theWillie.v = theWillie.weapon:getVelocity()
williePete.addWillie(theWillie)
end
if event.id == 2 then -- hit
local what = "something"
if event.target then what = event.target:getName() end
--trigger.action.outText("Weapon " .. event.weapon:getTypeName() .. " fired by unit ".. theUnit:getName() .. " " .. pType .. " hit " .. what, 30)
-- may need to remove willie from willies
end
end
-- test if a projectile hit ground inside a wp zone
function williePete.isInside(theWillie)
local thePoint = theWillie.pos
local theUnitName = theWillie.firedBy -- may be dead already, but who cares
local theUnit = Unit.getByName(theUnitName)
if not theUnit then return false end -- unit dead
if not Unit.isExist(theUnit) then return false end -- dito
local thePlayer = williePete.playerGUIs[theUnitName]
if not thePlayer then return nil end
for idx, theZone in pairs(williePete.wpZones) do
if cfxZones.pointInZone(thePoint, theZone) then
-- we are inside. but is this the right coalition?
if thePlayer.coa == theZone.coalition then
--trigger.action.outText("Willie in " .. theZone.name, 30)
return theZone
else
--trigger.action.outText("Willie wrong coa", 30)
end
-- if we want to allow neutral zones (doens't make sense)
-- add another guard below
else
--trigger.action.outText("willie outside " .. theZone.name, 30)
end
end
return nil
end
-- update
function williePete.projectileHit(theWillie)
-- interpolate pos: half time between updates times last velocity
local vmod = dcsCommon.vMultScalar(theWillie.v, 0.5 / williePete.ups)
theWillie.pos = dcsCommon.vAdd(theWillie.pos, vmod)
--trigger.action.outText("Willie " .. theWillie.wt .. " expired at " .. dcsCommon.point2text(theWillie.pos) .. " interpolated.", 30)
-- reset last mark for player
local thePlayer = williePete.playerGUIs[theWillie.firedBy]
thePlayer.pos = nil
thePlayer.wpInZone = nil
-- check if this is within a wpZones
local theZone = williePete.isInside(theWillie)
if not theZone then
if williePete.verbose then
trigger.action.outText("+++wp: wp expired outside zone", 30)
end
return
end
-- if we receive a zone, we know that the player's
-- coalition matches the one of the zone
thePlayer.expiryTime = timer.getTime() + williePete.wpMaxTime -- set timeout in which player can give fire command
thePlayer.pos = theWillie.pos -- remember the loc
thePlayer.wpInZone = theZone -- remember the zone
-- mark point with smoke blue
--dcsCommon.markPointWithSmoke(theWillie.pos, 4)
if cfxArtilleryZones then
--cfxArtilleryZones.doParametricFireAt(theWillie.pos, 50, 10)
else
-- mark point with smoke blue
--dcsCommon.markPointWithSmoke(theWillie.pos, 4)
end
end
function williePete.updateWP()
timer.scheduleFunction(williePete.updateWP, {}, timer.getTime() + 1/williePete.ups)
local nextPete = {}
for idx, theWillie in pairs(williePete.willies) do
-- check if it still exists
if Weapon.isExist(theWillie.weapon) then
-- update loc, proceed to next round
theWillie.pos = theWillie.weapon:getPoint()
theWillie.v = theWillie.weapon:getVelocity()
table.insert(nextPete, theWillie)
else
-- weapon disappeared: it has hit something
-- but unguided rockets do not create an event for that
williePete.projectileHit(theWillie)
-- no longer propagates to next round
end
end
williePete.willies = nextPete
end
function williePete.playerUpdate()
timer.scheduleFunction(williePete.playerUpdate, {}, timer.getTime() + 2) -- check 30 times a minute
-- zone still checked in updates for zones
for idx, theZone in pairs(williePete.wpZones) do
-- make sure any unit checked in is still inside
-- the zone that they checked in, or they are checked out
--local zp = cfxZones.getPoint(theZone)
for idy, unitInfo in pairs(theZone.checkedIn) do
-- make sure unit still exists
local dropUnit = true
local theUnit = Unit.getByName(unitInfo.name)
if theUnit and Unit.isExist(theUnit) then
local up = theUnit:getPoint()
up.y = 0
local isInside, dist = cfxZones.isPointInsideZone(up, theZone, theZone.checkInRange)
if isInside then
dropUnit = false
end
end
if dropUnit then
-- remove from zone check-in
-- williePete.doCheckOut(unitInfo)
timer.scheduleFunction(williePete.doCheckOut, unitInfo, timer.getTime() + 0.1) -- to not muck up iteration
end
end
end
-- menu updates for all players
end
--
-- Config & Start
--
function williePete.readConfigZone()
local theZone = cfxZones.getZoneByName("wpConfig")
if not theZone then
if williePete.verbose then
trigger.action.outText("+++wp: NO config zone!", 30)
end
theZone = cfxZones.createSimpleZone("wpConfig")
end
local facTypes = cfxZones.getStringFromZoneProperty(theZone, "facTypes", "all")
facTypes = string.upper(facTypes)
-- make this an array
local allTypes = {}
if dcsCommon.containsString(facTypes, ",") then
allTypes = dcsCommon.splitString(facTypes, ",")
else
table.insert(allTypes, facTypes)
end
williePete.facTypes = dcsCommon.trimArray(allTypes)
-- how long a wp is active. must not be more than 5 minutes
williePete.wpMaxTime = cfxZones.getNumberFromZoneProperty(theZone, "wpMaxTime", 3 * 60)
-- default check-in range, added to target zone's range and used
-- for auto-check-out
williePete.checkInRange = cfxZones.getNumberFromZoneProperty(theZone, "checkInRange", 10000) -- 10 km outside
williePete.ackSound = cfxZones.getStringFromZoneProperty(theZone, "ackSound", "some")
williePete.guiSound = cfxZones.getStringFromZoneProperty(theZone, "guiSound", "some")
williePete.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
if williePete.verbose then
trigger.action.outText("+++msgr: read config", 30)
end
end
function williePete.start()
if not dcsCommon.libCheck("cfx williePete",
williePete.requiredLibs) then
return false
end
-- read config
williePete.readConfigZone()
-- collect all wp target zones
local attrZones = cfxZones.getZonesWithAttributeNamed("wpTarget")
for k, aZone in pairs(attrZones) do
williePete.createWPZone(aZone) -- process attribute and add to zone
williePete.addWPZone(aZone) -- remember it so we can smoke it
end
-- add event handler
world.addEventHandler(williePete)
-- initialize all players from MX
williePete.startPlayerGUI()
-- start updates
williePete.updateWP() -- for tracking wp, at ups
williePete.playerUpdate() -- for tracking players, at 1/s
trigger.action.outText("williePete v" .. williePete.version .. " loaded.", 30)
return true
end
-- let's go
if not williePete.start() then
trigger.action.outText("cf/x Willie Pete aborted: missing libraries", 30)
williePete = nil
end
--[[--
Mechanics:
- unit checks in with arty zone. if not in range of arty zone + safe dist, error 'not in range' is returned, else <artillery zone: status> is sent. <status can be ready, firing, or reloading>. Zone will advise on status change when checked in.
- if unit leaves arty zone + safe dist, <leaving zone> is displayed and <checkout> is invoked
- unit can check out any time
- when checked in, any wp hitting the ground is remembered if still inside target zone
- player then gives 'target marked'
- if artillery on cooldown, or wp not inside zone error, else fire sequence starts, and cooldown starts for entire zone
--]]--