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 = {}
airtank.version = "1.0.1" airtank.version = "1.0.2"
-- Module to extinguish fires controlled by the 'inferno' module. -- Module to extinguish fires controlled by the 'inferno' module.
-- For 'airtank' fire extinguisher aircraft modules. -- For 'airtank' fire extinguisher aircraft modules.
airtank.requiredLibs = { airtank.requiredLibs = {
@ -11,7 +11,7 @@ airtank.requiredLibs = {
Version History Version History
1.0.0 - Initial release 1.0.0 - Initial release
1.0.1 - removed attachTo: bug 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 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 if data.lastDeparture then -- and data.lastDeparture + 60 < now then
return return
end end
data.lastDeparture = now data.lastDeparture = now
if data.carrying < data.capacity * 0.5 then if (not fireCtrl) or
trigger.action.outTextForGroup(data.gID, "Good luck, " .. pName .. ", remember to top off your tanks before going in.", 30) (fireCtrl and fireCtrl.enabled) then
else local msg = "Good luck, " .. pName .. ", remember to top off your tanks before going in."
trigger.action.outTextForGroup(data.gID, "Good luck and godspeed, " .. pName .. "!", 30) if data.carrying > data.capacity * 0.5 then
end msg = "Good luck and godspeed, " .. pName .. "!"
trigger.action.outSoundForGroup(data.gID, airtank.actionSound) end
trigger.action.outTextForGroup(data.gID, msg, 30)
trigger.action.outSoundForGroup(data.gID, airtank.actionSound)
end
end end
return return
end 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 = {}
csarManager.version = "4.3.0" csarManager.version = "4.4.0"
csarManager.ups = 1 csarManager.ups = 1
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
@ -11,10 +11,14 @@ csarManager.ups = 1
4.2.1 - added Chinook to csar default set (via common) 4.2.1 - added Chinook to csar default set (via common)
4.3.0 - pilot's smoke can now extinguish immediately 4.3.0 - pilot's smoke can now extinguish immediately
- keepSmoke option - 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 AUTOMATICALLY WITH playerScore
INTEGRATES WITH LIMITED AIRFRAMES INTEGRATES WITH LIMITED AIRFRAMES
INTEGRATES AUTOMATICALLY WITH SCRIBE INTEGRATES AUTOMATICALLY WITH SCRIBE
INTEGRATES AUTOMATICALL WITH CSARFX
SUPPORTS PERSISTENCE SUPPORTS PERSISTENCE
--]]-- --]]--
@ -511,10 +515,9 @@ function csarManager.heloLanded(theUnit)
-- handle smoke -- handle smoke
if csarManager.keepSmoke then if csarManager.keepSmoke then
-- trigger.action.outText("keeping smokeName <" .. theMission.smokeName .. "> running", 30)
else else
trigger.action.effectSmokeStop(theMission.smokeName) trigger.action.effectSmokeStop(theMission.smokeName)
-- trigger.action.outText("smokeName <" .. theMission.smokeName .. "> removed", 30)
end end
local args = {} local args = {}
@ -566,8 +569,7 @@ function csarManager.heloDeparted(theUnit)
if not dcsCommon.isTroopCarrier(theUnit, csarManager.troopCarriers) then return end if not dcsCommon.isTroopCarrier(theUnit, csarManager.troopCarriers) then return end
-- if we have timed extractions (i.e. not instantaneous), -- if we have timed extractions (i.e. not instantaneous),
-- then we need to check if we take off after the timer runs out -- then we need to check if we take off after the timer runs out
-- when we take off, all that needs to be done is to change the state -- when we take off, all that needs to be done is to change the state
-- to airborne, and then set the status flag -- to airborne, and then set the status flag
local conf = csarManager.getUnitConfig(theUnit) local conf = csarManager.getUnitConfig(theUnit)
@ -607,7 +609,6 @@ function csarManager.airframeCrashed(theUnit)
local theGroup = theUnit:getGroup() local theGroup = theUnit:getGroup()
conf.id = theGroup:getID() conf.id = theGroup:getID()
-- may want to do something, for now just nothing -- may want to do something, for now just nothing
end end
function csarManager.airframeDitched(theUnit) function csarManager.airframeDitched(theUnit)
@ -622,7 +623,6 @@ function csarManager.airframeDitched(theUnit)
if #conf.troopsOnBoard > 0 then if #conf.troopsOnBoard > 0 then
-- this is where we can create a new CSAR mission -- 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.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 for i=1, #conf.troopsOnBoard do
local msn = conf.troopsOnBoard[i] -- picked up unit(s) local msn = conf.troopsOnBoard[i] -- picked up unit(s)
local theRescuedPilot = msn.name local theRescuedPilot = msn.name
@ -636,7 +636,6 @@ function csarManager.airframeDitched(theUnit)
local myName = conf.name local myName = conf.name
cargoSuper.removeAllMassForCargo(myName, "Evacuees") -- will allocate new empty table cargoSuper.removeAllMassForCargo(myName, "Evacuees") -- will allocate new empty table
end end
-- --
-- --
-- M E N U H A N D L I N G & R E S P O N S E -- 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) function csarManager.removeCommsFromConfig(conf)
csarManager.clearCommsSubmenus(conf) csarManager.clearCommsSubmenus(conf)
if conf.myMainMenu then if conf.myMainMenu then
missionCommands.removeItemForGroup(conf.id, conf.myMainMenu) missionCommands.removeItemForGroup(conf.id, conf.myMainMenu)
conf.myMainMenu = nil conf.myMainMenu = nil
@ -663,13 +661,11 @@ end
function csarManager.removeComms(theUnit) function csarManager.removeComms(theUnit)
if not theUnit then return end if not theUnit then return end
if not theUnit:isExist() then return end if not theUnit:isExist() then return end
local group = theUnit:getGroup() local group = theUnit:getGroup()
local id = group:getID() local id = group:getID()
local conf = csarManager.getUnitConfig(theUnit) local conf = csarManager.getUnitConfig(theUnit)
conf.id = id conf.id = id
conf.unit = theUnit conf.unit = theUnit
csarManager.removeCommsFromConfig(conf) csarManager.removeCommsFromConfig(conf)
end end
@ -821,7 +817,6 @@ function csarManager.doListCSARRequests(args)
report = report .. "\n\nWARNING: NO CSAR BASES TO DELIVER EVACUEES TO" report = report .. "\n\nWARNING: NO CSAR BASES TO DELIVER EVACUEES TO"
end end
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, csarManager.actionSound) trigger.action.outSoundForGroup(conf.id, csarManager.actionSound)
end end
@ -863,10 +858,7 @@ function csarManager.doStatusCarrying(args)
report = report .. "\n\nTotal added weigth: " .. 10 + #conf.troopsOnBoard * csarManager.pilotWeight .. "kg" report = report .. "\n\nTotal added weigth: " .. 10 + #conf.troopsOnBoard * csarManager.pilotWeight .. "kg"
end end
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, csarManager.actionSound) trigger.action.outSoundForGroup(conf.id, csarManager.actionSound)
end end
@ -881,23 +873,19 @@ function csarManager.unloadOne(args)
local theUnit = conf.unit local theUnit = conf.unit
if not theUnit then return end -- ?? if not theUnit then return end -- ??
if not Unit.isExist(theUnit) then return end if not Unit.isExist(theUnit) then return end
local myName = theUnit:getName() local myName = theUnit:getName()
local report = "NYI: unload one" local report = "NYI: unload one"
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, csarManager.actionSound) -- "Quest Snare 3.wav") trigger.action.outSoundForGroup(conf.id, csarManager.actionSound)
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, csarManager.actionSound) -- "Quest Snare 3.wav") trigger.action.outSoundForGroup(conf.id, csarManager.actionSound)
else else
-- simulate a crash but for one unit -- simulate a crash but for one unit
local theSide = theUnit:getCoalition() local theSide = theUnit:getCoalition()
@ -911,12 +899,11 @@ 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, csarManager.actionSound) -- "Quest Snare 3.wav") trigger.action.outSoundForCoalition(theSide, csarManager.actionSound)
-- recalc weight -- recalc weight
local totalMass = 10 + #conf.troopsOnBoard * csarManager.pilotWeight local totalMass = 10 + #conf.troopsOnBoard * csarManager.pilotWeight
trigger.action.setUnitInternalCargo(myName, totalMass) -- 10 kg as empty + per-unit time people 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
end end
@ -1207,10 +1194,8 @@ function csarManager.update() -- every second
csarMission.group = nil -- no more evacuees csarMission.group = nil -- no more evacuees
-- turn off smoke? -- turn off smoke?
if csarManager.keepSmoke then if csarManager.keepSmoke then
-- trigger.action.outText("keeping smokeName <" .. csarMission.smokeName .. "> running", 30)
else else
trigger.action.effectSmokeStop(csarMission.smokeName) trigger.action.effectSmokeStop(csarMission.smokeName)
-- trigger.action.outText("smokeName <" .. csarMission.smokeName .. "> removed", 30)
end end
needsGC = true -- need filtering missions needsGC = true -- need filtering missions
@ -1232,11 +1217,7 @@ function csarManager.update() -- every second
end end
trigger.action.outSoundForGroup(uID, csarManager.pickupSound) trigger.action.outSoundForGroup(uID, csarManager.pickupSound)
end -- hovered long enough
--return -- we only ever rescue one
end -- hovered long enough
-- return -- only ever one winch op
else -- too high for hover 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" 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 csarMission.hoveringUnits[uName] = nil -- reset timer
@ -1246,7 +1227,6 @@ function csarManager.update() -- every second
csarMission.hoveringUnits[uName] = nil csarMission.hoveringUnits[uName] = nil
end end
trigger.action.outTextForGroup(uID, hoverMsg, 30, true) trigger.action.outTextForGroup(uID, hoverMsg, 30, true)
--return -- only ever one winch op
else else
-- remove the hover indicator for this unit -- remove the hover indicator for this unit
csarMission.hoveringUnits[uName] = nil csarMission.hoveringUnits[uName] = nil
@ -1275,8 +1255,6 @@ function csarManager.update() -- every second
-- check if their flag value has changed -- check if their flag value has changed
if theZone.startCSAR then if theZone.startCSAR then
-- this should always be true, but you never know -- 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 if theZone:testZoneFlag(theZone.startCSAR, theZone.triggerMethod, "lastCSARVal") then
local theMission = csarManager.createCSARMissionFromZone(theZone) local theMission = csarManager.createCSARMissionFromZone(theZone)
csarManager.addMission(theMission, theZone) csarManager.addMission(theMission, theZone)
@ -1289,21 +1267,25 @@ function csarManager.update() -- every second
end end
end end
function csarManager.createCSARMissionFromZone(theZone) -- orides pos, name from parachuted msn
function csarManager.createCSARMissionFromZone(theZone, pos, name)
-- set up random point in zone -- set up random point in zone
local mPoint = theZone:getPoint() local mPoint = theZone:getPoint()
if theZone.rndLoc then mPoint = theZone:createRandomPointInZone() end if theZone.rndLoc then mPoint = theZone:createRandomPointInZone() end
if pos then mPoint = pos end -- WILL respect onRoad and inPopulated
if theZone.onRoad then if theZone.onRoad then
mPoint.x, mPoint.z = land.getClosestPointOnRoads('roads',mPoint.x, mPoint.z) mPoint.x, mPoint.z = land.getClosestPointOnRoads('roads',mPoint.x, mPoint.z)
elseif theZone.inPopulated then elseif theZone.inPopulated then
local aPoint = theZone:createRandomPointInPopulatedZone(theZone.clearance) -- no more maxTries: theZone.maxTries) local aPoint = theZone:createRandomPointInPopulatedZone(theZone.clearance) -- no more maxTries: theZone.maxTries)
mPoint = aPoint -- safety in case we need to mod aPoint mPoint = aPoint -- safety in case we need to mod aPoint
end end
if not name then name = theZone.csarName end -- respect oride
local theMission = csarManager.createCSARMissionData( local theMission = csarManager.createCSARMissionData(
mPoint, mPoint,
theZone.csarSide, -- theSide theZone.csarSide, -- theSide
theZone.csarFreq, -- freq theZone.csarFreq, -- freq
theZone.csarName, -- name name, -- theZone.csarName, -- name
theZone.numCrew, -- numCrew theZone.numCrew, -- numCrew
theZone.timeLimit, -- timeLimit theZone.timeLimit, -- timeLimit
theZone.csarMapMarker, -- mapMarker theZone.csarMapMarker, -- mapMarker
@ -1312,7 +1294,6 @@ function csarManager.createCSARMissionFromZone(theZone)
theMission.inPopulated = theZone.inPopulated -- transfer for csarFX theMission.inPopulated = theZone.inPopulated -- transfer for csarFX
return theMission return theMission
end end
-- --
-- create a CSAR Mission for a unit -- 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 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
local point = theUnit:getPoint() local point = theUnit:getPoint()
local coal = theUnit:getCoalition() local coal = theUnit:getCoalition()
local csarPoint = dcsCommon.randomPointInCircle(radius, radius/2, point.x, point.z) local csarPoint = dcsCommon.randomPointInCircle(radius, radius/2, point.x, point.z)
csarPoint.y = csarPoint.z csarPoint.y = csarPoint.z
local surf = land.getSurfaceType(csarPoint) local surf = land.getSurfaceType(csarPoint)
csarPoint.y = land.getHeight(csarPoint) csarPoint.y = land.getHeight(csarPoint)
@ -1343,15 +1321,54 @@ function csarManager.createCSARforUnit(theUnit, pilotName, radius, silent, 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, csarManager.actionSound) -- "Quest Snare 3.wav") trigger.action.outSoundForGroup(coal, csarManager.actionSound)
end end
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 if not coa then coa = theUnit:getCoalition() end
local pos = theUnit:getPoint() local pos = theUnit:getPoint()
-- unit DOES NOT HAVE GROUP!!! (unless water splashdown) -- unit DOES NOT HAVE GROUP!!! (unless water splashdown)
-- create a CSAR mission now -- 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) local theMission = csarManager.createCSARMissionData(pos, coa, nil, name, nil, nil, nil, 0.1, nil)
csarManager.addMission(theMission) csarManager.addMission(theMission)
trigger.action.outTextForCoalition(coa, "MAYDAY MAYDAY MAYDAY! ".. name .. " requesting extraction after eject!", 30) trigger.action.outTextForCoalition(coa, "MAYDAY MAYDAY MAYDAY! ".. name .. " requesting extraction after eject!", 30)
@ -1373,13 +1390,15 @@ end
function csarManager.addCSARZone(theZone) function csarManager.addCSARZone(theZone)
table.insert(csarManager.csarZones, 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 end
function csarManager.readCSARZone(theZone) function csarManager.readCSARZone(theZone)
-- zones have attribute "CSAR" -- zones have attribute "CSAR"
-- gather data, and then create a mission from this -- gather data, and then create a mission from this
local mName = theZone:getStringFromZoneProperty("CSAR", theZone.name) local mName = theZone:getStringFromZoneProperty("CSAR", theZone.name)
-- if mName == "" then mName = theZone.name end
local theSide = theZone:getCoalitionFromZoneProperty("coalition", 0) local theSide = theZone:getCoalitionFromZoneProperty("coalition", 0)
theZone.csarSide = theSide theZone.csarSide = theSide
theZone.csarName = mName -- now deprecating name attributes theZone.csarName = mName -- now deprecating name attributes
@ -1443,17 +1462,17 @@ function csarManager.readCSARZone(theZone)
theZone.clearance = theZone:getNumberFromZoneProperty("inBuiltup", 10) theZone.clearance = theZone:getNumberFromZoneProperty("inBuiltup", 10)
end end
end end
-- maxTries is decommed
-- theZone.maxTries = theZone:getNumberFromZoneProperty("maxTries", 20)
if theZone.onRoad and theZone.inPopulated then if theZone.onRoad and theZone.inPopulated then
trigger.action.outText("warning: competing 'onRoad' and 'inPopulated' attributes in zone <" .. theZone.name .. ">. Using 'onRoad'.", 30) trigger.action.outText("warning: competing 'onRoad' and 'inPopulated' attributes in zone <" .. theZone.name .. ">. Using 'onRoad'.", 30)
theZone.inPopulated = false theZone.inPopulated = false
end 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 -- add to list of startable csar
if theZone.startCSAR then if theZone.startCSAR or isAutoCSAR then
if persistence and persistence.hasDate then if persistence and persistence.hasData then
-- we load data instead of spawning on start -- we load data instead of spawning on start
else else
csarManager.addCSARZone(theZone) csarManager.addCSARZone(theZone)
@ -1465,10 +1484,13 @@ function csarManager.readCSARZone(theZone)
csarManager.addMission(theMission, theZone) csarManager.addMission(theMission, theZone)
end end
if isAutoCSAR then
if deferred and not theZone.startCSAR then trigger.action.outText("+++csar: warning - CSAR Mission in Zone <" .. theZone.name .. "> can only be started by autoCSAR", 30)
trigger.action.outText("+++csar: warning - CSAR Mission in Zone <" .. theZone.name .. "> can't be started", 30)
end end
if csarManager.verbose or theZone.verbose then
trigger.action.outText("+++csar: processed CSAR zone <" .. theZone.name .. ">", 30)
end
end end
function csarManager.processCSARZones() function csarManager.processCSARZones()

View File

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

View File

@ -1,5 +1,5 @@
fireCtrl = {} fireCtrl = {}
fireCtrl.version = "1.1.0" fireCtrl.version = "1.2.0"
fireCtrl.requiredLibs = { fireCtrl.requiredLibs = {
"dcsCommon", "dcsCommon",
"cfxZones", "cfxZones",
@ -24,6 +24,9 @@ fireCtrl.roots = {}
- notifications attribute - notifications attribute
- cleanup - cleanup
- UI attribute - UI attribute
1.2.0 - onStart attribute
ctrOn? attribute
ctrOff? attribute
--]]-- --]]--
function fireCtrl.checkinPlayer(pName, gName, uName, uType) function fireCtrl.checkinPlayer(pName, gName, uName, uType)
@ -124,7 +127,6 @@ function fireCtrl.redirectAction (args)
end end
function fireCtrl.doStatus(args) function fireCtrl.doStatus(args)
-- trigger.action.outText("status call", 30)
local gName = args[1] local gName = args[1]
local uName = args[2] local uName = args[2]
local gID = args[3] local gID = args[3]
@ -236,6 +238,25 @@ end
function fireCtrl.update() function fireCtrl.update()
timer.scheduleFunction(fireCtrl.update, {}, timer.getTime() + 1/fireCtrl.ups) 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 -- check the numbers of fires burning
local f = 0 local f = 0
local cells = 0 local cells = 0
@ -302,6 +323,8 @@ function fireCtrl.saveData()
-- save current heroes. simple clone -- save current heroes. simple clone
local theHeroes = dcsCommon.clone(fireCtrl.heroes) local theHeroes = dcsCommon.clone(fireCtrl.heroes)
theData.theHeroes = theHeroes theData.theHeroes = theHeroes
theData.hasEnabled = true
theData.enabled = fireCtrl.enabled
return theData, fireCtrl.sharedData -- second val only if shared return theData, fireCtrl.sharedData -- second val only if shared
end end
@ -316,6 +339,9 @@ function fireCtrl.loadData()
end end
local theHeroes = theData.theHeroes local theHeroes = theData.theHeroes
fireCtrl.heroes = theHeroes fireCtrl.heroes = theHeroes
if theData.hasEnabled then
fireCtrl.enabled = theData.enabled
end
end end
-- --
@ -340,6 +366,21 @@ function fireCtrl.readConfigZone()
fireCtrl.notifications = theZone:getBoolFromZoneProperty("notifications", true) fireCtrl.notifications = theZone:getBoolFromZoneProperty("notifications", true)
fireCtrl.UI = theZone:getBoolFromZoneProperty("UI", 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 if theZone:hasProperty("attachTo:") then
local attachTo = theZone:getStringFromZoneProperty("attachTo:", "<none>") local attachTo = theZone:getStringFromZoneProperty("attachTo:", "<none>")
if radioMenu then -- requires optional radio menu to have loaded if radioMenu then -- requires optional radio menu to have loaded

View File

@ -1,5 +1,5 @@
cfxHeloTroops = {} cfxHeloTroops = {}
cfxHeloTroops.version = "4.2.3" cfxHeloTroops.version = "5.0.0"
cfxHeloTroops.verbose = false cfxHeloTroops.verbose = false
cfxHeloTroops.autoDrop = true cfxHeloTroops.autoDrop = true
cfxHeloTroops.autoPickup = false cfxHeloTroops.autoPickup = false
@ -24,6 +24,13 @@ cfxHeloTroops.requestRange = 500 -- meters
4.2.2 - support for attachTo: 4.2.2 - support for attachTo:
4.2.3 - dropZone supports 'keepWait' attribute 4.2.3 - dropZone supports 'keepWait' attribute
- dropZone supports 'setWait' 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 cfxHeloTroops.minTime = 3 -- seconds beween tandings
@ -42,6 +49,27 @@ cfxHeloTroops.dropZones = {} -- dict
-- persistence support -- persistence support
cfxHeloTroops.deployedTroops = {} 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 -- drop zones
-- --
@ -68,7 +96,7 @@ function cfxHeloTroops.resetConfig(conf)
-- the other fields info for troops picked up -- the other fields info for troops picked up
conf.troopsOnBoard = {} -- table with the following conf.troopsOnBoard = {} -- table with the following
conf.troopsOnBoard.name = "***reset***" conf.troopsOnBoard.name = "***reset***"
conf.dropFormation = "circle_out" -- may be chosen later? conf.dropFormation = "circle_out" -- used to derive formation, delta, phi in formation2dml, use formation2text for text representation
conf.timeStamp = timer.getTime() -- to avoid double-dipping conf.timeStamp = timer.getTime() -- to avoid double-dipping
end end
@ -76,7 +104,7 @@ function cfxHeloTroops.createDefaultConfig(theUnit)
local conf = {} local conf = {}
cfxHeloTroops.resetConfig(conf) cfxHeloTroops.resetConfig(conf)
conf.myMainMenu = nil -- this is where the main menu for group will be stored 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 return conf
end end
@ -256,13 +284,19 @@ function cfxHeloTroops.removeComms(theUnit)
end end
function cfxHeloTroops.addConfigMenu(conf) 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" local onOff = "OFF"
if conf.autoDrop then onOff = "ON" end if conf.autoDrop then onOff = "ON" end
local theCommand = missionCommands.addCommandForGroup( local theCommand = missionCommands.addCommandForGroup(
conf.id, conf.id,
'Auto-Drop: ' .. onOff .. ' - Select to change', 'Auto-Drop: ' .. onOff .. ' - Select to change',
conf.myMainMenu, conf.myDeployMenu,
cfxHeloTroops.redirectToggleConfig, cfxHeloTroops.redirectToggleConfig,
{conf, "drop"} {conf, "drop"}
) )
@ -272,11 +306,53 @@ function cfxHeloTroops.addConfigMenu(conf)
theCommand = missionCommands.addCommandForGroup( theCommand = missionCommands.addCommandForGroup(
conf.id, conf.id,
'Auto-Pickup: ' .. onOff .. ' - Select to change', 'Auto-Pickup: ' .. onOff .. ' - Select to change',
conf.myMainMenu, conf.myDeployMenu,
cfxHeloTroops.redirectToggleConfig, cfxHeloTroops.redirectToggleConfig,
{conf, "pickup"} {conf, "pickup"}
) )
table.insert(conf.myCommands, theCommand) 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 end
function cfxHeloTroops.setCommsMenu(theUnit) function cfxHeloTroops.setCommsMenu(theUnit)
@ -618,6 +694,23 @@ function cfxHeloTroops.doToggleConfig(args)
cfxHeloTroops.setCommsMenu(conf.unit) cfxHeloTroops.setCommsMenu(conf.unit)
end 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 -- Deploying Troops
-- --
@ -725,6 +818,8 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
if closestDropZone.dropCoa == 0 or closestDropZone.dropCoa == theCoalition then if closestDropZone.dropCoa == 0 or closestDropZone.dropCoa == theCoalition then
if not closestDropZone.keepWait then dropWait = true end if not closestDropZone.keepWait then dropWait = true end
end end
else
dropWait = true -- outside of any drop zones
end end
-- see if we are in a drop zone -- see if we are in a drop zone
if dropWait then if dropWait then
@ -746,7 +841,16 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
trigger.action.outTextForGroup(conf.id, "+++ <" .. conf.troopsOnBoard.name .. "> added 'wait' orders: <".. orders .. ">", 30) 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 else trigger.action.outTextForGroup(conf.id, "+++ <" .. conf.troopsOnBoard.name .. "> keeping orders (".. orders .. ")", 30) end
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 chopperZone = cfxZones.createSimpleZone("choppa", p, 12) -- 12 m radius around choppa
local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition ( local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
@ -754,8 +858,8 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
theName, -- group name, may be tracked theName, -- group name, may be tracked
chopperZone, chopperZone,
unitTypes, unitTypes,
conf.dropFormation, f, --conf.dropFormation,
90, uh * 57.2958, -- heading in degrees, may need a formation offset like 90
nil, -- liveries not yet supported nil, -- liveries not yet supported
canDrive) canDrive)
-- persistence management -- persistence management

View File

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

View File

@ -1,9 +1,17 @@
sittingDucks = {} sittingDucks = {}
sittingDucks.verbose = false 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.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 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 -- Destroying a client stand-in on an airfield will block that
-- Slot for players. Multiplayer only -- Slot for players. Multiplayer only
@ -22,6 +30,7 @@ function sittingDucks:onEvent(event)
-- home in on the kill event -- home in on the kill event
if event.id == 8 then -- dead event if event.id == 8 then -- dead event
local theUnit = event.initiator local theUnit = event.initiator
if not theUnit.getName then return end -- dcs jul-11 and jul-22 bugs
local deadName = theUnit:getName() local deadName = theUnit:getName()
if not deadName then return end if not deadName then return end
-- look at stopGap's collection of stand-ins -- look at stopGap's collection of stand-ins

View File

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

View File

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