Version 2.4.5

csarFX, dropFormation in heloTroops
This commit is contained in:
Christian Franz 2025-03-13 09:49:23 +01:00
parent 2f80033077
commit 59d8bb30cf
14 changed files with 27572 additions and 25163 deletions

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -1,5 +1,5 @@
airtank = {}
airtank.version = "1.0.1"
airtank.version = "1.0.2"
-- Module to extinguish fires controlled by the 'inferno' module.
-- For 'airtank' fire extinguisher aircraft modules.
airtank.requiredLibs = {
@ -11,7 +11,7 @@ airtank.requiredLibs = {
Version History
1.0.0 - Initial release
1.0.1 - removed attachTo: bug
1.0.2 - integration with fireCtrl.enabled
--]]--
airtank.tanks = {} -- player data by GROUP name, will break with multi-unit groups
@ -154,13 +154,16 @@ function airtank:onEvent(theEvent)
if data.lastDeparture then -- and data.lastDeparture + 60 < now then
return
end
data.lastDeparture = now
if data.carrying < data.capacity * 0.5 then
trigger.action.outTextForGroup(data.gID, "Good luck, " .. pName .. ", remember to top off your tanks before going in.", 30)
else
trigger.action.outTextForGroup(data.gID, "Good luck and godspeed, " .. pName .. "!", 30)
end
trigger.action.outSoundForGroup(data.gID, airtank.actionSound)
data.lastDeparture = now
if (not fireCtrl) or
(fireCtrl and fireCtrl.enabled) then
local msg = "Good luck, " .. pName .. ", remember to top off your tanks before going in."
if data.carrying > data.capacity * 0.5 then
msg = "Good luck and godspeed, " .. pName .. "!"
end
trigger.action.outTextForGroup(data.gID, msg, 30)
trigger.action.outSoundForGroup(data.gID, airtank.actionSound)
end
end
return
end

358
modules/csarFX.lua Normal file
View File

@ -0,0 +1,358 @@
csarFX = {}
csarFX.version = "2.0.5"
--[[--
VERSION HISTORY
2.0.5 Initial Version
csarFX - makes better SAR and can turn SAR into CSAR
Copyright (c) 2024-2025 by Christian Franz
WARNING:
csarFX must run AFTER csarManager to install its callbacks, so
any csar mission that runs earlier does NOT receive any csarFX adornments
--]]--
csarFX.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
"csarManager",
}
-- static object type strings to add as debris csar missions placed on a road
csarFX.roadDebbies = {"VAZ Car", "IKARUS Bus", "LAZ Bus", "LiAZ Bus", "GAZ-3307", "ZIL-131 KUNG", "MAZ-6303", "ZIL-4331", "Ural-375", "GAZ-3307"
, "ZIL-131 KUNG", "MAZ-6303", "ZIL-4331", "Ural-375", "GAZ-3307",
"VAZ Car",
-- Massun's stuff
"P20_01", "TugHarlan", "r11_volvo_drivable", }
csarFX.landDebbies = {"Hummer", }
csarFX.seaDebbies = {"speedboat","ZWEZDNY", "speedboat", --2x prob speed
}
csarFX.seaSpecials = {"Orca",}
--[[--
theMission Data Structure:
- zone: a trigger zone.
- locations : {} array of locations for units in zone
- name - mission name
- group - all evacuees
- side: coalition
- missionID - id
- timeStamp - when it was created
- inPopulated - passed by csarManager zone creation
--]]--
function csarFX.addRoadDebris(theMission, center) -- in vec2
local cty = dcsCommon.getACountryForCoalition(0)
local theStatic, x, z = dcsCommon.createStaticObjectForCoalitionInRandomRing(cty, dcsCommon.pickRandom(csarFX.roadDebbies), center.x, center.y, 5, 7, nil, true)
table.insert(theMission.debris, theStatic)
if math.random(1000) > 500 then
-- two-car crash, high probability for fire
local otherStatic = dcsCommon.createStaticObjectForCoalitionInRandomRing(cty, dcsCommon.pickRandom(csarFX.roadDebbies), x, z, 2, 4, nil, true)
table.insert(theMission.debris, otherStatic)
if math.random(1000) > 400 then
local smokeType = 1
if math.random(1000) > 500 then smokeType = 5 end
local smokePoint = {x=x, y=land.getHeight({x=x, y=z}), z=z}
trigger.action.effectSmokeBig(smokePoint, smokeType , 0.1 , theMission.name)
table.insert(theMission.fires, theMission.name)
end
else
if math.random(1000) > 800 then
local smokeType = 1
if math.random(1000) > 500 then smokeType = 5 end
local smokePoint = {x=x, y=land.getHeight({x=x, y=z}), z=z}
trigger.action.effectSmokeBig(smokePoint , smokeType , 0.1 , theMission.name)
table.insert(theMission.fires, theMission.name)
end
end
end
function csarFX.addWaterDebris(theMission, center) -- in vec2
local cty = dcsCommon.getACountryForCoalition(0)
if math.random(1000) > 500 then -- 50% chance
local theStatic, x, z = dcsCommon.createStaticObjectForCoalitionInRandomRing(cty, dcsCommon.pickRandom(csarFX.seaDebbies), center.x, center.y, 100, 200, nil, true)
table.insert(theMission.debris, theStatic)
end
-- add an orca (very rare)
if math.random(1000) < 10 then -- 1% chance for water
local theStatic, x, z = dcsCommon.createStaticObjectForCoalitionInRandomRing(cty, dcsCommon.pickRandom(csarFX.seaSpecials), center.x, center.y, 30, 50, nil, true)
table.insert(theMission.debris, theStatic)
end
end
function csarFX.localDebris(debris, center, dist, theZone, theMission)
local cty = dcsCommon.getACountryForCoalition(theZone.csarSide)
local theStatic, x, z = dcsCommon.createStaticObjectForCoalitionInRandomRing(cty, dcsCommon.pickRandom(debris), center.x, center.y, dist, 2*dist, nil, false) -- zed is ded
table.insert(theMission.debris, theStatic)
local smokeType = 1
if math.random(1000) > 500 then smokeType = 5 end
local smokePoint = {x=x, y=land.getHeight({x=x, y=z}), z=z}
trigger.action.effectSmokeBig(smokePoint, smokeType, 0.1 , theMission.name)
table.insert(theMission.fires, theMission.name)
end
function csarFX.addLandDebris(theMission, center) -- in vec2
local cty = dcsCommon.getACountryForCoalition(0)
if math.random(1000) > 500 then -- 50% chance
local theStatic, x, z = dcsCommon.createStaticObjectForCoalitionInRandomRing(cty, dcsCommon.pickRandom(csarFX.landDebbies), center.x, center.y, 10, 15, nil, false) -- zed is ded
table.insert(theMission.debris, theStatic)
if math.random(1000) > 500 then
local smokeType = 1
if math.random(1000) > 500 then smokeType = 5 end
local smokePoint = {x=x, y=land.getHeight({x=x, y=z}), z=z}
trigger.action.effectSmokeBig(smokePoint, smokeType, 0.1 , theMission.name)
table.insert(theMission.fires, theMission.name)
end
end
end
function csarFX.addPopulatedDebris(theMission, center)
if math.random(1000) > 500 then
local rp = dcsCommon.randomPointOnPerimeter(5, center.x, center.z)
local smokeType = 1
if math.random(1000) > 500 then smokeType = 5 end
local smokePoint = {x=rp.x, y=land.getHeight({x=rp.x, y=rp.z}), z=rp.z}
trigger.action.effectSmokeBig(smokePoint, smokeType, 0.1 , theMission.name)
table.insert(theMission.fires, theMission.name)
end
end
--
-- mission created callback
--
function csarFX.missionCreatedCB(theMission, theZone)
-- get location of first (usually only) evacuee
local loc = {}
loc.x = theMission.locations[1].x
loc.y = 0
loc.z = theMission.locations[1].z
loc.y = loc.z -- loc is now vec2!
theMission.debris = {}
theMission.fires = {}
theMission.enemies = {}
theMission.enemyNames = {}
if theZone then
if theZone.enemies then
-- generate enemies
local coa = dcsCommon.getEnemyCoalitionFor(theZone.csarSide)
local cty = dcsCommon.getACountryForCoalition(coa)
local numEnemies = dcsCommon.randomBetween(theZone.emin, theZone.emax)
for i=1, numEnemies do
local gName = dcsCommon.uuid("cesar")
local gData = dcsCommon.createEmptyGroundGroupData (gName)
local theType = dcsCommon.pickRandom(theZone.enemies)
local range = dcsCommon.randomBetween(theZone.rmin, theZone.rmax)
local p = dcsCommon.randomPointOnPerimeter(range, 0, 0)local uData = dcsCommon.createGroundUnitData(gName .. "-e", theType, false)
local heading = math.random(360) * 0.0174533
dcsCommon.addUnitToGroupData(uData, gData, 0, 0, heading)
dcsCommon.moveGroupDataTo(gData, loc.x + p.x, loc.z + p.z)
local theEnemies = coalition.addGroup(cty, Group.Category.GROUND, gData)
if theEnemies then
table.insert(theMission.enemies, theEnemies)
local gNameS = theEnemies:getName()
table.insert(theMission.enemyNames, gNameS)
end
end
-- add a nasty if defined
if theZone.nasties then
local gName = dcsCommon.uuid("cesar-n")
local gData = dcsCommon.createEmptyGroundGroupData (gName)
local theType = dcsCommon.pickRandom(theZone.nasties)
local range = dcsCommon.randomBetween(theZone.rmin, theZone.rmax)
local p = dcsCommon.randomPointOnPerimeter(range, 0, 0)local uData = dcsCommon.createGroundUnitData(gName .. "-n", theType, false)
local heading = math.random(360) * 0.0174533
dcsCommon.addUnitToGroupData(uData, gData, 0, 0, heading)
dcsCommon.moveGroupDataTo(gData, loc.x + p.x, loc.z + p.z)
local theEnemies = coalition.addGroup(cty, Group.Category.GROUND, gData)
if theEnemies then
table.insert(theMission.enemies, theEnemies)
local gNameS = theEnemies:getName()
table.insert(theMission.enemyNames, gNameS)
end
end
-- generate debris
if theZone.debris then -- theZone:hasProperty("debris") then
csarFX.localDebris(theZone.debris, loc, 1000, theZone, theMission)
end
return -- no further adornments
end
end
-- see if this is a land or sea mission?
-- access first unit's location
local landType = land.getSurfaceType(loc)
-- init debris and fires for house cleaning
if theMission.inPopulated then
-- theMission calls for in populated. create some marker
-- directly next to the guy
csarFX.addPopulatedDebris(theMission, loc)
elseif landType == 3 then -- deep water
csarFX.addWaterDebris(theMission, loc)
elseif landType == 4 then -- road
csarFX.addRoadDebris(theMission, loc)
else -- anywhere else. Includes shallow water
csarFX.addLandDebris(theMission, loc)
end
end
function csarFX.makeEnemiesCongregate(theMission)
if not theMission.enemies then return end
for idx, theEnemyName in pairs(theMission.enemyNames) do
local theEnemy = Group.getByName(theEnemyName)
if theEnemy then
local loc = theMission.locations[1]
if not loc then return end
local p = {}
p.x = loc.x
p.y = 0
p.z = loc.z
if Group.isExist(theEnemy) then
local here = dcsCommon.getGroupLocation(theEnemy, true, theEnemyName)
if not here then
trigger.action.outText("+++csFx: no (here) for <" .. theEnemyName .. ">, skipping.", 30)
else
cfxCommander.makeGroupGoThere(theEnemy, p, 4, nil, 5) -- 5 m/s = 18 kmh, NIL FORMATION, 5 seconds in the future
end
end
else -- enemy gone
end
end
end
function csarFX.smokeStartedCB(theMission, uName)
if not csarFX.congregateOnSmoke then return end
-- start congregation of units
if theMission.enemies then
csarFX.makeEnemiesCongregate(theMission)
end
end
--
-- evacuee picked up callback
--
function csarFX.PickUpCB(theMission)
end
--
-- Mission completed callback
--
function csarFX.missionCompleteCB(theCoalition, success, numRescued, notes, theMission)
if not success then
-- schedule cleanup in the future
timer.scheduleFunction(csarFX.doCleanUpMission, theMission, timer.getTime() + csarFX.cleanupDelay)
return
end
csarFX.doCleanUpMission(theMission) -- clean up now.
end
-- synch/asynch call for cleaning up after Mission
-- if mission isn't successful, we wait some time before
-- we remove smoke, debris, enemies
function csarFX.doCleanUpMission(theMission)
-- deallocate scenery fx
if theMission.debris then
for idx, theDeb in pairs(theMission.debris) do
if theDeb and Object.isExist(theDeb) then
Object.destroy(theDeb)
end
end
end
if theMission.fires then
for idx, theFlame in pairs(theMission.fires) do
trigger.action.effectSmokeStop(theFlame)
end
end
if theMission.enemies then
for idx, theGroup in pairs(theMission.enemies) do
if theGroup and Group.isExist(theGroup) then
Group.destroy(theGroup)
end
end
end
end
-- start and hook into csarManager
function csarFX.readConfig()
local theZone = cfxZones.getZoneByName("csarFXConfig")
if not theZone then
theZone = cfxZones.createSimpleZone("csarFXConfig")
end
csarFX.verbose = theZone.verbose
--csarFX.landDebris = theZone:getBoolFromZoneProperty("landDebris", true)
--csarFX.seaDebris = theZone:getBoolFromZoneProperty("seaDebris", true)
--csarFX.roadDebris = theZone:getBoolFromZoneProperty("roadDebris", true)
csarFX.congregateOnSmoke = theZone:getBoolFromZoneProperty("congregate", true)
csarFX.cleanupDelay = theZone:getNumberFromZoneProperty("cleanupDelay", 10 * 60)
if theZone:hasProperty("landTypes") then
local hTypes = theZone:getStringFromZoneProperty("landTypes", "xxx")
local typeArray = dcsCommon.splitString(hTypes, ",")
typeArray = dcsCommon.trimArray(typeArray)
csarFX.landDebbies = typeArray
end
if theZone:hasProperty("roadTypes") then
local hTypes = theZone:getStringFromZoneProperty("roadTypes", "xxx")
local typeArray = dcsCommon.splitString(hTypes, ",")
typeArray = dcsCommon.trimArray(typeArray)
csarFX.roadDebbies = typeArray
end
if theZone:hasProperty("seaTypes") then
local hTypes = theZone:getStringFromZoneProperty("seaTypes", "xxx")
local typeArray = dcsCommon.splitString(hTypes, ",")
typeArray = dcsCommon.trimArray(typeArray)
csarFX.seaDebbies = typeArray
end
end
function csarFX.amendCSARZones()
-- process csar zones and amend the attributes
local csarBases = cfxZones.zonesWithProperty("CSAR")
-- now add all zones to my zones table, and init additional info
-- from properties
for k, theZone in pairs(csarBases) do
if theZone:hasProperty("enemies") then
local hTypes = theZone:getStringFromZoneProperty("enemies", "xxx")
local typeArray = dcsCommon.splitString(hTypes, ",")
typeArray = dcsCommon.trimArray(typeArray)
theZone.enemies = typeArray
end
if theZone:hasProperty("nasties") then
local hTypes = theZone:getStringFromZoneProperty("nasties", "xxx")
local typeArray = dcsCommon.splitString(hTypes, ",")
typeArray = dcsCommon.trimArray(typeArray)
theZone.nasties = typeArray
end
local dmin, dmax = theZone:getPositiveRangeFromZoneProperty("range", 1) -- range of enemies
theZone.rmin = dmin * 1000
theZone.rmax = dmax * 1000
local emin, emax = theZone:getPositiveRangeFromZoneProperty("strength", 1) -- number of enemies
theZone.emin = emin
theZone.emax = emax
if theZone:hasProperty("debris") then
local hTypes = theZone:getStringFromZoneProperty("debris", "xxx")
local typeArray = dcsCommon.splitString(hTypes, ",")
typeArray = dcsCommon.trimArray(typeArray)
theZone.debris = typeArray
end
end
end
function csarFX.start()
if not dcsCommon.libCheck("cfx CSAR FX", csarFX.requiredLibs) then
trigger.action.outText("cf/x CSAR FX aborted: missing libraries", 30)
return
end
-- read config
csarFX.readConfig()
-- amend csarZones
csarFX.amendCSARZones()
-- install callbacks
csarManager.installNewMissionCallback(csarFX.missionCreatedCB)
csarManager.installPickupCallback(csarFX.PickUpCB)
csarManager.installCallback(csarFX.missionCompleteCB)
csarManager.installSmokeCallback(csarFX.smokeStartedCB)
trigger.action.outText("csarFX v" .. csarFX.version .. " started", 30)
end
csarFX.start()
--[[--
to do: integrate with autoCSAR so if a plane gets shot down over a CSAR zone, that zone is triggered, and csarFX gets invoked as well.
--]]--

View File

@ -1,5 +1,5 @@
csarManager = {}
csarManager.version = "4.3.0"
csarManager.version = "4.4.0"
csarManager.ups = 1
--[[-- VERSION HISTORY
@ -11,10 +11,14 @@ csarManager.ups = 1
4.2.1 - added Chinook to csar default set (via common)
4.3.0 - pilot's smoke can now extinguish immediately
- keepSmoke option
4.4.0 - csarMissions created by autoCSAR can now trigger
CSAR zones, which can in turn integrate with csarFX
for enemies etc.
INTEGRATES AUTOMATICALLY WITH playerScore
INTEGRATES WITH LIMITED AIRFRAMES
INTEGRATES AUTOMATICALLY WITH SCRIBE
INTEGRATES AUTOMATICALL WITH CSARFX
SUPPORTS PERSISTENCE
--]]--
@ -511,10 +515,9 @@ function csarManager.heloLanded(theUnit)
-- handle smoke
if csarManager.keepSmoke then
-- trigger.action.outText("keeping smokeName <" .. theMission.smokeName .. "> running", 30)
else
trigger.action.effectSmokeStop(theMission.smokeName)
-- trigger.action.outText("smokeName <" .. theMission.smokeName .. "> removed", 30)
end
local args = {}
@ -566,8 +569,7 @@ function csarManager.heloDeparted(theUnit)
if not dcsCommon.isTroopCarrier(theUnit, csarManager.troopCarriers) then return end
-- if we have timed extractions (i.e. not instantaneous),
-- then we need to check if we take off after the timer runs out
-- when we take off, all that needs to be done is to change the state
-- to airborne, and then set the status flag
local conf = csarManager.getUnitConfig(theUnit)
@ -607,7 +609,6 @@ function csarManager.airframeCrashed(theUnit)
local theGroup = theUnit:getGroup()
conf.id = theGroup:getID()
-- may want to do something, for now just nothing
end
function csarManager.airframeDitched(theUnit)
@ -622,7 +623,6 @@ function csarManager.airframeDitched(theUnit)
if #conf.troopsOnBoard > 0 then
-- this is where we can create a new CSAR mission
trigger.action.outTextForCoalition(theSide, theUnit:getName() .. " abandoned while evacuating " .. #conf.troopsOnBoard .. " pilots. There many be survivors.", 30)
-- trigger.action.outSoundForCoalition(conf.id, "Quest Snare 3.wav")
for i=1, #conf.troopsOnBoard do
local msn = conf.troopsOnBoard[i] -- picked up unit(s)
local theRescuedPilot = msn.name
@ -636,7 +636,6 @@ function csarManager.airframeDitched(theUnit)
local myName = conf.name
cargoSuper.removeAllMassForCargo(myName, "Evacuees") -- will allocate new empty table
end
--
--
-- M E N U H A N D L I N G & R E S P O N S E
@ -653,7 +652,6 @@ end
function csarManager.removeCommsFromConfig(conf)
csarManager.clearCommsSubmenus(conf)
if conf.myMainMenu then
missionCommands.removeItemForGroup(conf.id, conf.myMainMenu)
conf.myMainMenu = nil
@ -663,13 +661,11 @@ end
function csarManager.removeComms(theUnit)
if not theUnit then return end
if not theUnit:isExist() then return end
local group = theUnit:getGroup()
local id = group:getID()
local conf = csarManager.getUnitConfig(theUnit)
conf.id = id
conf.unit = theUnit
csarManager.removeCommsFromConfig(conf)
end
@ -821,7 +817,6 @@ function csarManager.doListCSARRequests(args)
report = report .. "\n\nWARNING: NO CSAR BASES TO DELIVER EVACUEES TO"
end
report = report .. "\n"
trigger.action.outTextForGroup(conf.id, report, 30)
trigger.action.outSoundForGroup(conf.id, csarManager.actionSound)
end
@ -863,10 +858,7 @@ function csarManager.doStatusCarrying(args)
report = report .. "\n\nTotal added weigth: " .. 10 + #conf.troopsOnBoard * csarManager.pilotWeight .. "kg"
end
report = report .. "\n"
trigger.action.outTextForGroup(conf.id, report, 30)
trigger.action.outSoundForGroup(conf.id, csarManager.actionSound)
end
@ -881,23 +873,19 @@ function csarManager.unloadOne(args)
local theUnit = conf.unit
if not theUnit then return end -- ??
if not Unit.isExist(theUnit) then return end
local myName = theUnit:getName()
local report = "NYI: unload one"
if theUnit:inAir() then
report = "STRONGLY recommend we land first, sir!"
trigger.action.outTextForGroup(conf.id, report, 30)
trigger.action.outSoundForGroup(conf.id, csarManager.actionSound) -- "Quest Snare 3.wav")
trigger.action.outSoundForGroup(conf.id, csarManager.actionSound)
return
end
if #conf.troopsOnBoard < 1 then
report = "No evacuees on board."
trigger.action.outTextForGroup(conf.id, report, 30)
trigger.action.outSoundForGroup(conf.id, csarManager.actionSound) -- "Quest Snare 3.wav")
trigger.action.outSoundForGroup(conf.id, csarManager.actionSound)
else
-- simulate a crash but for one unit
local theSide = theUnit:getCoalition()
@ -911,12 +899,11 @@ 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, csarManager.actionSound) -- "Quest Snare 3.wav")
trigger.action.outSoundForCoalition(theSide, csarManager.actionSound)
-- recalc weight
local totalMass = 10 + #conf.troopsOnBoard * csarManager.pilotWeight
trigger.action.setUnitInternalCargo(myName, totalMass) -- 10 kg as empty + per-unit time people
--trigger.action.outText("unit <" .. myName .. ">, internal cargo now <" .. totalMass .. ">kg", 30)
end
end
@ -1207,10 +1194,8 @@ function csarManager.update() -- every second
csarMission.group = nil -- no more evacuees
-- turn off smoke?
if csarManager.keepSmoke then
-- trigger.action.outText("keeping smokeName <" .. csarMission.smokeName .. "> running", 30)
else
trigger.action.effectSmokeStop(csarMission.smokeName)
-- trigger.action.outText("smokeName <" .. csarMission.smokeName .. "> removed", 30)
end
needsGC = true -- need filtering missions
@ -1232,11 +1217,7 @@ function csarManager.update() -- every second
end
trigger.action.outSoundForGroup(uID, csarManager.pickupSound)
--return -- we only ever rescue one
end -- hovered long enough
-- return -- only ever one winch op
end -- hovered long enough
else -- too high for hover
hoverMsg = "Evacuee " .. d * 1 .. "m on your " .. oclock .. " o'clock; land or descend to between 10 and 90 AGL for winching"
csarMission.hoveringUnits[uName] = nil -- reset timer
@ -1246,7 +1227,6 @@ function csarManager.update() -- every second
csarMission.hoveringUnits[uName] = nil
end
trigger.action.outTextForGroup(uID, hoverMsg, 30, true)
--return -- only ever one winch op
else
-- remove the hover indicator for this unit
csarMission.hoveringUnits[uName] = nil
@ -1275,8 +1255,6 @@ function csarManager.update() -- every second
-- check if their flag value has changed
if theZone.startCSAR then
-- this should always be true, but you never know
-- local currVal = theZone:getFlagValue(theZone.startCSAR)
-- if currVal ~= theZone.lastCSARVal then
if theZone:testZoneFlag(theZone.startCSAR, theZone.triggerMethod, "lastCSARVal") then
local theMission = csarManager.createCSARMissionFromZone(theZone)
csarManager.addMission(theMission, theZone)
@ -1289,21 +1267,25 @@ function csarManager.update() -- every second
end
end
function csarManager.createCSARMissionFromZone(theZone)
-- orides pos, name from parachuted msn
function csarManager.createCSARMissionFromZone(theZone, pos, name)
-- set up random point in zone
local mPoint = theZone:getPoint()
if theZone.rndLoc then mPoint = theZone:createRandomPointInZone() end
if pos then mPoint = pos end -- WILL respect onRoad and inPopulated
if theZone.onRoad then
mPoint.x, mPoint.z = land.getClosestPointOnRoads('roads',mPoint.x, mPoint.z)
elseif theZone.inPopulated then
local aPoint = theZone:createRandomPointInPopulatedZone(theZone.clearance) -- no more maxTries: theZone.maxTries)
mPoint = aPoint -- safety in case we need to mod aPoint
end
if not name then name = theZone.csarName end -- respect oride
local theMission = csarManager.createCSARMissionData(
mPoint,
theZone.csarSide, -- theSide
theZone.csarFreq, -- freq
theZone.csarName, -- name
name, -- theZone.csarName, -- name
theZone.numCrew, -- numCrew
theZone.timeLimit, -- timeLimit
theZone.csarMapMarker, -- mapMarker
@ -1312,7 +1294,6 @@ function csarManager.createCSARMissionFromZone(theZone)
theMission.inPopulated = theZone.inPopulated -- transfer for csarFX
return theMission
end
--
-- create a CSAR Mission for a unit
--
@ -1320,12 +1301,9 @@ function csarManager.createCSARforUnit(theUnit, pilotName, radius, silent, score
if not silent then silent = false end
if not radius then radius = 1000 end
if not pilotName then pilotName = "Eddie" end
local point = theUnit:getPoint()
local coal = theUnit:getCoalition()
local csarPoint = dcsCommon.randomPointInCircle(radius, radius/2, point.x, point.z)
csarPoint.y = csarPoint.z
local surf = land.getSurfaceType(csarPoint)
csarPoint.y = land.getHeight(csarPoint)
@ -1343,15 +1321,54 @@ function csarManager.createCSARforUnit(theUnit, pilotName, radius, silent, 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, csarManager.actionSound) -- "Quest Snare 3.wav")
trigger.action.outSoundForGroup(coal, csarManager.actionSound)
end
end
function csarManager.createCSARForParachutist(theUnit, name, coa) -- invoked with parachute guy on ground as theUnit
function csarManager.getClosestInZoneForCoa(point, theZones, coa)
if not theZones then return nil end
local lPoint = {x=point.x, y=0, z=point.z}
local currDelta = math.huge
local closestZone = nil
for zName, zData in pairs(theZones) do
if zData.csarSide == coa and zData.autoTrigger and zData:pointInZone(lPoint) then
local zPoint = cfxZones.getPoint(zData)
local delta = dcsCommon.dist(lPoint, zPoint)
trigger.action.outText("delta is <" .. delta .. ">", 30)
if (delta < currDelta) then
currDelta = delta
closestZone = zData
end
end
end
return closestZone
end
function csarManager.createCSARForParachutist(theUnit, name, coa) -- invoked with parachute guy on ground as theUnit, usually from autoCSAR
trigger.action.outText("Enter createCSARForParachutist", 30)
if not coa then coa = theUnit:getCoalition() end
local pos = theUnit:getPoint()
-- unit DOES NOT HAVE GROUP!!! (unless water splashdown)
-- create a CSAR mission now
-- see if pilot is down in CSAR zone
local theCsarZone = csarManager.getClosestInZoneForCoa(pos, csarManager.csarZones, coa)
if theCsarZone then
-- we use this CSAR zone to generate a CSR at spot pos for coa
if csarManager.verbose or theCsarZone.verbose then
trigger.action.outText("+++CSAR: generating CSAR mission via para drop in zone <" .. theCsarZone.name .. ">", 30)
end
local theMission = csarManager.createCSARMissionFromZone(theCsarZone, pos, name)
csarManager.addMission(theMission, theCsarZone)
--theZone.lastCSARVal = currVal
if csarManager.verbose or theCsarZone.verbose then
trigger.action.outText("+++csar: started parachuted CSAR mission for <" .. theCsarZone.csarName .. ">", 30)
end
trigger.action.outTextForCoalition(coa, "MAYDAY MAYDAY MAYDAY! ".. name .. " requesting extraction from hostile territory!", 30)
trigger.action.outSoundForGroup(coa, csarManager.actionSound)
return
end
local theMission = csarManager.createCSARMissionData(pos, coa, nil, name, nil, nil, nil, 0.1, nil)
csarManager.addMission(theMission)
trigger.action.outTextForCoalition(coa, "MAYDAY MAYDAY MAYDAY! ".. name .. " requesting extraction after eject!", 30)
@ -1373,13 +1390,15 @@ end
function csarManager.addCSARZone(theZone)
table.insert(csarManager.csarZones, theZone)
if csarManager.verbose or theZone.verbose then
trigger.action.outText("+++csar: added csar zone <" .. theZone.name .. ">", 30)
end
end
function csarManager.readCSARZone(theZone)
-- zones have attribute "CSAR"
-- gather data, and then create a mission from this
local mName = theZone:getStringFromZoneProperty("CSAR", theZone.name)
-- if mName == "" then mName = theZone.name end
local theSide = theZone:getCoalitionFromZoneProperty("coalition", 0)
theZone.csarSide = theSide
theZone.csarName = mName -- now deprecating name attributes
@ -1443,17 +1462,17 @@ function csarManager.readCSARZone(theZone)
theZone.clearance = theZone:getNumberFromZoneProperty("inBuiltup", 10)
end
end
-- maxTries is decommed
-- theZone.maxTries = theZone:getNumberFromZoneProperty("maxTries", 20)
if theZone.onRoad and theZone.inPopulated then
trigger.action.outText("warning: competing 'onRoad' and 'inPopulated' attributes in zone <" .. theZone.name .. ">. Using 'onRoad'.", 30)
theZone.inPopulated = false
end
theZone.autoTrigger = theZone:getBoolFromZoneProperty("autoTrigger", true) -- autoCSAR coop
local isAutoCSAR = theZone.autoTrigger
if deferred and not theZone.startCSAR then isAutoCSAR = true end
-- add to list of startable csar
if theZone.startCSAR then
if persistence and persistence.hasDate then
if theZone.startCSAR or isAutoCSAR then
if persistence and persistence.hasData then
-- we load data instead of spawning on start
else
csarManager.addCSARZone(theZone)
@ -1465,10 +1484,13 @@ function csarManager.readCSARZone(theZone)
csarManager.addMission(theMission, theZone)
end
if deferred and not theZone.startCSAR then
trigger.action.outText("+++csar: warning - CSAR Mission in Zone <" .. theZone.name .. "> can't be started", 30)
if isAutoCSAR then
trigger.action.outText("+++csar: warning - CSAR Mission in Zone <" .. theZone.name .. "> can only be started by autoCSAR", 30)
end
if csarManager.verbose or theZone.verbose then
trigger.action.outText("+++csar: processed CSAR zone <" .. theZone.name .. ">", 30)
end
end
function csarManager.processCSARZones()

View File

@ -1630,7 +1630,7 @@ end
-- formations:
-- (default) "line" (left to right along x) -- that is Y direction
-- "line_v" a line top to bottom -- that is X direction
-- "chevron" - left to right middle too top
-- "chevron" - left to right middle to top
-- "scattered", "random" -- random, innerRadius used to clear area in center
-- "circle", "circle_forward" -- circle, forward facing
-- "circle_in" -- circle, inwarf facing

View File

@ -1,5 +1,5 @@
fireCtrl = {}
fireCtrl.version = "1.1.0"
fireCtrl.version = "1.2.0"
fireCtrl.requiredLibs = {
"dcsCommon",
"cfxZones",
@ -24,6 +24,9 @@ fireCtrl.roots = {}
- notifications attribute
- cleanup
- UI attribute
1.2.0 - onStart attribute
ctrOn? attribute
ctrOff? attribute
--]]--
function fireCtrl.checkinPlayer(pName, gName, uName, uType)
@ -124,7 +127,6 @@ function fireCtrl.redirectAction (args)
end
function fireCtrl.doStatus(args)
-- trigger.action.outText("status call", 30)
local gName = args[1]
local uName = args[2]
local gID = args[3]
@ -236,6 +238,25 @@ end
function fireCtrl.update()
timer.scheduleFunction(fireCtrl.update, {}, timer.getTime() + 1/fireCtrl.ups)
-- see if on/off
if fireCtrl.ctrOn and cfxZones.testZoneFlag(fireCtrl, fireCtrl.ctrOn, fireCtrl.method, "lastCtrOn") then
fireCtrl.enabled = true
if fireCtrl.verbose then
trigger.action.outText("+++fCtrl: turning fire control on.", 30)
end
end
if fireCtrl.ctrOff and cfxZones.testZoneFlag(fireCtrl, fireCtrl.ctrOff, fireCtrl.method, "lastCtrOff") then
fireCtrl.enabled = false
if fireCtrl.verbose then
trigger.action.outText("+++fCtrl: turning fire control OFF.", 30)
end
end
-- are we on?
if not fireCtrl.enabled then return end
-- check the numbers of fires burning
local f = 0
local cells = 0
@ -302,6 +323,8 @@ function fireCtrl.saveData()
-- save current heroes. simple clone
local theHeroes = dcsCommon.clone(fireCtrl.heroes)
theData.theHeroes = theHeroes
theData.hasEnabled = true
theData.enabled = fireCtrl.enabled
return theData, fireCtrl.sharedData -- second val only if shared
end
@ -316,6 +339,9 @@ function fireCtrl.loadData()
end
local theHeroes = theData.theHeroes
fireCtrl.heroes = theHeroes
if theData.hasEnabled then
fireCtrl.enabled = theData.enabled
end
end
--
@ -340,6 +366,21 @@ function fireCtrl.readConfigZone()
fireCtrl.notifications = theZone:getBoolFromZoneProperty("notifications", true)
fireCtrl.UI = theZone:getBoolFromZoneProperty("UI", true)
fireCtrl.enabled = theZone:getBoolFromZoneProperty("onStart", true)
if theZone:hasProperty("ctrOn?") then
fireCtrl.ctrOn = theZone:getStringFromZoneProperty("ctrOn?", "<none>")
fireCtrl.lastCtrOn = trigger.misc.getUserFlag(fireCtrl.ctrOn)
end
if not fireCtrl.enabled and not fireCtrl.ctrOn then
trigger.action.outText("***WARNING: fireCtrl cannot be turned on!", 30)
end
if theZone:hasProperty("ctrOff?") then
fireCtrl.ctrOff = theZone:getStringFromZoneProperty("ctrOff?", "<none>")
fireCtrl.lastCtrOff = trigger.misc.getUserFlag(fireCtrl.ctrOff)
end
fireCtrl.method = theZone:getStringFromZoneProperty("method", "change")
if theZone:hasProperty("attachTo:") then
local attachTo = theZone:getStringFromZoneProperty("attachTo:", "<none>")
if radioMenu then -- requires optional radio menu to have loaded

View File

@ -1,5 +1,5 @@
cfxHeloTroops = {}
cfxHeloTroops.version = "4.2.3"
cfxHeloTroops.version = "5.0.0"
cfxHeloTroops.verbose = false
cfxHeloTroops.autoDrop = true
cfxHeloTroops.autoPickup = false
@ -24,6 +24,13 @@ cfxHeloTroops.requestRange = 500 -- meters
4.2.2 - support for attachTo:
4.2.3 - dropZone supports 'keepWait' attribute
- dropZone supports 'setWait' attribute
4.2.4 - dropWait is always active outside of drop zones
5.0.0 - drop options and formation menus. Supported formations
circle_out
chevron
line left
line right
scattered behind
--]]--
cfxHeloTroops.minTime = 3 -- seconds beween tandings
@ -42,6 +49,27 @@ cfxHeloTroops.dropZones = {} -- dict
-- persistence support
cfxHeloTroops.deployedTroops = {}
--
-- drop formation helpers
--
function cfxHeloTroops.formation2text(inFormation)
if inFormation == "circle_out" then return "Circle Around" end
if inFormation == "chevron" then return "Chevron in Front" end
if inFormation == "lineLeft" then return "Line to Port" end
if inFormation == "lineRight" then return "Line to Starboard" end
if inFormation == "gaggle" then return "Gaggle Behind" end
return "ErrFormation"
end
function cfxHeloTroops.formation2dml(inFormation) -- returns formation, delta, phi
if inFormation == "circle_out" then return "circle_out", 0, 0 end
if inFormation == "chevron" then return "chevron", 0, 0 end
if inFormation == "lineLeft" then return "line_v", 12, 4.71239 end -- 4.71239 is 270 degrees
if inFormation == "lineRight" then return "line_v", 12, 1.57079633 end -- 1.57079633 is 90 degrees
if inFormation == "gaggle" then return "scattered", 24, 3.14159265 end -- 3.14159265 is pi is 180 degrees
trigger.action.outText("+++heloT: unknown drop formation <>, using circle_out, 0 ,0", 30)
return "circle_out", 0, 0
end
--
-- drop zones
--
@ -68,7 +96,7 @@ function cfxHeloTroops.resetConfig(conf)
-- the other fields info for troops picked up
conf.troopsOnBoard = {} -- table with the following
conf.troopsOnBoard.name = "***reset***"
conf.dropFormation = "circle_out" -- may be chosen later?
conf.dropFormation = "circle_out" -- used to derive formation, delta, phi in formation2dml, use formation2text for text representation
conf.timeStamp = timer.getTime() -- to avoid double-dipping
end
@ -76,7 +104,7 @@ function cfxHeloTroops.createDefaultConfig(theUnit)
local conf = {}
cfxHeloTroops.resetConfig(conf)
conf.myMainMenu = nil -- this is where the main menu for group will be stored
conf.myCommands = nil -- this is where we put all teh commands in
conf.myCommands = nil -- this is where we put all commands in. Why?
return conf
end
@ -256,13 +284,19 @@ function cfxHeloTroops.removeComms(theUnit)
end
function cfxHeloTroops.addConfigMenu(conf)
-- we add a menu showing current configs
-- we add a menu for auto-drop-off and drop formation
-- trigger.action.outText("enter addConfigMenu for <" .. conf.unit:getName() .. ">", 30)
if conf.myDeployMenu then
missionCommands.removeItemForGroup(conf.id, conf.myDeployMenu)
end
conf.myDeployMenu = missionCommands.addSubMenuForGroup(conf.id, 'Deployment Options', conf.myMainMenu)
local onOff = "OFF"
if conf.autoDrop then onOff = "ON" end
local theCommand = missionCommands.addCommandForGroup(
conf.id,
'Auto-Drop: ' .. onOff .. ' - Select to change',
conf.myMainMenu,
conf.myDeployMenu,
cfxHeloTroops.redirectToggleConfig,
{conf, "drop"}
)
@ -272,11 +306,53 @@ function cfxHeloTroops.addConfigMenu(conf)
theCommand = missionCommands.addCommandForGroup(
conf.id,
'Auto-Pickup: ' .. onOff .. ' - Select to change',
conf.myMainMenu,
conf.myDeployMenu,
cfxHeloTroops.redirectToggleConfig,
{conf, "pickup"}
)
table.insert(conf.myCommands, theCommand)
if conf.myFormationMenu then
missionCommands.removeItemForGroup(conf.id, conf.myFormationMenu)
end
conf.myFormationMenu = missionCommands.addSubMenuForGroup(conf.id, "Set Formation (" .. cfxHeloTroops.formation2text(conf.dropFormation) .. ")", conf.myDeployMenu)
theCommand = missionCommands.addCommandForGroup(
conf.id,
'Circle Around',
conf.myFormationMenu,
cfxHeloTroops.redirectDropFormation,
{conf, "circle_out"}
)
theCommand = missionCommands.addCommandForGroup(
conf.id,
'Chevron in Front',
conf.myFormationMenu,
cfxHeloTroops.redirectDropFormation,
{conf, "chevron"}
)
theCommand = missionCommands.addCommandForGroup(
conf.id,
'Line to Port',
conf.myFormationMenu,
cfxHeloTroops.redirectDropFormation,
{conf, "lineLeft"}
)
theCommand = missionCommands.addCommandForGroup(
conf.id,
'Line to Starboard',
conf.myFormationMenu,
cfxHeloTroops.redirectDropFormation,
{conf, "lineRight"}
)
theCommand = missionCommands.addCommandForGroup(
conf.id,
'Gaggle Behind',
conf.myFormationMenu,
cfxHeloTroops.redirectDropFormation,
{conf, "gaggle"}
)
end
function cfxHeloTroops.setCommsMenu(theUnit)
@ -618,6 +694,23 @@ function cfxHeloTroops.doToggleConfig(args)
cfxHeloTroops.setCommsMenu(conf.unit)
end
--
-- set formation
--
function cfxHeloTroops.redirectDropFormation(args)
timer.scheduleFunction(cfxHeloTroops.doDropFormation, args, timer.getTime() + 0.1)
end
function cfxHeloTroops.doDropFormation(args)
local conf = args[1]
local newFormation = args[2]
conf.dropFormation = newFormation
if cfxHeloTroops.verbose then
trigger.action.outText("Switching <" .. conf.unit:getName() .. ">'s troop deploy formation to <" .. newFormation .. ">", 40)
end
cfxHeloTroops.setCommsMenu(conf.unit)
end
--
-- Deploying Troops
--
@ -725,6 +818,8 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
if closestDropZone.dropCoa == 0 or closestDropZone.dropCoa == theCoalition then
if not closestDropZone.keepWait then dropWait = true end
end
else
dropWait = true -- outside of any drop zones
end
-- see if we are in a drop zone
if dropWait then
@ -746,7 +841,16 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
trigger.action.outTextForGroup(conf.id, "+++ <" .. conf.troopsOnBoard.name .. "> added 'wait' orders: <".. orders .. ">", 30)
else trigger.action.outTextForGroup(conf.id, "+++ <" .. conf.troopsOnBoard.name .. "> keeping orders (".. orders .. ")", 30) end
end
-- calculate drop point using delta, phi and unit heading
local f = "circle_out"
local delta = 0
local phi = 0
f, delta, phi = cfxHeloTroops.formation2dml(conf.dropFormation)
if cfxHeloTroops.verbose then
trigger.action.outText("formation: <" .. f .. ">, delta <" .. delta .. ">, phi <" .. phi .. ">", 30)
end
local uh = dcsCommon.getUnitHeading(theUnit)
p = dcsCommon.pointInDirectionOfPointXYY(uh + phi, delta, p)
local chopperZone = cfxZones.createSimpleZone("choppa", p, 12) -- 12 m radius around choppa
local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
@ -754,8 +858,8 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
theName, -- group name, may be tracked
chopperZone,
unitTypes,
conf.dropFormation,
90,
f, --conf.dropFormation,
uh * 57.2958, -- heading in degrees, may need a formation offset like 90
nil, -- liveries not yet supported
canDrive)
-- persistence management

View File

@ -1,5 +1,5 @@
cfxPlayerScore = {}
cfxPlayerScore.version = "5.2.1"
cfxPlayerScore.version = "5.2.2"
cfxPlayerScore.name = "cfxPlayerScore" -- compatibility with flag bangers
cfxPlayerScore.firstSave = true -- to force overwrite
--[[-- VERSION HISTORY
@ -19,6 +19,8 @@ cfxPlayerScore.firstSave = true -- to force overwrite
5.2.1 - Event 20 (CA join) corrected typo
- wiping score on enter and birth
- more robust initscore
5.2.2 - fixed typo in feat zone
TODO: Kill event no longer invoked for map objetcs, attribute
to faction now, reverse invocation direction with PlayerScore
TODO: better wildcard support for kill events
@ -81,7 +83,7 @@ function cfxPlayerScore.addFeatZone(theZone)
theZone.featType = "KILL"
end
theZone.featDesc = theZone:getStringFromZoneProperty("description", "(some feat)")
theZone.featNum = ctheZone:getNumberFromZoneProperty("awardLimit", -1) -- how many times this can be awarded, -1 is infinite
theZone.featNum = theZone:getNumberFromZoneProperty("awardLimit", -1) -- how many times this can be awarded, -1 is infinite
theZone.ppOnce = theZone:getBoolFromZoneProperty("awardOnce", false)
theZone.awardedTo = {} -- by player name: true/false
table.insert(cfxPlayerScore.featZones, theZone)

View File

@ -1,9 +1,17 @@
sittingDucks = {}
sittingDucks.verbose = false
sittingDucks.version = "1.0.0"
sittingDucks.version = "1.0.1"
sittingDucks.ssbDisabled = 100 -- must match the setting of SSB, usually 100
sittingDucks.resupplyTime = -1 -- seconds until "reinforcements" reopen the slot, set to -1 to turn off, 3600 is one hour
--[[
Version History
1.0.0 Initial Version
1.0.1 DCS releases 2024-jul-11 and 2024-jul-22 bugs hardening
--]]--
--
-- Destroying a client stand-in on an airfield will block that
-- Slot for players. Multiplayer only
@ -22,6 +30,7 @@ function sittingDucks:onEvent(event)
-- home in on the kill event
if event.id == 8 then -- dead event
local theUnit = event.initiator
if not theUnit.getName then return end -- dcs jul-11 and jul-22 bugs
local deadName = theUnit:getName()
if not deadName then return end
-- look at stopGap's collection of stand-ins

View File

@ -1,14 +1,15 @@
cfxSmokeZone = {}
cfxSmokeZone.version = "3.0.0"
cfxSmokeZone.version = "3.0.1"
cfxSmokeZone.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
}
--[[--
--[[-- Copyright (c) 2021-2025 by Christian Franz
Version History
3.0.0 - now supports immediate smoke stop
- supports persistence
- code cleanup
3.0.1 - fixed data load error
--]]--
cfxSmokeZone.smokeZones = {}
cfxSmokeZone.updateDelay = 5 * 60 -- every 5 minutes
@ -143,7 +144,7 @@ function cfxSmokeZone.start()
callbacks.persistData = cfxSmokeZone.saveData
persistence.registerModule("smokeZones", callbacks)
-- now load my data
persistence.loadData() -- will start with update
cfxSmokeZone.loadData() -- will start with update
end
-- start update and checkflag loops
cfxSmokeZone.update() -- also starts all unpaused

View File

@ -1,5 +1,5 @@
unitZone={}
unitZone.version = "2.0.1"
unitZone.version = "2.0.2"
unitZone.verbose = false
unitZone.ups = 1
unitZone.requiredLibs = {
@ -18,6 +18,7 @@ unitZone.requiredLibs = {
- filter synonym
- direct#, directInv# synonyms
2.0.1 - code hardening
2.0.2 - removed backward compat code for deprecated coalition, uzcoalition attributes
--]]--
unitZone.unitZones = {}
@ -81,13 +82,14 @@ function unitZone.createUnitZone(theZone)
-- coalition
theZone.uzCoalition = theZone:getCoalitionFromZoneProperty("unitZone", 0) -- now with main attribute
-- DEPRECATED 2023 SEPT: provided for legacy compatibility
--[[-- DEPRECATED 2023 SEPT: provided for legacy compatibility
if theZone:hasProperty("coalition") then
theZone.uzCoalition = theZone:getCoalitionFromZoneProperty("coalition", 0) -- 0 = all
elseif theZone:hasProperty("uzCoalition") then
theZone.uzCoalition = theZone:getCoalitionFromZoneProperty("uzCoalition", 0)
end
REMOVED 20250308
--]]--
-- DML Method
theZone.uzMethod = theZone:getStringFromZoneProperty("method", "inc")
if theZone:hasProperty("uzMethod") then