DML/modules/csarFX.lua
Christian Franz 59d8bb30cf Version 2.4.5
csarFX, dropFormation in heloTroops
2025-03-13 09:49:23 +01:00

358 lines
13 KiB
Lua

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.
--]]--