Version 1.1.2

More persistence
This commit is contained in:
Christian Franz 2022-08-11 12:33:28 +02:00
parent 7ad32b89c4
commit 78ff9a10e0
22 changed files with 1445 additions and 239 deletions

Binary file not shown.

Binary file not shown.

View File

@ -451,7 +451,7 @@ function FARPZones.saveData()
-- iterate all farp data and put them into a container each -- iterate all farp data and put them into a container each
for theZone, theFARP in pairs(FARPZones.allFARPZones) do for theZone, theFARP in pairs(FARPZones.allFARPZones) do
fName = theZone.name fName = theZone.name
trigger.action.outText("frpZ persistence: processing FARP <" .. fName .. ">", 30) --trigger.action.outText("frpZ persistence: processing FARP <" .. fName .. ">", 30)
local fData = {} local fData = {}
fData.owner = theFARP.owner fData.owner = theFARP.owner
fData.defenderData = dcsCommon.clone(theFARP.defenderData) fData.defenderData = dcsCommon.clone(theFARP.defenderData)

View File

@ -1,5 +1,5 @@
rndFlags = {} rndFlags = {}
rndFlags.version = "1.3.2" rndFlags.version = "1.4.0"
rndFlags.verbose = false rndFlags.verbose = false
rndFlags.requiredLibs = { rndFlags.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
@ -30,6 +30,8 @@ rndFlags.requiredLibs = {
- added 'rndDone!' flag - added 'rndDone!' flag
- rndMethod defaults to "inc" - rndMethod defaults to "inc"
1.3.2 - moved flagArrayFromString to dcsCommon 1.3.2 - moved flagArrayFromString to dcsCommon
- minor clean-up
1.4.0 - persistence
--]] --]]
@ -39,60 +41,16 @@ function rndFlags.addRNDZone(aZone)
table.insert(rndFlags.rndGen, aZone) table.insert(rndFlags.rndGen, aZone)
end end
function rndFlags.getRNDByName(aName)
for idx, theRND in pairs(rndFlags.rndGen) do
if theRND.name == aName then return theRND end
end
return nil
end
function rndFlags.flagArrayFromString(inString) function rndFlags.flagArrayFromString(inString)
return dcsCommon.flagArrayFromString(inString, rndFlags.verbose) return dcsCommon.flagArrayFromString(inString, rndFlags.verbose)
--[[--
if string.len(inString) < 1 then
trigger.action.outText("+++RND: empty flags", 30)
return {}
end
if rndFlags.verbose then
trigger.action.outText("+++RND: processing <" .. inString .. ">", 30)
end
local flags = {}
local rawElements = dcsCommon.splitString(inString, ",")
-- go over all elements
for idx, anElement in pairs(rawElements) do
if dcsCommon.stringStartsWithDigit(anElement) and dcsCommon.containsString(anElement, "-") then
-- interpret this as a range
local theRange = dcsCommon.splitString(anElement, "-")
local lowerBound = theRange[1]
lowerBound = tonumber(lowerBound)
local upperBound = theRange[2]
upperBound = tonumber(upperBound)
if lowerBound and upperBound then
-- swap if wrong order
if lowerBound > upperBound then
local temp = upperBound
upperBound = lowerBound
lowerBound = temp
end
-- now add add numbers to flags
for f=lowerBound, upperBound do
table.insert(flags, f)
end
else
-- bounds illegal
trigger.action.outText("+++RND: ignored range <" .. anElement .. "> (range)", 30)
end
else
-- single number
f = dcsCommon.trim(anElement) -- DML flag upgrade: accept strings tonumber(anElement)
if f then
table.insert(flags, f)
else
trigger.action.outText("+++RND: ignored element <" .. anElement .. "> (single)", 30)
end
end
end
if rndFlags.verbose then
trigger.action.outText("+++RND: <" .. #flags .. "> flags total", 30)
end
return flags
--]]--
end end
-- --
@ -214,7 +172,6 @@ function rndFlags.fire(theZone)
if rndFlags.verbose or theZone.verbose then if rndFlags.verbose or theZone.verbose then
trigger.action.outText("+++RND: RND " .. theZone.name .. " ran out of flags. Will fire 'done' instead ", 30) trigger.action.outText("+++RND: RND " .. theZone.name .. " ran out of flags. Will fire 'done' instead ", 30)
end end
if theZone.doneFlag then if theZone.doneFlag then
cfxZones.pollFlag(theZone.doneFlag, theZone.rndMethod, theZone) cfxZones.pollFlag(theZone.doneFlag, theZone.rndMethod, theZone)
end end
@ -281,15 +238,67 @@ end
function rndFlags.startCycle() function rndFlags.startCycle()
for idx, theZone in pairs(rndFlags.rndGen) do for idx, theZone in pairs(rndFlags.rndGen) do
if theZone.onStart then if theZone.onStart then
if rndFlags.verbose or theZone.verbose then if theZone.isStarted then
trigger.action.outText("+++RND: starting " .. theZone.name, 30) -- suppressed by persistence
else
if rndFlags.verbose or theZone.verbose then
trigger.action.outText("+++RND: starting " .. theZone.name, 30)
end
rndFlags.fire(theZone)
end end
rndFlags.fire(theZone)
end end
end end
end end
--
-- Load / Save data
--
function rndFlags.saveData()
local theData = {}
local allRND = {}
for idx, theRND in pairs(rndFlags.rndGen) do
local theName = theRND.name
local rndData = {}
-- save data for this RND
rndData.myFlags = dcsCommon.clone(theRND.myFlags)
allRND[theName] = rndData
end
theData.allRND = allRND
return theData
end
function rndFlags.loadData()
if not persistence then return end
local theData = persistence.getSavedDataForModule("rndFlags")
if not theData then
if rndFlags.verbose then
trigger.action.outText("+++RND Persistence: no save date received, skipping.", 30)
end
return
end
local allRND = theData.allRND
if not allRND then
if rndFlags.verbose then
trigger.action.outText("+++RND Persistence - no data, skipping", 30)
end
return
end -- no data, no proccing
for theName, rData in pairs(allRND) do
local theRND = rndFlags.getRNDByName(theName)
if theRND then
-- get current myFlags
local myFlags = dcsCommon.clone(rData.myFlags)
theRND.myFlags = myFlags
theRND.isStarted = true -- we are initted, NO ON START
else
trigger.action.outText("+++RND persistecne: can't synch RND <" .. theName .. ">", 30)
end
end
end
-- --
-- start module and read config -- start module and read config
-- --
@ -312,7 +321,7 @@ end
function rndFlags.start() function rndFlags.start()
-- lib check -- lib check
if not dcsCommon.libCheck then if not dcsCommon then
trigger.action.outText("RNDFlags requires dcsCommon", 30) trigger.action.outText("RNDFlags requires dcsCommon", 30)
return false return false
end end
@ -345,6 +354,15 @@ function rndFlags.start()
rndFlags.addRNDZone(aZone) -- remember it so we can smoke it rndFlags.addRNDZone(aZone) -- remember it so we can smoke it
end end
if persistence then
-- sign up for persistence
callbacks = {}
callbacks.persistData = rndFlags.saveData
persistence.registerModule("rndFlags", callbacks)
-- now load my data
rndFlags.loadData()
end
-- start cycle -- start cycle
timer.scheduleFunction(rndFlags.startCycle, {}, timer.getTime() + 0.25) timer.scheduleFunction(rndFlags.startCycle, {}, timer.getTime() + 0.25)
@ -361,4 +379,3 @@ if not rndFlags.start() then
rndFlags = nil rndFlags = nil
end end
-- TODO: move flags to RND!, rename RND to RND!, deprecate flags!

View File

@ -1,5 +1,5 @@
cfxGroundTroops = {} cfxGroundTroops = {}
cfxGroundTroops.version = "1.7.6" cfxGroundTroops.version = "1.7.7"
cfxGroundTroops.ups = 1 cfxGroundTroops.ups = 1
cfxGroundTroops.verbose = false cfxGroundTroops.verbose = false
cfxGroundTroops.requiredLibs = { cfxGroundTroops.requiredLibs = {
@ -61,6 +61,7 @@ cfxGroundTroops.deployedTroops = {}
-- 1.7.4 - verbose flag, warnings suppressed -- 1.7.4 - verbose flag, warnings suppressed
-- 1.7.5 - some troop.group hardening with isExist() -- 1.7.5 - some troop.group hardening with isExist()
-- 1.7.6 - fixed switchToOffroad -- 1.7.6 - fixed switchToOffroad
-- 1.7.7 - no longer case sensitive for orders
-- an entry into the deployed troop has the following attributes -- an entry into the deployed troop has the following attributes
@ -580,17 +581,17 @@ function cfxGroundTroops.updateTroops(troop)
-- their order is removed, and the 'real' orders -- their order is removed, and the 'real' orders
-- are revealed. For now, do nothing -- are revealed. For now, do nothing
cfxGroundTroops.updateWait(troop) cfxGroundTroops.updateWait(troop)
--REMEMBER: LOWER CASE ONLY!
elseif troop.orders == "guard" then elseif troop.orders == "guard" then
cfxGroundTroops.updateGuards(troop) cfxGroundTroops.updateGuards(troop)
elseif troop.orders == "attackOwnedZone" then elseif troop.orders == "attackownedzone" then
cfxGroundTroops.updateZoneAttackers(troop) cfxGroundTroops.updateZoneAttackers(troop)
elseif troop.orders == "laze" then elseif troop.orders == "laze" then
cfxGroundTroops.updateLaze(troop) cfxGroundTroops.updateLaze(troop)
elseif troop.orders == "attackZone" then elseif troop.orders == "attackzone" then
cfxGroundTroops.updateAttackers(troop) cfxGroundTroops.updateAttackers(troop)
else else
@ -752,7 +753,7 @@ function cfxGroundTroops.updateSingleScheduled(params)
-- check max speed of group. if < 0.1 then note and increase -- check max speed of group. if < 0.1 then note and increase
-- speedWarning. if not, reset speed warning -- speedWarning. if not, reset speed warning
if troops.orders == "attackOwnedZone" and dcsCommon.getGroupMaxSpeed(troops.group) < 0.1 then if troops.orders == "attackownedzone" and dcsCommon.getGroupMaxSpeed(troops.group) < 0.1 then
if not troops.speedWarning then troops.speedWarning = 0 end if not troops.speedWarning then troops.speedWarning = 0 end
troops.speedWarning = troops.speedWarning + 1 troops.speedWarning = troops.speedWarning + 1
else else
@ -918,7 +919,7 @@ function cfxGroundTroops.getTroopReport(theSide, ignoreInfantry)
if troop.side == theSide and troop.group:isExist() then if troop.side == theSide and troop.group:isExist() then
local unitNum = troop.group:getSize() local unitNum = troop.group:getSize()
report = report .. "\n" .. troop.name .. " (".. unitNum .."): <" .. troop.orders .. ">" report = report .. "\n" .. troop.name .. " (".. unitNum .."): <" .. troop.orders .. ">"
if troop.orders == "attackOwnedZone" then if troop.orders == "attackownedzone" then
if troop.destination then if troop.destination then
report = report .. " move towards " .. troop.destination.name report = report .. " move towards " .. troop.destination.name
else else
@ -944,19 +945,16 @@ function cfxGroundTroops.createGroundTroops(inGroup, range, orders)
local newTroops = {} local newTroops = {}
if not orders then if not orders then
orders = "guard" orders = "guard"
--trigger.action.outText("+++ adding ground troops <".. inGroup:getName() ..">with default orders", 30)
else
--trigger.action.outText("+++ adding ground troops <".. inGroup:getName() ..">with orders " .. orders, 30)
end end
if orders:lower() == "lase" then if orders:lower() == "lase" then
orders = "laze" -- we use WRONG spelling here, cause we're cool orders = "laze" -- we use WRONG spelling here, cause we're cool. yeah, right.
end end
newTroops.insideDestination = false newTroops.insideDestination = false
newTroops.unscheduleCount = 0 -- will count up as we aren't scheduled newTroops.unscheduleCount = 0 -- will count up as we aren't scheduled
newTroops.speedWarning = 0 newTroops.speedWarning = 0
newTroops.isOffroad = false -- if true, we switched to direct orders, not roads, after standstill newTroops.isOffroad = false -- if true, we switched to direct orders, not roads, after standstill
newTroops.group = inGroup newTroops.group = inGroup
newTroops.orders = orders newTroops.orders = orders:lower()
newTroops.coalition = inGroup:getCoalition() newTroops.coalition = inGroup:getCoalition()
newTroops.side = newTroops.coalition -- because we'e been using both. newTroops.side = newTroops.coalition -- because we'e been using both.
newTroops.name = inGroup:getName() newTroops.name = inGroup:getName()
@ -972,7 +970,8 @@ function cfxGroundTroops.addGroundTroopsToPool(troops) -- troops MUST be a table
trigger.action.outText("+++ adding ground troops with unsupported troop signature", 30) trigger.action.outText("+++ adding ground troops with unsupported troop signature", 30)
return return
end end
if not troops.orders then troops.orders = "guard" end
troops.orders = troops.orders:lower()
troops.reschedule = true -- in case we use scheduled update troops.reschedule = true -- in case we use scheduled update
-- we now add to internal array. this is worked on by all -- we now add to internal array. this is worked on by all
-- update meths, on scheduled upadtes, it is only used to -- update meths, on scheduled upadtes, it is only used to

View File

@ -146,8 +146,6 @@ end
function cfxGroups.start() function cfxGroups.start()
cfxGroups.fetchAllGroupsFromDCS() -- read all groups from mission. cfxGroups.fetchAllGroupsFromDCS() -- read all groups from mission.
-- cfxGroups.showAllGroups()
trigger.action.outText("cfxGroups version " .. cfxGroups.version .. " started", 30) trigger.action.outText("cfxGroups version " .. cfxGroups.version .. " started", 30)
return true return true

View File

@ -1,5 +1,5 @@
cfxHeloTroops = {} cfxHeloTroops = {}
cfxHeloTroops.version = "2.1.0" cfxHeloTroops.version = "2.2.0"
cfxHeloTroops.verbose = false cfxHeloTroops.verbose = false
cfxHeloTroops.autoDrop = true cfxHeloTroops.autoDrop = true
cfxHeloTroops.autoPickup = false cfxHeloTroops.autoPickup = false
@ -18,6 +18,10 @@ cfxHeloTroops.pickupRange = 100 -- meters
-- -- check spawner legality by types -- -- check spawner legality by types
-- -- updated types to include 2.7.6 additions to infantry -- -- updated types to include 2.7.6 additions to infantry
-- -- updated types to include stinger/manpads -- -- updated types to include stinger/manpads
-- 2.2.0 -- minor maintenance (dcsCommon)
-- -- (re?) connected readConfigZone (wtf?)
-- -- persistence support
-- -- made legalTroops entrirely optional and defer to dcsComon else
-- --
-- cfxHeloTroops -- a module to pick up and drop infantry. Can be used with any helo, -- cfxHeloTroops -- a module to pick up and drop infantry. Can be used with any helo,
@ -40,10 +44,15 @@ cfxHeloTroops.requiredLibs = {
cfxHeloTroops.unitConfigs = {} -- all configs are stored by unit's name cfxHeloTroops.unitConfigs = {} -- all configs are stored by unit's name
cfxHeloTroops.myEvents = {3, 4, 5} -- 3- takeoff, 4 - land, 5 - crash cfxHeloTroops.myEvents = {3, 4, 5} -- 3- takeoff, 4 - land, 5 - crash
cfxHeloTroops.legalTroops = {"Soldier AK", "Infantry AK", "Infantry AK ver2", "Infantry AK ver3", "Infantry AK Ins", "Soldier M249", "Soldier M4 GRG", "Soldier M4", "Soldier RPG", "Paratrooper AKS-74", "Paratrooper RPG-16", "Stinger comm dsr", "Stinger comm", "Soldier stinger", "SA-18 Igla-S comm", "SA-18 Igla-S manpad", "Igla manpad INS", "SA-18 Igla comm", "SA-18 Igla manpad",}
-- legalTroops now optional, else check against dcsCommon.typeIsInfantry
--cfxHeloTroops.legalTroops = {"Soldier AK", "Infantry AK", "Infantry AK ver2", "Infantry AK ver3", "Infantry AK Ins", "Soldier M249", "Soldier M4 GRG", "Soldier M4", "Soldier RPG", "Paratrooper AKS-74", "Paratrooper RPG-16", "Stinger comm dsr", "Stinger comm", "Soldier stinger", "SA-18 Igla-S comm", "SA-18 Igla-S manpad", "Igla manpad INS", "SA-18 Igla comm", "SA-18 Igla manpad",}
cfxHeloTroops.troopWeight = 100 -- kg average weight per trooper cfxHeloTroops.troopWeight = 100 -- kg average weight per trooper
-- persistence support
cfxHeloTroops.deployedTroops = {}
function cfxHeloTroops.resetConfig(conf) function cfxHeloTroops.resetConfig(conf)
conf.autoDrop = cfxHeloTroops.autoDrop --if true, will drop troops on-board upon touchdown conf.autoDrop = cfxHeloTroops.autoDrop --if true, will drop troops on-board upon touchdown
conf.autoPickup = cfxHeloTroops.autoPickup -- if true will load nearest troops upon touchdown conf.autoPickup = cfxHeloTroops.autoPickup -- if true will load nearest troops upon touchdown
@ -113,7 +122,7 @@ function cfxHeloTroops.preProcessor(event)
-- make sure it has an initiator -- make sure it has an initiator
if not event.initiator then return false end -- no initiator if not event.initiator then return false end -- no initiator
local theUnit = event.initiator local theUnit = event.initiator
if not cfxPlayer.isPlayerUnit(theUnit) then return false end -- not a player unit if not dcsCommon.isPlayerUnit(theUnit) then return false end -- not a player unit
local cat = theUnit:getCategory() local cat = theUnit:getCategory()
if cat ~= Group.Category.HELICOPTER then return false end if cat ~= Group.Category.HELICOPTER then return false end
@ -414,9 +423,16 @@ function cfxHeloTroops.addGroundMenu(conf)
local typeArray = dcsCommon.splitString(theTypes, ',') local typeArray = dcsCommon.splitString(theTypes, ',')
typeArray = dcsCommon.trimArray(typeArray) typeArray = dcsCommon.trimArray(typeArray)
local allLegal = true local allLegal = true
-- check agianst default (dcsCommon) or own definition (if exists)
for idy, aType in pairs(typeArray) do for idy, aType in pairs(typeArray) do
if not dcsCommon.arrayContainsString(cfxHeloTroops.legalTroops, aType) then if cfxHeloTroops.legalTroops then
allLegal = false if not dcsCommon.arrayContainsString(cfxHeloTroops.legalTroops, aType) then
allLegal = false
end
else
if not dcsCommon.typeIsInfantry(aType) then
allLegal = false
end
end end
end end
if allLegal then if allLegal then
@ -503,9 +519,16 @@ function cfxHeloTroops.filterTroopsByType(unitsToLoad)
local pass = true local pass = true
for iT, sT in pairs(aT) do for iT, sT in pairs(aT) do
-- check if this is a valid type -- check if this is a valid type
if not dcsCommon.arrayContainsString(cfxHeloTroops.legalTroops, sT) then if cfxHeloTroops.legalTroops then
pass = false if not dcsCommon.arrayContainsString(cfxHeloTroops.legalTroops, sT) then
break pass = false
break
end
else
if not dcsCommon.typeIsInfantry(sT) then
pass = false
break
end
end end
end end
if pass then if pass then
@ -604,13 +627,21 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
local chopperZone = cfxZones.createSimpleZone("choppa", p, 12) -- 12 m ratius around choppa local chopperZone = cfxZones.createSimpleZone("choppa", p, 12) -- 12 m ratius around choppa
--local theCoalition = theUnit:getCountry() -- make it choppers country --local theCoalition = theUnit:getCountry() -- make it choppers country
local theCoalition = theUnit:getGroup():getCoalition() -- make it choppers COALITION local theCoalition = theUnit:getGroup():getCoalition() -- make it choppers COALITION
local theGroup = cfxZones.createGroundUnitsInZoneForCoalition ( local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
theCoalition, theCoalition,
conf.troopsOnBoard.name, -- dcsCommon.uuid("Assault"), -- maybe use config name as loaded from the group conf.troopsOnBoard.name, -- dcsCommon.uuid("Assault"), -- maybe use config name as loaded from the group
chopperZone, chopperZone,
unitTypes, unitTypes,
conf.dropFormation, conf.dropFormation,
90) 90)
-- persistence management
local troopData = {}
troopData.groupData = theData
troopData.orders = orders -- always set
troopData.side = theCoalition
troopData.range = range
cfxHeloTroops.deployedTroops[theData.name] = troopData
local troop = cfxGroundTroops.createGroundTroops(theGroup, range, orders) -- use default range and orders local troop = cfxGroundTroops.createGroundTroops(theGroup, range, orders) -- use default range and orders
-- instead of scheduling tasking in one second, we add to -- instead of scheduling tasking in one second, we add to
-- ground troops pool, and the troop pool manager will assign some enemies -- ground troops pool, and the troop pool manager will assign some enemies
@ -663,8 +694,13 @@ function cfxHeloTroops.doLoadGroup(args)
-- TODO: ensure compatibility with CSAR module -- TODO: ensure compatibility with CSAR module
group:destroy() group:destroy()
-- now immediately run a GC so this group is removed
-- from any save data
cfxHeloTroops.GC()
-- say so -- say so
trigger.action.outTextForGroup(conf.id, "Team '".. conf.troopsOnBoard.name .."' aboard, ready to go!", 30) trigger.action.outTextForGroup(conf.id, "Team '".. conf.troopsOnBoard.name .."' aboard, ready to go!", 30)
-- reset menu -- reset menu
cfxHeloTroops.removeComms(conf.unit) cfxHeloTroops.removeComms(conf.unit)
cfxHeloTroops.setCommsMenu(conf.unit) cfxHeloTroops.setCommsMenu(conf.unit)
@ -748,6 +784,32 @@ function cfxHeloTroops.playerChangeEvent(evType, description, player, data)
end end
--
-- Regular GC and housekeeping
--
function cfxHeloTroops.GC()
-- GC run. remove all my dead remembered troops
local filteredAttackers = {}
local before = #cfxHeloTroops.deployedTroops
for gName, gData in pairs (cfxHeloTroops.deployedTroops) do
-- all we need to do is get the group of that name
-- and if it still returns units we are fine
local gameGroup = Group.getByName(gName)
if gameGroup and gameGroup:isExist() and gameGroup:getSize() > 0 then
filteredAttackers[gName] = gData
end
end
cfxHeloTroops.deployedTroops = filteredAttackers
if cfxHeloTroops.verbose then
trigger.action.outText("helo troops GC ran: before <" .. before .. ">, after <" .. #cfxHeloTroops.deployedTroops .. ">", 30)
end
end
function cfxHeloTroops.houseKeeping()
timer.scheduleFunction(cfxHeloTroops.houseKeeping, {}, timer.getTime() + 5 * 60) -- every 5 minutes
cfxHeloTroops.GC()
end
-- --
-- read config zone -- read config zone
@ -757,7 +819,7 @@ function cfxHeloTroops.readConfigZone()
local theZone = cfxZones.getZoneByName("heloTroopsConfig") local theZone = cfxZones.getZoneByName("heloTroopsConfig")
if not theZone then if not theZone then
trigger.action.outText("+++heloT: no config zone!", 30) trigger.action.outText("+++heloT: no config zone!", 30)
return theZone = cfxZones.createSimpleZone("heloTroopsConfig")
end end
cfxHeloTroops.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false) cfxHeloTroops.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
@ -781,6 +843,60 @@ function cfxHeloTroops.readConfigZone()
end end
--
-- Load / Save data
--
function cfxHeloTroops.saveData()
local theData = {}
local allTroopData = {}
-- run a GC pre-emptively
cfxHeloTroops.GC()
-- now simply iterate and save all deployed troops
for gName, gData in pairs(cfxHeloTroops.deployedTroops) do
local sData = dcsCommon.clone(gData)
dcsCommon.synchGroupData(sData.groupData)
allTroopData[gName] = sData
end
theData.troops = allTroopData
return theData
end
function cfxHeloTroops.loadData()
if not persistence then return end
local theData = persistence.getSavedDataForModule("cfxHeloTroops")
if not theData then
if cfxHeloTroops.verbose then
trigger.action.outText("+++heloT: no save date received, skipping.", 30)
end
return
end
-- simply spawn all troops that we have carried around and
-- were still alive when we saved. Troops that were picked
-- up by helos never made it to the save file
local allTroopData = theData.troops
for gName, gdTroop in pairs (allTroopData) do
local gData = gdTroop.groupData
local orders = gdTroop.orders
local side = gdTroop.side
local range = gdTroop.range
local cty = gData.cty
local cat = gData.cat
-- now spawn, but first
-- add to my own deployed queue so we can save later
local gdClone = dcsCommon.clone(gdTroop)
cfxHeloTroops.deployedTroops[gName] = gdClone
local theGroup = coalition.addGroup(cty, cat, gData)
-- post-proccing for cfxGroundTroops
-- add to groundTroops
local newTroops = cfxGroundTroops.createGroundTroops(theGroup, range, orders)
cfxGroundTroops.addGroundTroopsToPool(newTroops)
end
end
-- --
-- Start -- Start
-- --
@ -791,7 +907,11 @@ function cfxHeloTroops.start()
return false return false
end end
-- read config zone -- read config zone
cfxHeloTroops.readConfigZone()
-- start housekeeping
cfxHeloTroops.houseKeeping()
-- install callbacks for helo-relevant events -- install callbacks for helo-relevant events
dcsCommon.addEventHandler(cfxHeloTroops.somethingHappened, cfxHeloTroops.preProcessor, cfxHeloTroops.postProcessor) dcsCommon.addEventHandler(cfxHeloTroops.somethingHappened, cfxHeloTroops.preProcessor, cfxHeloTroops.postProcessor)
@ -807,6 +927,18 @@ function cfxHeloTroops.start()
cfxPlayer.addMonitor(cfxHeloTroops.playerChangeEvent) cfxPlayer.addMonitor(cfxHeloTroops.playerChangeEvent)
trigger.action.outText("cf/x Helo Troops v" .. cfxHeloTroops.version .. " started", 30) trigger.action.outText("cf/x Helo Troops v" .. cfxHeloTroops.version .. " started", 30)
-- now load all save data and populate map with troops that
-- we deployed last save.
if persistence then
-- sign up for persistence
callbacks = {}
callbacks.persistData = cfxHeloTroops.saveData
persistence.registerModule("cfxHeloTroops", callbacks)
-- now load my data
cfxHeloTroops.loadData()
end
return true return true
end end

View File

@ -1,5 +1,5 @@
cfxMX = {} cfxMX = {}
cfxMX.version = "1.2.1" cfxMX.version = "1.2.2"
cfxMX.verbose = false cfxMX.verbose = false
--[[-- --[[--
Mission data decoder. Access to ME-built mission structures Mission data decoder. Access to ME-built mission structures
@ -18,7 +18,9 @@ cfxMX.verbose = false
- added references for allFixed, allHelo, allGround, allSea, allStatic - added references for allFixed, allHelo, allGround, allSea, allStatic
1.2.1 - added countryByName 1.2.1 - added countryByName
- added linkByName - added linkByName
1.2.2 - fixed ctry bug in countryByName
- playerGroupByName
- playerUnitByName
--]]-- --]]--
cfxMX.groupNamesByID = {} cfxMX.groupNamesByID = {}
cfxMX.groupIDbyName = {} cfxMX.groupIDbyName = {}
@ -29,7 +31,10 @@ cfxMX.allFixedByName = {}
cfxMX.allHeloByName = {} cfxMX.allHeloByName = {}
cfxMX.allGroundByName = {} cfxMX.allGroundByName = {}
cfxMX.allSeaByName = {} cfxMX.allSeaByName = {}
cfxMX.allStaticByName ={} cfxMX.allStaticByName = {}
cfxMX.playerGroupByName = {} -- returns data only if a player is in group
cfxMX.playerUnitByName = {} -- returns data only if this is a player unit
function cfxMX.getGroupFromDCSbyName(aName, fetchOriginal) function cfxMX.getGroupFromDCSbyName(aName, fetchOriginal)
if not fetchOriginal then fetchOriginal = false end if not fetchOriginal then fetchOriginal = false end
@ -145,14 +150,11 @@ function cfxMX.getStaticFromDCSbyName(aName, fetchOriginal)
theStatic.groupId = group_data.groupId theStatic.groupId = group_data.groupId
-- copy linked unit data -- copy linked unit data
theStatic.linkUnit = linkUnit theStatic.linkUnit = linkUnit
end end
return theStatic, category, countryID, groupName return theStatic, category, countryID, groupName
end -- if name match end -- if name match
end -- for all units end -- for all units
end -- has groups end -- has groups
end -- is a static end -- is a static
end --if has category data end --if has category data
end --if plane, helo etc... category end --if plane, helo etc... category
@ -207,7 +209,8 @@ function cfxMX.createCrossReferences()
cfxMX.groupNamesByID[aID] = aName cfxMX.groupNamesByID[aID] = aName
cfxMX.groupIDbyName[aName] = aID cfxMX.groupIDbyName[aName] = aID
cfxMX.groupDataByName[aName] = group_data cfxMX.groupDataByName[aName] = group_data
cfxMX.countryByName[aName] = cntry_id cfxMX.countryByName[aName] = countryID -- !!! was cntry_id
-- now make the type-specific xrefs -- now make the type-specific xrefs
if obj_type_name == "helicopter" then if obj_type_name == "helicopter" then
cfxMX.allHeloByName[aName] = group_data cfxMX.allHeloByName[aName] = group_data
@ -223,7 +226,18 @@ function cfxMX.createCrossReferences()
-- should be impossible, but still -- should be impossible, but still
trigger.action.outText("+++MX: <" .. obj_type_name .. "> unknown type for <" .. aName .. ">", 30) trigger.action.outText("+++MX: <" .. obj_type_name .. "> unknown type for <" .. aName .. ">", 30)
end end
end -- now iterate all units in this group
-- for player into
for unit_num, unit_data in pairs(group_data.units) do
if unit_data.skill then
if unit_data.skill == "Client" or unit_data.skill == "Player" then
-- player unit
cfxMX.playerUnitByName[unit_data.name] = unit_data
cfxMX.playerGroupByName[aName] = group_data -- inefficient, but works
end -- if unit skill client
end -- if has skill
end -- for all units
end -- for all groups
end --if has category data end --if has category data
end --if plane, helo etc... category end --if plane, helo etc... category
end --for all objects in country end --for all objects in country

View File

@ -1,5 +1,5 @@
cfxOwnedZones = {} cfxOwnedZones = {}
cfxOwnedZones.version = "1.2.0" cfxOwnedZones.version = "1.2.1"
cfxOwnedZones.verbose = false cfxOwnedZones.verbose = false
cfxOwnedZones.announcer = true cfxOwnedZones.announcer = true
cfxOwnedZones.name = "cfxOwnedZones" cfxOwnedZones.name = "cfxOwnedZones"
@ -45,6 +45,7 @@ cfxOwnedZones.name = "cfxOwnedZones"
1.2.0 - support for persistence 1.2.0 - support for persistence
- conq+1 --> conquered! - conq+1 --> conquered!
- no cfxGroundTroop bug (no delay) - no cfxGroundTroop bug (no delay)
1.2.1 - fix in load to correctly re-establish all attackers for subsequent save
--]]-- --]]--
cfxOwnedZones.requiredLibs = { cfxOwnedZones.requiredLibs = {
@ -797,6 +798,7 @@ end
function cfxOwnedZones.GC() function cfxOwnedZones.GC()
-- GC run. remove all my dead remembered troops -- GC run. remove all my dead remembered troops
local before = #cfxOwnedZones.spawnedAttackers
local filteredAttackers = {} local filteredAttackers = {}
for gName, gData in pairs (cfxOwnedZones.spawnedAttackers) do for gName, gData in pairs (cfxOwnedZones.spawnedAttackers) do
-- all we need to do is get the group of that name -- all we need to do is get the group of that name
@ -807,6 +809,9 @@ function cfxOwnedZones.GC()
end end
end end
cfxOwnedZones.spawnedAttackers = filteredAttackers cfxOwnedZones.spawnedAttackers = filteredAttackers
if cfxOwnedZones.verbose then
trigger.action.outText("owned zones GC ran: before <" .. before .. ">, after <" .. #cfxOwnedZones.spawnedAttackers .. ">", 30)
end
end end
function cfxOwnedZones.update() function cfxOwnedZones.update()
@ -967,7 +972,7 @@ function cfxOwnedZones.loadData()
local cty = gData.cty local cty = gData.cty
local cat = gData.cat local cat = gData.cat
-- add to my own attacker queue so we can save later -- add to my own attacker queue so we can save later
local dClone = dcsCommon.clone(gData) local dClone = dcsCommon.clone(gdTroop)
cfxOwnedZones.spawnedAttackers[gName] = dClone cfxOwnedZones.spawnedAttackers[gName] = dClone
local theGroup = coalition.addGroup(cty, cat, gData) local theGroup = coalition.addGroup(cty, cat, gData)
if cfxGroundTroops then if cfxGroundTroops then

View File

@ -1,5 +1,5 @@
cfxPlayerScore = {} cfxPlayerScore = {}
cfxPlayerScore.version = "1.3.1" cfxPlayerScore.version = "1.4.0"
cfxPlayerScore.badSound = "Death BRASS.wav" cfxPlayerScore.badSound = "Death BRASS.wav"
cfxPlayerScore.scoreSound = "Quest Snare 3.wav" cfxPlayerScore.scoreSound = "Quest Snare 3.wav"
cfxPlayerScore.announcer = true cfxPlayerScore.announcer = true
@ -23,12 +23,15 @@ cfxPlayerScore.announcer = true
number that is given under OBJECT ID when number that is given under OBJECT ID when
using assign as... using assign as...
1.3.1 - isStaticObject() to better detect buildings after Match 22 patch 1.3.1 - isStaticObject() to better detect buildings after Match 22 patch
1.3.2 - corrected ground default score
- removed dependency to cfxPlayer
1.4.0 - persistence support
- better unit-->static switch support for generic type kill
--]]-- --]]--
cfxPlayerScore.requiredLibs = { cfxPlayerScore.requiredLibs = {
"dcsCommon", -- this is doing score keeping "dcsCommon", -- this is doing score keeping
"cfxPlayer", -- player events, comms
"cfxZones", -- zones for config "cfxZones", -- zones for config
} }
cfxPlayerScore.playerScore = {} -- init to empty cfxPlayerScore.playerScore = {} -- init to empty
@ -46,14 +49,15 @@ cfxPlayerScore.typeScore = {}
-- --
cfxPlayerScore.aircraft = 50 cfxPlayerScore.aircraft = 50
cfxPlayerScore.helo = 40 cfxPlayerScore.helo = 40
cfxPlayer.ground = 10 cfxPlayerScore.ground = 10
cfxPlayerScore.ship = 80 cfxPlayerScore.ship = 80
cfxPlayerScore.train = 5 cfxPlayerScore.train = 5
function cfxPlayerScore.cat2BaseScore(inCat) function cfxPlayerScore.cat2BaseScore(inCat)
if inCat == 0 then return cfxPlayerScore.aircraft end -- airplane if inCat == 0 then return cfxPlayerScore.aircraft end -- airplane
if inCat == 1 then return cfxPlayerScore.helo end -- helo if inCat == 1 then return cfxPlayerScore.helo end -- helo
if inCat == 2 then return cfxPlayer.ground end -- ground if inCat == 2 then return cfxPlayerScore.ground end -- ground
if inCat == 3 then return cfxPlayerScore.ship end -- ship if inCat == 3 then return cfxPlayerScore.ship end -- ship
if inCat == 4 then return cfxPlayerScore.train end -- train if inCat == 4 then return cfxPlayerScore.train end -- train
@ -69,7 +73,10 @@ function cfxPlayerScore.object2score(inVictim) -- does not have group
if type(inName) == "number" then if type(inName) == "number" then
inName = tostring(inName) inName = tostring(inName)
end end
-- now, since 2.7x DCS turns units into static objects for
-- cooking off, so first thing we need to do is do a name check
local objectScore = cfxPlayerScore.typeScore[inName] local objectScore = cfxPlayerScore.typeScore[inName]
if not objectScore then if not objectScore then
-- try the type desc -- try the type desc
@ -80,6 +87,20 @@ function cfxPlayerScore.object2score(inVictim) -- does not have group
if type(objectScore) == "string" then if type(objectScore) == "string" then
objectScore = tonumber(objectScore) objectScore = tonumber(objectScore)
end end
if objectScore then return objectScore end
-- we now try and get the general type of the killed object
local desc = inVictim:getDesc() -- Object.getDesc(inVictim)
local attributes = desc.attributes
if attributes then
if attributes["Vehicles"] or attributes["Ground vehicles"] or attributes["Ground Units"] then return cfxPlayerScore.ground end
if attributes["Helicopters"] then return cfxPlayerScore.helo end
if attributes["Planes"] then return cfxPlayerScore.aircraft end
if attributes["Ships"] then return cfxPlayerScore.ship end
-- trains can't be detected
end
if not objectScore then return 0 end if not objectScore then return 0 end
return objectScore return objectScore
end end
@ -224,7 +245,7 @@ function cfxPlayerScore.preProcessor(theEvent)
return false return false
end end
local wasPlayer = cfxPlayer.isPlayerUnit(killer) local wasPlayer = dcsCommon.isPlayerUnit(killer)
return wasPlayer return wasPlayer
end end
return false return false
@ -245,7 +266,7 @@ function cfxPlayerScore.killDetected(theEvent)
-- we are only getting called when and if -- we are only getting called when and if
-- a kill occured and killer was a player -- a kill occured and killer was a player
-- and target exists -- and target exists
-- trigger.action.outText("KILL EVENT", 30)
local killer = theEvent.initiator local killer = theEvent.initiator
local killerName = killer:getPlayerName() local killerName = killer:getPlayerName()
if not killerName then killerName = "<nil>" end if not killerName then killerName = "<nil>" end
@ -255,13 +276,14 @@ function cfxPlayerScore.killDetected(theEvent)
local victim = theEvent.target local victim = theEvent.target
-- was it a player kill? -- was it a player kill?
local pk = cfxPlayer.isPlayerUnit(victim) local pk = dcsCommon.isPlayerUnit(victim)
-- was it a scenery object? -- was it a scenery object?
local wasBuilding = dcsCommon.isSceneryObject(victim) local wasBuilding = dcsCommon.isSceneryObject(victim)
if wasBuilding then if wasBuilding then
-- these objects have no coalition; we simply award the score if -- these objects have no coalition; we simply award the score if
-- it exists in look-up table. -- it exists in look-up table.
--trigger.action.outText("KILL SCENERY", 30)
local staticScore = cfxPlayerScore.object2score(victim) local staticScore = cfxPlayerScore.object2score(victim)
if staticScore > 0 then if staticScore > 0 then
trigger.action.outSoundForCoalition(killSide, cfxPlayerScore.scoreSound) trigger.action.outSoundForCoalition(killSide, cfxPlayerScore.scoreSound)
@ -282,10 +304,11 @@ function cfxPlayerScore.killDetected(theEvent)
--if not victim.getGroup then --if not victim.getGroup then
if isStO then if isStO then
-- static objects have no group -- static objects have no group
local staticName = victim:getName() -- on statics, this returns local staticName = victim:getName() -- on statics, this returns
-- name as entered in TOP LINE -- name as entered in TOP LINE
local staticScore = cfxPlayerScore.object2score(victim) local staticScore = cfxPlayerScore.object2score(victim)
-- trigger.action.outText("KILL STATIC with score " .. staticScore, 30)
if staticScore > 0 then if staticScore > 0 then
-- this was a named static, return the score - unless our own -- this was a named static, return the score - unless our own
if fraternicide then if fraternicide then
@ -295,6 +318,7 @@ function cfxPlayerScore.killDetected(theEvent)
trigger.action.outSoundForCoalition(killSide, cfxPlayerScore.scoreSound) trigger.action.outSoundForCoalition(killSide, cfxPlayerScore.scoreSound)
end end
staticScore = scoreMod * staticScore staticScore = scoreMod * staticScore
cfxPlayerScore.logKillForPlayer(killerName, victim)
cfxPlayerScore.awardScoreTo(killSide, staticScore, killerName) cfxPlayerScore.awardScoreTo(killSide, staticScore, killerName)
else else
-- no score, no mentions -- no score, no mentions
@ -368,7 +392,7 @@ function cfxPlayerScore.readConfigZone(theZone)
-- default scores -- default scores
cfxPlayerScore.aircraft = cfxZones.getNumberFromZoneProperty(theZone, "aircraft", 50) cfxPlayerScore.aircraft = cfxZones.getNumberFromZoneProperty(theZone, "aircraft", 50)
cfxPlayerScore.helo = cfxZones.getNumberFromZoneProperty(theZone, "helo", 40) cfxPlayerScore.helo = cfxZones.getNumberFromZoneProperty(theZone, "helo", 40)
cfxPlayer.ground = cfxZones.getNumberFromZoneProperty(theZone, "ground", 10) cfxPlayerScore.ground = cfxZones.getNumberFromZoneProperty(theZone, "ground", 10)
cfxPlayerScore.ship = cfxZones.getNumberFromZoneProperty(theZone, "ship", 80) cfxPlayerScore.ship = cfxZones.getNumberFromZoneProperty(theZone, "ship", 80)
cfxPlayerScore.train = cfxZones.getNumberFromZoneProperty(theZone, "train", 5) cfxPlayerScore.train = cfxZones.getNumberFromZoneProperty(theZone, "train", 5)
@ -382,6 +406,36 @@ function cfxPlayerScore.readConfigZone(theZone)
end end
end end
--
-- load / save
--
function cfxPlayerScore.saveData()
local theData = {}
local theScore = dcsCommon.clone(cfxPlayerScore.playerScore)
theData.theScore = theScore
return theData
end
function cfxPlayerScore.loadData()
if not persistence then return end
local theData = persistence.getSavedDataForModule("cfxPlayerScore")
if not theData then
if cfxPlayerScore.verbose then
trigger.action.outText("+++playerscore: no save date received, skipping.", 30)
end
return
end
local theScore = theData.theScore
cfxPlayerScore.playerScore = theScore
end
--
-- start
--
function cfxPlayerScore.start() function cfxPlayerScore.start()
if not dcsCommon.libCheck("cfx Player Score", if not dcsCommon.libCheck("cfx Player Score",
cfxPlayerScore.requiredLibs) cfxPlayerScore.requiredLibs)
@ -413,6 +467,18 @@ function cfxPlayerScore.start()
dcsCommon.addEventHandler(cfxPlayerScore.killDetected, dcsCommon.addEventHandler(cfxPlayerScore.killDetected,
cfxPlayerScore.preProcessor, cfxPlayerScore.preProcessor,
cfxPlayerScore.postProcessor) cfxPlayerScore.postProcessor)
-- now load all save data and populate map with troops that
-- we deployed last save.
if persistence then
-- sign up for persistence
callbacks = {}
callbacks.persistData = cfxPlayerScore.saveData
persistence.registerModule("cfxPlayerScore", callbacks)
-- now load my data
cfxPlayerScore.loadData()
end
trigger.action.outText("cfxPlayerScore v" .. cfxPlayerScore.version .. " started", 30) trigger.action.outText("cfxPlayerScore v" .. cfxPlayerScore.version .. " started", 30)
return true return true
end end

View File

@ -1,5 +1,5 @@
cfxSSBClient = {} cfxSSBClient = {}
cfxSSBClient.version = "2.0.3" cfxSSBClient.version = "2.1.0"
cfxSSBClient.verbose = false cfxSSBClient.verbose = false
cfxSSBClient.singleUse = false -- set to true to block crashed planes cfxSSBClient.singleUse = false -- set to true to block crashed planes
-- NOTE: singleUse (true) requires SSB to disable immediate respawn after kick -- NOTE: singleUse (true) requires SSB to disable immediate respawn after kick
@ -40,6 +40,9 @@ Version History
- added verbosity - added verbosity
2.0.3 - getPlayerName nil-trap on cloned player planes guard 2.0.3 - getPlayerName nil-trap on cloned player planes guard
in onEvent in onEvent
2.1.0 - slotState
- persistence
WHAT IT IS WHAT IT IS
SSB Client is a small script that forms the client-side counterpart to SSB Client is a small script that forms the client-side counterpart to
@ -88,6 +91,7 @@ cfxSSBClient.playerGroups = {}
cfxSSBClient.closedAirfields = {} -- list that closes airfields for any aircrafts cfxSSBClient.closedAirfields = {} -- list that closes airfields for any aircrafts
cfxSSBClient.playerPlanes = {} -- names of units that a player is flying cfxSSBClient.playerPlanes = {} -- names of units that a player is flying
cfxSSBClient.crashedGroups = {} -- names of groups to block after crash of their player-flown plane cfxSSBClient.crashedGroups = {} -- names of groups to block after crash of their player-flown plane
cfxSSBClient.slotState = {} -- keeps a record of which slot has which value. For persistence and debugging
function cfxSSBClient.closeAirfieldNamed(name) function cfxSSBClient.closeAirfieldNamed(name)
@ -191,6 +195,7 @@ function cfxSSBClient.setSlotAccessForGroup(theGroup)
end end
-- set the ssb flag for this group so the server can see it -- set the ssb flag for this group so the server can see it
trigger.action.setUserFlag(theName, blockState) trigger.action.setUserFlag(theName, blockState)
cfxSSBClient.slotState[theName] = blockState
if cfxSSBClient.verbose then if cfxSSBClient.verbose then
trigger.action.outText("+++SSB: group ".. theName .. ": " .. comment, 30) trigger.action.outText("+++SSB: group ".. theName .. ": " .. comment, 30)
end end
@ -312,7 +317,7 @@ function cfxSSBClient:onEvent(event)
-- block this slot. -- block this slot.
trigger.action.setUserFlag(gName, cfxSSBClient.disabledFlagValue) trigger.action.setUserFlag(gName, cfxSSBClient.disabledFlagValue)
cfxSSBClient.slotState[gName] = cfxSSBClient.disabledFlagValue
-- remember this plane to not re-enable if -- remember this plane to not re-enable if
-- airfield changes hands later -- airfield changes hands later
cfxSSBClient.crashedGroups[gName] = thePilot -- set to crash pilot cfxSSBClient.crashedGroups[gName] = thePilot -- set to crash pilot
@ -425,6 +430,48 @@ function cfxSSBClient.readConfigZone()
cfxSSBClient.disabledFlagValue = cfxZones.getNumberFromZoneProperty(theZone, "disabledFlagValue", cfxSSBClient.enabledFlagValue + 100) cfxSSBClient.disabledFlagValue = cfxZones.getNumberFromZoneProperty(theZone, "disabledFlagValue", cfxSSBClient.enabledFlagValue + 100)
end end
--
-- load / save
--
function cfxSSBClient.saveData()
local theData = {}
local states = dcsCommon.clone(cfxSSBClient.slotState)
local crashed = dcsCommon.clone(cfxSSBClient.crashedGroups)
theData.states = states
theData.crashed = crashed
return theData
end
function cfxSSBClient.loadData()
if not persistence then return end
local theData = persistence.getSavedDataForModule("cfxSSBClient")
if not theData then
if cfxSSBClient.verbose then
trigger.action.outText("+++cfxSSB: no save date received, skipping.", 30)
end
return
end
cfxSSBClient.slotState = theData.states
if not cfxSSBClient.slotState then
trigger.action.outText("SSBClient: nil slot state on load", 30)
cfxSSBClient.slotState = {}
end
for slot, state in pairs (cfxSSBClient.slotState) do
trigger.action.setUserFlag(slot, state)
if state > 0 and cfxSSBClient.verbose then
trigger.action.outText("SSB: blocked <" .. slot .. "> on load", 30)
end
end
cfxSSBClient.crashedGroups = theData.crashed
if not cfxSSBClient.crashedGroups then
cfxSSBClient.crashedGroups = {}
trigger.action.outText("SSBClient: nil crashers on load", 30)
end
end
-- --
-- start -- start
-- --
@ -455,6 +502,16 @@ function cfxSSBClient.start()
-- now turn on ssb -- now turn on ssb
trigger.action.setUserFlag("SSB",100) trigger.action.setUserFlag("SSB",100)
-- persistence: load states
if persistence then
-- sign up for persistence
callbacks = {}
callbacks.persistData = cfxSSBClient.saveData
persistence.registerModule("cfxSSBClient", callbacks)
-- now load my data
cfxSSBClient.loadData()
end
-- say hi! -- say hi!
trigger.action.outText("cfxSSBClient v".. cfxSSBClient.version .. " running, SBB enabled", 30) trigger.action.outText("cfxSSBClient v".. cfxSSBClient.version .. " running, SBB enabled", 30)

View File

@ -1,15 +1,21 @@
cfxSpawnZones = {} cfxSpawnZones = {}
cfxSpawnZones.version = "1.6.0" cfxSpawnZones.version = "1.7.0"
cfxSpawnZones.requiredLibs = { cfxSpawnZones.requiredLibs = {
"dcsCommon", -- common is of course needed for everything "dcsCommon", -- common is of course needed for everything
-- pretty stupid to check for this since we -- pretty stupid to check for this since we
-- need common to invoke the check, but anyway -- need common to invoke the check, but anyway
"cfxZones", -- Zones, of course "cfxZones", -- Zones, of course
"cfxCommander", -- to make troops do stuff "cfxCommander", -- to make troops do stuff
"cfxGroundTroops", -- generic data module for weight "cfxGroundTroops", -- for ordering then around
} }
cfxSpawnZones.ups = 1 cfxSpawnZones.ups = 1
cfxSpawnZones.verbose = false cfxSpawnZones.verbose = false
-- persistence: all groups we ever spawned.
-- is regularly GC'd
cfxSpawnZones.spawnedGroups = {}
-- --
-- Zones that conform with this requirements spawn toops automatically -- Zones that conform with this requirements spawn toops automatically
-- *** DOES NOT EXTEND ZONES *** LINKED OWNER via masterOwner *** -- *** DOES NOT EXTEND ZONES *** LINKED OWNER via masterOwner ***
@ -54,6 +60,7 @@ cfxSpawnZones.verbose = false
-- 1.5.2 - activate?, pause? flag -- 1.5.2 - activate?, pause? flag
-- 1.5.3 - spawn?, spawnUnits? flags -- 1.5.3 - spawn?, spawnUnits? flags
-- 1.6.0 - trackwith interface for group tracker -- 1.6.0 - trackwith interface for group tracker
-- 1.7.0 - persistence support
-- --
-- new version requires cfxGroundTroops, where they are -- new version requires cfxGroundTroops, where they are
-- --
@ -203,12 +210,14 @@ function cfxSpawnZones.createSpawner(inZone)
theSpawner.formation = "circle_out" theSpawner.formation = "circle_out"
theSpawner.formation = cfxZones.getStringFromZoneProperty(inZone, "formation", "circle_out") theSpawner.formation = cfxZones.getStringFromZoneProperty(inZone, "formation", "circle_out")
theSpawner.paused = cfxZones.getBoolFromZoneProperty(inZone, "paused", false) theSpawner.paused = cfxZones.getBoolFromZoneProperty(inZone, "paused", false)
theSpawner.orders = cfxZones.getStringFromZoneProperty(inZone, "orders", "guard") theSpawner.orders = cfxZones.getStringFromZoneProperty(inZone, "orders", "guard"):lower()
--theSpawner.orders = cfxZones.getZoneProperty(inZone, "orders") --theSpawner.orders = cfxZones.getZoneProperty(inZone, "orders")
-- used to assign special orders, default is 'guard', use "laze" to make them laze targets. can be 'wait-' which may auto-convert to 'guard' after pick-up by helo, to be handled outside. -- used to assign special orders, default is 'guard', use "laze" to make them laze targets. can be 'wait-' which may auto-convert to 'guard' after pick-up by helo, to be handled outside.
-- use "train" to tell them to HOLD WEAPONS, don't move and don't participate in loop, so we have in effect target dummies -- use "train" to tell them to HOLD WEAPONS, don't move and don't participate in loop, so we have in effect target dummies
-- can also use order 'dummy' or 'dummies' to switch to train -- can also use order 'dummy' or 'dummies' to switch to train
if theSpawner.orders:lower() == "dummy" or theSpawner.orders:lower() == "dummies" then theSpawner.orders = "train" end if theSpawner.orders:lower() == "dummy" or theSpawner.orders:lower() == "dummies" then theSpawner.orders = "train" end
if theSpawner.orders:lower() == "training" then theSpawner.orders = "train" end
theSpawner.range = cfxZones.getNumberFromZoneProperty(inZone, "range", 300) -- if we have a range, for example enemy detection for Lasing or engage range theSpawner.range = cfxZones.getNumberFromZoneProperty(inZone, "range", 300) -- if we have a range, for example enemy detection for Lasing or engage range
theSpawner.maxSpawns = cfxZones.getNumberFromZoneProperty(inZone, "maxSpawns", -1) -- if there is a limit on how many troops can spawn. -1 = endless spawns theSpawner.maxSpawns = cfxZones.getNumberFromZoneProperty(inZone, "maxSpawns", -1) -- if there is a limit on how many troops can spawn. -1 = endless spawns
@ -238,6 +247,7 @@ end
function cfxSpawnZones.getSpawnerForZoneNamed(aName) function cfxSpawnZones.getSpawnerForZoneNamed(aName)
local aZone = cfxZones.getZoneByName(aName) local aZone = cfxZones.getZoneByName(aName)
if not aZone then return nil end
return cfxSpawnZones.getSpawnerForZone(aZone) return cfxSpawnZones.getSpawnerForZone(aZone)
end end
@ -331,7 +341,7 @@ function cfxSpawnZones.spawnWithSpawner(aSpawner)
local theCoalition = coalition.getCountryCoalition(theCountry) local theCoalition = coalition.getCountryCoalition(theCountry)
-- trigger.action.outText("+++ spawn: coal <" .. theCoalition .. "> from country <" .. theCountry .. ">", 30) -- trigger.action.outText("+++ spawn: coal <" .. theCoalition .. "> from country <" .. theCountry .. ">", 30)
local theGroup = cfxZones.createGroundUnitsInZoneForCoalition ( local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
theCoalition, theCoalition,
aSpawner.baseName .. "-" .. aSpawner.count, -- must be unique aSpawner.baseName .. "-" .. aSpawner.count, -- must be unique
aSpawner.zone, aSpawner.zone,
@ -340,15 +350,26 @@ function cfxSpawnZones.spawnWithSpawner(aSpawner)
aSpawner.heading) aSpawner.heading)
aSpawner.theSpawn = theGroup aSpawner.theSpawn = theGroup
aSpawner.count = aSpawner.count + 1 aSpawner.count = aSpawner.count + 1
-- we may also want to add this to auto ground troops pool
-- we not only want to, we absolutely need to in order -- isnert into collector for persistence
-- to make this work. local troopData = {}
troopData.groupData = theData
troopData.orders = aSpawner.orders -- always set
troopData.side = theCoalition
troopData.target = aSpawner.target -- can be nil!
troopData.tracker = theZone.trackWith -- taken from ZONE!!, can be nil
troopData.range = aSpawner.range
cfxSpawnZones.spawnedGroups[theData.name] = troopData
if aSpawner.orders and ( if aSpawner.orders and (
aSpawner.orders:lower() == "training" or aSpawner.orders:lower() == "training" or
aSpawner.orders:lower() == "train" ) aSpawner.orders:lower() == "train" )
then then
-- make them ROE "HOLD" -- make them ROE "HOLD"
-- remember to do this in persistence as well!
-- they aren't fed to cfxGroundTroops.
-- we should update groundTroops to simply
-- drop those with 'train' or 'training'
cfxCommander.scheduleOptionForGroup( cfxCommander.scheduleOptionForGroup(
theGroup, theGroup,
AI.Option.Ground.id.ROE, AI.Option.Ground.id.ROE,
@ -434,6 +455,23 @@ end
-- --
-- U P D A T E -- U P D A T E
-- --
function cfxSpawnZones.GC()
-- GC run. remove all my dead remembered troops
local filteredAttackers = {}
local before = #cfxSpawnZones.spawnedGroups
for gName, gData in pairs (cfxSpawnZones.spawnedGroups) do
-- all we need to do is get the group of that name
-- and if it still returns units we are fine
local gameGroup = Group.getByName(gName)
if gameGroup and gameGroup:isExist() and gameGroup:getSize() > 0 then
filteredAttackers[gName] = gData
end
end
cfxSpawnZones.spawnedGroups = filteredAttackers
if cfxSpawnZones.verbose then
trigger.action.outText("spawn zones GC ran: before <" .. before .. ">, after <" .. #cfxSpawnZones.spawnedGroups .. ">", 30)
end
end
function cfxSpawnZones.update() function cfxSpawnZones.update()
cfxSpawnZones.updateSchedule = timer.scheduleFunction(cfxSpawnZones.update, {}, timer.getTime() + 1/cfxSpawnZones.ups) cfxSpawnZones.updateSchedule = timer.scheduleFunction(cfxSpawnZones.update, {}, timer.getTime() + 1/cfxSpawnZones.ups)
@ -445,9 +483,9 @@ function cfxSpawnZones.update()
local group = spawner.theSpawn local group = spawner.theSpawn
if group:isExist() then if group:isExist() then
-- see how many members of this group are still alive -- see how many members of this group are still alive
local liveUnits = dcsCommon.getLiveGroupUnits(group) local liveUnits = group:getSize() --dcsCommon.getLiveGroupUnits(group)
-- spawn is still alive, will not spawn -- spawn is still alive, will not spawn
if #liveUnits > 1 then if liveUnits > 1 then
-- we may want to check if this member is still inside -- we may want to check if this member is still inside
-- of spawn location. currently we don't do that -- of spawn location. currently we don't do that
needsSpawn = false needsSpawn = false
@ -526,6 +564,158 @@ function cfxSpawnZones.update()
end end
end end
function cfxSpawnZones.houseKeeping()
timer.scheduleFunction(cfxSpawnZones.houseKeeping, {}, timer.getTime() + 5 * 60) -- every 5 minutes
cfxSpawnZones.GC()
end
--
-- LOAD/SAVE
--
function cfxSpawnZones.saveData()
local theData = {}
local allSpawnerData = {}
-- now iterate all spawners and collect their data
for theZone, theSpawner in pairs(cfxSpawnZones.allSpawners) do
local zName = theZone.name
local spawnData = {}
if theSpawner.spawn and theSpawner.spawn:isExist() then
spawnData.spawn = theSpawner.spawn:getName()
end
spawnData.count = theSpawner.count
spawnData.paused = theSpawner.paused
spawnData.cdStarted = theSpawner.cdStarted
spawnData.cdTimer = theSpawner.cdTimer - timer.getTime() -- what remains of the cooldown time
allSpawnerData[zName] = spawnData
end
-- run a GC
cfxSpawnZones.GC()
-- now collect all living groups
-- no longer required to check if group is lively
local allLivingTroopData = {}
for gName, gData in pairs(cfxSpawnZones.spawnedGroups) do
local sData = dcsCommon.clone(gData)
dcsCommon.synchGroupData(sData.groupData)
allLivingTroopData[gName] = sData
end
theData.spawnerData = allSpawnerData
theData.troopData = allLivingTroopData
return theData
end
function cfxSpawnZones.loadData()
if not persistence then return end
local theData = persistence.getSavedDataForModule("cfxSpawnZones")
if not theData then
if cfxSpawnZones.verbose then
trigger.action.outText("+++spwn: no save date received, skipping.", 30)
end
return
end
-- we begin by re-spawning all spawned groups so that the
-- spwners can then later link to them
local allTroopData = theData.troopData
for gName, gdTroop in pairs (allTroopData) do
local gData = gdTroop.groupData
local orders = gdTroop.orders
local target = gdTroop.target
local tracker = gdTroop.tracker
local side = gdTroop.side
local range = gdTroop.range
local cty = gData.cty
local cat = gData.cat
-- now spawn, but first
-- add to my own attacker queue so we can save later
local gdClone = dcsCommon.clone(gdTroop)
cfxSpawnZones.spawnedGroups[gName] = gdClone
local theGroup = coalition.addGroup(cty, cat, gData)
-- post-proccing for 'train' orders
if orders and (orders == "train" ) then
-- make them ROE "HOLD"
cfxCommander.scheduleOptionForGroup(
theGroup,
AI.Option.Ground.id.ROE,
AI.Option.Ground.val.ROE.WEAPON_HOLD,
1.0)
else
-- add to groundTroops
local newTroops = cfxGroundTroops.createGroundTroops(theGroup, range, orders)
cfxGroundTroops.addGroundTroopsToPool(newTroops)
-- engage a target zone
if target then
local destZone = cfxZones.getZoneByName(target)
if destZone then
newTroops.destination = destZone
cfxGroundTroops.makeTroopsEngageZone(newTroops)
end
end
end
-- post-proccing for trackwith [may not be needed when we]
-- have persistence in the tracking module. that module
-- simply schedules re-connecting after one second
end
-- now set up all spawners with save data
local allSpawnerData = theData.spawnerData
for zName, sData in pairs (allSpawnerData) do
local theZone = cfxZones.getZoneByName(zName)
if theZone then
local theSpawner = cfxSpawnZones.getSpawnerForZone(theZone)
if theSpawner then
theSpawner.inited = true -- inited by persistence
theSpawner.count = sData.count
theSpawner.paused = sData.paused
theSpawner.cdStarted = sData.cdStarted
if theSpawner.cdStarted then
theSpawner.cdTimer = timer.getTime() + sData.cdTimer
else
theSpawner.cdTimer = -1
end
if sData.spawn then
local theGroup = Group.getByName(sData.spawn)
if theGroup then
theSpawner.spawn = theGroup
else
trigger.action.outText("+++spwn (persistence): can't re-connect spawner <" .. zName .. "> with group <" .. sData.spawn .. ">, skipping", 30)
end
end
else
trigger.action.outText("+++spwn (persistence): can't find spawner for zone <" .. zName .. ">, skipping", 30)
end
else
trigger.action.outText("+++spwn (persistence): can't find zone <" .. zName .. "> for spawner, skipping", 30)
end
end
end
--
-- START
--
function cfxSpawnZones.initialSpawnCheck(aSpawner)
if not aSpawner.paused
and cfxSpawnZones.verifySpawnOwnership(aSpawner)
and aSpawner.maxSpawns ~= 0
and not aSpawner.inited
then
cfxSpawnZones.spawnWithSpawner(aSpawner)
-- update spawn count and make sure we haven't spawned the one and only
if aSpawner.maxSpawns > 0 then
aSpawner.maxSpawns = aSpawner.maxSpawns - 1
end
if aSpawner.maxSpawns == 0 then
aSpawner.paused = true
trigger.action.outText("+++ maxspawn -- turning off zone " .. aSpawner.zone.name, 30)
end
end
end
function cfxSpawnZones.start() function cfxSpawnZones.start()
if not dcsCommon.libCheck("cfx Spawn Zones", if not dcsCommon.libCheck("cfx Spawn Zones",
cfxSpawnZones.requiredLibs) then cfxSpawnZones.requiredLibs) then
@ -540,22 +730,29 @@ function cfxSpawnZones.start()
for k, aZone in pairs(attrZones) do for k, aZone in pairs(attrZones) do
local aSpawner = cfxSpawnZones.createSpawner(aZone) local aSpawner = cfxSpawnZones.createSpawner(aZone)
cfxSpawnZones.addSpawner(aSpawner) cfxSpawnZones.addSpawner(aSpawner)
if not aSpawner.paused and cfxSpawnZones.verifySpawnOwnership(aSpawner) and aSpawner.maxSpawns ~= 0 then end
cfxSpawnZones.spawnWithSpawner(aSpawner)
-- update spawn count and make sure we haven't spawned the one and only -- we now do persistence
if aSpawner.maxSpawns > 0 then if persistence then
aSpawner.maxSpawns = aSpawner.maxSpawns - 1 -- sign up for persistence
end callbacks = {}
if aSpawner.maxSpawns == 0 then callbacks.persistData = cfxSpawnZones.saveData
aSpawner.paused = true persistence.registerModule("cfxSpawnZones", callbacks)
trigger.action.outText("+++ maxspawn -- turning off zone " .. aSpawner.zone.name, 30) -- now load my data
end cfxSpawnZones.loadData()
end end
-- we now spawn if not taken care of by load / save
for theZone, aSpawner in pairs(cfxSpawnZones.allSpawners) do
cfxSpawnZones.initialSpawnCheck(aSpawner)
end end
-- and start the regular update calls -- and start the regular update calls
cfxSpawnZones.update() cfxSpawnZones.update()
-- start housekeeping
cfxSpawnZones.houseKeeping()
trigger.action.outText("cfx Spawn Zones v" .. cfxSpawnZones.version .. " started.", 30) trigger.action.outText("cfx Spawn Zones v" .. cfxSpawnZones.version .. " started.", 30)
return true return true
end end

View File

@ -1,5 +1,5 @@
changer = {} changer = {}
changer.version = "1.0.2" changer.version = "1.0.3"
changer.verbose = false changer.verbose = false
changer.ups = 1 changer.ups = 1
changer.requiredLibs = { changer.requiredLibs = {
@ -12,6 +12,7 @@ changer.changers = {}
1.0.0 - Initial version 1.0.0 - Initial version
1.0.1 - Better guards in config to avoid <none> Zone getter warning 1.0.1 - Better guards in config to avoid <none> Zone getter warning
1.0.2 - on/off: verbosity 1.0.2 - on/off: verbosity
1.0.3 - NOT on/off
Transmogrify an incoming signal to an output signal Transmogrify an incoming signal to an output signal
- not - not
@ -111,6 +112,12 @@ function changer.createChangerWithZone(theZone)
if cfxZones.hasProperty(theZone, "changeOn/Off?") then if cfxZones.hasProperty(theZone, "changeOn/Off?") then
theZone.changerOnOff = cfxZones.getStringFromZoneProperty(theZone, "changeOn/Off?", "*<none>", 1) theZone.changerOnOff = cfxZones.getStringFromZoneProperty(theZone, "changeOn/Off?", "*<none>", 1)
end end
if cfxZones.hasProperty(theZone, "NOT On/Off?") then
theZone.changerOnOffINV = cfxZones.getStringFromZoneProperty(theZone, "NOT On/Off?", "*<none>", 1)
end
if cfxZones.hasProperty(theZone, "NOT changeOn/Off?") then
theZone.changerOnOffINV = cfxZones.getStringFromZoneProperty(theZone, "NOT changeOn/Off?", "*<none>", 1)
end
end end
-- --
@ -207,6 +214,10 @@ function changer.update()
end end
-- do processing if not paused -- do processing if not paused
if aZone.changerOnOff and aZone.changerOnOffINV then
trigger.action.outText("+++chgr: WARNING - zone <" .. aZone.name .. "> has conflicting change On/off inputs, disregating inverted input (NOT changeOn/off?)", 30)
end
if not aZone.changerPaused then if not aZone.changerPaused then
if aZone.changerOnOff then if aZone.changerOnOff then
if cfxZones.getFlagValue(aZone.changerOnOff, aZone) > 0 then if cfxZones.getFlagValue(aZone.changerOnOff, aZone) > 0 then
@ -216,7 +227,15 @@ function changer.update()
trigger.action.outText("+++chgr: " .. aZone.name .. " gate closed.", 30) trigger.action.outText("+++chgr: " .. aZone.name .. " gate closed.", 30)
end end
end end
else elseif aZone.changerOnOffINV then
if cfxZones.getFlagValue(aZone.changerOnOffINV, aZone) == 0 then
changer.process(aZone)
else
if changer.verbose or aZone.verbose then
trigger.action.outText("+++chgr: " .. aZone.name .. " gate closed.", 30)
end
end
else
changer.process(aZone) changer.process(aZone)
end end
end end

View File

@ -1,5 +1,5 @@
cloneZones = {} cloneZones = {}
cloneZones.version = "1.4.9" cloneZones.version = "1.5.0"
cloneZones.verbose = false cloneZones.verbose = false
cloneZones.requiredLibs = { cloneZones.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
@ -17,6 +17,9 @@ cloneZones.callbacks = {}
cloneZones.unitXlate = {} cloneZones.unitXlate = {}
cloneZones.groupXlate = {} -- used to translate original groupID to cloned. only holds last spawned group id cloneZones.groupXlate = {} -- used to translate original groupID to cloned. only holds last spawned group id
cloneZones.uniqueCounter = 9200000 -- we start group numbering here cloneZones.uniqueCounter = 9200000 -- we start group numbering here
cloneZones.allClones = {} -- all clones spawned, regularly GC'd
cloneZones.allCObjects = {} -- all clones objects
--[[-- --[[--
Clones Groups from ME mission data Clones Groups from ME mission data
Copyright (c) 2022 by Christian Franz and cf/x AG Copyright (c) 2022 by Christian Franz and cf/x AG
@ -53,6 +56,7 @@ cloneZones.uniqueCounter = 9200000 -- we start group numbering here
1.4.8 - added 'wipe?' synonym 1.4.8 - added 'wipe?' synonym
1.4.9 - onRoad option 1.4.9 - onRoad option
- rndHeading option - rndHeading option
1.5.0 - persistence
--]]-- --]]--
@ -289,7 +293,7 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
theZone.onRoad = cfxZones.getBoolFromZoneProperty(theZone, "onRoad", false) theZone.onRoad = cfxZones.getBoolFromZoneProperty(theZone, "onRoad", false)
if theZone.rndLoc and theZone.verbose then if theZone.rndLoc and theZone.verbose then
trigger.action.outText("+++ rndloc on for " .. theZone.name, 30) --trigger.action.outText("+++ rndloc on for " .. theZone.name, 30)
end end
-- we end with clear plate -- we end with clear plate
@ -767,6 +771,12 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
-- now spawn all raw data -- now spawn all raw data
for idx, rawData in pairs (dataToSpawn) do for idx, rawData in pairs (dataToSpawn) do
-- now spawn and save to clones -- now spawn and save to clones
-- first norm and clone data for later save
rawData.cty = rawData.CZctry
rawData.cat = rawData.CZtheCat
local theData = dcsCommon.clone(rawData)
cloneZones.allClones[rawData.name] = theData
local theGroup = coalition.addGroup(rawData.CZctry, rawData.CZtheCat, rawData) local theGroup = coalition.addGroup(rawData.CZctry, rawData.CZtheCat, rawData)
table.insert(spawnedGroups, theGroup) table.insert(spawnedGroups, theGroup)
@ -888,20 +898,25 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
end end
local isCargo = rawData.canCargo local isCargo = rawData.canCargo
rawData.cty = ctry
-- save for persistence
local theData = dcsCommon.clone(rawData)
cfxZones.allCObjects[rawData.name] = theData
local theStatic = coalition.addStaticObject(ctry, rawData) local theStatic = coalition.addStaticObject(ctry, rawData)
local newStaticID = tonumber(theStatic:getID()) local newStaticID = tonumber(theStatic:getID())
table.insert(spawnedStatics, theStatic) table.insert(spawnedStatics, theStatic)
-- we don't mix groups with units, so no lookup tables for -- we don't mix groups with units, so no lookup tables for
-- statics -- statics
if newStaticID == rawData.CZTargetID then if newStaticID == rawData.CZTargetID then
-- trigger.action.outText("Static ID OK: " .. newStaticID .. " for " .. rawData.name, 30)
else else
trigger.action.outText("Static ID mismatch: " .. newStaticID .. " vs (target) " .. rawData.CZTargetID .. " for " .. rawData.name, 30) trigger.action.outText("Static ID mismatch: " .. newStaticID .. " vs (target) " .. rawData.CZTargetID .. " for " .. rawData.name, 30)
end end
cloneZones.unitXlate[origID] = newStaticID -- same as units cloneZones.unitXlate[origID] = newStaticID -- same as units
cloneZones.invokeCallbacks(theZone, "did spawn static", theStatic) cloneZones.invokeCallbacks(theZone, "did spawn static", theStatic)
--]]--
if cloneZones.verbose or spawnZone.verbose then if cloneZones.verbose or spawnZone.verbose then
trigger.action.outText("Static spawn: spawned " .. aStaticName, 30) trigger.action.outText("Static spawn: spawned " .. aStaticName, 30)
end end
@ -1058,7 +1073,7 @@ function cloneZones.update()
if aZone.deSpawnFlag then if aZone.deSpawnFlag then
local currTriggerVal = cfxZones.getFlagValue(aZone.deSpawnFlag, aZone) -- trigger.misc.getUserFlag(aZone.deSpawnFlag) local currTriggerVal = cfxZones.getFlagValue(aZone.deSpawnFlag, aZone) -- trigger.misc.getUserFlag(aZone.deSpawnFlag)
if currTriggerVal ~= aZone.lastDeSpawnValue then if currTriggerVal ~= aZone.lastDeSpawnValue then
if cloneZones.verbose then if cloneZones.verbose or aZone.verbose then
trigger.action.outText("+++clnZ: DEspawn triggered for <" .. aZone.name .. ">", 30) trigger.action.outText("+++clnZ: DEspawn triggered for <" .. aZone.name .. ">", 30)
end end
cloneZones.despawnAll(aZone) cloneZones.despawnAll(aZone)
@ -1103,15 +1118,249 @@ function cloneZones.onStart()
--trigger.action.outText("+++clnZ: Enter atStart", 30) --trigger.action.outText("+++clnZ: Enter atStart", 30)
for idx, theZone in pairs(cloneZones.cloners) do for idx, theZone in pairs(cloneZones.cloners) do
if theZone.onStart then if theZone.onStart then
if cloneZones.verbose then if theZone.isStarted then
trigger.action.outText("+++clnZ: atStart will spawn for <"..theZone.name .. ">", 30) if cloneZones.verbose or theZone.verbose then
trigger.action.outText("+++clnz: onStart pre-empted for <" .. theZone.name .. "> by persistence", 30)
end
else
if cloneZones.verbose or theZone.verbose then
trigger.action.outText("+++clnZ: onStart spawing for <"..theZone.name .. ">", 30)
end
cloneZones.spawnWithCloner(theZone)
end end
cloneZones.spawnWithCloner(theZone)
end end
end end
end end
--
-- Regular GC and housekeeping
--
function cloneZones.GC()
-- GC run. remove all my dead remembered troops
local filteredAttackers = {}
for gName, gData in pairs (cloneZones.allClones) do
-- all we need to do is get the group of that name
-- and if it still returns units we are fine
local gameGroup = Group.getByName(gName)
if gameGroup and gameGroup:isExist() and gameGroup:getSize() > 0 then
-- we now filter for categories. we currently only let
-- ground units pass
-- better make this configurabele by option later
if gData.cat == 0 and false then -- block aircraft
elseif gData.cat == 1 and false then -- block helos
elseif gData.cat == 2 and false then -- block ground
elseif gData.cat == 3 and false then -- block ship
elseif gData.cat == 4 and false then -- block trains
else
-- not filtered, persist
filteredAttackers[gName] = gData
end
end
end
cloneZones.allClones = filteredAttackers
filteredAttackers = {}
for gName, gData in pairs (cloneZones.allCObjects) do
-- all we need to do is get the group of that name
-- and if it still returns units we are fine
local theObject = StaticObject.getByName(gName)
if theObject and theObject:isExist() then
filteredAttackers[gName] = gData
if theObject:getLife() < 1 then
gData.dead = true
end
end
end
cloneZones.allCObjects = filteredAttackers
end
function cloneZones.houseKeeping()
timer.scheduleFunction(cloneZones.houseKeeping, {}, timer.getTime() + 5 * 60) -- every 5 minutes
cloneZones.GC()
end
--
-- LOAD / SAVE
--
function cloneZones.synchGroupMXData(theData)
-- we iterate the group's units one by one and update them
local newUnits = {}
local allUnits = theData.units
for idx, unitData in pairs(allUnits) do
local uName = unitData.name
local gUnit = Unit.getByName(uName)
if gUnit and gUnit:isExist() then
unitData.heading = dcsCommon.getUnitHeading(gUnit)
pos = gUnit:getPoint()
unitData.x = pos.x
unitData.y = pos.z -- (!!)
-- add aircraft handling here (alt, speed etc)
-- perhaps even curtail route
table.insert(newUnits, unitData)
end
end
theData.units = newUnits
end
function cloneZones.synchMXObjData(theData)
local oName = theData.name
local theObject = StaticObject.getByName(oName)
theData.heading = dcsCommon.getUnitHeading(theObject)
pos = theObject:getPoint()
theData.x = pos.x
theData.y = pos.z -- (!!)
theData.isDead = theObject:getLife() < 1
theData.dead = theData.isDead
end
function cloneZones.saveData()
local theData = {}
local allCloneData = {}
local allSOData = {}
-- run a GC pre-emptively
cloneZones.GC()
-- now simply iterate and save all deployed clones
for gName, gData in pairs(cloneZones.allClones) do
local sData = dcsCommon.clone(gData)
cloneZones.synchGroupMXData(sData)
allCloneData[gName] = sData
end
-- now simply iterate and save all deployed clones
for gName, gData in pairs(cloneZones.allCObjects) do
local sData = dcsCommon.clone(gData)
cloneZones.synchMXObjData(sData)
allSOData[gName] = sData
end
-- now save all cloner stati
local cloners = {}
for idx, theCloner in pairs(cloneZones.cloners) do
local cData = {}
local cName = theCloner.name
-- mySpawns: all groups i'm curently observing for empty!
-- myStatics: dto for objects
local mySpawns = {}
for idx, aGroup in pairs(theCloner.mySpawns) do
if aGroup and aGroup:isExist() and aGroup:getSize() > 0 then
table.insert(mySpawns, aGroup:getName())
end
end
cData.mySpawns = mySpawns
local myStatics = {}
for idx, aStatic in pairs(theCloner.myStatics) do
table.insert(myStatics, aStatic:getName())
end
cData.myStatics = myStatics
cData.isStarted = theCloner.isStarted -- to prevent onStart
cloners[cName] = cData
end
-- save globals
theData.cuid = cloneZones.uniqueCounter -- replace whatever is larger
theData.uuid = dcsCommon.simpleUUID -- replace whatever is larger
-- save to struct and pass back
theData.clones = allCloneData
theData.objects = allSOData
theData.cloneZones = cloners
return theData
end
function cloneZones.loadData()
if not persistence then return end
local theData = persistence.getSavedDataForModule("cloneZones")
if not theData then
if cloneZones.verbose then
trigger.action.outText("+++clnZ: no save date received, skipping.", 30)
end
return
end
-- spawn all units
local allClones = theData.clones
for gName, gData in pairs (allClones) do
local cty = gData.cty
local cat = gData.cat
-- now spawn, but first
-- add to my own deployed queue so we can save later
local gdClone = dcsCommon.clone(gData)
cloneZones.allClones[gName] = gdClone
local theGroup = coalition.addGroup(cty, cat, gData)
end
-- spawn all static objects
local allObjects = theData.objects
for oName, oData in pairs(allObjects) do
local newStatic = dcsCommon.clone(oData)
-- add link info if it exists
newStatic.linkUnit = cfxMX.linkByName[oName]
if newStatic.linkUnit and unitPersistence.verbose then
trigger.action.outText("+++unitPersistence: linked static <" .. oName .. "> to unit <" .. newStatic.linkUnit .. ">", 30)
end
local cty = staticData.cty
-- local cat = staticData.cat
-- spawn new one, replacing same.named old, dead if required
gStatic = coalition.addStaticObject(cty, newStatic)
-- processing for cargoManager
if oData.canCargo then
if cfxCargoManager then
cfxCargoManager.addCargo(gStatic)
end
end
-- add the original data block to be remembered
-- for next save
cloneZones.allCObjects[oName] = oData
end
-- now update all spawners and reconnect them with their spawns
local allCloners = theData.cloneZones
for cName, cData in pairs(allCloners) do
local theCloner = cloneZones.getCloneZoneByName(cName)
if theCloner then
theCloner.isStarted = true -- ALWAYS TRUE WHEN WE COME HERE! cData.isStarted
local mySpawns = {}
for idx, aName in pairs(cData.mySpawns) do
local theGroup = Group.getByName(aName)
if theGroup then
table.insert(mySpawns, theGroup)
else
trigger.action.outText("+++clnZ - persistence: can't reconnect cloner <" .. cName .. "> with clone group <".. aName .. ">", 30)
end
end
theCloner.mySpawns = mySpawns
local myStatics = {}
for idx, aName in pairs(cData.myStatics) do
local theStatic = StaticObject.getByName(aName)
if theStatic then
table.insert(myStatics, theStatic)
else
trigger.action.outText("+++clnZ - persistence: can't reconnect cloner <" .. cName .. "> with static <".. aName .. ">", 30)
end
end
theCloner.myStatics = myStatics
else
trigger.action.outText("+++clnZ - persistence: cannot synch cloner <" .. cName .. ">, does not exist", 30)
end
end
-- finally, synch uid and uuid
if theData.cuid and theData.cuid > cloneZones.uniqueCounter then
cloneZones.uniqueCounter = theData.cuid
end
if theData.uuiD and theData.uuid > dcsCommon.simpleUUID then
dcsCommon.simpleUUID = theData.uuid
end
end
-- --
-- START -- START
-- --
@ -1155,16 +1404,28 @@ function cloneZones.start()
cloneZones.addCloneZone(aZone) -- remember it so we can smoke it cloneZones.addCloneZone(aZone) -- remember it so we can smoke it
end end
-- run through onStart, but leave at least a few -- update all cloners and spawned clones from file
-- cycles to go through object removal so statics if persistence then
-- can spawn on ground. onStart is being deprecated, the -- sign up for persistence
-- raiseFlag module covers this since the first time callbacks = {}
-- raiseFlag is run is t0 + 0.5s callbacks.persistData = cloneZones.saveData
persistence.registerModule("cloneZones", callbacks)
-- now load my data
cloneZones.loadData()
end
-- schedule onStart, and leave at least a few
-- cycles to go through object removal
-- persistencey has loaded isStarted if a cloner was
-- already started
timer.scheduleFunction(cloneZones.onStart, {}, timer.getTime() + 0.1) timer.scheduleFunction(cloneZones.onStart, {}, timer.getTime() + 0.1)
-- start update -- start update
cloneZones.update() cloneZones.update()
-- start housekeeping
cloneZones.houseKeeping()
trigger.action.outText("cfx Clone Zones v" .. cloneZones.version .. " started.", 30) trigger.action.outText("cfx Clone Zones v" .. cloneZones.version .. " started.", 30)
return true return true
end end

View File

@ -1,5 +1,5 @@
dcsCommon = {} dcsCommon = {}
dcsCommon.version = "2.7.0" dcsCommon.version = "2.7.1"
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
2.2.6 - compassPositionOfARelativeToB 2.2.6 - compassPositionOfARelativeToB
- clockPositionOfARelativeToB - clockPositionOfARelativeToB
@ -89,6 +89,9 @@ dcsCommon.version = "2.7.0"
- new getSceneryObjectInZoneByName() - new getSceneryObjectInZoneByName()
2.7.0 - new synchGroupData() 2.7.0 - new synchGroupData()
clone, topClone and copyArray now all nil-trap clone, topClone and copyArray now all nil-trap
2.7.1 - new isPlayerUnit() -- moved from cfxPlayer
new getAllExistingPlayerUnitsRaw - from cfxPlayer
new typeIsInfantry()
--]]-- --]]--
@ -103,7 +106,8 @@ dcsCommon.version = "2.7.0"
-- globals -- globals
dcsCommon.cbID = 0 -- callback id for simple callback scheduling dcsCommon.cbID = 0 -- callback id for simple callback scheduling
dcsCommon.troopCarriers = {"Mi-8MT", "UH-1H", "Mi-24P"} -- Ka-50 and Gazelle can't carry troops dcsCommon.troopCarriers = {"Mi-8MT", "UH-1H", "Mi-24P"} -- Ka-50 and Gazelle can't carry troops
dcsCommon.coalitionSides = {0, 1, 2}
-- verify that a module is loaded. obviously not required -- verify that a module is loaded. obviously not required
-- for dcsCommon, but all higher-order modules -- for dcsCommon, but all higher-order modules
function dcsCommon.libCheck(testingFor, requiredLibs) function dcsCommon.libCheck(testingFor, requiredLibs)
@ -2248,6 +2252,28 @@ function dcsCommon.isTroopCarrier(theUnit)
return false return false
end end
function dcsCommon.isPlayerUnit(theUnit)
-- new patch. simply check if getPlayerName returns something
if not theUnit then return false end
if not theUnit.getPlayerName then return false end -- map/static object
local pName = theUnit:getPlayerName()
if pName then return true end
return false
end
function dcsCommon.getAllExistingPlayerUnitsRaw()
local apu = {}
for idx, theSide in pairs(dcsCommon.coalitionSides) do
local thePlayers = coalition.getPlayers(theSide)
for idy, theUnit in pairs (thePlayers) do
if theUnit and theUnit:isExist() then
table.insert(apu, theUnit)
end
end
end
return apu
end
function dcsCommon.getUnitAlt(theUnit) function dcsCommon.getUnitAlt(theUnit)
if not theUnit then return 0 end if not theUnit then return 0 end
if not theUnit:isExist() then return 0 end if not theUnit:isExist() then return 0 end
@ -2335,10 +2361,7 @@ function dcsCommon.getUnitHeadingDegrees(theUnit)
return heading * 57.2958 -- 180 / math.pi return heading * 57.2958 -- 180 / math.pi
end end
function dcsCommon.unitIsInfantry(theUnit) function dcsCommon.typeIsInfantry(theType)
if not theUnit then return false end
if not theUnit:isExist() then return end
local theType = theUnit:getTypeName()
local isInfantry = local isInfantry =
dcsCommon.containsString(theType, "infantry", false) or dcsCommon.containsString(theType, "infantry", false) or
dcsCommon.containsString(theType, "paratrooper", false) or dcsCommon.containsString(theType, "paratrooper", false) or
@ -2349,6 +2372,23 @@ function dcsCommon.unitIsInfantry(theUnit)
return isInfantry return isInfantry
end end
function dcsCommon.unitIsInfantry(theUnit)
if not theUnit then return false end
if not theUnit:isExist() then return end
local theType = theUnit:getTypeName()
--[[--
local isInfantry =
dcsCommon.containsString(theType, "infantry", false) or
dcsCommon.containsString(theType, "paratrooper", false) or
dcsCommon.containsString(theType, "stinger", false) or
dcsCommon.containsString(theType, "manpad", false) or
dcsCommon.containsString(theType, "soldier", false) or
dcsCommon.containsString(theType, "SA-18 Igla", false)
return isInfantry
--]]--
return dcsCommon.typeIsInfantry(theType)
end
function dcsCommon.coalition2county(inCoalition) function dcsCommon.coalition2county(inCoalition)
-- simply return UN troops for 0 neutral, -- simply return UN troops for 0 neutral,
-- joint red for 1 red -- joint red for 1 red

View File

@ -1,5 +1,5 @@
groupTracker = {} groupTracker = {}
groupTracker.version = "1.1.3" groupTracker.version = "1.1.4"
groupTracker.verbose = false groupTracker.verbose = false
groupTracker.ups = 1 groupTracker.ups = 1
groupTracker.requiredLibs = { groupTracker.requiredLibs = {
@ -20,6 +20,12 @@ groupTracker.trackers = {}
1.1.3 - spellings 1.1.3 - spellings
- addGroupToTrackerNamed bug removed accessing tracker - addGroupToTrackerNamed bug removed accessing tracker
- new removeGroupNamedFromTrackerNamed() - new removeGroupNamedFromTrackerNamed()
1.1.4 - destroy? input
- allGone! output
- triggerMethod
- method
- isDead optimization
--]]-- --]]--
function groupTracker.addTracker(theZone) function groupTracker.addTracker(theZone)
@ -87,7 +93,7 @@ function groupTracker.addGroupToTrackerNamed(theGroup, trackerName)
return return
end end
if not theGroup:isExist() then if not Group.isExist(theGroup) then
trigger.action.outText("+++gTrk: group does not exist when adding to tracker <" .. trackerName .. ">", 30) trigger.action.outText("+++gTrk: group does not exist when adding to tracker <" .. trackerName .. ">", 30)
return return
end end
@ -147,6 +153,17 @@ function groupTracker.createTrackerWithZone(theZone)
theZone.trackedGroups = {} theZone.trackedGroups = {}
theZone.trackerMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "inc")
if cfxZones.hasProperty(theZone, "trackerMethod") then
theZone.trackerMethod = cfxZones.getStringFromZoneProperty(theZone, "trackerMethod", "inc")
end
theZone.trackerTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "triggerMethod", "change")
if cfxZones.hasProperty(theZone, "trackerTriggerMethod") then
theZone.trackerTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "trackerTriggerMethod", "change")
end
if cfxZones.hasProperty(theZone, "numGroups") then if cfxZones.hasProperty(theZone, "numGroups") then
theZone.tNumGroups = cfxZones.getStringFromZoneProperty(theZone, "numGroups", "*<none>") theZone.tNumGroups = cfxZones.getStringFromZoneProperty(theZone, "numGroups", "*<none>")
-- we may need to zero this flag -- we may need to zero this flag
@ -181,6 +198,16 @@ function groupTracker.createTrackerWithZone(theZone)
end end
end end
if cfxZones.hasProperty(theZone, "destroy?") then
theZone.destroyFlag = cfxZones.getStringFromZoneProperty(theZone, "destroy?", "*<none>")
theZone.lastDestroyValue = cfxZones.getFlagValue(theZone.destroyFlag, theZone)
end
if cfxZones.hasProperty(theZone, "allGone!") then
theZone.allGoneFlag = cfxZones.getStringFromZoneProperty(theZone, "allGone!", "<None>") -- note string on number default
end
theZone.lastGroupCount = 0
if theZone.verbose or groupTracker.verbose then if theZone.verbose or groupTracker.verbose then
trigger.action.outText("gTrck: processed <" .. theZone.name .. ">", 30) trigger.action.outText("gTrck: processed <" .. theZone.name .. ">", 30)
end end
@ -189,12 +216,23 @@ end
-- --
-- update -- update
-- --
function groupTracker.destroyAllInZone(theZone)
for idx, theGroup in pairs(theZone.trackedGroups) do
if Group.isExist(theGroup) then
theGroup:destroy()
end
end
-- we keep all groups in trackedGroups so we
-- generate a host of destroy events when we run through
-- checkGroups next
end
function groupTracker.checkGroups(theZone) function groupTracker.checkGroups(theZone)
local filteredGroups = {} local filteredGroups = {}
for idx, theGroup in pairs(theZone.trackedGroups) do for idx, theGroup in pairs(theZone.trackedGroups) do
-- see if this group can be transferred -- see if this group can be transferred
local isDead = false local isDead = false
if theGroup:isExist() then --[[ if theGroup.isExist and theGroup:isExist() then
local allUnits = theGroup:getUnits() local allUnits = theGroup:getUnits()
isDead = true isDead = true
for idy, aUnit in pairs(allUnits) do for idy, aUnit in pairs(allUnits) do
@ -203,6 +241,8 @@ function groupTracker.checkGroups(theZone)
break break
end end
end end
--]]--
if Group.isExist(theGroup) and theGroup:getSize() > 0 then
else else
isDead = true -- no longer exists isDead = true -- no longer exists
end end
@ -238,7 +278,22 @@ function groupTracker.update()
timer.scheduleFunction(groupTracker.update, {}, timer.getTime() + 1/groupTracker.ups) timer.scheduleFunction(groupTracker.update, {}, timer.getTime() + 1/groupTracker.ups)
for idx, theZone in pairs(groupTracker.trackers) do for idx, theZone in pairs(groupTracker.trackers) do
if theZone.destroyFlag and cfxZones.testZoneFlag(theZone, theZone.destroyFlag, theZone.trackerTriggerMethod, "lastDestroyValue") then
groupTracker.destroyAllInZone(theZone)
if groupTracker.verbose or theZone.verbose then
trigger.action.outText("+++gTrk: destroying all groups tracked with <" .. theZone.name .. ">", 30)
end
end
groupTracker.checkGroups(theZone) groupTracker.checkGroups(theZone)
-- see if we need to bang on empty!
local currCount = #theZone.trackedGroups
if theZone.allGoneFlag and currCount == 0 and currCount ~= theZone.lastGroupCount then
cfxZones.pollFlag(aZone.allGoneFlag, aZone.trackerMethod, aZone)
end
theZone.lastGroupCount = currCount
end end
end end

View File

@ -1,5 +1,5 @@
limitedAirframes = {} limitedAirframes = {}
limitedAirframes.version = "1.4.0" limitedAirframes.version = "1.5.0"
limitedAirframes.verbose = false limitedAirframes.verbose = false
limitedAirframes.enabled = true -- can be turned off limitedAirframes.enabled = true -- can be turned off
limitedAirframes.userCanToggle = true -- F10 menu? limitedAirframes.userCanToggle = true -- F10 menu?
@ -18,7 +18,7 @@ limitedAirframes.requiredLibs = {
-- pretty stupid to check for this since we -- pretty stupid to check for this since we
-- need common to invoke the check, but anyway -- need common to invoke the check, but anyway
"cfxZones", -- Zones, of course for safe landings "cfxZones", -- Zones, of course for safe landings
"cfxPlayer", -- "cfxPlayer", no longer needed
} }
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
@ -48,8 +48,9 @@ limitedAirframes.requiredLibs = {
- 1.4.0 - DML integration, verbosity, clean-up, QoL improvements - 1.4.0 - DML integration, verbosity, clean-up, QoL improvements
redSafe, blueSafe with attribute, backward compatible redSafe, blueSafe with attribute, backward compatible
currRed currRed
- 1.4.1 - removed dependency to cfxPlayer
- 1.5.0 - persistence support
--]]-- --]]--
-- limitedAirframes manages the number of available player airframes -- limitedAirframes manages the number of available player airframes
@ -123,6 +124,7 @@ function limitedAirframes.readConfigZone()
end end
-- remember me -- remember me
limitedAirframes.config = theZone limitedAirframes.config = theZone
limitedAirframes.name = "limitedAirframes" -- so we can call cfxZones with ourself as param
limitedAirframes.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false) limitedAirframes.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
@ -131,22 +133,22 @@ function limitedAirframes.readConfigZone()
end end
-- ok, for each property, load it if it exists -- ok, for each property, load it if it exists
if cfxZones.hasProperty(theZone, "enabled") then -- if cfxZones.hasProperty(theZone, "enabled") then
limitedAirframes.enabled = cfxZones.getBoolFromZoneProperty(theZone, "enabled", true) limitedAirframes.enabled = cfxZones.getBoolFromZoneProperty(theZone, "enabled", true)
end -- end
if cfxZones.hasProperty(theZone, "userCanToggle") then -- if cfxZones.hasProperty(theZone, "userCanToggle") then
limitedAirframes.userCanToggle = cfxZones.getBoolFromZoneProperty(theZone, "userCanToggle", true) limitedAirframes.userCanToggle = cfxZones.getBoolFromZoneProperty(theZone, "userCanToggle", true)
end -- end
if cfxZones.hasProperty(theZone, "maxRed") then -- if cfxZones.hasProperty(theZone, "maxRed") then
limitedAirframes.maxRed = cfxZones.getNumberFromZoneProperty(theZone, "maxRed", -1) limitedAirframes.maxRed = cfxZones.getNumberFromZoneProperty(theZone, "maxRed", -1)
end -- end
if cfxZones.hasProperty(theZone, "maxBlue") then -- if cfxZones.hasProperty(theZone, "maxBlue") then
limitedAirframes.maxBlue = cfxZones.getNumberFromZoneProperty(theZone, "maxBlue", -1) limitedAirframes.maxBlue = cfxZones.getNumberFromZoneProperty(theZone, "maxBlue", -1)
end -- end
limitedAirframes.numRed = cfxZones.getStringFromZoneProperty(theZone, "#red", "*none") limitedAirframes.numRed = cfxZones.getStringFromZoneProperty(theZone, "#red", "*none")
limitedAirframes.numBlue = cfxZones.getStringFromZoneProperty(theZone, "#blue", "*none") limitedAirframes.numBlue = cfxZones.getStringFromZoneProperty(theZone, "#blue", "*none")
@ -318,7 +320,7 @@ function limitedAirframes.preProcessor(event)
end end
if not cfxPlayer.isPlayerUnit(theUnit) then if not dcsCommon.isPlayerUnit(theUnit) then
-- not a player unit. Events 5 and 6 have been -- not a player unit. Events 5 and 6 have been
-- handled before, so we can safely ignore -- handled before, so we can safely ignore
return false return false
@ -828,6 +830,37 @@ function limitedAirframes.pilotsRescued(theCoalition, success, numRescued, notes
trigger.action.outSoundForCoalition(theCoalition, limitedAirframes.warningSound)--"Quest Snare 3.wav") trigger.action.outSoundForCoalition(theCoalition, limitedAirframes.warningSound)--"Quest Snare 3.wav")
end end
--
-- Load / Save
--
function limitedAirframes.saveData()
local theData = {}
theData.currRed = limitedAirframes.currRed
theData.currBlue = limitedAirframes.currBlue
return theData
end
function limitedAirframes.loadData()
if not persistence then return end
local theData = persistence.getSavedDataForModule("limitedAirframes")
if not theData then
if limitedAirframes.verbose then
trigger.action.outText("+++limA: no save date received, skipping.", 30)
end
return
end
if theData.currRed then
limitedAirframes.currRed = theData.currRed
end
if theData.currBlue then
limitedAirframes.currBlue = theData.currBlue
end
end
-- --
-- START -- START
-- --
@ -885,7 +918,7 @@ function limitedAirframes.start()
limitedAirframes.currBlue = limitedAirframes.maxBlue limitedAirframes.currBlue = limitedAirframes.maxBlue
-- collect active player unit names -- collect active player unit names
local allPlayerUnits = cfxPlayer.getAllExistingPlayerUnitsRaw() local allPlayerUnits = dcsCommon.getAllExistingPlayerUnitsRaw()
for i=1, #allPlayerUnits do for i=1, #allPlayerUnits do
local aUnit = allPlayerUnits[i] local aUnit = allPlayerUnits[i]
limitedAirframes.addPlayerUnit(aUnit) limitedAirframes.addPlayerUnit(aUnit)
@ -905,6 +938,16 @@ function limitedAirframes.start()
else else
trigger.action.outText("+++limA: NO CSAR integration", 30) trigger.action.outText("+++limA: NO CSAR integration", 30)
end end
-- persistence: load states
if persistence then
-- sign up for persistence
callbacks = {}
callbacks.persistData = limitedAirframes.saveData
persistence.registerModule("limitedAirframes", callbacks)
-- now load my data
limitedAirframes.loadData()
end
-- say hi -- say hi
trigger.action.outText("cf/x Limited Airframes v" .. limitedAirframes.version .. " started: R:".. limitedAirframes.maxRed .. "/B:" .. limitedAirframes.maxBlue, 30) trigger.action.outText("cf/x Limited Airframes v" .. limitedAirframes.version .. " started: R:".. limitedAirframes.maxRed .. "/B:" .. limitedAirframes.maxBlue, 30)

View File

@ -1,5 +1,5 @@
persistence = {} persistence = {}
persistence.version = "1.0.1" persistence.version = "1.0.2"
persistence.ups = 1 -- once every 1 seconds persistence.ups = 1 -- once every 1 seconds
persistence.verbose = false persistence.verbose = false
persistence.active = false persistence.active = false
@ -21,6 +21,7 @@ persistence.requiredLibs = {
- spelling - spelling
- cfxZones interface - cfxZones interface
- always output save location - always output save location
1.0.2 - QoL when verbosity is on
PROVIDES LOAD/SAVE ABILITY TO MODULES PROVIDES LOAD/SAVE ABILITY TO MODULES
@ -304,9 +305,9 @@ function persistence.missionStartDataLoad()
-- we are done for now. modules check in -- we are done for now. modules check in
-- after persistence and load their own data -- after persistence and load their own data
-- when they detect that there is data to load -- when they detect that there is data to load
if persistence.verbose then
trigger.action.outText("+++persistence: basic import complete.", 30) trigger.action.outText("+++persistence: successfully read mission save data", 30)
end
end end
-- --
@ -345,6 +346,10 @@ function persistence.saveMissionData()
if persistence.verbose then if persistence.verbose then
trigger.action.outText("+++persistence: gathered data from <" .. moduleName .. ">", 30) trigger.action.outText("+++persistence: gathered data from <" .. moduleName .. ">", 30)
end end
else
if persistence.verbose then
trigger.action.outText("+++persistence: NO DATA gathered data from <" .. moduleName .. ">, module returned NIL", 30)
end
end end
end end

View File

@ -88,44 +88,7 @@ function radioMenu.createRadioMenuWithZone(theZone)
if theZone.menuVisible then if theZone.menuVisible then
radioMenu.installMenu(theZone) radioMenu.installMenu(theZone)
end end
--[[--
-- now do the two options
local menuA = cfxZones.getStringFromZoneProperty(theZone, "itemA", "<no A submenu>")
if theZone.coalition == 0 then
theZone.menuA = missionCommands.addCommand(menuA, theZone.rootMenu, radioMenu.redirectMenuX, {theZone, "A"})
else
theZone.menuA = missionCommands.addCommandForCoalition(theZone.coalition, menuA, theZone.rootMenu, radioMenu.redirectMenuX, {theZone, "A"})
end
if cfxZones.hasProperty(theZone, "itemB") then
local menuB = cfxZones.getStringFromZoneProperty(theZone, "itemB", "<no B submenu>")
if theZone.coalition == 0 then
theZone.menuB = missionCommands.addCommand(menuB, theZone.rootMenu, radioMenu.redirectMenuX, {theZone, "B"})
else
theZone.menuB = missionCommands.addCommandForCoalition(theZone.coalition, menuB, theZone.rootMenu, radioMenu.redirectMenuX, {theZone, "B"})
end
end
if cfxZones.hasProperty(theZone, "itemC") then
local menuC = cfxZones.getStringFromZoneProperty(theZone, "itemC", "<no C submenu>")
if theZone.coalition == 0 then
theZone.menuC = missionCommands.addCommand(menuC, theZone.rootMenu, radioMenu.redirectMenuX, {theZone, "C"})
else
theZone.menuC = missionCommands.addCommandForCoalition(theZone.coalition, menuC, theZone.rootMenu, radioMenu.redirectMenuX, {theZone, "C"})
end
end
if cfxZones.hasProperty(theZone, "itemD") then
local menuD = cfxZones.getStringFromZoneProperty(theZone, "itemD", "<no D submenu>")
if theZone.coalition == 0 then
theZone.menuD = missionCommands.addCommand(menuD, theZone.rootMenu, radioMenu.redirectMenuX, {theZone, "D"})
else
theZone.menuD = missionCommands.addCommandForCoalition(theZone.coalition, menuD, theZone.rootMenu, radioMenu.redirectMenuX, {theZone, "D"})
end
end
--]]--
-- get the triggers & methods here -- get the triggers & methods here
theZone.radioMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "inc") theZone.radioMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "inc")
if cfxZones.hasProperty(theZone, "radioMethod") then if cfxZones.hasProperty(theZone, "radioMethod") then

18
modules/sixthSense.lua Normal file
View File

@ -0,0 +1,18 @@
sixthSense = {}
sixthSense.version = "1.0.0"
-- sniff out dead events and log them
function sixthSense:onEvent(event)
if event.id == 8 then -- S_EVENT_DEAD
if event.initiator then
local theObject = event.initiator
trigger.action.outText("DEAD event: " .. theObject:getName(), 30)
else
trigger.action.outText("DEAD event, no initiator", 30)
end
end
end
-- add event handler
world.addEventHandler(sixthSense)
trigger.action.outText("sixthSense v" .. sixthSense.version .. " loaded.", 30)

View File

@ -1289,4 +1289,5 @@ end
-q d.e.f returns string "asdasda..." -q d.e.f returns string "asdasda..."
-q sada reuturs <nil> -q sada reuturs <nil>
- xref: which zones/attributes reference a flag, g.g. '-xref go'
--]]-- --]]--

View File

@ -1,5 +1,5 @@
unitPersistence = {} unitPersistence = {}
unitPersistence.version = '1.0.1' unitPersistence.version = '1.1.1'
unitPersistence.verbose = false unitPersistence.verbose = false
unitPersistence.updateTime = 60 -- seconds. Once every minute check statics unitPersistence.updateTime = 60 -- seconds. Once every minute check statics
unitPersistence.requiredLibs = { unitPersistence.requiredLibs = {
@ -15,6 +15,11 @@ unitPersistence.requiredLibs = {
- handles linked static objects - handles linked static objects
- does no longer mess with heliports - does no longer mess with heliports
- update statics once a minute, not second - update statics once a minute, not second
1.0.2 - fixes coalition bug for static objects
1.1.0 - added air and sea units - for filtering destroyed units
1.1.1 - fixed static link (again)
- fixed air spawn (fixed wing)
REQUIRES PERSISTENCE AND MX REQUIRES PERSISTENCE AND MX
@ -23,6 +28,10 @@ unitPersistence.requiredLibs = {
--]]-- --]]--
unitPersistence.groundTroops = {} -- local buffered copy that we unitPersistence.groundTroops = {} -- local buffered copy that we
-- maintain from save to save -- maintain from save to save
unitPersistence.fixedWing = {}
unitPersistence.rotorWing = {}
unitPersistence.ships = {}
unitPersistence.statics = {} -- locally unpacked and buffered static objects unitPersistence.statics = {} -- locally unpacked and buffered static objects
-- --
@ -49,18 +58,24 @@ function unitPersistence.saveData()
local uName = theUnitData.name local uName = theUnitData.name
local gUnit = Unit.getByName(uName) local gUnit = Unit.getByName(uName)
if gUnit and gUnit:isExist() then if gUnit and gUnit:isExist() then
-- got a live one! if gUnit:isActive() then
gotALiveOne = true theUnitData.isDead = gUnit:getLife() < 1
-- update x and y and heading if not theUnitData.isDead then
theUnitData.heading = dcsCommon.getUnitHeading(gUnit) -- got a live one!
pos = gUnit:getPoint() gotALiveOne = true
theUnitData.x = pos.x -- update x, y and heading
theUnitData.y = pos.z -- (!!) theUnitData.heading = dcsCommon.getUnitHeading(gUnit)
-- ground units do not use alt pos = gUnit:getPoint()
theUnitData.x = pos.x
theUnitData.y = pos.z -- (!!)
end
else
gotALiveOne = true -- not yet activated
end
else else
theUnitData.isDead = true theUnitData.isDead = true
end -- is alive and exists? end -- is alive and exists?
end -- unit not dead end -- unit maybe not dead
end -- iterate units in group end -- iterate units in group
groupData.isDead = not gotALiveOne groupData.isDead = not gotALiveOne
end -- if group is not dead end -- if group is not dead
@ -69,6 +84,136 @@ function unitPersistence.saveData()
end end
end end
-- aircraft
for groupName, groupData in pairs(unitPersistence.fixedWing) do
-- we update this record live and save it to file
if not groupData.isDead then
local gotALiveOne = false
local allUnits = groupData.units
for idx, theUnitData in pairs(allUnits) do
if not theUnitData.isDead then
local uName = theUnitData.name
local gUnit = Unit.getByName(uName)
if gUnit and gUnit:isExist() then
-- update x and y and heading if active and alive
if gUnit:isActive() then -- only overwrite if active
theUnitData.isDead = gUnit:getLife() < 1
if not theUnitData.isDead then
gotALiveOne = true -- this group is still alive
theUnitData.heading = dcsCommon.getUnitHeading(gUnit)
pos = gUnit:getPoint()
theUnitData.x = pos.x
theUnitData.y = pos.z -- (!!)
theUnitData.alt = pos.y
theUnitData.alt_type = "BARO"
theUnitData.speed = dcsCommon.vMag(gUnit:getVelocity())
-- we now could get fancy and do some proccing of the
-- waypoints and make the one it's nearest to its
-- current waypoint, curtailing all others, but that
-- may easily mess with waypoint actions, so we don't
end
else
gotALiveOne = true -- has not yet been activated, live
end
else
theUnitData.isDead = true
-- trigger.action.outText("+++unitPersistence - unit <" .. uName .. "> of group <" .. groupName .. "> is dead or non-existant", 30)
end -- is alive and exists?
end -- unit maybe not dead
end -- iterate units in group
groupData.isDead = not gotALiveOne
end -- if group is not dead
if unitPersistence.verbose then
trigger.action.outText("unitPersistence: save - processed air group <" .. groupName .. ">.", 30)
end
end
-- helos
for groupName, groupData in pairs(unitPersistence.rotorWing) do
-- we update this record live and save it to file
if not groupData.isDead then
local gotALiveOne = false
local allUnits = groupData.units
for idx, theUnitData in pairs(allUnits) do
if not theUnitData.isDead then
local uName = theUnitData.name
local gUnit = Unit.getByName(uName)
if gUnit and gUnit:isExist() then
-- update x and y and heading if active and alive
if gUnit:isActive() then -- only overwrite if active
theUnitData.isDead = gUnit:getLife() < 1
if not theUnitData.isDead then
gotALiveOne = true -- this group is still alive
theUnitData.heading = dcsCommon.getUnitHeading(gUnit)
pos = gUnit:getPoint()
theUnitData.x = pos.x
theUnitData.y = pos.z -- (!!)
theUnitData.alt = pos.y
theUnitData.alt_type = "BARO"
theUnitData.speed = dcsCommon.vMag(gUnit:getVelocity())
-- we now could get fancy and do some proccing of the
-- waypoints and make the one it's nearest to its
-- current waypoint, curtailing all others, but that
-- may easily mess with waypoint actions, so we don't
end
else
gotALiveOne = true -- has not yet been activated, live
end
else
theUnitData.isDead = true
trigger.action.outText("+++unitPersistence - unit <" .. uName .. "> of group <" .. groupName .. "> is dead or non-existant", 30)
end -- is alive and exists?
end -- unit maybe not dead
end -- iterate units in group
groupData.isDead = not gotALiveOne
end -- if group is not dead
if unitPersistence.verbose then
trigger.action.outText("unitPersistence: save - processed helo group <" .. groupName .. ">.", 30)
end
end
-- ships
for groupName, groupData in pairs(unitPersistence.ships) do
-- we update this record live and save it to file
if not groupData.isDead then
local gotALiveOne = false
local allUnits = groupData.units
for idx, theUnitData in pairs(allUnits) do
if not theUnitData.isDead then
local uName = theUnitData.name
local gUnit = Unit.getByName(uName)
if gUnit and gUnit:isExist() then
-- update x and y and heading if active and alive
if gUnit:isActive() then -- only overwrite if active
theUnitData.isDead = gUnit:getLife() < 1
if not theUnitData.isDead then
gotALiveOne = true -- this group is still alive
theUnitData.heading = dcsCommon.getUnitHeading(gUnit)
pos = gUnit:getPoint()
theUnitData.x = pos.x
theUnitData.y = pos.z -- (!!)
-- we only filter dead ships and don't mess with others
-- during load, so we are doing this solely for possible
-- later expansions
end
else
gotALiveOne = true -- has not yet been activated, live
end
else
theUnitData.isDead = true
if unitPersistence.verbose then
trigger.action.outText("+++unitPersistence - unit <" .. uName .. "> of group <" .. groupName .. "> is dead or non-existant", 30)
end
end -- is alive and exists?
end -- unit maybe not dead
end -- iterate units in group
groupData.isDead = not gotALiveOne
end -- if group is not dead
if unitPersistence.verbose then
trigger.action.outText("unitPersistence: save - processed ship group <" .. groupName .. ">.", 30)
end
end
-- process all static objects placed with ME -- process all static objects placed with ME
for oName, oData in pairs(unitPersistence.statics) do for oName, oData in pairs(unitPersistence.statics) do
if not oData.isDead or oData.lateActivation then if not oData.isDead or oData.lateActivation then
@ -96,6 +241,10 @@ function unitPersistence.saveData()
theData.version = unitPersistence.version theData.version = unitPersistence.version
theData.ground = unitPersistence.groundTroops theData.ground = unitPersistence.groundTroops
theData.fixedWing = unitPersistence.fixedWing
theData.rotorWing = unitPersistence.rotorWing
theData.ships = unitPersistence.ships
theData.statics = unitPersistence.statics theData.statics = unitPersistence.statics
return theData return theData
end end
@ -103,6 +252,13 @@ end
-- --
-- Load Mission Data -- Load Mission Data
-- --
function unitPersistence.delayedSpawn(args)
local cat = args.cat
local cty = args.cty
local newGroup = args.newGroup
local theGroup = coalition.addGroup(cty, cat, newGroup)
end
function unitPersistence.loadMission() function unitPersistence.loadMission()
local theData = persistence.getSavedDataForModule("unitPersistence") local theData = persistence.getSavedDataForModule("unitPersistence")
if not theData then if not theData then
@ -137,7 +293,6 @@ function unitPersistence.loadMission()
-- filter all dead groups -- filter all dead groups
if theUnitData.isDead then if theUnitData.isDead then
-- skip it -- skip it
else else
-- add it to new group -- add it to new group
table.insert(newUnits, theUnitData) table.insert(newUnits, theUnitData)
@ -145,29 +300,129 @@ function unitPersistence.loadMission()
end end
-- replace old unit setup with new -- replace old unit setup with new
newGroup.units = newUnits newGroup.units = newUnits
local cty = groupData.cty local cty = groupData.cty
local cat = groupData.cat local cat = groupData.cat
-- destroy the old group -- spawn new one, replaces old one
--theGroup:destroy() -- will be replaced
-- spawn new one
theGroup = coalition.addGroup(cty, cat, newGroup) theGroup = coalition.addGroup(cty, cat, newGroup)
if not theGroup then if not theGroup then
trigger.action.outText("+++ failed to add modified group <" .. groupName .. ">") trigger.action.outText("+++ failed to add modified group <" .. groupName .. ">", 30)
end
if unitPersistence.verbose then
-- trigger.action.outText("+++unitPersistence: updated group <" .. groupName .. "> of cat <" .. cat .. "> for cty <" .. cty .. ">", 30)
end end
end end
end end
unitPersistence.groundTroops = theData.ground
else else
if unitPersistence.verbose then if unitPersistence.verbose then
trigger.action.outText("+++unitPersistence: no ground unit data.", 30) trigger.action.outText("+++unitPersistence: no ground unit data.", 30)
end end
end end
if theData.fixedWing then
for groupName, groupData in pairs(theData.fixedWing) do
--trigger.action.outText("+++ start loading group <" .. groupName .. ">", 30)
local theGroup = Group.getByName(groupName)
if not theGroup then
mismatchWarning = true
elseif groupData.isDead then
theGroup:destroy()
elseif groupData.isPlayer then
-- skip it
else
local newGroup = dcsCommon.clone(groupData)
local newUnits = {}
for idx, theUnitData in pairs(groupData.units) do
-- filter all dead groups
if theUnitData.isDead then
-- skip it
else
-- add it to new group
table.insert(newUnits, theUnitData)
end
end
-- replace old unit setup with (delayed) new
newGroup.units = newUnits
local cty = groupData.cty
local cat = groupData.cat
-- spawn new one, replaces old one
theGroup:destroy()
local args = {}
args.cty = cty
args.cat = cat
args.newGroup = newGroup
-- since DCS can't replace a group directly (none will appear), we introduce a brief interval for things to settle
timer.scheduleFunction(unitPersistence.delayedSpawn, args, timer.getTime()+0.5)
end
end
unitPersistence.fixedWing = theData.fixedWing
else
if unitPersistence.verbose then
trigger.action.outText("+++unitPersistence: no aircraft (fixed wing) unit data.", 30)
end
end
if theData.rotorWing then
for groupName, groupData in pairs(theData.rotorWing) do
local theGroup = Group.getByName(groupName)
if not theGroup then
mismatchWarning = true
elseif groupData.isDead then
theGroup:destroy()
elseif groupData.isPlayer then
-- skip it
else
local newGroup = dcsCommon.clone(groupData)
local newUnits = {}
for idx, theUnitData in pairs(groupData.units) do
-- filter all dead groups
if theUnitData.isDead then
-- skip it
else
-- add it to new group
table.insert(newUnits, theUnitData)
end
end
-- replace old unit setup with new
newGroup.units = newUnits
local cty = groupData.cty
local cat = groupData.cat
-- spawn new one, replaces old one
theGroup = coalition.addGroup(cty, cat, newGroup)
if not theGroup then
trigger.action.outText("+++ failed to add modified group <" .. groupName .. ">", 30)
end
end
end
unitPersistence.rotorWing = theData.rotorWing
else
if unitPersistence.verbose then
trigger.action.outText("+++unitPersistence: no rotor wing unit data.", 30)
end
end
if theData.ships then
for groupName, groupData in pairs(theData.ships) do
local theGroup = Group.getByName(groupName)
if not theGroup then
mismatchWarning = true
elseif groupData.isDead then
-- when entire group is destroyed, we will also
-- destroy group. Else all survive
-- we currently don't dick around with carrieres unless they are dead
theGroup:destroy()
else
-- do nothing
end
end
unitPersistence.ships = theData.ships
else
if unitPersistence.verbose then
trigger.action.outText("+++unitPersistence: no rotor wing unit data.", 30)
end
end
-- and now the same for static objects -- and now the same for static objects
if theData.statics then if theData.statics then
for name, staticData in pairs(theData.statics) do for name, staticData in pairs(theData.statics) do
@ -177,8 +432,6 @@ function unitPersistence.loadMission()
if unitPersistence.verbose then if unitPersistence.verbose then
trigger.action.outText("+++unitPersistence: static <" .. name .. "> is late activate, no update", 30) trigger.action.outText("+++unitPersistence: static <" .. name .. "> is late activate, no update", 30)
end end
--elseif not theStatic then
-- mismatchWarning = true
elseif staticData.category == "Heliports" then elseif staticData.category == "Heliports" then
-- FARPS are static objects that HATE to be -- FARPS are static objects that HATE to be
-- messed with, so we don't -- messed with, so we don't
@ -188,7 +441,7 @@ function unitPersistence.loadMission()
else else
local newStatic = dcsCommon.clone(staticData) local newStatic = dcsCommon.clone(staticData)
-- add link info if it exists -- add link info if it exists
newStatic.linkUnit = cfxMX.linkByName[name] newStatic.linkUnit = cfxMX.linkByName[staticData.groupName]
if newStatic.linkUnit and unitPersistence.verbose then if newStatic.linkUnit and unitPersistence.verbose then
trigger.action.outText("+++unitPersistence: linked static <" .. name .. "> to unit <" .. newStatic.linkUnit .. ">", 30) trigger.action.outText("+++unitPersistence: linked static <" .. name .. "> to unit <" .. newStatic.linkUnit .. ">", 30)
end end
@ -197,15 +450,16 @@ function unitPersistence.loadMission()
-- spawn new one, replacing same.named old, dead if required -- spawn new one, replacing same.named old, dead if required
gStatic = coalition.addStaticObject(cty, newStatic) gStatic = coalition.addStaticObject(cty, newStatic)
if not gStatic then if not gStatic then
trigger.action.outText("+++ failed to add modified static <" .. name .. ">") trigger.action.outText("+++ failed to add modified static <" .. name .. ">", 30)
end end
if unitPersistence.verbose then if unitPersistence.verbose then
local note = "" local note = ""
if newStatic.dead then note = " (dead)" end if newStatic.dead then note = " (dead)" end
-- trigger.action.outText("+++unitPersistence: updated static <" .. name .. "> for cty <" .. cty .. ">" .. note, 30) trigger.action.outText("+++unitPersistence: updated static <" .. name .. "> for cty <" .. cty .. ">" .. note, 30)
end end
end end
end end
unitPersistence.statics = theData.statics
end end
if mismatchWarning then if mismatchWarning then
@ -268,6 +522,7 @@ function unitPersistence.start()
-- create a local copy of the entire groundForces data that -- create a local copy of the entire groundForces data that
-- we maintain internally. It's fixed, and we work on our -- we maintain internally. It's fixed, and we work on our
-- own copy for speed -- own copy for speed
unitPersistence.groundTroops = {}
for gname, data in pairs(cfxMX.allGroundByName) do for gname, data in pairs(cfxMX.allGroundByName) do
local gd = dcsCommon.clone(data) -- copy the record local gd = dcsCommon.clone(data) -- copy the record
gd.isDead = false -- init new field to alive gd.isDead = false -- init new field to alive
@ -275,14 +530,71 @@ function unitPersistence.start()
gd.cat = cfxMX.catText2ID("vehicle") gd.cat = cfxMX.catText2ID("vehicle")
local gGroup = Group.getByName(gname) local gGroup = Group.getByName(gname)
if not gGroup then if not gGroup then
trigger.action.outText("+++warning: group <" .. gname .. "> does not exist in-game!?", 30) trigger.action.outText("+++warning: ground group <" .. gname .. "> does not exist in-game!?", 30)
else else
local firstUnit = gGroup:getUnit(1) gd.cty = cfxMX.countryByName[gname]
gd.cty = firstUnit:getCountry()
unitPersistence.groundTroops[gname] = gd unitPersistence.groundTroops[gname] = gd
end end
end end
-- now add all aircraft
unitPersistence.fixedWing = {}
for gname, data in pairs(cfxMX.allFixedByName) do
local gd = dcsCommon.clone(data) -- copy the record
gd.isDead = false -- init new field to alive
gd.isPlayer = (cfxMX.playerGroupByName[gname] ~= nil)
-- coalition and country
gd.cat = cfxMX.catText2ID("plane") -- 0
gd.cty = cfxMX.countryByName[gname]
local gGroup = Group.getByName(gname)
if gd.isPlayer then
-- skip
elseif not gGroup then
trigger.action.outText("+++warning: fixed-wing group <" .. gname .. "> does not exist in-game!?", 30)
else
unitPersistence.fixedWing[gname] = gd
end
end
-- and helicopters
unitPersistence.rotorWing = {}
for gname, data in pairs(cfxMX.allHeloByName) do
local gd = dcsCommon.clone(data) -- copy the record
gd.isDead = false -- init new field to alive
gd.isPlayer = (cfxMX.playerGroupByName[gname] ~= nil)
-- coalition and country
gd.cat = cfxMX.catText2ID("helicopter") -- 1
gd.cty = cfxMX.countryByName[gname]
local gGroup = Group.getByName(gname)
if gd.isPlayer then
-- skip
elseif not gGroup then
trigger.action.outText("+++warning: helo group <" .. gname .. "> does not exist in-game!?", 30)
else
unitPersistence.rotorWing[gname] = gd
end
end
-- finally ships
-- we only do ships to remove them when they are dead because
-- messing with ships can give problems: aircraft carriers.
unitPersistence.ships = {}
for gname, data in pairs(cfxMX.allSeaByName) do
local gd = dcsCommon.clone(data) -- copy the record
gd.isDead = false -- init new field to alive
-- coalition and country
gd.cat = cfxMX.catText2ID("ship") -- 3
gd.cty = cfxMX.countryByName[gname]
local gGroup = Group.getByName(gname)
if gd.isPlayer then
-- skip
elseif not gGroup then
trigger.action.outText("+++warning: ship group <" .. gname .. "> does not exist in-game!?", 30)
else
unitPersistence.ships[gname] = gd
end
end
-- make local copies of all static MX objects -- make local copies of all static MX objects
-- that we also maintain internally, and convert them to game -- that we also maintain internally, and convert them to game
-- spawnable objects -- spawnable objects
@ -294,19 +606,18 @@ function unitPersistence.start()
local theStatic = dcsCommon.clone(staticData) local theStatic = dcsCommon.clone(staticData)
theStatic.isDead = false theStatic.isDead = false
theStatic.groupId = mxData.groupId theStatic.groupId = mxData.groupId
theStatic.groupName = name -- save top-level name
theStatic.cat = cfxMX.catText2ID("static") theStatic.cat = cfxMX.catText2ID("static")
theStatic.cty = cfxMX.countryByName[name] theStatic.cty = cfxMX.countryByName[name]
--trigger.action.outText("Processed MX static group <" .. name .. ">, object <" .. name .. "> with cty <" .. theStatic.cty .. ">",30)
local gameOb = StaticObject.getByName(theStatic.name) local gameOb = StaticObject.getByName(theStatic.name)
if not gameOb then if not gameOb then
if unitPersistence.verbose then if unitPersistence.verbose then
trigger.action.outText("+++unitPersistence: static object <" .. theStatic.name .. "> has late activation", 30) trigger.action.outText("+++unitPersistence: static object <" .. theStatic.name .. "> has late activation", 30)
end end
theStatic.lateActivation = true theStatic.lateActivation = true
else
--theStatic.cty = gameOb:getCountry()
--unitPersistence.statics[theStatic.name] = theStatic
end end
unitPersistence.statics[theStatic.name] = theStatic unitPersistence.statics[theStatic.name] = theStatic -- HERE WE CHANGE FROM GROUP NAME TO STATIC NAME!!!
end end
end end
@ -328,6 +639,11 @@ if not unitPersistence.start() then
unitPersistence = nil unitPersistence = nil
end end
--[[-- --[[--
ToDo: linked statics and linked units on restore - waypoint analysis for aircraft so
- whan they have take off as Inital WP and they are moving
then we change the first WP to 'turning point'
- waypoint analysis to match waypoint to position. very difficult if waypoints describe a circle.
- group analysis for carriers to be able to process groups that do
not contain carriers
--]]-- --]]--