Version 2.3.5

WHpersistence
This commit is contained in:
Christian Franz 2024-10-24 08:37:44 +02:00
parent a70fadc550
commit 0c1eb53a89
13 changed files with 450 additions and 286 deletions

Binary file not shown.

Binary file not shown.

96
modules/WHpersistence.lua Normal file
View File

@ -0,0 +1,96 @@
WHpersistence = {}
WHpersistence.version = "1.0.0"
WHpersistence.requiredLibs = {
"dcsCommon",
"cfxZones",
"persistence",
}
--
-- load / save (game data)
--
function WHpersistence.saveData()
local theData = {}
local theWH = {}
-- generate all WH data from all my airfields
local allMyBase = world:getAirbases()
for idx, theBase in pairs(allMyBase) do
local name = theBase:getName()
local WH = theBase:getWarehouse()
local inv = WH:getInventory()
theWH[name] = inv
end
theData.theWH = theWH
return theData, WHpersistence.sharedData -- second val currently nil
end
function WHpersistence.loadData()
if not persistence then return end
local shared = nil
local theData = persistence.getSavedDataForModule("WHpersistence")
if (not theData) or not (theData.theWH) then
if WHpersistence.verbose then
trigger.action.outText("+++WHp: no save date received, skipping.", 30)
end
return
end
-- set up all warehouses from data loaded
for name, inv in pairs(theData.theWH) do
trigger.action.outText("+++restoring <" .. name .. ">", 30)
local theBase = Airbase.getByName(name)
if theBase then
local theWH = theBase:getWarehouse()
if theWH then
-- we go through weapon, liquids and aircraft
for idx, liq in pairs(inv.liquids) do
theWH:setLiquidAmount(idx, liq)
trigger.action.outText(name .. ": Liq <" .. idx .. "> : <" .. liq .. ">", 30)
end
for ref, num in pairs(inv.weapon) do
theWH:setItem(ref, num)
end
for ref, num in pairs(inv.aircraft) do
theWH:setItem(ref, num)
end
else
trigger.action.outText(name .. ": no warehouse")
end
else
trigger.action.outText(name .. ": no airbase")
end
end
end
--
-- config
--
function WHpersistence.readConfigZone()
local theZone = cfxZones.getZoneByName("WHpersistenceConfig")
if not theZone then
theZone = cfxZones.createSimpleZone("WHpersistenceConfig")
end
WHpersistence.verbose = theZone.verbose
end
--
-- GO
--
function WHpersistence.start()
if not dcsCommon.libCheck then
trigger.action.outText("cfx WHpersistence requires dcsCommon", 30)
return false
end
if not dcsCommon.libCheck("cfx Raise Flag", WHpersistence.requiredLibs)then return false end
WHpersistence.readConfigZone()
if persistence then
callbacks = {}
callbacks.persistData = WHpersistence.saveData
persistence.registerModule("WHpersistence", callbacks)
-- now load my data
WHpersistence.loadData()
end
trigger.action.outText("cfx WHpersistence v" .. WHpersistence.version .. " started.", 30)
return true
end
if not WHpersistence.start() then
trigger.action.outText("cfx WHpersistence aborted: missing libraries", 30)
WHpersistence = nil
end

View File

@ -1,5 +1,5 @@
airfield = {}
airfield.version = "2.2.0"
airfield.version = "2.3.0"
airfield.requiredLibs = {
"dcsCommon",
"cfxZones",
@ -22,6 +22,12 @@ airfield.allAirfields = {} -- inexed by af name, db entries: base, cat
2.1.0 - added support for makeNeutral?
2.1.1 - bug fixing for DCS 2.9x airfield retrofit
2.2.0 - dmlZone:getCoalition() / masterowner adaptation for owner
2.3.0 - increased verbosity on persistence
- airfield delayed release after data load
- locked owner on start
- release after start
- redrawing airfields when releasing
- cleanup
--]]--
-- init all airfields DB
@ -31,7 +37,6 @@ function airfield.collectAll()
local dropped = 0
for idx, aBase in pairs(allBases) do
local entry = {}
--local cat = Airbase.getCategory(aBase) -- DCS 2.9 hardened
-- ho! dcs 2.9.x retrofit screwed with Airfield.getCategory.
local cat = aBase:getDesc().category
-- cats: 0 = airfield, 1 = farp, 2 = ship
@ -44,20 +49,18 @@ function airfield.collectAll()
count = count + 1
else
dropped = dropped + 1
-- trigger.action.outText("***dropped airbase <" .. aBase:getName() .. ">, cat = <" .. cat .. ">", 30)
end
end
if airfield.verbose then
trigger.action.outText("+++airF: init - count = <" .. count .. ">, dropped = <" .. dropped .. ">", 30)
end
end
--
-- setting up airfield
--
airfield.collector = {}
function airfield.createAirFieldFromZone(theZone)
theZone.farps = theZone:getBoolFromZoneProperty("farps", false)
local filterCat = 0
if (theZone.farps) then filterCat = {0, 1} end -- bases and farps
local p = theZone:getPoint()
@ -65,17 +68,15 @@ function airfield.createAirFieldFromZone(theZone)
theZone.airfield = theBase
theZone.afName = theBase:getName()
-- set zone's owner
-- set zone's owner
theZone.owner = theBase:getCoalition()
theZone.mismatchCount = airfield.gracePeriod
if theZone.verbose or airfield.verbose then
trigger.action.outText("+++airF: airfield zone <" .. theZone.name .. "> associates with <" .. theZone.afName .. ">, current owner is <" .. theZone.owner .. ">", 30)
end
-- methods
theZone.method = theZone:getStringFromZoneProperty("method", "inc")
theZone.triggerMethod = theZone:getStringFromZoneProperty("triggerMethod", "change")
if theZone:hasProperty("red!") then
theZone.redCap = theZone:getStringFromZoneProperty("red!")
end
@ -92,19 +93,16 @@ function airfield.createAirFieldFromZone(theZone)
theZone.makeBlue = theZone:getStringFromZoneProperty("makeBlue?", "<none>")
theZone.lastMakeBlue = trigger.misc.getUserFlag(theZone.makeBlue)
end
if theZone:hasProperty("makeNeutral?") then
theZone.makeNeutral = theZone:getStringFromZoneProperty("makeNeutral?", "<none>")
theZone.lastMakeNeutral = trigger.misc.getUserFlag(theZone.makeNeutral)
end
if theZone:hasProperty("autoCap?") then
theZone.autoCap = theZone:getStringFromZoneProperty("autoCap?", "<none>")
theZone.lastAutoCap = trigger.misc.getUserFlag(theZone.autoCap)
end
theZone.directControl = theZone:getBoolFromZoneProperty("directControl", false)
if theZone.directControl then
airfield.assumeControl(theZone)
end
@ -144,6 +142,14 @@ function airfield.createAirFieldFromZone(theZone)
airfield.showAirfield(theZone)
theBase:autoCapture(false) -- lock down owner to avoid contested at beginning
-- set up collector to free ownership later
if theZone:hasProperty("fixed") then
airfield.collector[theBase] = false -- autocap off after delay
else
airfield.collector[theBase] = true
end
-- now mark this zone as handled
local entry = airfield.allAirfields[theZone.afName]
if not entry then
@ -179,7 +185,6 @@ function airfield.showAirfield(theZone)
theZone.ownerMark = nil
end
if not theZone.show then return end -- we don't show in map
local lineColor = theZone.redLine -- {1.0, 0, 0, 1.0} -- red
local fillColor = theZone.redFill -- {1.0, 0, 0, 0.2} -- red
local owner = theZone:getCoalition() -- .owner
@ -190,10 +195,7 @@ function airfield.showAirfield(theZone)
lineColor = theZone.neutralLine -- {0.8, 0.8, 0.8, 1.0}
fillColor = theZone.neutralFill -- {0.8, 0.8, 0.8, 0.2}
end
theZone.ownerMark = airfield.markAirfieldOnMap(theZone.airfield, lineColor, fillColor)
end
function airfield.assumeControl(theZone)
@ -211,7 +213,6 @@ function airfield.relinquishControl(theZone)
end
theBase:autoCapture(true) -- turn off autocap
end
--
-- event handling
--
@ -252,7 +253,6 @@ function airfield.airfieldCaptured(theBase)
if theZone.verbose or airfield.verbose then
trigger.action.outText("+++airF: capturing <" .. bName .. "> for zone <" .. theZone.name .. ">", 30)
end
local newCoa = theBase:getCoalition()
theZone.owner = newCoa
@ -272,7 +272,6 @@ function airfield.airfieldCaptured(theBase)
if theZone.blueCap and newCoa == 2 then
theZone:pollFlag(theZone.blueCap, theZone.method)
end
end
function airfield:onEvent(event)
@ -348,7 +347,6 @@ function airfield.update()
theZone.owner = 0
end
if theZone.autoCap and theZone:testZoneFlag(theZone.autoCap, theZone.triggerMethod, "lastAutoCap") then
if theAirfield:autoCaptureIsOn() then
-- do nothing
@ -386,16 +384,12 @@ function airfield.GC()
trigger.action.outText("+++airF: corrected ownership after grace period", 30)
end
end
end
end
end
--
-- LOAD / SAVE
--
function airfield.saveData()
local theData = {}
local allAF = {}
@ -411,6 +405,21 @@ function airfield.saveData()
return theData
end
function airfield.releaseFields(releaseMe)
for theAF, rel in pairs(releaseMe) do
theAF:autoCapture(rel)
if airfield.verbose then
trigger.action.outText("+++airF: releasing AF <" .. theAF:getName() .. "> to saved cap state <" .. dcsCommon.bool2Text(rel) .. ">", 30)
end
end
for name, theZone in pairs(airfield.myAirfields) do
airfield.showAirfield(theZone)
if airfield.verbose or theZone.verbose then
trigger.action.outText("+++airF: redrawing <" .. theZone.name .. ">", 30)
end
end
end
function airfield.loadData()
if not persistence then return end
local theData = persistence.getSavedDataForModule("airfield")
@ -418,6 +427,7 @@ function airfield.loadData()
if airfield.verbose then
trigger.action.outText("+++airF persistence: no save data received, skipping.", 30)
end
timer.scheduleFunction(airfield.releaseFields, airfield.collector, timer.getTime() + 2)
return
end
@ -426,9 +436,12 @@ function airfield.loadData()
if airfield.verbose then
trigger.action.outText("+++airF persistence: no airfield data, skipping", 30)
end
timer.scheduleFunction(airfield.releaseFields, airfield.collector, timer.getTime() + 2)
return
end
airfield.collector = {} -- overwrite existing
for theName, AFData in pairs(allAF) do
local theZone = airfield.myAirfields[theName]
if theZone then
@ -438,20 +451,22 @@ function airfield.loadData()
theAirfield:autoCapture(false)
theAirfield:setCoalition(AFData.owner)
theZone.owner = AFData.owner
if airfield.verbose or theZone.verbose then
trigger.action.outText("+++airF: setting AF Zone <" .. theZone.name .. ">, owner from file to <" .. theZone.owner .. ">", 30)
end
-- set ownedBy#
if theZone.ownedBy then
trigger.action.setUserFlag(theZone.ownedBy, theZone.owner)
end
-- set owning mode: autocap or direct
theAirfield:autoCapture(AFData.autocapActive)
--theAirfield:autoCapture(AFData.autocapActive)
airfield.collector[theAirfield] = AFData.autocapActive
else
trigger.action.outText("+++airF persistence: cannot synch airfield <" .. theName .. ">, skipping", 40)
end
end
timer.scheduleFunction(airfield.releaseFields, airfield.collector, timer.getTime() + 2)
end
--
-- start up
--
@ -461,8 +476,6 @@ function airfield.readConfig()
theZone = cfxZones.createSimpleZone("airfieldConfig")
end
airfield.verbose = theZone.verbose
-- airfield.farps = theZone:getBoolFromZoneProperty("farps", false)
-- colors for line and fill
airfield.redLine = theZone:getRGBAVectorFromZoneProperty("redLine", {1.0, 0, 0, 1.0})
airfield.redFill = theZone:getRGBAVectorFromZoneProperty("redFill", {1.0, 0, 0, 0.2})
@ -470,7 +483,6 @@ function airfield.readConfig()
airfield.blueFill = theZone:getRGBAVectorFromZoneProperty("blueFill", {0.0, 0, 1.0, 0.2})
airfield.neutralLine = theZone:getRGBAVectorFromZoneProperty("neutralLine", {0.8, 0.8, 0.8, 1.0})
airfield.neutralFill = theZone:getRGBAVectorFromZoneProperty("neutralFill", {0.8, 0.8, 0.8, 0.2})
airfield.showAll = theZone:getBoolFromZoneProperty("show", false)
end
@ -485,13 +497,10 @@ end
function airfield.start()
if not dcsCommon.libCheck("cfx airfield", airfield.requiredLibs)
then return false end
-- set up DB
airfield.collectAll()
-- read config
airfield.readConfig()
-- read bases
local abZones = cfxZones.zonesWithProperty("airfield")
for idx, aZone in pairs(abZones) do
@ -512,6 +521,8 @@ function airfield.start()
persistence.registerModule("airfield", callbacks)
-- now load my data
airfield.loadData()
else
timer.scheduleFunction(airfield.releaseFields, airfield.collector, timer.getTime() + 2) -- release airfields when not loaded from storage
end
-- start update in 1 second

View File

@ -1,5 +1,5 @@
autoCSAR = {}
autoCSAR.version = "2.2.0"
autoCSAR.version = "2.2.1"
autoCSAR.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
@ -20,6 +20,7 @@ autoCSAR.trackedEjects = {} -- we start tracking on eject
2.2.0 - new noExploit option in config
- no csar mission if pilot lands too close to airbase or farp
and noExploit is on
2.2.1 - DCS hardening for isExist
--]]--
autoCSAR.forbidden = {} -- indexed by name, contains point
autoCSAR.killDist = 2100 -- meters from center of forbidden
@ -173,7 +174,9 @@ function autoCSAR:onEvent(event)
local coa = event.initiator:getCoalition()
-- see if pilot has ejector seat and prepare to connect one with the other
local info = nil
if event.target and event.target:isExist() then
if event.target
and event.target.isExist
and event.target:isExist() then -- DCS hardening
info = {}
info.coa = coa
info.seat = event.target

View File

@ -1,5 +1,5 @@
cloneZones = {}
cloneZones.version = "2.4.0"
cloneZones.version = "2.5.0"
cloneZones.verbose = false
cloneZones.requiredLibs = {
"dcsCommon", -- always
@ -57,6 +57,8 @@ cloneZones.respawnOnGroupID = true
path) with wiper module
- using "wipe?" will now create a warning
2.4.0 - reworked masterOwner to fit with dmlZone
2.5.0 - re-establish spawn zone in persistence to provide
empty! detection through saves (missed hasClones)
--]]--
--
@ -90,7 +92,6 @@ function cloneZones.invokeCallbacks(theZone, reason, args)
if not theZone then return end
if not reason then reason = "<none>" end
if not args then args = {} end
-- invoke anyone who wants to know that a group
-- of people was rescued.
for idx, cb in pairs(cloneZones.callbacks) do
@ -105,7 +106,6 @@ function cloneZones.partOfGroupDataInZone(theZone, theUnits)
local zP = cfxZones.getPoint(theZone)
zP = theZone:getDCSOrigin() -- don't use getPoint now.
zP.y = 0
for idx, aUnit in pairs(theUnits) do
local uP = {}
uP.x = aUnit.x
@ -197,7 +197,6 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
table.insert(theZone.myStatics, aStatic)
end
end
cloneZones.despawnAll(theZone)
if (#theZone.cloneNames + #theZone.staticNames) < 1 then
if cloneZones.verbose then
@ -210,18 +209,12 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
trigger.action.outText(theZone.name .. " clone template saved", 30)
end
end
-- declutter
theZone.declutter = theZone:getBoolFromZoneProperty("declutter", false)
-- watchflags
theZone.cloneTriggerMethod = theZone:getStringFromZoneProperty("triggerMethod", "change")
if theZone:hasProperty("cloneTriggerMethod") then
theZone.cloneTriggerMethod = theZone:getStringFromZoneProperty("cloneTriggerMethod", "change")
end
-- f? and spawn? and other synonyms map to the same
if theZone:hasProperty("f?") then
theZone.spawnFlag = theZone:getStringFromZoneProperty("f?", "none")
elseif theZone:hasProperty("in?") then
@ -231,12 +224,10 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
elseif theZone:hasProperty("clone?") then
theZone.spawnFlag = theZone:getStringFromZoneProperty("clone?", "none")
end
if theZone.spawnFlag then
theZone.lastSpawnValue = theZone:getFlagValue(theZone.spawnFlag)
end
-- deSpawn?
if theZone:hasProperty("deSpawn?") then
theZone.deSpawnFlag = theZone:getStringFromZoneProperty( "deSpawn?", "none")
elseif theZone:hasProperty("deClone?") then
@ -268,29 +259,6 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
if theZone:hasProperty("method") then
theZone.cloneMethod = theZone:getStringFromZoneProperty("method", "inc") -- note string on number default
end
--[[--
if theZone:hasProperty("masterOwner") then
theZone.masterOwner = theZone:getStringFromZoneProperty( "masterOwner", "*")
theZone.masterOwner = dcsCommon.trim(theZone.masterOwner)
if theZone.masterOwner == "*" then
theZone.masterOwner = theZone.name
if theZone.verbose then
trigger.action.outText("+++clnZ: masterOwner for <" .. theZone.name .. "> set successfully to to itself, currently owned by faction <" .. theZone.owner .. ">", 30)
end
end
if theZone.verbose or cloneZones.verbose then
trigger.action.outText("+++clnZ: ownership of <" .. theZone.name .. "> tied to zone <" .. theZone.masterOwner .. ">", 30)
end
-- check that the zone exists in DCS
local theMaster = cfxZones.getZoneByName(theZone.masterOwner)
if not theMaster then
trigger.action.outText("clnZ: WARNING: cloner's <" .. theZone.name .. "> master owner named <" .. theZone.masterOwner .. "> does not exist!", 30)
end
theZone.masterOwner = theMaster
end
--]]--
theZone.turn = theZone:getNumberFromZoneProperty("turn", 0)
-- interface to groupTracker
@ -329,7 +297,6 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
theZone.inBuiltup = theZone:getNumberFromZoneProperty("inBuiltup", 10) -- 10 meter radius must be free -- small houses
end
theZone.rndHeading = theZone:getBoolFromZoneProperty("rndHeading", false)
theZone.onRoad = theZone:getBoolFromZoneProperty("onRoad", false)
theZone.onPerimeter = theZone:getBoolFromZoneProperty("onPerimeter", false)
@ -338,15 +305,12 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
theZone.identical = theZone:getBoolFromZoneProperty("identical", false)
if theZone.identical == false then theZone.identical = nil end
end
if theZone:hasProperty("nameScheme") then
theZone.nameScheme = theZone:getStringFromZoneProperty( "nameScheme", "<o>-<uid>") -- default to [<original name> "-" <uuid>]
end
if theZone:hasProperty("groupScheme") then
theZone.groupScheme = theZone:getStringFromZoneProperty("groupScheme", "<o>-<uid>")
end
if theZone.identical and theZone.nameScheme then
trigger.action.outText("+++clnZ: WARNING - clone zone <" .. theZone.name .. "> has both IDENTICAL and NAMESCHEME/GROUPSCHEME attributes. nameScheme is ignored.", 30)
theZone.nameScheme = nil
@ -539,7 +503,6 @@ function cloneZones.updateLocationsInGroupData(theData, zoneDelta, adjustAllWayp
-- now process departing slot if given
if departFromAerodrome then
-- we may need alt from land to add here, maybe later
-- now process parking slots, and choose closest slot
-- per unit's location
if fromParking then
@ -658,11 +621,9 @@ function cloneZones.uniqueNameStaticData(theData, theCloneZone, sourcename)
if theCloneZone and theCloneZone.nameScheme then
local schema = theCloneZone.nameScheme
newName, iterCount = cloneZones.nameFromSchema(schema, theData.name, theCloneZone, sourceName, iterCount)
if theCloneZone.verbose then
trigger.action.outText("clnZ: zone <" .. theCloneZone.name .. "> static schema <" .. schema .. ">: <" .. theData.name .. "> --> <" .. newName .. ">", 30)
end
theData.name = newName -- dcsCommon.uuid(theData.name)
else
-- default naming scheme: <name>-<uuid>
@ -697,19 +658,6 @@ end
function cloneZones.resolveOwnership(spawnZone, ctry)
if not spawnZone.masterOwner then return ctry end -- old code
--[[--
local masterZone = cfxZones.getZoneByName(spawnZone.masterOwner)
if not masterZone then
trigger.action.outText("+++clnZ: cloner " .. spawnZone.name .. " could not find master owner <" .. spawnZone.masterOwner .. ">", 30)
return ctry
end
if not masterZone.owner then
return ctry
end
--]]--
-- ctry = dcsCommon.getACountryForCoalition(masterZone.owner)
ctry = dcsCommon.getACountryForCoalition(spawnZone:getCoalition())
return ctry
end
@ -1249,6 +1197,8 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
-- clone for persistence
local theData = dcsCommon.clone(rawData)
-- remember the zone that spawned this particular group
theData.CZspawner = spawnZone.name
cloneZones.allClones[rawData.name] = theData
if cloneZones.verbose or spawnZone.verbose then
@ -1273,7 +1223,6 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
info.timeLimit = now + timeLimit
info.cloneZone = spawnZone
table.insert(cloneZones.despawnPlan, info)
-- trigger.action.outText("+++clne: scheduled auto-despawn for <" .. info.name .. "> in <" .. timeLimit .. "> secs", 30)
end
-- turn off AI if disabled
@ -1446,6 +1395,7 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
rawData.cty = ctry
-- save for persistence
local theData = dcsCommon.clone(rawData)
theData.CZspawner = spawnZone.name -- remember spawner
cloneZones.allCObjects[rawData.name] = theData
if cloneZones.verbose or spawnZone.verbose then
@ -1466,7 +1416,6 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
info.timeLimit = now + timeLimit
info.cloneZone = spawnZone
table.insert(cloneZones.despawnPlan, info)
-- trigger.action.outText("+++clne: scheduled auto-despawn for OBJECT <" .. info.name .. "> in <" .. timeLimit .. "> secs", 30)
end
-- we don't mix groups with units, so no lookup tables for
-- statics
@ -1528,9 +1477,7 @@ end
function cloneZones.spawnWithSpawner(theZone)
-- analog to cfxSpawnZones.spawnWithSpawner(theSpawner)
-- glue code for helo troops and other modules
-- we may want to check if cloner isn't emtpy first
cloneZones.spawnWithCloner(theZone)
end
@ -1702,15 +1649,6 @@ end
function cloneZones.resolveOwningCoalition(theZone)
return theZone:getCoalition()
--[[--
if not theZone.masterOwner then return theZone.owner end
local masterZone = cfxZones.getZoneByName(theZone.masterOwner)
if not masterZone then
trigger.action.outText("+++clnZ: cloner " .. theZone.name .. " could not find master owner <" .. theZone.masterOwner .. ">", 30)
return theZone.owner
end
return masterZone.owner
--]]--
end
function cloneZones.getRequestableClonersInRange(aPoint, aRange, aSide)
@ -1821,7 +1759,6 @@ function cloneZones.update()
local filtered = {}
for idx, theInfo in pairs(cloneZones.despawnPlan) do
if theInfo.timeLimit < now then
-- trigger.action.outText("+++clne: auto-despawning <" .. theInfo.name .. ">", 30)
if theInfo.isObject then
-- dealloc static object
local theObject = theInfo.theGroup
@ -1857,7 +1794,6 @@ function cloneZones.doOnStart()
end
end
end
--
-- Regular GC and housekeeping
--
@ -1884,7 +1820,6 @@ function cloneZones.GC()
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
@ -1904,8 +1839,6 @@ function cloneZones.houseKeeping()
timer.scheduleFunction(cloneZones.houseKeeping, {}, timer.getTime() + 5 * 60) -- every 5 minutes
cloneZones.GC()
end
--
-- LOAD / SAVE
--
@ -1969,7 +1902,7 @@ function cloneZones.saveData()
cData.myUniqueCounter = theCloner.myUniqueCounter
cData.oSize = theCloner.oSize
cData.lastSize = theCloner.lastSize
-- mySpawns: all groups i'm curently observing for empty!
-- mySpawns: all groups I'm curently observing for empty!
-- myStatics: dto for objects
local mySpawns = {}
for idx, aGroup in pairs(theCloner.mySpawns) do
@ -2071,6 +2004,7 @@ function cloneZones.loadData()
local theGroup = Group.getByName(aName)
if theGroup then
table.insert(mySpawns, theGroup)
theCloner.hasClones = true -- was missing!
else
trigger.action.outText("+++clnZ - persistence: can't reconnect cloner <" .. cName .. "> with clone group <".. aName .. ">", 30)
end
@ -2082,6 +2016,7 @@ function cloneZones.loadData()
local theStatic = StaticObject.getByName(aName)
if theStatic then
table.insert(myStatics, theStatic)
theCloner.hasClones = true -- was missing!S
else
trigger.action.outText("+++clnZ - persistence: can't reconnect cloner <" .. cName .. "> with static <".. aName .. ">", 30)
end
@ -2115,24 +2050,16 @@ function cloneZones.readConfigZone()
end
theZone = cfxZones.createSimpleZone("cloneZonesConfig")
end
if theZone:hasProperty("uniqueCount") then
cloneZones.uniqueCounter = theZone:getNumberFromZoneProperty("uniqueCount", cloneZone.uniqueCounter)
end
if theZone:hasProperty("localCount") then
cloneZones.lclUniqueCounter = theZone:getNumberFromZoneProperty("localCount", cloneZone.lclUniqueCounter)
end
if theZone:hasProperty("globalCount") then
cloneZones.globalCounter = theZone:getNumberFromZoneProperty("globalCount", cloneZone.globalCounter)
end
cloneZones.verbose = theZone:getBoolFromZoneProperty("verbose", false)
if cloneZones.verbose then
trigger.action.outText("+++clnZ: read config", 30)
end
cloneZones.verbose = theZone.verbose
end
function cloneZones.start()
@ -2201,4 +2128,5 @@ end
make example where transport can be different plane types but have same name
support 'orders' to complete replace routes, and pass to groundCommander like spawner. only for ground troops
maxCycles - maximum number of clone cycles. emulate with countdown?
--]]--

View File

@ -1,5 +1,5 @@
cfxHeloTroops = {}
cfxHeloTroops.version = "3.1.3"
cfxHeloTroops.version = "3.1.5"
cfxHeloTroops.verbose = false
cfxHeloTroops.autoDrop = true
cfxHeloTroops.autoPickup = false
@ -23,7 +23,8 @@ cfxHeloTroops.requestRange = 500 -- meters
3.1.3 - decycled structures (destination zone) on save
- upcycled structures (destination) on load
- loadSound and disembarkSound
3.1.4 - guarding destination access in save
3.1.5 - more guarding of destination access
--]]--
@ -927,8 +928,13 @@ function cfxHeloTroops.saveData()
local sData = dcsCommon.clone(gData)
dcsCommon.synchGroupData(sData.groupData)
if sData.destination then
net.log("cfxHeloTroops: decycling troop 'destination' for <" .. sData.destination:getName() .. ">")
sData.destination = sData.destination:getName()
if type(sData.destination) == "table" and (sData.destination.name) then
net.log("cfxHeloTroops: decycling troop 'destination' for <" .. sData.destination.name .. ">")
sData.destination = sData.destination.name
else
sData.destination = nil
net.log("cfxHeloTroops: decycling deployed troops 'destination' nilling for safety")
end
end
allTroopData[gName] = sData
end

View File

@ -1,6 +1,6 @@
impostors={}
impostors.version = "1.1.0"
impostors.version = "1.2.0"
impostors.verbose = false
impostors.ups = 1
impostors.requiredLibs = {
@ -21,7 +21,7 @@ impostors.uniqueCounter = 8200000 -- clones start at 9200000
1.1.0 - filtered dead units during spawns
cleanup
some performance boost for mx lookup
1.2.0 - filters dead groups entirely
LIMITATIONS:
must be on ground (or would be very silly
does not work with any units deployed on ships
@ -296,7 +296,7 @@ function impostors.spawnGroupsFromImpostor(theZone)
local deadUnits = {} -- collect all dead units for immediate delete
-- after spawning
local filtered = {}
for idx, groupName in pairs(theZone.groupNames) do
-- get my group data from MX based on my name
-- we get from MX so we get all path and order info
@ -306,67 +306,75 @@ function impostors.spawnGroupsFromImpostor(theZone)
local cat = cfxMX.groupCatByName[groupName]
local ctry = cfxMX.countryByName[groupName]
local impostorGroup = theZone.myImpostors[groupName]
local relinkZones = {}
-- now iterate all units in that group, and remove their impostors
for idy, theUnit in pairs(rawData.units) do
if theUnit and theUnit.name then
local impName = impostorGroup[theUnit.name]
if not impName then
if theZone.verbose then
trigger.action.outText("group <" .. groupName .. ">: no impostor for <" .. theUnit.name .. ">", 30)
end
else
local impStat = StaticObject.getByName(impName)
if impStat and impStat:isExist() and impStat:getLife() > 1 then
-- still alive. read x, y and heading
local sp = impStat:getPoint()
theUnit.x = sp.x
theUnit.y = sp.z -- !!!
theUnit.heading = dcsCommon.getUnitHeading(impStat) -- should also work for statics
-- should automatically handle ["livery_id"]
relinkZones[theUnit.name] = cfxZones.zonesLinkedToUnit(impStat)
if impostorGroup then
table.insert(filtered, groupName)
local relinkZones = {}
-- now iterate all units in that group, and remove their impostors
for idy, theUnit in pairs(rawData.units) do
if theUnit and theUnit.name then
local impName = impostorGroup[theUnit.name]
if not impName then
if theZone.verbose then
trigger.action.outText("group <" .. groupName .. ">: no impostor for <" .. theUnit.name .. ">", 30)
end
else
-- dead
table.insert(deadUnits, theUnit.name)
end
-- destroy imp
if impStat and impStat:isExist() then
impStat:destroy()
local impStat = StaticObject.getByName(impName)
if impStat and impStat:isExist() and impStat:getLife() > 1 then
-- still alive. read x, y and heading
local sp = impStat:getPoint()
theUnit.x = sp.x
theUnit.y = sp.z -- !!!
theUnit.heading = dcsCommon.getUnitHeading(impStat) -- should also work for statics
-- should automatically handle ["livery_id"]
relinkZones[theUnit.name] = cfxZones.zonesLinkedToUnit(impStat)
else
-- dead
table.insert(deadUnits, theUnit.name)
end
-- destroy imp
if impStat and impStat:isExist() then
impStat:destroy()
end
end
end
end
end
-- destroy impostor info
theZone.myImpostors[groupName] = nil
theZone.impostor = false
-- destroy impostor info
theZone.myImpostors[groupName] = nil
theZone.impostor = false -- is this good?
-- now create the group
if theZone.blinkTime <= 0 then
-- immediate spawn
--local newGroup = coalition.addGroup(ctry, cfxMX.catText2ID(cat), rawData)
local newGroup = coalition.addGroup(ctry, cat, rawData)
impostors.relinkZonesForGroup(relinkZones, newGroup)
if theZone.trackWith and groupTracker.addGroupToTrackerNamed then
-- add these groups to the group tracker
if theZone.verbose or impostors.verbose then
trigger.action.outText("+++ipst: attempting to add group <" .. newGroup:getName() .. "> to tracker <" .. theZone.trackWith .. ">", 30)
-- now create the group
if theZone.blinkTime <= 0 then
-- immediate spawn
--local newGroup = coalition.addGroup(ctry, cfxMX.catText2ID(cat), rawData)
local newGroup = coalition.addGroup(ctry, cat, rawData)
impostors.relinkZonesForGroup(relinkZones, newGroup)
if theZone.trackWith and groupTracker.addGroupToTrackerNamed then
-- add these groups to the group tracker
if theZone.verbose or impostors.verbose then
trigger.action.outText("+++ipst: attempting to add group <" .. newGroup:getName() .. "> to tracker <" .. theZone.trackWith .. ">", 30)
end
groupTracker.addGroupToTrackerNamed(newGroup, theZone.trackWith)
end
groupTracker.addGroupToTrackerNamed(newGroup, theZone.trackWith)
else
-- scheduled spawn
theZone.blinkCount = theZone.blinkCount + 1 -- so healthcheck avoids false positives
local args = {}
args.ctry = ctry
args.cat = cat -- cfxMX.catText2ID(cat)
args.rawData = rawData
args.theZone = theZone
args.relinkZones = relinkZones
timer.scheduleFunction(impostors.delayedSpawn, args, timer.getTime() + theZone.blinkTime)
end
else
-- scheduled spawn
theZone.blinkCount = theZone.blinkCount + 1 -- so healthcheck avoids false positives
local args = {}
args.ctry = ctry
args.cat = cat -- cfxMX.catText2ID(cat)
args.rawData = rawData
args.theZone = theZone
args.relinkZones = relinkZones
timer.scheduleFunction(impostors.delayedSpawn, args, timer.getTime() + theZone.blinkTime)
if theZone.verbose or impostors.verbose then
trigger.action.outText("No impostor group named <" .. groupName .. "> any more, skipped.", 30)
end
-- theZone.myImpostors[groupName] = nil
end
end
theZone.groupNames = filtered -- filter out non-existing
-- now remove all dead units
if theZone.blinkTime <= 0 then
for idx, unitName in pairs(deadUnits) do

View File

@ -1,5 +1,5 @@
noGap = {}
noGap.version = "1.0.1"
noGap.version = "1.3.1"
noGap.verbose = false
noGap.ignoreMe = "-ng" -- ignore altogether
@ -21,10 +21,6 @@ noGap.requiredLibs = {
Advantage: multiple-ship player groups look better, less code
Disadvantage: incompatibe with SSB/slotBlock
What it does:
Replace all player units with static aircraft until the first time
that a player slots into that plane. Static is then replaced with live player unit.
DOES NOT SUPPORT SHIP-BASED AIRCRAFT
For multiplayer, NoGapGUI must run on the server (only server)
@ -38,12 +34,18 @@ noGap.requiredLibs = {
Version History
1.0.0 - Initial version
1.0.1 - added "from runway"
1.3.1 - in line with stopGap 1.3.0
- allNeutral attribute
- DCS dynamic player spawn compatibility
- shallow water also qualifies as carrier based
- noParking
- kickTheDead
- refreshInterval, reinforced guards
--]]--
noGap.standInUnits = {} -- static replacement, if filled; indexed by name
noGap.liveUnits = {} -- live in-game units, checked regularly
noGap.allPlayerUnits = {} -- for update check to get server notification
noGap.allPlayerUnits = {} -- for update check to get server notification, excludes dynamically spawned units
noGap.noGapZones = {} -- DML only
function noGap.staticMXFromUnitMX(theGroup, theUnit)
@ -57,6 +59,12 @@ function noGap.staticMXFromUnitMX(theGroup, theUnit)
theStatic.type = theUnit.type
theStatic.name = theUnit.name -- same as ME unit
theStatic.cty = cfxMX.countryByName[theGroup.name]
theStatic.payload = theUnit.payload -- not supported (yet) by DCS
theStatic.onboard_num = theUnit.onboard_num -- not supported
-- DML only: allNeutral
if noGap.allNeutral then
theStatic.cty = dcsCommon.getACountryForCoalition(0)
end
return theStatic
end
@ -82,10 +90,20 @@ function noGap.isGroundStart(theGroup)
if action == "Turning Point" then return false end
if action == "Landing" then return false end
if action == "From Runway" then return false end
if noGap.noParking then
local loAct = string.lower(action)
if loAct == "from parking area" or
loAct == "from parking area hot" then
if noGap.verbose then
trigger.action.outText("StopG: Player Group <" .. theGroup.name .. "> NOPARKING: [" .. action .. "] must be skipped.", 30)
end
return false
end
end
-- aircraft is on the ground - but is it in water (carrier)?
local u1 = theGroup.units[1]
local sType = land.getSurfaceType(u1) -- has fields x and y
if sType == 3 then return false end
if sType == 3 or sType == 2 then return false end
if noGap.verbose then
trigger.action.outText("noG: Player Group <" .. theGroup.name .. "> GROUND BASED: " .. action .. ", land type " .. sType, 30)
end
@ -107,7 +125,7 @@ function noGap.ignoreMXUnit(theUnit) -- DML-only
return false
end
function noGap.createStandInForMXData(group, theUnit) -- group, theUnit are MX data blocks
function noGap.createStandInForMXData(group, theUnit) -- WARNING: group and theUnit are MX data blocks
local sgMatch = theUnit.name:sub(-#noGap.ignoreMe) == noGap.ignoreMe or group.name:sub(-#noGap.ignoreMe) == noGap.ignoreMe
local spMatch = theUnit.name:sub(-#noGap.spIgnore) == noGap.spIgnore or group.name:sub(-#noGap.spIgnore) == noGap.spIgnore
local zoneIgnore = noGap.ignoreMXUnit(theUnit)
@ -139,7 +157,6 @@ function noGap.createStandInForMXData(group, theUnit) -- group, theUnit are MX d
end
end
end
end
function noGap.fillGaps()
@ -175,6 +192,7 @@ function noGap.turnOff()
StaticObject.destroy(standIn)
end
noGap.standInUnits = {}
noGap.running = false
end
function noGap.turnOn()
@ -183,6 +201,23 @@ function noGap.turnOn()
end
-- populate all empty (non-taken) slots with stand-ins
noGap.fillGaps()
noGap.running = true
end
function noGap.refreshAll() -- restore all statics
if noGap.refreshInterval > 0 then
-- re-schedule invocation
timer.scheduleFunction(noGap.refreshAll, {}, timer.getTime() + noGap.refreshInterval)
if not noGap.enabled then return end
if noGap.running then
noGap.turnOff() -- kill all statics
-- turn back on in half a second
timer.scheduleFunction(noGap.turnOn, {}, timer.getTime() + 0.5)
end
if stopGap.verbose then
noGap.action.outText("+++noG: refreshing all static", 30)
end
end
end
--
@ -193,11 +228,18 @@ function noGap:onEvent(event)
if not event.id then return end
if not event.initiator then return end
local theUnit = event.initiator
if (not theUnit.getPlayerName) or (not theUnit:getPlayerName()) then
return
end -- no player unit.
if cfxMX.isDynamicPlayer(theUnit) then
if noGap.verbose then
trigger.action.outText("+++noG: unit <" .. theUnit:getName() .. "> controlled by <" .. theUnit:getPlayerName() .. "> is dynamically spawned, ignoring.", 30)
end
return
end -- ignore all dynamically spawned aircraft
if event.id == 15 then -- we act on player unit birth
if (not theUnit.getPlayerName) or (not theUnit:getPlayerName()) then
return
end -- no player unit.
local uName = theUnit:getName()
if noGap.standInUnits[uName] then
@ -208,11 +250,35 @@ function noGap:onEvent(event)
trigger.action.outText("+++noG: removed static for <" ..uName .. ">, player inbound", 30)
end
end
noGap.liveUnits[uName] = theUnit
noGap.liveUnits[uName] = theUnit -- dynamic never show up here
-- reset noGapGUI flag, it has done its job. Unit is live
-- we can reset it for next iteration
trigger.action.setUserFlag("NG"..uName, 0)
end
if id == 6 then -- eject, ignore for now
end
if (id == 9) or (id == 30) or (id == 5) then -- dead, lost, crash
local pName = theUnit:getPlayerName()
timer.scheduleFunction(noGap.kickplayer, pName, timer.getTime() + 1)
end
end
noGap.kicks = {}
function noGap.kickplayer(args)
if not noGap.kickTheDead then return end
local pName = args
for i,slot in pairs(net.get_player_list()) do
local nn = net.get_name(slot)
if nn == pName then
if noGap.kicks[nn] then
if timer.getTime() < noGap.kicks[nn] then return end
end
net.force_player_slot(slot, 0, '')
noGap.kicks[nn] = timer.getTime() + 5 -- avoid too many kicks in 5 seconds
end
end
end
--
@ -312,7 +378,7 @@ function noGap.update()
end
--
-- read stopGapZone (DML only)
-- read noGap Zone (DML only)
--
function noGap.createNoGapZone(theZone)
local ng = theZone:getBoolFromZoneProperty("noGap", true)
@ -331,8 +397,11 @@ noGap.name = "noGapConfig" -- cfxZones compatibility here
function noGap.readConfigZone(theZone)
-- currently nothing to do
noGap.verbose = theZone.verbose
noGap.ssbEnabled = theZone:getBoolFromZoneProperty("ssb", true)
noGap.enabled = theZone:getBoolFromZoneProperty("onStart", true)
noGap.timeOut = theZone:getNumberFromZoneProperty("timeOut", 0) -- default to off
noGap.noParking = theZone:getBoolFromZoneProperty("noParking", false)
if theZone:hasProperty("on?") then
noGap.turnOnFlag = theZone:getStringFromZoneProperty("on?", "*<none>")
noGap.lastTurnOnFlag = trigger.misc.getUserFlag(noGap.turnOnFlag)
@ -350,6 +419,10 @@ function noGap.readConfigZone(theZone)
trigger.action.outText("+++noG: turned off", 30)
end
end
noGap.refreshInterval = theZone:getNumberFromZoneProperty("refresh", -1) -- default: no refresh
noGap.kickTheDead = theZone:getBoolFromZoneProperty("kickDead", true)
noGap.allNeutral = theZone:getBoolFromZoneProperty("allNeutral", false)
end
--
@ -394,6 +467,11 @@ function noGap.start()
-- start update in 1 second
timer.scheduleFunction(noGap.update, {}, timer.getTime() + 1)
-- start refresh cycle if refresh (>0)
if noGap.refreshInterval > 0 then
timer.scheduleFunction(noGap.refreshAll, {}, timer.getTime() + noGap.refreshInterval)
end
-- say hi!
local mp = " (SP - <" .. sgDetect .. ">)"
if sgDetect > 0 then mp = " -- MP GUI Detected (" .. sgDetect .. ")!" end

View File

@ -1,5 +1,5 @@
radioMenu = {}
radioMenu.version = "4.0.1"
radioMenu.version = "4.0.2"
radioMenu.verbose = false
radioMenu.ups = 1
radioMenu.requiredLibs = {
@ -20,6 +20,7 @@ radioMenu.lateGroups = {} -- dict by ID
4.0.0 - added infrastructure for dynamic players (DCS 2.9.6)
- added dynamic player support
4.0.1 - MX no longer optional, so ask for it
4.0.2 - ackSnd now also broadcasts to all if no group
--]]--
function radioMenu.addRadioMenu(theZone)
@ -770,7 +771,11 @@ function radioMenu.doMenuX(args)
radioMenu.radioOutMsg(ack, gid, theZone)
end
if ackSnd then
trigger.action.outSoundForGroup(gid, ackSnd)
if (not gid) or gid == 0 then
trigger.action.outSound(ackSnd)
else
trigger.action.outSoundForGroup(gid, ackSnd)
end
end
end

View File

@ -1,107 +1,144 @@
raiseFlag = {}
raiseFlag.version = "2.0.0"
raiseFlag.verbose = false
raiseFlag.version = "3.0.0"
raiseFlag.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
}
raiseFlag.flags = {}
raiseFlag.flags = {} -- my minions
--[[--
Raise A Flag module -- (c) 2022-23 by Christian Franz and cf/x AG
(c) 2022-23 by Christian Franz and cf/x AG
Version History
1.0.0 - initial release
1.0.1 - synonym "raiseFlag!"
1.1.0 - DML update
1.2.0 - Watchflag update
1.2.1 - support for 'inc', 'dec', 'flip'
2.0.0 - dmlZones
- full method support
- full DML upgrade
- method attribute (synonym to 'value')
3.0.0 - switched to polling
- remains# attribute
- support for persistence
- zone-individual verbosity
- code cleanup, removed deprecated attributes
--]]--
function raiseFlag.addRaiseFlag(theZone)
table.insert(raiseFlag.flags, theZone)
raiseFlag.flags[theZone.name] = theZone
end
function raiseFlag.getRaiseFlagByName(aName)
for idx, aZone in pairs(raiseFlag.flags) do
if aName == aZone.name then return aZone end
end
if raiseFlag.verbose then
trigger.action.outText("+++rFlg: no raiseFlag with name <" .. aName ..">", 30)
end
return nil
end
--
-- read attributes
--
function raiseFlag.createRaiseFlagWithZone(theZone)
-- get flag from faiseFlag itself
if theZone:hasProperty("raiseFlag") then
theZone.raiseFlag = theZone:getStringFromZoneProperty("raiseFlag", "<none>") -- the flag to raise
else
theZone.raiseFlag = theZone:getStringFromZoneProperty("raiseFlag!", "<none>") -- the flag to raise
end
-- pre-method DML raiseFlag is now upgraded to method.
-- flagValue now carries the method
if theZone:hasProperty("value") then -- backward compatibility
theZone.flagValue = theZone:getStringFromZoneProperty("value", "inc") -- value to set to. default is command 'inc'
else
theZone.flagValue = theZone:getStringFromZoneProperty("method", "inc")
end
theZone.raiseFlag = theZone:getStringFromZoneProperty("raiseFlag!", "<none>") -- the flag to raise
theZone.flagValue = theZone:getStringFromZoneProperty("method", "inc")
theZone.flagValue = theZone.flagValue:lower()
theZone.minAfterTime, theZone.maxAfterTime = theZone:getPositiveRangeFromZoneProperty("afterTime", -1)
theZone.raiseTriggerMethod = theZone:getStringFromZoneProperty( "triggerMethod", "change")
if theZone:hasProperty("raiseTriggerMethod") then
theZone.raiseTriggerMethod = theZone:getStringFromZoneProperty("raiseTriggerMethod", "change")
end
if theZone:hasProperty("stopFlag?") then
theZone.triggerStopFlag = theZone:getStringFromZoneProperty( "stopFlag?", "none")
theZone.lastTriggerStopValue = theZone:getFlagValue(theZone.triggerStopFlag) -- save last value
end
theZone.scheduleID = nil
if theZone:hasProperty("timeLeft#") then
theZone.timeLeft = theZone:getStringFromZoneProperty("timeLeft#", "none")
end
theZone.raiseStopped = false
-- now simply schedule for invocation
local args = {}
args.theZone = theZone
if theZone.minAfterTime < 1 then
timer.scheduleFunction(raiseFlag.triggered, args, timer.getTime() + 0.5)
theZone.deadLine = -1 -- will always trigger
else
local delay = cfxZones.randomDelayFromPositiveRange(theZone.minAfterTime, theZone.maxAfterTime)
timer.scheduleFunction(raiseFlag.triggered, args, timer.getTime() + delay)
theZone.deadLine = timer.getTime() + delay
end
end
function raiseFlag.triggered(args)
local theZone = args.theZone
if theZone.raiseStopped then return end
-- if we get here, we aren't stopped and do the flag pull
function raiseFlag.triggered(theZone)
if theZone.raiseStopped then return end -- should be filtered
local command = theZone.flagValue
theZone:pollFlag(theZone.raiseFlag, command)
if raiseFlag.verbose or theZone.verbose then
trigger.action.outText("+++rFlg - raising <" .. theZone.raiseFlag .. "> with method '" .. command .. "'" ,30)
trigger.action.outText("+++rFlg - raising <" .. theZone.raiseFlag .. "> with method '" .. command .. "'" .. " for zone <" .. theZone.name .. ">" ,30)
end
end
--
-- update
--
function raiseFlag.update()
-- call me in a second to poll triggers
timer.scheduleFunction(raiseFlag.update, {}, timer.getTime() + 1)
for idx, aZone in pairs(raiseFlag.flags) do
-- make sure to re-start before reading time limit
if aZone:testZoneFlag(aZone.triggerStopFlag, aZone.raiseTriggerMethod, "lastTriggerStopValue") then
theZone.raiseStopped = true -- we are done, no flag!
function raiseFlag.update(firstUpdate)
local now = timer.getTime()
timer.scheduleFunction(raiseFlag.update, false, now + 1) -- we always beat on .5 offset!
local filtered = {}
for zName, theZone in pairs(raiseFlag.flags) do
-- see if this timer has run out
if theZone.deadLine < now then
if theZone.verbose then
trigger.action.outText("+++rFlg: will raise flag <" .. theZone.name .. ">", 30)
end
raiseFlag.triggered(theZone)
if theZone.timeLeft then theZone:setFlagValue(theZone.timeLeft, 0) end
theZone.raiseStopped = true -- will filter
elseif theZone.timeLeft then
local rem = theZone.deadLine - now
theZone:setFlagValue(theZone.timeLeft, rem)
if theZone.verbose then
trigger.action.outText("+++rFlg: time left on <" .. theZone.name .. ">:" .. math.floor(rem) .. " secs", 30)
end
end
if theZone:testZoneFlag(theZone.triggerStopFlag, theZone.raiseTriggerMethod, "lastTriggerStopValue") then
theZone.raiseStopped = true -- filter
end
if not theZone.raiseStopped then
filtered[theZone.name] = theZone
end
end
raiseFlag.flags = filtered
end
--
-- load / save (game data)
--
function raiseFlag.saveData()
local theData = {}
local theFlags = {}
local now = timer.getTime()
for name, theZone in pairs (raiseFlag.flags) do
-- note: we only process existing!
theFlags[name] = theZone.deadLine - now
end
-- save current deadlines
theData.theFlags = theFlags
return theData, raiseFlag.sharedData -- second val currently nil
end
function raiseFlag.loadData()
if not persistence then return end
local shared = nil
local theData = persistence.getSavedDataForModule("raiseFlag")
if (not theData) then
if raiseFlag.verbose then
trigger.action.outText("+++rFlg: no save date received, skipping.", 30)
end
return
end
local theFlags = theData.theFlags
-- filter and reset timers
local filtered = {}
local now = timer.getTime()
for name, deadLine in pairs(theFlags) do
local theZone = raiseFlag.flags[name]
if theZone then
theZone.deadLine = now + deadLine
filtered[name] = theZone
if theZone.verbose then
trigger.action.outText("+++rFlg: (persistence) reset zone <" .. name .. "> to <" .. theZone.deadLine .. "> (<" .. deadLine .. ">)", 30)
end
else
trigger.action.outText("+++rFlag: (persistence) filtered <" .. name .. ">, does not exist in miz.", 30)
end
end
for name, theZone in pairs(raiseFlag.flags) do
if not filtered[name] and theZone.verbose then
trigger.action.outText("+++rFlg: (persistence) filtered spent/non-saved <" .. name .. ">.", 30)
end
end
raiseFlag.flags = filtered
end
--
-- config & go!
--
@ -117,39 +154,30 @@ function raiseFlag.readConfigZone()
end
function raiseFlag.start()
-- lib check
if not dcsCommon.libCheck then
trigger.action.outText("cfx raise flag requires dcsCommon", 30)
return false
end
if not dcsCommon.libCheck("cfx Raise Flag", raiseFlag.requiredLibs) then
return false
end
-- read config
if not dcsCommon.libCheck("cfx Raise Flag", raiseFlag.requiredLibs)then return false end
raiseFlag.readConfigZone()
-- process raise Flags Zones
local attrZones = cfxZones.getZonesWithAttributeNamed("raiseFlag")
for k, aZone in pairs(attrZones) do
raiseFlag.createRaiseFlagWithZone(aZone) -- process attributes
raiseFlag.addRaiseFlag(aZone) -- add to list
end
-- try synonym
attrZones = cfxZones.getZonesWithAttributeNamed("raiseFlag!")
for k, aZone in pairs(attrZones) do
raiseFlag.createRaiseFlagWithZone(aZone) -- process attributes
raiseFlag.addRaiseFlag(aZone) -- add to list
end
-- start update
raiseFlag.update()
if persistence then
callbacks = {}
callbacks.persistData = raiseFlag.saveData
persistence.registerModule("raiseFlag", callbacks)
-- now load my data
raiseFlag.loadData()
end
-- start update at 0.5 secs mark
timer.scheduleFunction(raiseFlag.update, true, timer.getTime() + 0.5)
trigger.action.outText("cfx raiseFlag v" .. raiseFlag.version .. " started.", 30)
return true
end
-- let's go!
if not raiseFlag.start() then
trigger.action.outText("cfx Raise Flag aborted: missing libraries", 30)
raiseFlag = nil

View File

@ -1,5 +1,5 @@
slotty = {}
slotty.version = "1.1.0"
slotty.version = "1.2.0"
--[[--
Single-player slot blocking and slot blocking fallback
for multiplayer when SSB is not installed on server.
@ -12,7 +12,7 @@ slotty.version = "1.1.0"
Version history
1.0.0 - Initial version
1.1.0 - "noSlotty" global disable flag, anti-mirror SSB flag
1.2.0 - better (delayed) nilling for cfxSSB ocupied clients to
1.2.0 - better (delayed) nilling for cfxSSB occupied clients to
- avoid a race condition
--]]--

View File

@ -58,7 +58,7 @@ stopGap.requiredLibs = {
required
1.3.0 - carriers in shallow waters also no longer handled as viable
- noParking option
1.3.1 - stronger guards on refresh
--]]--
stopGap.standInGroups = {} -- idx by name, if set has a static
@ -239,6 +239,7 @@ function stopGap.refreshAll() -- restore all statics
if stopGap.refreshInterval > 0 then
-- re-schedule invocation
timer.scheduleFunction(stopGap.refreshAll, {}, timer.getTime() + stopGap.refreshInterval)
if not stopGap.enabled then return end -- why do we have enabled and running?
if stopGap.running then
stopGap.turnOff() -- kill all statics
-- turn back on in half a second