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
for theZone, theFARP in pairs(FARPZones.allFARPZones) do
fName = theZone.name
trigger.action.outText("frpZ persistence: processing FARP <" .. fName .. ">", 30)
--trigger.action.outText("frpZ persistence: processing FARP <" .. fName .. ">", 30)
local fData = {}
fData.owner = theFARP.owner
fData.defenderData = dcsCommon.clone(theFARP.defenderData)

View File

@ -1,5 +1,5 @@
rndFlags = {}
rndFlags.version = "1.3.2"
rndFlags.version = "1.4.0"
rndFlags.verbose = false
rndFlags.requiredLibs = {
"dcsCommon", -- always
@ -30,6 +30,8 @@ rndFlags.requiredLibs = {
- added 'rndDone!' flag
- rndMethod defaults to "inc"
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)
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)
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
--
@ -214,7 +172,6 @@ function rndFlags.fire(theZone)
if rndFlags.verbose or theZone.verbose then
trigger.action.outText("+++RND: RND " .. theZone.name .. " ran out of flags. Will fire 'done' instead ", 30)
end
if theZone.doneFlag then
cfxZones.pollFlag(theZone.doneFlag, theZone.rndMethod, theZone)
end
@ -281,15 +238,67 @@ end
function rndFlags.startCycle()
for idx, theZone in pairs(rndFlags.rndGen) do
if theZone.onStart then
if rndFlags.verbose or theZone.verbose then
trigger.action.outText("+++RND: starting " .. theZone.name, 30)
if theZone.isStarted then
-- suppressed by persistence
else
if rndFlags.verbose or theZone.verbose then
trigger.action.outText("+++RND: starting " .. theZone.name, 30)
end
rndFlags.fire(theZone)
end
rndFlags.fire(theZone)
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
--
@ -312,7 +321,7 @@ end
function rndFlags.start()
-- lib check
if not dcsCommon.libCheck then
if not dcsCommon then
trigger.action.outText("RNDFlags requires dcsCommon", 30)
return false
end
@ -345,6 +354,15 @@ function rndFlags.start()
rndFlags.addRNDZone(aZone) -- remember it so we can smoke it
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
timer.scheduleFunction(rndFlags.startCycle, {}, timer.getTime() + 0.25)
@ -361,4 +379,3 @@ if not rndFlags.start() then
rndFlags = nil
end
-- TODO: move flags to RND!, rename RND to RND!, deprecate flags!

View File

@ -1,5 +1,5 @@
cfxGroundTroops = {}
cfxGroundTroops.version = "1.7.6"
cfxGroundTroops.version = "1.7.7"
cfxGroundTroops.ups = 1
cfxGroundTroops.verbose = false
cfxGroundTroops.requiredLibs = {
@ -61,6 +61,7 @@ cfxGroundTroops.deployedTroops = {}
-- 1.7.4 - verbose flag, warnings suppressed
-- 1.7.5 - some troop.group hardening with isExist()
-- 1.7.6 - fixed switchToOffroad
-- 1.7.7 - no longer case sensitive for orders
-- 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
-- are revealed. For now, do nothing
cfxGroundTroops.updateWait(troop)
--REMEMBER: LOWER CASE ONLY!
elseif troop.orders == "guard" then
cfxGroundTroops.updateGuards(troop)
elseif troop.orders == "attackOwnedZone" then
elseif troop.orders == "attackownedzone" then
cfxGroundTroops.updateZoneAttackers(troop)
elseif troop.orders == "laze" then
cfxGroundTroops.updateLaze(troop)
elseif troop.orders == "attackZone" then
elseif troop.orders == "attackzone" then
cfxGroundTroops.updateAttackers(troop)
else
@ -752,7 +753,7 @@ function cfxGroundTroops.updateSingleScheduled(params)
-- check max speed of group. if < 0.1 then note and increase
-- 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
troops.speedWarning = troops.speedWarning + 1
else
@ -918,7 +919,7 @@ function cfxGroundTroops.getTroopReport(theSide, ignoreInfantry)
if troop.side == theSide and troop.group:isExist() then
local unitNum = troop.group:getSize()
report = report .. "\n" .. troop.name .. " (".. unitNum .."): <" .. troop.orders .. ">"
if troop.orders == "attackOwnedZone" then
if troop.orders == "attackownedzone" then
if troop.destination then
report = report .. " move towards " .. troop.destination.name
else
@ -944,19 +945,16 @@ function cfxGroundTroops.createGroundTroops(inGroup, range, orders)
local newTroops = {}
if not orders then
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
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
newTroops.insideDestination = false
newTroops.unscheduleCount = 0 -- will count up as we aren't scheduled
newTroops.speedWarning = 0
newTroops.isOffroad = false -- if true, we switched to direct orders, not roads, after standstill
newTroops.group = inGroup
newTroops.orders = orders
newTroops.orders = orders:lower()
newTroops.coalition = inGroup:getCoalition()
newTroops.side = newTroops.coalition -- because we'e been using both.
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)
return
end
if not troops.orders then troops.orders = "guard" end
troops.orders = troops.orders:lower()
troops.reschedule = true -- in case we use scheduled update
-- we now add to internal array. this is worked on by all
-- update meths, on scheduled upadtes, it is only used to

View File

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

View File

@ -1,5 +1,5 @@
cfxHeloTroops = {}
cfxHeloTroops.version = "2.1.0"
cfxHeloTroops.version = "2.2.0"
cfxHeloTroops.verbose = false
cfxHeloTroops.autoDrop = true
cfxHeloTroops.autoPickup = false
@ -18,6 +18,10 @@ cfxHeloTroops.pickupRange = 100 -- meters
-- -- check spawner legality by types
-- -- updated types to include 2.7.6 additions to infantry
-- -- 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,
@ -40,10 +44,15 @@ cfxHeloTroops.requiredLibs = {
cfxHeloTroops.unitConfigs = {} -- all configs are stored by unit's name
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
-- persistence support
cfxHeloTroops.deployedTroops = {}
function cfxHeloTroops.resetConfig(conf)
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
@ -113,7 +122,7 @@ function cfxHeloTroops.preProcessor(event)
-- make sure it has an initiator
if not event.initiator then return false end -- no 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()
if cat ~= Group.Category.HELICOPTER then return false end
@ -414,9 +423,16 @@ function cfxHeloTroops.addGroundMenu(conf)
local typeArray = dcsCommon.splitString(theTypes, ',')
typeArray = dcsCommon.trimArray(typeArray)
local allLegal = true
-- check agianst default (dcsCommon) or own definition (if exists)
for idy, aType in pairs(typeArray) do
if not dcsCommon.arrayContainsString(cfxHeloTroops.legalTroops, aType) then
allLegal = false
if cfxHeloTroops.legalTroops then
if not dcsCommon.arrayContainsString(cfxHeloTroops.legalTroops, aType) then
allLegal = false
end
else
if not dcsCommon.typeIsInfantry(aType) then
allLegal = false
end
end
end
if allLegal then
@ -503,9 +519,16 @@ function cfxHeloTroops.filterTroopsByType(unitsToLoad)
local pass = true
for iT, sT in pairs(aT) do
-- check if this is a valid type
if not dcsCommon.arrayContainsString(cfxHeloTroops.legalTroops, sT) then
pass = false
break
if cfxHeloTroops.legalTroops then
if not dcsCommon.arrayContainsString(cfxHeloTroops.legalTroops, sT) then
pass = false
break
end
else
if not dcsCommon.typeIsInfantry(sT) then
pass = false
break
end
end
end
if pass then
@ -604,13 +627,21 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
local chopperZone = cfxZones.createSimpleZone("choppa", p, 12) -- 12 m ratius around choppa
--local theCoalition = theUnit:getCountry() -- make it choppers country
local theCoalition = theUnit:getGroup():getCoalition() -- make it choppers COALITION
local theGroup = cfxZones.createGroundUnitsInZoneForCoalition (
local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
theCoalition,
conf.troopsOnBoard.name, -- dcsCommon.uuid("Assault"), -- maybe use config name as loaded from the group
chopperZone,
unitTypes,
conf.dropFormation,
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
-- instead of scheduling tasking in one second, we add to
-- 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
group:destroy()
-- now immediately run a GC so this group is removed
-- from any save data
cfxHeloTroops.GC()
-- say so
trigger.action.outTextForGroup(conf.id, "Team '".. conf.troopsOnBoard.name .."' aboard, ready to go!", 30)
-- reset menu
cfxHeloTroops.removeComms(conf.unit)
cfxHeloTroops.setCommsMenu(conf.unit)
@ -748,6 +784,32 @@ function cfxHeloTroops.playerChangeEvent(evType, description, player, data)
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
@ -757,7 +819,7 @@ function cfxHeloTroops.readConfigZone()
local theZone = cfxZones.getZoneByName("heloTroopsConfig")
if not theZone then
trigger.action.outText("+++heloT: no config zone!", 30)
return
theZone = cfxZones.createSimpleZone("heloTroopsConfig")
end
cfxHeloTroops.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
@ -781,6 +843,60 @@ function cfxHeloTroops.readConfigZone()
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
--
@ -791,7 +907,11 @@ function cfxHeloTroops.start()
return false
end
-- read config zone
-- read config zone
cfxHeloTroops.readConfigZone()
-- start housekeeping
cfxHeloTroops.houseKeeping()
-- install callbacks for helo-relevant events
dcsCommon.addEventHandler(cfxHeloTroops.somethingHappened, cfxHeloTroops.preProcessor, cfxHeloTroops.postProcessor)
@ -807,6 +927,18 @@ function cfxHeloTroops.start()
cfxPlayer.addMonitor(cfxHeloTroops.playerChangeEvent)
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
end

View File

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

View File

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

View File

@ -1,5 +1,5 @@
cfxPlayerScore = {}
cfxPlayerScore.version = "1.3.1"
cfxPlayerScore.version = "1.4.0"
cfxPlayerScore.badSound = "Death BRASS.wav"
cfxPlayerScore.scoreSound = "Quest Snare 3.wav"
cfxPlayerScore.announcer = true
@ -23,12 +23,15 @@ cfxPlayerScore.announcer = true
number that is given under OBJECT ID when
using assign as...
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 = {
"dcsCommon", -- this is doing score keeping
"cfxPlayer", -- player events, comms
"cfxZones", -- zones for config
}
cfxPlayerScore.playerScore = {} -- init to empty
@ -46,14 +49,15 @@ cfxPlayerScore.typeScore = {}
--
cfxPlayerScore.aircraft = 50
cfxPlayerScore.helo = 40
cfxPlayer.ground = 10
cfxPlayerScore.ground = 10
cfxPlayerScore.ship = 80
cfxPlayerScore.train = 5
function cfxPlayerScore.cat2BaseScore(inCat)
if inCat == 0 then return cfxPlayerScore.aircraft end -- airplane
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 == 4 then return cfxPlayerScore.train end -- train
@ -69,7 +73,10 @@ function cfxPlayerScore.object2score(inVictim) -- does not have group
if type(inName) == "number" then
inName = tostring(inName)
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]
if not objectScore then
-- try the type desc
@ -80,6 +87,20 @@ function cfxPlayerScore.object2score(inVictim) -- does not have group
if type(objectScore) == "string" then
objectScore = tonumber(objectScore)
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
return objectScore
end
@ -224,7 +245,7 @@ function cfxPlayerScore.preProcessor(theEvent)
return false
end
local wasPlayer = cfxPlayer.isPlayerUnit(killer)
local wasPlayer = dcsCommon.isPlayerUnit(killer)
return wasPlayer
end
return false
@ -245,7 +266,7 @@ function cfxPlayerScore.killDetected(theEvent)
-- we are only getting called when and if
-- a kill occured and killer was a player
-- and target exists
-- trigger.action.outText("KILL EVENT", 30)
local killer = theEvent.initiator
local killerName = killer:getPlayerName()
if not killerName then killerName = "<nil>" end
@ -255,13 +276,14 @@ function cfxPlayerScore.killDetected(theEvent)
local victim = theEvent.target
-- was it a player kill?
local pk = cfxPlayer.isPlayerUnit(victim)
local pk = dcsCommon.isPlayerUnit(victim)
-- was it a scenery object?
local wasBuilding = dcsCommon.isSceneryObject(victim)
if wasBuilding then
-- these objects have no coalition; we simply award the score if
-- it exists in look-up table.
--trigger.action.outText("KILL SCENERY", 30)
local staticScore = cfxPlayerScore.object2score(victim)
if staticScore > 0 then
trigger.action.outSoundForCoalition(killSide, cfxPlayerScore.scoreSound)
@ -282,10 +304,11 @@ function cfxPlayerScore.killDetected(theEvent)
--if not victim.getGroup then
if isStO then
-- static objects have no group
local staticName = victim:getName() -- on statics, this returns
-- name as entered in TOP LINE
local staticScore = cfxPlayerScore.object2score(victim)
-- trigger.action.outText("KILL STATIC with score " .. staticScore, 30)
if staticScore > 0 then
-- this was a named static, return the score - unless our own
if fraternicide then
@ -295,6 +318,7 @@ function cfxPlayerScore.killDetected(theEvent)
trigger.action.outSoundForCoalition(killSide, cfxPlayerScore.scoreSound)
end
staticScore = scoreMod * staticScore
cfxPlayerScore.logKillForPlayer(killerName, victim)
cfxPlayerScore.awardScoreTo(killSide, staticScore, killerName)
else
-- no score, no mentions
@ -368,7 +392,7 @@ function cfxPlayerScore.readConfigZone(theZone)
-- default scores
cfxPlayerScore.aircraft = cfxZones.getNumberFromZoneProperty(theZone, "aircraft", 50)
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.train = cfxZones.getNumberFromZoneProperty(theZone, "train", 5)
@ -382,6 +406,36 @@ function cfxPlayerScore.readConfigZone(theZone)
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()
if not dcsCommon.libCheck("cfx Player Score",
cfxPlayerScore.requiredLibs)
@ -413,6 +467,18 @@ function cfxPlayerScore.start()
dcsCommon.addEventHandler(cfxPlayerScore.killDetected,
cfxPlayerScore.preProcessor,
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)
return true
end

View File

@ -1,5 +1,5 @@
cfxSSBClient = {}
cfxSSBClient.version = "2.0.3"
cfxSSBClient.version = "2.1.0"
cfxSSBClient.verbose = false
cfxSSBClient.singleUse = false -- set to true to block crashed planes
-- NOTE: singleUse (true) requires SSB to disable immediate respawn after kick
@ -40,6 +40,9 @@ Version History
- added verbosity
2.0.3 - getPlayerName nil-trap on cloned player planes guard
in onEvent
2.1.0 - slotState
- persistence
WHAT IT IS
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.playerPlanes = {} -- names of units that a player is flying
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)
@ -191,6 +195,7 @@ function cfxSSBClient.setSlotAccessForGroup(theGroup)
end
-- set the ssb flag for this group so the server can see it
trigger.action.setUserFlag(theName, blockState)
cfxSSBClient.slotState[theName] = blockState
if cfxSSBClient.verbose then
trigger.action.outText("+++SSB: group ".. theName .. ": " .. comment, 30)
end
@ -312,7 +317,7 @@ function cfxSSBClient:onEvent(event)
-- block this slot.
trigger.action.setUserFlag(gName, cfxSSBClient.disabledFlagValue)
cfxSSBClient.slotState[gName] = cfxSSBClient.disabledFlagValue
-- remember this plane to not re-enable if
-- airfield changes hands later
cfxSSBClient.crashedGroups[gName] = thePilot -- set to crash pilot
@ -425,6 +430,48 @@ function cfxSSBClient.readConfigZone()
cfxSSBClient.disabledFlagValue = cfxZones.getNumberFromZoneProperty(theZone, "disabledFlagValue", cfxSSBClient.enabledFlagValue + 100)
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
--
@ -455,6 +502,16 @@ function cfxSSBClient.start()
-- now turn on ssb
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!
trigger.action.outText("cfxSSBClient v".. cfxSSBClient.version .. " running, SBB enabled", 30)

View File

@ -1,15 +1,21 @@
cfxSpawnZones = {}
cfxSpawnZones.version = "1.6.0"
cfxSpawnZones.version = "1.7.0"
cfxSpawnZones.requiredLibs = {
"dcsCommon", -- common is of course needed for everything
-- pretty stupid to check for this since we
-- need common to invoke the check, but anyway
"cfxZones", -- Zones, of course
"cfxCommander", -- to make troops do stuff
"cfxGroundTroops", -- generic data module for weight
"cfxGroundTroops", -- for ordering then around
}
cfxSpawnZones.ups = 1
cfxSpawnZones.verbose = false
-- persistence: all groups we ever spawned.
-- is regularly GC'd
cfxSpawnZones.spawnedGroups = {}
--
-- Zones that conform with this requirements spawn toops automatically
-- *** DOES NOT EXTEND ZONES *** LINKED OWNER via masterOwner ***
@ -54,6 +60,7 @@ cfxSpawnZones.verbose = false
-- 1.5.2 - activate?, pause? flag
-- 1.5.3 - spawn?, spawnUnits? flags
-- 1.6.0 - trackwith interface for group tracker
-- 1.7.0 - persistence support
--
-- new version requires cfxGroundTroops, where they are
--
@ -203,12 +210,14 @@ function cfxSpawnZones.createSpawner(inZone)
theSpawner.formation = "circle_out"
theSpawner.formation = cfxZones.getStringFromZoneProperty(inZone, "formation", "circle_out")
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")
-- 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
-- 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() == "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.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)
local aZone = cfxZones.getZoneByName(aName)
if not aZone then return nil end
return cfxSpawnZones.getSpawnerForZone(aZone)
end
@ -331,7 +341,7 @@ function cfxSpawnZones.spawnWithSpawner(aSpawner)
local theCoalition = coalition.getCountryCoalition(theCountry)
-- trigger.action.outText("+++ spawn: coal <" .. theCoalition .. "> from country <" .. theCountry .. ">", 30)
local theGroup = cfxZones.createGroundUnitsInZoneForCoalition (
local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
theCoalition,
aSpawner.baseName .. "-" .. aSpawner.count, -- must be unique
aSpawner.zone,
@ -340,15 +350,26 @@ function cfxSpawnZones.spawnWithSpawner(aSpawner)
aSpawner.heading)
aSpawner.theSpawn = theGroup
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
-- to make this work.
-- isnert into collector for persistence
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 (
aSpawner.orders:lower() == "training" or
aSpawner.orders:lower() == "train" )
then
-- 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(
theGroup,
AI.Option.Ground.id.ROE,
@ -434,6 +455,23 @@ end
--
-- 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()
cfxSpawnZones.updateSchedule = timer.scheduleFunction(cfxSpawnZones.update, {}, timer.getTime() + 1/cfxSpawnZones.ups)
@ -445,9 +483,9 @@ function cfxSpawnZones.update()
local group = spawner.theSpawn
if group:isExist() then
-- 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
if #liveUnits > 1 then
if liveUnits > 1 then
-- we may want to check if this member is still inside
-- of spawn location. currently we don't do that
needsSpawn = false
@ -526,6 +564,158 @@ function cfxSpawnZones.update()
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()
if not dcsCommon.libCheck("cfx Spawn Zones",
cfxSpawnZones.requiredLibs) then
@ -540,22 +730,29 @@ function cfxSpawnZones.start()
for k, aZone in pairs(attrZones) do
local aSpawner = cfxSpawnZones.createSpawner(aZone)
cfxSpawnZones.addSpawner(aSpawner)
if not aSpawner.paused and cfxSpawnZones.verifySpawnOwnership(aSpawner) and aSpawner.maxSpawns ~= 0 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
-- we now do persistence
if persistence then
-- sign up for persistence
callbacks = {}
callbacks.persistData = cfxSpawnZones.saveData
persistence.registerModule("cfxSpawnZones", callbacks)
-- now load my data
cfxSpawnZones.loadData()
end
-- we now spawn if not taken care of by load / save
for theZone, aSpawner in pairs(cfxSpawnZones.allSpawners) do
cfxSpawnZones.initialSpawnCheck(aSpawner)
end
-- and start the regular update calls
cfxSpawnZones.update()
-- start housekeeping
cfxSpawnZones.houseKeeping()
trigger.action.outText("cfx Spawn Zones v" .. cfxSpawnZones.version .. " started.", 30)
return true
end

View File

@ -1,5 +1,5 @@
changer = {}
changer.version = "1.0.2"
changer.version = "1.0.3"
changer.verbose = false
changer.ups = 1
changer.requiredLibs = {
@ -12,6 +12,7 @@ changer.changers = {}
1.0.0 - Initial version
1.0.1 - Better guards in config to avoid <none> Zone getter warning
1.0.2 - on/off: verbosity
1.0.3 - NOT on/off
Transmogrify an incoming signal to an output signal
- not
@ -111,6 +112,12 @@ function changer.createChangerWithZone(theZone)
if cfxZones.hasProperty(theZone, "changeOn/Off?") then
theZone.changerOnOff = cfxZones.getStringFromZoneProperty(theZone, "changeOn/Off?", "*<none>", 1)
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
--
@ -207,6 +214,10 @@ function changer.update()
end
-- 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 aZone.changerOnOff 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)
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)
end
end

View File

@ -1,5 +1,5 @@
cloneZones = {}
cloneZones.version = "1.4.9"
cloneZones.version = "1.5.0"
cloneZones.verbose = false
cloneZones.requiredLibs = {
"dcsCommon", -- always
@ -17,6 +17,9 @@ cloneZones.callbacks = {}
cloneZones.unitXlate = {}
cloneZones.groupXlate = {} -- used to translate original groupID to cloned. only holds last spawned group id
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
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.9 - onRoad option
- rndHeading option
1.5.0 - persistence
--]]--
@ -289,7 +293,7 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
theZone.onRoad = cfxZones.getBoolFromZoneProperty(theZone, "onRoad", false)
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
-- we end with clear plate
@ -767,6 +771,12 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
-- now spawn all raw data
for idx, rawData in pairs (dataToSpawn) do
-- 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)
table.insert(spawnedGroups, theGroup)
@ -888,20 +898,25 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
end
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 newStaticID = tonumber(theStatic:getID())
table.insert(spawnedStatics, theStatic)
-- we don't mix groups with units, so no lookup tables for
-- statics
if newStaticID == rawData.CZTargetID then
-- trigger.action.outText("Static ID OK: " .. newStaticID .. " for " .. rawData.name, 30)
else
trigger.action.outText("Static ID mismatch: " .. newStaticID .. " vs (target) " .. rawData.CZTargetID .. " for " .. rawData.name, 30)
end
cloneZones.unitXlate[origID] = newStaticID -- same as units
cloneZones.invokeCallbacks(theZone, "did spawn static", theStatic)
--]]--
if cloneZones.verbose or spawnZone.verbose then
trigger.action.outText("Static spawn: spawned " .. aStaticName, 30)
end
@ -1058,7 +1073,7 @@ function cloneZones.update()
if aZone.deSpawnFlag then
local currTriggerVal = cfxZones.getFlagValue(aZone.deSpawnFlag, aZone) -- trigger.misc.getUserFlag(aZone.deSpawnFlag)
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)
end
cloneZones.despawnAll(aZone)
@ -1103,15 +1118,249 @@ function cloneZones.onStart()
--trigger.action.outText("+++clnZ: Enter atStart", 30)
for idx, theZone in pairs(cloneZones.cloners) do
if theZone.onStart then
if cloneZones.verbose then
trigger.action.outText("+++clnZ: atStart will spawn for <"..theZone.name .. ">", 30)
if theZone.isStarted then
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
cloneZones.spawnWithCloner(theZone)
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
--
@ -1155,16 +1404,28 @@ function cloneZones.start()
cloneZones.addCloneZone(aZone) -- remember it so we can smoke it
end
-- run through onStart, but leave at least a few
-- cycles to go through object removal so statics
-- can spawn on ground. onStart is being deprecated, the
-- raiseFlag module covers this since the first time
-- raiseFlag is run is t0 + 0.5s
-- update all cloners and spawned clones from file
if persistence then
-- sign up for persistence
callbacks = {}
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)
-- start update
cloneZones.update()
-- start housekeeping
cloneZones.houseKeeping()
trigger.action.outText("cfx Clone Zones v" .. cloneZones.version .. " started.", 30)
return true
end

View File

@ -1,5 +1,5 @@
dcsCommon = {}
dcsCommon.version = "2.7.0"
dcsCommon.version = "2.7.1"
--[[-- VERSION HISTORY
2.2.6 - compassPositionOfARelativeToB
- clockPositionOfARelativeToB
@ -89,6 +89,9 @@ dcsCommon.version = "2.7.0"
- new getSceneryObjectInZoneByName()
2.7.0 - new synchGroupData()
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
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.coalitionSides = {0, 1, 2}
-- verify that a module is loaded. obviously not required
-- for dcsCommon, but all higher-order modules
function dcsCommon.libCheck(testingFor, requiredLibs)
@ -2248,6 +2252,28 @@ function dcsCommon.isTroopCarrier(theUnit)
return false
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)
if not theUnit 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
end
function dcsCommon.unitIsInfantry(theUnit)
if not theUnit then return false end
if not theUnit:isExist() then return end
local theType = theUnit:getTypeName()
function dcsCommon.typeIsInfantry(theType)
local isInfantry =
dcsCommon.containsString(theType, "infantry", false) or
dcsCommon.containsString(theType, "paratrooper", false) or
@ -2349,6 +2372,23 @@ function dcsCommon.unitIsInfantry(theUnit)
return isInfantry
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)
-- simply return UN troops for 0 neutral,
-- joint red for 1 red

View File

@ -1,5 +1,5 @@
groupTracker = {}
groupTracker.version = "1.1.3"
groupTracker.version = "1.1.4"
groupTracker.verbose = false
groupTracker.ups = 1
groupTracker.requiredLibs = {
@ -20,6 +20,12 @@ groupTracker.trackers = {}
1.1.3 - spellings
- addGroupToTrackerNamed bug removed accessing tracker
- new removeGroupNamedFromTrackerNamed()
1.1.4 - destroy? input
- allGone! output
- triggerMethod
- method
- isDead optimization
--]]--
function groupTracker.addTracker(theZone)
@ -87,7 +93,7 @@ function groupTracker.addGroupToTrackerNamed(theGroup, trackerName)
return
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)
return
end
@ -147,6 +153,17 @@ function groupTracker.createTrackerWithZone(theZone)
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
theZone.tNumGroups = cfxZones.getStringFromZoneProperty(theZone, "numGroups", "*<none>")
-- we may need to zero this flag
@ -181,6 +198,16 @@ function groupTracker.createTrackerWithZone(theZone)
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
trigger.action.outText("gTrck: processed <" .. theZone.name .. ">", 30)
end
@ -189,12 +216,23 @@ end
--
-- 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)
local filteredGroups = {}
for idx, theGroup in pairs(theZone.trackedGroups) do
-- see if this group can be transferred
local isDead = false
if theGroup:isExist() then
--[[ if theGroup.isExist and theGroup:isExist() then
local allUnits = theGroup:getUnits()
isDead = true
for idy, aUnit in pairs(allUnits) do
@ -203,6 +241,8 @@ function groupTracker.checkGroups(theZone)
break
end
end
--]]--
if Group.isExist(theGroup) and theGroup:getSize() > 0 then
else
isDead = true -- no longer exists
end
@ -238,7 +278,22 @@ function groupTracker.update()
timer.scheduleFunction(groupTracker.update, {}, timer.getTime() + 1/groupTracker.ups)
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)
-- 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

View File

@ -1,5 +1,5 @@
limitedAirframes = {}
limitedAirframes.version = "1.4.0"
limitedAirframes.version = "1.5.0"
limitedAirframes.verbose = false
limitedAirframes.enabled = true -- can be turned off
limitedAirframes.userCanToggle = true -- F10 menu?
@ -18,7 +18,7 @@ limitedAirframes.requiredLibs = {
-- pretty stupid to check for this since we
-- need common to invoke the check, but anyway
"cfxZones", -- Zones, of course for safe landings
"cfxPlayer",
-- "cfxPlayer", no longer needed
}
--[[-- VERSION HISTORY
@ -48,8 +48,9 @@ limitedAirframes.requiredLibs = {
- 1.4.0 - DML integration, verbosity, clean-up, QoL improvements
redSafe, blueSafe with attribute, backward compatible
currRed
- 1.4.1 - removed dependency to cfxPlayer
- 1.5.0 - persistence support
--]]--
-- limitedAirframes manages the number of available player airframes
@ -123,6 +124,7 @@ function limitedAirframes.readConfigZone()
end
-- remember me
limitedAirframes.config = theZone
limitedAirframes.name = "limitedAirframes" -- so we can call cfxZones with ourself as param
limitedAirframes.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
@ -131,22 +133,22 @@ function limitedAirframes.readConfigZone()
end
-- 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)
end
-- end
if cfxZones.hasProperty(theZone, "userCanToggle") then
-- if cfxZones.hasProperty(theZone, "userCanToggle") then
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)
end
-- end
if cfxZones.hasProperty(theZone, "maxBlue") then
-- if cfxZones.hasProperty(theZone, "maxBlue") then
limitedAirframes.maxBlue = cfxZones.getNumberFromZoneProperty(theZone, "maxBlue", -1)
end
-- end
limitedAirframes.numRed = cfxZones.getStringFromZoneProperty(theZone, "#red", "*none")
limitedAirframes.numBlue = cfxZones.getStringFromZoneProperty(theZone, "#blue", "*none")
@ -318,7 +320,7 @@ function limitedAirframes.preProcessor(event)
end
if not cfxPlayer.isPlayerUnit(theUnit) then
if not dcsCommon.isPlayerUnit(theUnit) then
-- not a player unit. Events 5 and 6 have been
-- handled before, so we can safely ignore
return false
@ -828,6 +830,37 @@ function limitedAirframes.pilotsRescued(theCoalition, success, numRescued, notes
trigger.action.outSoundForCoalition(theCoalition, limitedAirframes.warningSound)--"Quest Snare 3.wav")
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
--
@ -885,7 +918,7 @@ function limitedAirframes.start()
limitedAirframes.currBlue = limitedAirframes.maxBlue
-- collect active player unit names
local allPlayerUnits = cfxPlayer.getAllExistingPlayerUnitsRaw()
local allPlayerUnits = dcsCommon.getAllExistingPlayerUnitsRaw()
for i=1, #allPlayerUnits do
local aUnit = allPlayerUnits[i]
limitedAirframes.addPlayerUnit(aUnit)
@ -905,6 +938,16 @@ function limitedAirframes.start()
else
trigger.action.outText("+++limA: NO CSAR integration", 30)
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
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.version = "1.0.1"
persistence.version = "1.0.2"
persistence.ups = 1 -- once every 1 seconds
persistence.verbose = false
persistence.active = false
@ -21,6 +21,7 @@ persistence.requiredLibs = {
- spelling
- cfxZones interface
- always output save location
1.0.2 - QoL when verbosity is on
PROVIDES LOAD/SAVE ABILITY TO MODULES
@ -304,9 +305,9 @@ function persistence.missionStartDataLoad()
-- we are done for now. modules check in
-- after persistence and load their own data
-- when they detect that there is data to load
if persistence.verbose then
trigger.action.outText("+++persistence: basic import complete.", 30)
end
trigger.action.outText("+++persistence: successfully read mission save data", 30)
end
--
@ -345,6 +346,10 @@ function persistence.saveMissionData()
if persistence.verbose then
trigger.action.outText("+++persistence: gathered data from <" .. moduleName .. ">", 30)
end
else
if persistence.verbose then
trigger.action.outText("+++persistence: NO DATA gathered data from <" .. moduleName .. ">, module returned NIL", 30)
end
end
end

View File

@ -88,44 +88,7 @@ function radioMenu.createRadioMenuWithZone(theZone)
if theZone.menuVisible then
radioMenu.installMenu(theZone)
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
theZone.radioMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "inc")
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 sada reuturs <nil>
- xref: which zones/attributes reference a flag, g.g. '-xref go'
--]]--

View File

@ -1,5 +1,5 @@
unitPersistence = {}
unitPersistence.version = '1.0.1'
unitPersistence.version = '1.1.1'
unitPersistence.verbose = false
unitPersistence.updateTime = 60 -- seconds. Once every minute check statics
unitPersistence.requiredLibs = {
@ -15,6 +15,11 @@ unitPersistence.requiredLibs = {
- handles linked static objects
- does no longer mess with heliports
- 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
@ -23,6 +28,10 @@ unitPersistence.requiredLibs = {
--]]--
unitPersistence.groundTroops = {} -- local buffered copy that we
-- maintain from save to save
unitPersistence.fixedWing = {}
unitPersistence.rotorWing = {}
unitPersistence.ships = {}
unitPersistence.statics = {} -- locally unpacked and buffered static objects
--
@ -49,18 +58,24 @@ function unitPersistence.saveData()
local uName = theUnitData.name
local gUnit = Unit.getByName(uName)
if gUnit and gUnit:isExist() then
-- got a live one!
gotALiveOne = true
-- update x and y and heading
theUnitData.heading = dcsCommon.getUnitHeading(gUnit)
pos = gUnit:getPoint()
theUnitData.x = pos.x
theUnitData.y = pos.z -- (!!)
-- ground units do not use alt
if gUnit:isActive() then
theUnitData.isDead = gUnit:getLife() < 1
if not theUnitData.isDead then
-- got a live one!
gotALiveOne = true
-- update x, y and heading
theUnitData.heading = dcsCommon.getUnitHeading(gUnit)
pos = gUnit:getPoint()
theUnitData.x = pos.x
theUnitData.y = pos.z -- (!!)
end
else
gotALiveOne = true -- not yet activated
end
else
theUnitData.isDead = true
end -- is alive and exists?
end -- unit not dead
end -- unit maybe not dead
end -- iterate units in group
groupData.isDead = not gotALiveOne
end -- if group is not dead
@ -69,6 +84,136 @@ function unitPersistence.saveData()
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
for oName, oData in pairs(unitPersistence.statics) do
if not oData.isDead or oData.lateActivation then
@ -96,6 +241,10 @@ function unitPersistence.saveData()
theData.version = unitPersistence.version
theData.ground = unitPersistence.groundTroops
theData.fixedWing = unitPersistence.fixedWing
theData.rotorWing = unitPersistence.rotorWing
theData.ships = unitPersistence.ships
theData.statics = unitPersistence.statics
return theData
end
@ -103,6 +252,13 @@ end
--
-- 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()
local theData = persistence.getSavedDataForModule("unitPersistence")
if not theData then
@ -137,7 +293,6 @@ function unitPersistence.loadMission()
-- filter all dead groups
if theUnitData.isDead then
-- skip it
else
-- add it to new group
table.insert(newUnits, theUnitData)
@ -145,29 +300,129 @@ function unitPersistence.loadMission()
end
-- replace old unit setup with new
newGroup.units = newUnits
local cty = groupData.cty
local cat = groupData.cat
-- destroy the old group
--theGroup:destroy() -- will be replaced
-- spawn new one
-- 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 .. ">")
end
if unitPersistence.verbose then
-- trigger.action.outText("+++unitPersistence: updated group <" .. groupName .. "> of cat <" .. cat .. "> for cty <" .. cty .. ">", 30)
trigger.action.outText("+++ failed to add modified group <" .. groupName .. ">", 30)
end
end
end
unitPersistence.groundTroops = theData.ground
else
if unitPersistence.verbose then
trigger.action.outText("+++unitPersistence: no ground unit data.", 30)
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
if theData.statics then
for name, staticData in pairs(theData.statics) do
@ -177,8 +432,6 @@ function unitPersistence.loadMission()
if unitPersistence.verbose then
trigger.action.outText("+++unitPersistence: static <" .. name .. "> is late activate, no update", 30)
end
--elseif not theStatic then
-- mismatchWarning = true
elseif staticData.category == "Heliports" then
-- FARPS are static objects that HATE to be
-- messed with, so we don't
@ -188,7 +441,7 @@ function unitPersistence.loadMission()
else
local newStatic = dcsCommon.clone(staticData)
-- add link info if it exists
newStatic.linkUnit = cfxMX.linkByName[name]
newStatic.linkUnit = cfxMX.linkByName[staticData.groupName]
if newStatic.linkUnit and unitPersistence.verbose then
trigger.action.outText("+++unitPersistence: linked static <" .. name .. "> to unit <" .. newStatic.linkUnit .. ">", 30)
end
@ -197,15 +450,16 @@ function unitPersistence.loadMission()
-- spawn new one, replacing same.named old, dead if required
gStatic = coalition.addStaticObject(cty, newStatic)
if not gStatic then
trigger.action.outText("+++ failed to add modified static <" .. name .. ">")
trigger.action.outText("+++ failed to add modified static <" .. name .. ">", 30)
end
if unitPersistence.verbose then
local note = ""
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
unitPersistence.statics = theData.statics
end
if mismatchWarning then
@ -268,6 +522,7 @@ function unitPersistence.start()
-- create a local copy of the entire groundForces data that
-- we maintain internally. It's fixed, and we work on our
-- own copy for speed
unitPersistence.groundTroops = {}
for gname, data in pairs(cfxMX.allGroundByName) do
local gd = dcsCommon.clone(data) -- copy the record
gd.isDead = false -- init new field to alive
@ -275,14 +530,71 @@ function unitPersistence.start()
gd.cat = cfxMX.catText2ID("vehicle")
local gGroup = Group.getByName(gname)
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
local firstUnit = gGroup:getUnit(1)
gd.cty = firstUnit:getCountry()
gd.cty = cfxMX.countryByName[gname]
unitPersistence.groundTroops[gname] = gd
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
-- that we also maintain internally, and convert them to game
-- spawnable objects
@ -294,19 +606,18 @@ function unitPersistence.start()
local theStatic = dcsCommon.clone(staticData)
theStatic.isDead = false
theStatic.groupId = mxData.groupId
theStatic.groupName = name -- save top-level name
theStatic.cat = cfxMX.catText2ID("static")
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)
if not gameOb then
if unitPersistence.verbose then
trigger.action.outText("+++unitPersistence: static object <" .. theStatic.name .. "> has late activation", 30)
end
theStatic.lateActivation = true
else
--theStatic.cty = gameOb:getCountry()
--unitPersistence.statics[theStatic.name] = theStatic
end
unitPersistence.statics[theStatic.name] = theStatic
unitPersistence.statics[theStatic.name] = theStatic -- HERE WE CHANGE FROM GROUP NAME TO STATIC NAME!!!
end
end
@ -328,6 +639,11 @@ if not unitPersistence.start() then
unitPersistence = nil
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
--]]--