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 = {}
LZ.version = "1.0.0" LZ.version = "1.1.0"
LZ.verbose = false LZ.verbose = false
LZ.ups = 1 LZ.ups = 1
LZ.requiredLibs = { LZ.requiredLibs = {
@ -14,6 +14,7 @@ LZ.LZs = {}
Version History Version History
1.0.0 - initial version 1.0.0 - initial version
1.1.0 - persistence
--]]-- --]]--
@ -131,13 +132,12 @@ end
-- Misc Processing -- Misc Processing
-- --
function LZ.unitIsInterestingForZone(theUnit, theZone) function LZ.unitIsInterestingForZone(theUnit, theZone)
--trigger.action.outText("enter isInterestingB4pause for <" .. theUnit:getName() .. ">", 40)
-- see if zone is interested in this unit. -- see if zone is interested in this unit.
if theZone.isPaused then if theZone.isPaused then
return false return false
end end
-- trigger.action.outText("enter isinteresting for <" .. theUnit:getName() .. ">", 40)
if theZone.lzPlayerOnly then if theZone.lzPlayerOnly then
if not dcsCommon.isPlayerUnit(theUnit) then if not dcsCommon.isPlayerUnit(theUnit) then
if theZone.verbose or LZ.verbose then if theZone.verbose or LZ.verbose then
@ -198,11 +198,7 @@ function LZ.unitIsInterestingForZone(theUnit, theZone)
else else
-- we can return true since player and coa mismatch -- we can return true since player and coa mismatch
-- have already been filtered -- have already been filtered
--[[-- -- neither type, unit, nor group
local theGroup = theUnit:getGroup()
local coa = theGroup:getCoalition()
--
--]]--
return true -- theZone.coalition == coa end return true -- theZone.coalition == coa end
end end
@ -224,18 +220,10 @@ function LZ:onEvent(event)
return return
end end
--if LZ.verbose or true then
-- trigger.action.outText("+++LZ: on event proccing", 30)
--end
local theUnit = event.initiator local theUnit = event.initiator
if not Unit.isExist(theUnit) then return end if not Unit.isExist(theUnit) then return end
local p = theUnit:getPoint() 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 for idx, aZone in pairs(LZ.LZs) do
-- see if inside the zone -- see if inside the zone
local inZone, percent, dist = cfxZones.pointInZone(p, aZone) local inZone, percent, dist = cfxZones.pointInZone(p, aZone)
@ -258,8 +246,8 @@ function LZ:onEvent(event)
end end
end -- if interesting end -- if interesting
else else
if LZ.verbose or true then if LZ.verbose or aZone.verbose then
-- trigger.action.outText("+++LZ: unit <" .. theUnit:getName() .. "> not in zone <" .. aZone.name .. ">", 30) --trigger.action.outText("+++LZ: unit <" .. theUnit:getName() .. "> not in zone <" .. aZone.name .. ">", 30)
end end
end -- if in zone end -- if in zone
@ -292,13 +280,58 @@ function LZ.update()
end 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 -- Config & Start
-- --
function LZ.readConfigZone() function LZ.readConfigZone()
local theZone = cfxZones.getZoneByName("LZConfig") local theZone = cfxZones.getZoneByName("LZConfig")
if not theZone then if not theZone then
theZone = cfxZones.createSimpleZone(LZConfig) theZone = cfxZones.createSimpleZone("LZConfig")
if LZ.verbose then if LZ.verbose then
trigger.action.outText("+++LZ: NO config zone!", 30) trigger.action.outText("+++LZ: NO config zone!", 30)
end end
@ -336,6 +369,16 @@ function LZ.start()
-- connect event handler -- connect event handler
world.addEventHandler(LZ) 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 -- start update
LZ.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 = {}
cfxArtilleryZones.version = "2.2.1" cfxArtilleryZones.version = "2.2.2"
cfxArtilleryZones.requiredLibs = { cfxArtilleryZones.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
"cfxZones", -- Zones, of course "cfxZones", -- Zones, of course
@ -30,6 +30,7 @@ cfxArtilleryZones.verbose = false
- code cleanup - code cleanup
2.2.0 - DML Watchflag integration 2.2.0 - DML Watchflag integration
2.2.1 - minor code clean-up 2.2.1 - minor code clean-up
2.2.2 - new doParametricFireAt()
Artillery Target Zones *** EXTENDS ZONES *** 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 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) cfxArtilleryZones.invokeCallbacksFor('impact', args.zone, data)
end 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) function cfxArtilleryZones.doFireAt(aZone, maxDistFromCenter)
if type(aZone) == "string" then if type(aZone) == "string" then
local mZone = cfxArtilleryZones.findArtilleryZoneNamed(aZone) local mZone = cfxArtilleryZones.findArtilleryZoneNamed(aZone)

View File

@ -1,5 +1,5 @@
cfxHeloTroops = {} cfxHeloTroops = {}
cfxHeloTroops.version = "2.2.0" cfxHeloTroops.version = "2.3.0"
cfxHeloTroops.verbose = false cfxHeloTroops.verbose = false
cfxHeloTroops.autoDrop = true cfxHeloTroops.autoDrop = true
cfxHeloTroops.autoPickup = false cfxHeloTroops.autoPickup = false
@ -22,7 +22,9 @@ cfxHeloTroops.pickupRange = 100 -- meters
-- -- (re?) connected readConfigZone (wtf?) -- -- (re?) connected readConfigZone (wtf?)
-- -- persistence support -- -- persistence support
-- -- made legalTroops entrirely optional and defer to dcsComon else -- -- 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, -- cfxHeloTroops -- a module to pick up and drop infantry. Can be used with any helo,
-- might be used to configure to only certain -- might be used to configure to only certain
@ -511,30 +513,39 @@ end
function cfxHeloTroops.filterTroopsByType(unitsToLoad) function cfxHeloTroops.filterTroopsByType(unitsToLoad)
local filteredGroups = {} local filteredGroups = {}
for idx, aTeam in pairs(unitsToLoad) do for idx, aTeam in pairs(unitsToLoad) do
local group = aTeam.group local group = aTeam.group
local theTypes = dcsCommon.getGroupTypeString(group) local theTypes = dcsCommon.getGroupTypeString(group)
local aT = dcsCommon.splitString(theTypes, ",") local aT = dcsCommon.splitString(theTypes, ",")
local pass = true local pass = true
for iT, sT in pairs(aT) do for iT, sT in pairs(aT) do
-- check if this is a valid type -- check if this is a valid type
if cfxHeloTroops.legalTroops then if cfxHeloTroops.legalTroops then
if not dcsCommon.arrayContainsString(cfxHeloTroops.legalTroops, sT) then if not dcsCommon.arrayContainsString(cfxHeloTroops.legalTroops, sT) then
pass = false pass = false
break break
end end
else else
if not dcsCommon.typeIsInfantry(sT) then if not dcsCommon.typeIsInfantry(sT) then
pass = false pass = false
break break
end
end end
end end
if pass then end
table.insert(filteredGroups, aTeam) -- 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
end end
if pass then
table.insert(filteredGroups, aTeam)
end
end
return filteredGroups return filteredGroups
end end
-- --
@ -578,12 +589,43 @@ function cfxHeloTroops.redirectDeployTroops(args)
timer.scheduleFunction(cfxHeloTroops.doDeployTroops, args, timer.getTime() + 0.1) timer.scheduleFunction(cfxHeloTroops.doDeployTroops, args, timer.getTime() + 0.1)
end 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) function cfxHeloTroops.doDeployTroops(args)
local conf = args[1] local conf = args[1]
local what = args[2] local what = args[2]
-- deploy the troops I have on board in formation -- deploy the troops I have on board in formation
cfxHeloTroops.deployTroopsFromHelicopter(conf) 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 -- set own troops to 0 and erase type string
conf.troopsOnBoardNum = 0 conf.troopsOnBoardNum = 0
conf.troopsOnBoard = {} conf.troopsOnBoard = {}
@ -840,7 +882,7 @@ function cfxHeloTroops.readConfigZone()
cfxHeloTroops.autoDrop = cfxZones.getBoolFromZoneProperty(theZone, "autoDrop", false) cfxHeloTroops.autoDrop = cfxZones.getBoolFromZoneProperty(theZone, "autoDrop", false)
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)
end end
-- --

View File

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

View File

@ -1,5 +1,5 @@
cfxOwnedZones = {} cfxOwnedZones = {}
cfxOwnedZones.version = "1.2.1" cfxOwnedZones.version = "1.2.2"
cfxOwnedZones.verbose = false cfxOwnedZones.verbose = false
cfxOwnedZones.announcer = true cfxOwnedZones.announcer = true
cfxOwnedZones.name = "cfxOwnedZones" cfxOwnedZones.name = "cfxOwnedZones"
@ -46,6 +46,8 @@ cfxOwnedZones.name = "cfxOwnedZones"
- conq+1 --> conquered! - conq+1 --> conquered!
- no cfxGroundTroop bug (no delay) - no cfxGroundTroop bug (no delay)
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!
--]]-- --]]--
cfxOwnedZones.requiredLibs = { cfxOwnedZones.requiredLibs = {
@ -218,11 +220,20 @@ function cfxOwnedZones.addOwnedZone(aZone)
local paused = cfxZones.getBoolFromZoneProperty(aZone, "paused", false) local paused = cfxZones.getBoolFromZoneProperty(aZone, "paused", false)
aZone.paused = paused aZone.paused = paused
aZone.conqueredFlag = cfxZones.getStringFromZoneProperty(aZone, "conquered!", "*<cfxnone>") if cfxZones.hasProperty(aZone, "conquered!") then
if cfxZones.hasProperty(aZone, "conq+1") then aZone.conqueredFlag = cfxZones.getStringFromZoneProperty(aZone, "conquered!", "*<cfxnone>")
elseif cfxZones.hasProperty(aZone, "conq+1") then
aZone.conqueredFlag = cfxZones.getStringFromZoneProperty(aZone, "conq+1", "*<cfxnone>") aZone.conqueredFlag = cfxZones.getStringFromZoneProperty(aZone, "conq+1", "*<cfxnone>")
end 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.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)
@ -517,12 +528,19 @@ function cfxOwnedZones.zoneConquered(aZone, theSide, formerOwner) -- 0 = neutral
trigger.action.outSoundForCoalition(1, "Death BRASS.wav") trigger.action.outSoundForCoalition(1, "Death BRASS.wav")
end end
end end
-- increase conq flag
-- if aZone.conqueredFlag then if aZone.conqueredFlag then
-- local lastVal = trigger.misc.getUserFlag(aZone.conqueredFlag) cfxZones.pollFlag(aZone.conqueredFlag, "inc", aZone)
-- trigger.action.setUserFlag(aZone.conqueredFlag, lastVal + 1) end
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 -- invoke callbacks now
cfxOwnedZones.invokeConqueredCallbacks(aZone, theSide, formerOwner) cfxOwnedZones.invokeConqueredCallbacks(aZone, theSide, formerOwner)

View File

@ -1,5 +1,5 @@
cfxPlayerScore = {} cfxPlayerScore = {}
cfxPlayerScore.version = "1.4.0" cfxPlayerScore.version = "1.5.1"
cfxPlayerScore.badSound = "Death BRASS.wav" cfxPlayerScore.badSound = "Death BRASS.wav"
cfxPlayerScore.scoreSound = "Quest Snare 3.wav" cfxPlayerScore.scoreSound = "Quest Snare 3.wav"
cfxPlayerScore.announcer = true cfxPlayerScore.announcer = true
@ -27,9 +27,13 @@ cfxPlayerScore.announcer = true
- removed dependency to cfxPlayer - removed dependency to cfxPlayer
1.4.0 - persistence support 1.4.0 - persistence support
- better unit-->static switch support for generic type kill - 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 = { cfxPlayerScore.requiredLibs = {
"dcsCommon", -- this is doing score keeping "dcsCommon", -- this is doing score keeping
"cfxZones", -- zones for config "cfxZones", -- zones for config
@ -148,8 +152,10 @@ function cfxPlayerScore.getPlayerScore(playerName)
thePlayerScore = {} thePlayerScore = {}
thePlayerScore.name = playerName thePlayerScore.name = playerName
thePlayerScore.score = 0 -- score 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.totalKills = 0 -- number of kills total
thePlayerScore.featTypes = {} -- dict <featname> <number> of other things player did
thePlayerScore.totalFeats = 0
end end
return thePlayerScore return thePlayerScore
end end
@ -184,12 +190,51 @@ function cfxPlayerScore.logKillForPlayer(playerName, theUnit)
cfxPlayerScore.setPlayerScore(playerName, thePlayerScore) cfxPlayerScore.setPlayerScore(playerName, thePlayerScore)
end 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) function cfxPlayerScore.playerScore2text(thePlayerScore)
local desc = thePlayerScore.name .. " - score: ".. thePlayerScore.score .. " - kills: " .. thePlayerScore.totalKills .. "\n" local desc = thePlayerScore.name .. " - score: ".. thePlayerScore.score .. " - kills: " .. thePlayerScore.totalKills .. "\n"
-- now go through all killSide -- 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 for theType, quantity in pairs(thePlayerScore.killTypes) do
desc = desc .. " - " .. theType .. ": " .. quantity .. "\n" desc = desc .. " - " .. theType .. ": " .. quantity .. "\n"
end 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 return desc
end end

View File

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

View File

@ -1,5 +1,5 @@
cfxZones = {} cfxZones = {}
cfxZones.version = "2.8.5" cfxZones.version = "2.8.7"
-- 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
@ -92,6 +92,9 @@ cfxZones.version = "2.8.5"
- 2.8.5 - createGroundUnitsInZoneForCoalition() now always passes back a copy of the group data - 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 - data also contains cty = country and cat = category for easy spawn
- getFlagValue additional zone name guards - 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 cfxZones.verbose = false
@ -507,17 +510,19 @@ function cfxZones.isPointInsidePoly(thePoint, poly)
return true return true
end; 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 local p = {x=thePoint.x, y = 0, z = thePoint.z} -- zones have no altitude
if (theZone.isCircle) then if (theZone.isCircle) then
local zp = cfxZones.getPoint(theZone) local zp = cfxZones.getPoint(theZone)
local d = dcsCommon.dist(p, theZone.point) local d = dcsCommon.dist(p, theZone.point)
return d < theZone.radius return d < theZone.radius + radiusIncrease, d
end end
if (theZone.isPoly) then if (theZone.isPoly) then
--trigger.action.outText("zne: isPointInside: " .. theZone.name .. " is Polyzone!", 30) --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 end
trigger.action.outText("isPointInsideZone: Unknown zone type for " .. outerZone.name, 10) trigger.action.outText("isPointInsideZone: Unknown zone type for " .. outerZone.name, 10)
@ -1324,7 +1329,7 @@ end
function cfxZones.getFlagValue(theFlag, theZone) function cfxZones.getFlagValue(theFlag, theZone)
local zoneName = "<dummy>" local zoneName = "<dummy>"
if not theZone or not theZone.name then 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 else
zoneName = theZone.name -- for flag wildcards zoneName = theZone.name -- for flag wildcards
end end

View File

@ -1,5 +1,5 @@
csarManager = {} csarManager = {}
csarManager.version = "2.1.3" csarManager.version = "2.2.0"
csarManager.verbose = false csarManager.verbose = false
csarManager.ups = 1 csarManager.ups = 1
@ -41,6 +41,14 @@ csarManager.ups = 1
- 2.1.3 - theMassObject now local - 2.1.3 - theMassObject now local
- winch pickup now also adds weight so they can be returned - winch pickup now also adds weight so they can be returned
- made some improvements to performance by making vars local - 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", "cargoSuper",
-- "cfxCommander", -- needed if you want to hand-create CSAR missions -- "cfxCommander", -- needed if you want to hand-create CSAR missions
} }
-- integrates automatically with playerScore if installed
-- *** 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
--]]--
-- --
-- OPTIONS -- 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 csarManager.smokeColor = 4 -- when using smoke
@ -158,39 +100,49 @@ csarManager.csarCompleteCB = {}
-- --
-- CREATING A CSAR -- CREATING A CSAR
-- --
function csarManager.createDownedPilot(theMission) function csarManager.createDownedPilot(theMission, existingUnit)
if not cfxCommander then if not cfxCommander then
trigger.action.outText("+++CSAR: can't create mission, module cfxCommander is missing.", 30) trigger.action.outText("+++CSAR: can't create mission, module cfxCommander is missing.", 30)
return return
end end
local aLocation = {} if not existingUnit then
local aHeading = 0 -- in rads local aLocation = {}
local newTargetZone = theMission.zone local aHeading = 0 -- in rads
aLocation, aHeading = dcsCommon.randomPointOnPerimeter(newTargetZone.radius / 2 + 3, newTargetZone.point.x, newTargetZone.point.z) 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, local theBoyGroup = dcsCommon.createSingleUnitGroup(theMission.name,
"Soldier M4 GRG", -- "Soldier M4 GRG", "Soldier M4 GRG", -- "Soldier M4 GRG",
aLocation.x, aLocation.x,
aLocation.z, aLocation.z,
-aHeading + 1.5) -- + 1.5 to turn inwards -aHeading + 1.5) -- + 1.5 to turn inwards
-- WARNING: -- WARNING:
-- coalition.addGroup takes the COUNTRY of the group, and derives the -- 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 -- 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 -- is joint red, if 2 it is joint blue
local theSideCJTF = dcsCommon.coalition2county(theMission.side) -- get the correct county CJTF local theSideCJTF = dcsCommon.coalition2county(theMission.side) -- get the correct county CJTF
theMission.group = coalition.addGroup(theSideCJTF, theMission.group = coalition.addGroup(theSideCJTF,
Group.Category.GROUND, Group.Category.GROUND,
theBoyGroup) theBoyGroup)
if theBoyGroup then if theBoyGroup then
else
trigger.action.outText("+++csar: FAILED to create csar!", 30)
end
else else
trigger.action.outText("+++csar: FAILED to create csar!", 30) theMission.group = existingUnit:getGroup()
end end
-- we now use commands to send radio transmissions -- we now use commands to send radio transmissions
local ADF = 20 + math.random(90) local ADF = 20 + math.random(90)
if theMission.freq then ADF = theMission.freq else theMission.freq = ADF end 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 cfxCommander.scheduleCommands(theCommands, 2) -- in 2 seconds, so unit has time to percolate through DCS
end 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 -- create a type
if not timeLimit then timeLimit = -1 end if not timeLimit then timeLimit = -1 end
if not point then return nil 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 newMission.freq = freq -- if nil will make random
-- allocate units -- allocate units
csarManager.createDownedPilot(newMission) csarManager.createDownedPilot(newMission, parashootUnit)
-- update counter and return -- update counter and return
csarManager.missionID = csarManager.missionID + 1 csarManager.missionID = csarManager.missionID + 1
@ -262,6 +214,13 @@ function csarManager.removeMissionForGroup(theDownedGroup)
end end
csarManager.openMissions = newMissions -- this is the new batch csarManager.openMissions = newMissions -- this is the new batch
end 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 -- UNIT CONFIG
-- --
@ -308,7 +267,9 @@ end
function csarManager.removeConfigForUnitNamed(aName) function csarManager.removeConfigForUnitNamed(aName)
if not aName then return end 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 end
-- --
@ -376,6 +337,25 @@ end
-- --
function csarManager.successMission(who, where, theMission) 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, trigger.action.outTextForCoalition(theMission.side,
who .. " successfully evacuated " .. theMission.name .. " to " .. where .. "!", who .. " successfully evacuated " .. theMission.name .. " to " .. where .. "!",
30) 30)
@ -385,7 +365,7 @@ function csarManager.successMission(who, where, theMission)
csarManager.invokeCallbacks(theMission.side, true, 1, "success") 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 if csarManager.csarRedDelivered and theMission.side == 1 then
cfxZones.pollFlag(csarManager.csarRedDelivered, "inc", csarManager.configZone) cfxZones.pollFlag(csarManager.csarRedDelivered, "inc", csarManager.configZone)
@ -536,7 +516,7 @@ function csarManager.heloLanded(theUnit)
theMassObject) theMassObject)
end end
if didPickup then if didPickup then
trigger.action.outSoundForCoalition(mySide, "Quest Snare 3.wav") trigger.action.outSoundForCoalition(mySide, csarManager.actionSound) -- "Quest Snare 3.wav")
end end
-- reset unit's weight based on people on board -- reset unit's weight based on people on board
local totalMass = cargoSuper.calculateTotalMassFor(myName) local totalMass = cargoSuper.calculateTotalMassFor(myName)
@ -756,7 +736,7 @@ function csarManager.doListCSARRequests(args)
report = report .. "\n" report = report .. "\n"
trigger.action.outTextForGroup(conf.id, report, 30) 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 end
function csarManager.redirectStatusCarrying(args) function csarManager.redirectStatusCarrying(args)
@ -789,7 +769,7 @@ function csarManager.doStatusCarrying(args)
report = report .. "\n" report = report .. "\n"
trigger.action.outTextForGroup(conf.id, report, 30) 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 end
function csarManager.redirectUnloadOne(args) function csarManager.redirectUnloadOne(args)
@ -807,14 +787,14 @@ function csarManager.unloadOne(args)
if theUnit:inAir() then if theUnit:inAir() then
report = "STRONGLY recommend we land first, Sir!" report = "STRONGLY recommend we land first, Sir!"
trigger.action.outTextForGroup(conf.id, report, 30) 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 return
end end
if #conf.troopsOnBoard < 1 then if #conf.troopsOnBoard < 1 then
report = "No evacuees on board." report = "No evacuees on board."
trigger.action.outTextForGroup(conf.id, report, 30) 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 else
-- simulate a crash but for one unit -- simulate a crash but for one unit
@ -829,7 +809,7 @@ function csarManager.unloadOne(args)
--TODO: remove weight for this pilot! --TODO: remove weight for this pilot!
trigger.action.outTextForCoalition(theSide, myName .. " has aborted evacuating " .. msn.name .. ". New CSAR available.", 30) 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 -- recalc weight
trigger.action.setUnitInternalCargo(myName, 10 + #conf.troopsOnBoard * csarManager.pilotWeight) -- 10 kg as empty + per-unit time people 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 else
local msg = aMission.name .. " confirmed KIA, repeat KIA. Abort CSAR." local msg = aMission.name .. " confirmed KIA, repeat KIA. Abort CSAR."
trigger.action.outTextForCoalition(aMission.side, msg, 30) 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
end end
csarManager.openMissions = newMissions -- this is the new batch csarManager.openMissions = newMissions -- this is the new batch
@ -981,7 +961,7 @@ function csarManager.update() -- every second
end end
msg = msg .. "\n" msg = msg .. "\n"
trigger.action.outTextForGroup(uID, msg, 30) 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 table.insert(csarMission.messagedUnits, uName) -- remember that we messaged them so we don't do again
end end
-- also pop smoke if not popped already, or more than 5 minutes ago -- also pop smoke if not popped already, or more than 5 minutes ago
@ -1046,7 +1026,7 @@ function csarManager.update() -- every second
end end
trigger.action.outTextForGroup(uID, hoverMsg, 30, true) 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 return -- we only ever rescue one
end -- hovered long enough end -- hovered long enough
@ -1104,7 +1084,7 @@ end
-- --
-- create a CSAR Mission for a unit -- 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 silent then silent = false end
if not radius then radius = 1000 end if not radius then radius = 1000 end
if not pilotName then pilotName = "Eddie" 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 surf == 2 or surf == 3 then
if not silent 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.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 end
return return
end end
@ -1137,13 +1117,26 @@ function csarManager.createCSARforUnit(theUnit, pilotName, radius, silent)
1, 1,
nil, nil,
nil) nil)
theMission.score = score
csarManager.addMission(theMission) csarManager.addMission(theMission)
if not silent then if not silent then
trigger.action.outTextForCoalition(coal, "MAYDAY MAYDAY MAYDAY! ".. pilotName .. " in " .. theUnit:getTypeName() .. " ejected, report good chute. Prepare CSAR!", 30) 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
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 -- csar (mission) zones
@ -1199,6 +1192,9 @@ function csarManager.readCSARZone(theZone)
theZone.lastCSARVal = cfxZones.getFlagValue(theZone.startCSAR, theZone) theZone.lastCSARVal = cfxZones.getFlagValue(theZone.startCSAR, theZone)
end end
if cfxZones.hasProperty(theZone, "score") then
theZone.score = cfxZones.getNumberFromZoneProperty(theZone, "score", 100)
end
if (not deferred) then if (not deferred) then
local theMission = csarManager.createCSARMissionData( local theMission = csarManager.createCSARMissionData(
@ -1240,9 +1236,6 @@ end
-- Init & Start -- Init & Start
-- --
function csarManager.invokeCallbacks(theCoalition, success, numRescued, notes) function csarManager.invokeCallbacks(theCoalition, success, numRescued, notes)
-- invoke anyone who wants to know that a group -- invoke anyone who wants to know that a group
-- of people was rescued. -- of people was rescued.
@ -1261,7 +1254,7 @@ function csarManager.readConfigZone()
if csarManager.verbose then if csarManager.verbose then
trigger.action.outText("+++csar: NO config zone!", 30) trigger.action.outText("+++csar: NO config zone!", 30)
end end
return theZone = cfxZones.createSimpleZone("csarManagerConfig")
end end
csarManager.configZone = theZone -- save for flag banging compatibility csarManager.configZone = theZone -- save for flag banging compatibility
@ -1284,7 +1277,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) --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
@ -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.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.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) csarManager.vectoring = cfxZones.getBoolFromZoneProperty(theZone, "vectoring", true)
if csarManager.verbose then if csarManager.verbose then
@ -1373,4 +1369,5 @@ end
- when unloading one by menu, update weight!!! - when unloading one by menu, update weight!!!
-- allow neutral pick-up
--]]-- --]]--

View File

@ -1,5 +1,5 @@
dcsCommon = {} dcsCommon = {}
dcsCommon.version = "2.7.4" dcsCommon.version = "2.7.5"
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
2.2.6 - compassPositionOfARelativeToB 2.2.6 - compassPositionOfARelativeToB
- clockPositionOfARelativeToB - clockPositionOfARelativeToB
@ -101,6 +101,9 @@ dcsCommon.version = "2.7.4"
2.7.3 - new string2Array() 2.7.3 - new string2Array()
- additional guard for isPlayerUnit - additional guard for isPlayerUnit
2.7.4 - new array2string() 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 return nil
end 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 -- INIT

View File

@ -1,5 +1,5 @@
fireFX = {} fireFX = {}
fireFX.version = "1.0.0" fireFX.version = "1.1.0"
fireFX.verbose = false fireFX.verbose = false
fireFX.ups = 1 fireFX.ups = 1
fireFX.requiredLibs = { fireFX.requiredLibs = {
@ -8,6 +8,13 @@ fireFX.requiredLibs = {
} }
fireFX.fx = {} fireFX.fx = {}
--[[--
Version History
1.0.0 - Initial version
1.1.0 - persistence
--]]--
function fireFX.addFX(theZone) function fireFX.addFX(theZone)
table.insert(fireFX.fx, theZone) table.insert(fireFX.fx, theZone)
end end
@ -127,13 +134,62 @@ function fireFX.update()
end 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 -- Config & Start
-- --
function fireFX.readConfigZone() function fireFX.readConfigZone()
local theZone = cfxZones.getZoneByName("fireFXConfig") local theZone = cfxZones.getZoneByName("fireFXConfig")
if not theZone then if not theZone then
theZone = cfxZones.createSimpleZone(LZConfig) theZone = cfxZones.createSimpleZone("fireFX")
if fireFX.verbose then if fireFX.verbose then
trigger.action.outText("+++ffx: NO config zone!", 30) trigger.action.outText("+++ffx: NO config zone!", 30)
end end
@ -165,9 +221,20 @@ function fireFX.start()
fireFX.addFX(aZone) -- add to list fireFX.addFX(aZone) -- add to list
end 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 -- handle onStart
for idx, theZone in pairs(fireFX.fx) do 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) fireFX.startTheFire(theZone)
end end
end end

View File

@ -52,6 +52,8 @@ limitedAirframes.requiredLibs = {
- 1.4.1 - removed dependency to cfxPlayer - 1.4.1 - removed dependency to cfxPlayer
- 1.5.0 - persistence support - 1.5.0 - persistence support
- 1.5.1 - new "announcer" attribute - 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) function limitedAirframes.createCSAR(theUnit)
-- override if autoCSAR is installed
if autoCSAR then return end
-- only do this if we have installed CSAR Manager -- only do this if we have installed CSAR Manager
if csarManager and csarManager.createCSARforUnit then if csarManager and csarManager.createCSARforUnit then
csarManager.createCSARforUnit(theUnit, csarManager.createCSARforUnit(theUnit,

View File

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

View File

@ -1,5 +1,5 @@
pulseFlags = {} pulseFlags = {}
pulseFlags.version = "1.3.0" pulseFlags.version = "1.3.1"
pulseFlags.verbose = false pulseFlags.verbose = false
pulseFlags.requiredLibs = { pulseFlags.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
@ -36,6 +36,7 @@ pulseFlags.requiredLibs = {
- 1.2.3 deprecated paused/pulsePaused - 1.2.3 deprecated paused/pulsePaused
returned onStart, defaulting to true returned onStart, defaulting to true
- 1.3.0 persistence - 1.3.0 persistence
- 1.3.1 typos corrected
--]]-- --]]--
@ -326,7 +327,7 @@ function pulseFlags.loadData()
local theData = persistence.getSavedDataForModule("pulseFlags") local theData = persistence.getSavedDataForModule("pulseFlags")
if not theData then if not theData then
if pulseFlags.verbose 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 end
return return
end end

View File

@ -1,5 +1,5 @@
radioTrigger = {} radioTrigger = {}
radioTrigger.version = "1.0.0" radioTrigger.version = "1.0.1"
radioTrigger.verbose = false radioTrigger.verbose = false
radioTrigger.ups = 1 radioTrigger.ups = 1
radioTrigger.requiredLibs = { radioTrigger.requiredLibs = {
@ -11,6 +11,7 @@ radioTrigger.radioTriggers = {}
--[[-- --[[--
Version History Version History
1.0.0 - initial version 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 end
-- out flag -- out flag
theZone.rtOutFlag = cfxZones.getStringFromZoneProperty(theZone, "out!", "*<none>") if cfxZones.hasProperty(theZone, "out!") then
if cfxZones.hasProperty(theZone, "rtOut!") then theZone.rtOutFlag = cfxZones.getStringFromZoneProperty(theZone, "out!", "*<none>")
elseif cfxZones.hasProperty(theZone, "rtOut!") then
theZone.rtOutFlag = cfxZones.getStringFromZoneProperty(theZone, "rtOut!", "*<none>") theZone.rtOutFlag = cfxZones.getStringFromZoneProperty(theZone, "rtOut!", "*<none>")
end end
@ -66,8 +68,9 @@ end
-- --
function radioTrigger.process(theZone) function radioTrigger.process(theZone)
-- we are triggered, simply poll the out flag -- 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 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
--]]--