mirror of
https://github.com/weyne85/DML.git
synced 2025-10-29 16:57:49 +00:00
358 lines
13 KiB
Lua
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.
|
|
--]]-- |