Version 2.1.0

Shared Persistence, Stop Gap
This commit is contained in:
Christian Franz 2024-03-07 09:04:15 +01:00
parent a44f145218
commit 6088c4cfa1
15 changed files with 341 additions and 74 deletions

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,5 @@
bombRange = {} bombRange = {}
bombRange.version = "1.1.0" bombRange.version = "1.1.1"
bombRange.dh = 1 -- meters above ground level burst bombRange.dh = 1 -- meters above ground level burst
bombRange.requiredLibs = { bombRange.requiredLibs = {

View File

@ -1,5 +1,5 @@
cargoSuper = {} cargoSuper = {}
cargoSuper.version = "1.1.1" cargoSuper.version = "1.1.2"
--[[-- --[[--
version history version history
1.0.0 - initial version 1.0.0 - initial version
@ -14,7 +14,9 @@ version history
- getManifestForCategory alias for getManifestFor - getManifestForCategory alias for getManifestFor
- removeAllMassFor() - removeAllMassFor()
1.1.1 - deleteMassObject corrected index bug 1.1.1 - deleteMassObject corrected index bug
1.1.2 - removed a typo that did not reset mass correctly
in removeAllMassForCargo
CargoSuper manages weigth for a logical named unit. Weight can be added CargoSuper manages weigth for a logical named unit. Weight can be added
to arbitrary categories like 'passengers', 'cargo' or "whatever". In order to arbitrary categories like 'passengers', 'cargo' or "whatever". In order
to add weight to a unit, first create a massObject through createMassObject to add weight to a unit, first create a massObject through createMassObject
@ -102,14 +104,14 @@ function cargoSuper.removeMassObjectFrom(name, category, theMassObject, forget)
end end
-- DO NOT PUBLISH. Provided only for backwards compatibility -- DO NOT PUBLISH. Provided only for backwards compatibility
function cargoSuper.removeAllMassForCargo(name, catergory) function cargoSuper.removeAllMassForCargo(name, category)
if not category then category = "cSup!DefCat" end if not category then category = "cSup!DefCat" end
nameStats.reset(name, category, cargoSuper.cargos) nameStats.reset(name, category, cargoSuper.cargos)
end end
-- alias for removeAllMassForCargo -- alias for removeAllMassForCargo
function cargoSuper.removeAllMassForCategory(name, catergory) function cargoSuper.removeAllMassForCategory(name, category)
cargoSuper.removeAllMassForCargo(name, catergory) cargoSuper.removeAllMassForCargo(name, category)
end end
function cargoSuper.removeAllMassFor(name) function cargoSuper.removeAllMassFor(name)

View File

@ -1,5 +1,5 @@
csarManager = {} csarManager = {}
csarManager.version = "3.2.0" csarManager.version = "3.2.2"
csarManager.ups = 1 csarManager.ups = 1
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
@ -32,6 +32,12 @@ csarManager.ups = 1
- 3.2.0 - inPopulated csar option - 3.2.0 - inPopulated csar option
- clearance csar attribute - clearance csar attribute
- maxTries csar attribute - maxTries csar attribute
- 3.2.1 - comsRange attribute when mission times out
- rescueTypes option in config
3.2.2 - reset helicopter weight on birth
- cleanup
3.2.3 - hardening against *accidental* multi-unit player groups.
INTEGRATES AUTOMATICALLY WITH playerScore INTEGRATES AUTOMATICALLY WITH playerScore
INTEGRATES WITH LIMITED AIRFRAMES INTEGRATES WITH LIMITED AIRFRAMES
@ -102,8 +108,11 @@ function csarManager.createDownedPilot(theMission, existingUnit)
aHeading = math.random(360)/360 * 2 * 3.1415 aHeading = math.random(360)/360 * 2 * 3.1415
end end
theMission.locations = {} theMission.locations = {}
-- get a typeName for the unit to create from my list of
-- types
local rType = dcsCommon.pickRandom(csarManager.rescueTypes)
local theBoyGroup = dcsCommon.createSingleUnitGroup(theMission.name, local theBoyGroup = dcsCommon.createSingleUnitGroup(theMission.name,
"Soldier M4 GRG", -- "Soldier M4 GRG", rType, -- "Soldier M4 GRG",
aLocation.x, aLocation.x,
aLocation.z, aLocation.z,
-aHeading + 1.5) -- + 1.5 to turn inwards -aHeading + 1.5) -- + 1.5 to turn inwards
@ -181,7 +190,6 @@ function csarManager.createCSARMissionData(point, theSide, freq, name, numCrew,
-- set timeLimit if enabled -- set timeLimit if enabled
if timeLimit then if timeLimit then
local theLimit = cfxZones.randomDelayFromPositiveRange(timeLimit[1], timeLimit[2]) * 60 local theLimit = cfxZones.randomDelayFromPositiveRange(timeLimit[1], timeLimit[2]) * 60
-- trigger.action.outText("set time limit for mission to "..theLimit, 30)
newMission.expires = timer.getTime() + theLimit newMission.expires = timer.getTime() + theLimit
end end
@ -192,7 +200,6 @@ function csarManager.createCSARMissionData(point, theSide, freq, name, numCrew,
end end
function csarManager.addMission(theMission) function csarManager.addMission(theMission)
-- trigger.action.outText("enter addMission", 30)
table.insert(csarManager.openMissions, theMission) table.insert(csarManager.openMissions, theMission)
csarManager.invokeNewMissionCallbacks(theMission) csarManager.invokeNewMissionCallbacks(theMission)
end end
@ -318,9 +325,16 @@ function csarManager:onEvent(event)
-- we also need to make sure that there are no -- we also need to make sure that there are no
-- more troopsOnBoard -- more troopsOnBoard
local myName = theUnit:getName()
local conf = csarManager.getUnitConfig(theUnit) local conf = csarManager.getUnitConfig(theUnit)
conf.unit = theUnit conf.unit = theUnit
conf.troopsOnBoard = {} conf.troopsOnBoard = {}
local totalMass = cargoSuper.calculateTotalMassFor(myName)
-- now also set cargo weight for the unit
cargoSuper.removeAllMassForCargo(myName, "Evacuees") -- will allocate new empty table
totalMass = cargoSuper.calculateTotalMassFor(myName)
trigger.action.setUnitInternalCargo(myName, totalMass) -- super recalcs
end end
@ -448,11 +462,12 @@ function csarManager.heloLanded(theUnit)
"Evacuees", "Evacuees",
theMassObject) theMassObject)
msn = theMassObject.ref msn = theMassObject.ref
end end
-- reset weight -- reset weight
local totalMass = cargoSuper.calculateTotalMassFor(myName) local totalMass = cargoSuper.calculateTotalMassFor(myName)
trigger.action.setUnitInternalCargo(myName, totalMass) -- super recalcs trigger.action.setUnitInternalCargo(myName, totalMass) -- super recalcs
conf.troopsOnBoard = {} -- empty out troops on board conf.troopsOnBoard = {} -- empty out troops on board
-- we do *not* return so we can pick up troops on -- we do *not* return so we can pick up troops on
-- a CSARBASE if they were dropped there -- a CSARBASE if they were dropped there
@ -523,6 +538,7 @@ function csarManager.heloLanded(theUnit)
-- reset unit's weight based on people on board -- reset unit's weight based on people on board
local totalMass = cargoSuper.calculateTotalMassFor(myName) local totalMass = cargoSuper.calculateTotalMassFor(myName)
trigger.action.setUnitInternalCargo(myName, totalMass) -- 10 kg as empty + per-unit time people trigger.action.setUnitInternalCargo(myName, totalMass) -- 10 kg as empty + per-unit time people
end end
@ -733,6 +749,8 @@ function csarManager.doListCSARRequests(args)
local conf = args[1] local conf = args[1]
local param = args[2] local param = args[2]
local theUnit = conf.unit local theUnit = conf.unit
if not theUnit then return end -- ??
if not Unit.isExist(theUnit) then return end
local point = theUnit:getPoint() local point = theUnit:getPoint()
local theSide = theUnit:getCoalition() local theSide = theUnit:getCoalition()
@ -794,6 +812,8 @@ function csarManager.doStatusCarrying(args)
local conf = args[1] local conf = args[1]
local param = args[2] local param = args[2]
local theUnit = conf.unit local theUnit = conf.unit
if not theUnit then return end -- ??
if not Unit.isExist(theUnit) then return end
local now = timer.getTime() local now = timer.getTime()
-- build status report -- build status report
@ -837,6 +857,9 @@ function csarManager.unloadOne(args)
local conf = args[1] local conf = args[1]
local param = args[2] local param = args[2]
local theUnit = conf.unit local theUnit = conf.unit
if not theUnit then return end -- ??
if not Unit.isExist(theUnit) then return end
local myName = theUnit:getName() local myName = theUnit:getName()
local report = "NYI: unload one" local report = "NYI: unload one"
@ -869,7 +892,9 @@ function csarManager.unloadOne(args)
trigger.action.outSoundForCoalition(theSide, csarManager.actionSound) -- "Quest Snare 3.wav") trigger.action.outSoundForCoalition(theSide, csarManager.actionSound) -- "Quest Snare 3.wav")
-- recalc weight -- recalc weight
trigger.action.setUnitInternalCargo(myName, 10 + #conf.troopsOnBoard * csarManager.pilotWeight) -- 10 kg as empty + per-unit time people local totalMass = 10 + #conf.troopsOnBoard * csarManager.pilotWeight
trigger.action.setUnitInternalCargo(myName, totalMass) -- 10 kg as empty + per-unit time people
--trigger.action.outText("unit <" .. myName .. ">, internal cargo now <" .. totalMass .. ">kg", 30)
end end
end end
@ -882,6 +907,8 @@ function csarManager.directions(args)
local conf = args[1] local conf = args[1]
local param = args[2] local param = args[2]
local theUnit = conf.unit local theUnit = conf.unit
if not theUnit then return end -- ??
if not Unit.isExist(theUnit) then return end
local myName = theUnit:getName() local myName = theUnit:getName()
local theSide = theUnit:getCoalition() local theSide = theUnit:getCoalition()
local report = "Nothing to report." local report = "Nothing to report."
@ -997,12 +1024,23 @@ function csarManager.updateCSARMissions()
if stillRunning and stillAlive then if stillRunning and stillAlive then
table.insert(newMissions, aMission) table.insert(newMissions, aMission)
elseif stillAlive then elseif stillAlive then
-- expired.
local p = aMission.zone:getPoint() -- all missions have a zone
p.y = 0
local thePlayers = coalition.getPlayers(aMission.side)
local msg = aMission.name .. " is no longer responding. Abort rescue." local msg = aMission.name .. " is no longer responding. Abort rescue."
trigger.action.outTextForCoalition(aMission.side, msg, 30) for idx, theUnit in pairs (thePlayers) do
trigger.action.outSoundForCoalition(aMission.side, csarManager.lostSound) local up = theUnit:getPoint()
up.y = 0
local dist = math.floor (dcsCommon.dist(up, p))
if dist < csarManager.comsRange then
local ID = theUnit:getID()
trigger.action.outTextForUnit(ID, msg, 30)
trigger.action.outSoundForUnit(ID, csarManager.lostSound)
end
end
csarManager.invokeCallbacks(aMission.side, false, 1, "lost", aMission) csarManager.invokeCallbacks(aMission.side, false, 1, "lost", aMission)
if aMission.group and Group.isExist(aMission.group) then if aMission.group and Group.isExist(aMission.group) then
-- trigger.action.outText("removing group", 30)
Group.destroy(aMission.group) Group.destroy(aMission.group)
end end
else else
@ -1150,18 +1188,17 @@ function csarManager.update() -- every second
theMassObject) theMassObject)
local totalMass = cargoSuper.calculateTotalMassFor(uName) local totalMass = cargoSuper.calculateTotalMassFor(uName)
trigger.action.setUnitInternalCargo(uName, totalMass) trigger.action.setUnitInternalCargo(uName, totalMass)
if csarManager.verbose then if csarManager.verbose then
local allEvacuees = cargoSuper.getManifestFor(myName, "Evacuees") -- returns unlinked array local allEvacuees = cargoSuper.getManifestFor(myName, "Evacuees") -- returns unlinked array
trigger.action.outText("+++csar: <" .. uName .. "> now has <" .. #allEvacuees .. "> groups of evacuees on board, totalling " .. totalMass .. "kg", 30) trigger.action.outText("+++csar: <" .. uName .. "> now has <" .. #allEvacuees .. "> groups of evacuees on board, totalling " .. totalMass .. "kg", 30)
end end
--trigger.action.outTextForGroup(uID, hoverMsg, 30, true)
trigger.action.outSoundForGroup(uID, csarManager.pickupSound) trigger.action.outSoundForGroup(uID, csarManager.pickupSound)
--return -- we only ever rescue one --return -- we only ever rescue one
end -- hovered long enough end -- hovered long enough
--trigger.action.outTextForGroup(uID, hoverMsg, 30, true)
-- return -- only ever one winch op -- return -- only ever one winch op
else -- too high for hover else -- too high for hover
hoverMsg = "Evacuee " .. d * 1 .. "m on your " .. oclock .. " o'clock; land or descend to between 10 and 90 AGL for winching" hoverMsg = "Evacuee " .. d * 1 .. "m on your " .. oclock .. " o'clock; land or descend to between 10 and 90 AGL for winching"
@ -1328,13 +1365,11 @@ function csarManager.readCSARZone(theZone)
theZone.csarMapMarker = nil theZone.csarMapMarker = nil
if theZone:hasProperty("timeLimit") then if theZone:hasProperty("timeLimit") then
local tmin, tmax = theZone:getPositiveRangeFromZoneProperty("timeLimit", 1) local tmin, tmax = theZone:getPositiveRangeFromZoneProperty("timeLimit", 1)
-- trigger.action.outText("Read time limit for <" .. theZone.name .. ">: <" .. tmin .. ">, <" .. tmax .. ">", 30)
theZone.timeLimit = {tmin, tmax} theZone.timeLimit = {tmin, tmax}
else else
theZone.timeLimit = nil theZone.timeLimit = nil
end end
-- theZone.timeLimit = theZone:getNumberFromZoneProperty("timeLimit", 0)
-- if theZone.timeLimit == 0 then theZone.timeLimit = nil else theZone.timeLimit = timeLimit * 60 end
local deferred = theZone:getBoolFromZoneProperty("deferred", false) local deferred = theZone:getBoolFromZoneProperty("deferred", false)
@ -1389,7 +1424,6 @@ function csarManager.readCSARZone(theZone)
-- add to list of startable csar -- add to list of startable csar
if theZone.startCSAR then if theZone.startCSAR then
csarManager.addCSARZone(theZone) csarManager.addCSARZone(theZone)
-- trigger.action.outText("csar: added <".. theZone.name .."> to deferred csar missions", 30)
end end
if deferred and not theZone.startCSAR then if deferred and not theZone.startCSAR then
@ -1495,6 +1529,7 @@ function csarManager.readConfigZone()
csarManager.pickupSound = theZone:getStringFromZoneProperty("pickupSound", csarManager.actionSound) csarManager.pickupSound = theZone:getStringFromZoneProperty("pickupSound", csarManager.actionSound)
csarManager.vectoring = theZone:getBoolFromZoneProperty("vectoring", true) csarManager.vectoring = theZone:getBoolFromZoneProperty("vectoring", true)
csarManager.lostSound = theZone:getStringFromZoneProperty("lostSound", csarManager.actionSound) csarManager.lostSound = theZone:getStringFromZoneProperty("lostSound", csarManager.actionSound)
csarManager.comsRange = theZone:getNumberFromZoneProperty("comsRange", 40000)
-- add own troop carriers -- add own troop carriers
if theZone:hasProperty("troopCarriers") then if theZone:hasProperty("troopCarriers") then
@ -1512,6 +1547,13 @@ function csarManager.readConfigZone()
csarManager.addPrefix = theZone:getBoolFromZoneProperty("addPrefix", true) csarManager.addPrefix = theZone:getBoolFromZoneProperty("addPrefix", true)
csarManager.maxMissions = theZone:getNumberFromZoneProperty("maxMissions", 15) csarManager.maxMissions = theZone:getNumberFromZoneProperty("maxMissions", 15)
-- add types to use for the rescuee
local hTypes = theZone:getStringFromZoneProperty("rescueTypes", "Soldier M4 GRG")
local typeArray = dcsCommon.splitString(hTypes, ",")
typeArray = dcsCommon.trimArray(typeArray)
csarManager.rescueTypes = typeArray
if csarManager.verbose then if csarManager.verbose then
trigger.action.outText("+++csar: read config", 30) trigger.action.outText("+++csar: read config", 30)
end end
@ -1535,8 +1577,6 @@ function csarManager.start()
-- and populate the available mission. -- and populate the available mission.
csarManager.processCSARZones() csarManager.processCSARZones()
-- install callbacks for helo-relevant events
--dcsCommon.addEventHandler(csarManager.somethingHappened, csarManager.preProcessor, csarManager.postProcessor)
world.addEventHandler(csarManager) world.addEventHandler(csarManager)
-- now iterate through all player groups and install the CSAR Menu -- now iterate through all player groups and install the CSAR Menu

View File

@ -17,6 +17,7 @@ factoryZone.name = "factoryZone"
- productionTime config synonyme - productionTime config synonyme
- defendMe? attribute - defendMe? attribute
- triggered 'shocked' mode via defendMe - triggered 'shocked' mode via defendMe
3.1.1 - fixed a big with persistence
--]]-- --]]--
factoryZone.requiredLibs = { factoryZone.requiredLibs = {
@ -734,7 +735,7 @@ function factoryZone.loadData()
local allZoneData = theData.zoneData local allZoneData = theData.zoneData
for zName, zData in pairs(allZoneData) do for zName, zData in pairs(allZoneData) do
-- access zone -- access zone
local theZone = factoryZone.getOwnedZoneByName(zName) local theZone = factoryZone.getFactoryZoneByName(zName)-- was: factoryZone.getOwnedZoneByName(zName)
if theZone then if theZone then
if zData.defenderData then if zData.defenderData then
if theZone.defenders and theZone.defenders:isExist() then if theZone.defenders and theZone.defenders:isExist() then

View File

@ -93,16 +93,13 @@ function nameStats.addString(name, aString, path, rootNode)
if not name then return nil end if not name then return nil end
if not aString then return nil end if not aString then return nil end
local theLeaf = nameStats.getLeaf(name, path, rootNode) local theLeaf = nameStats.getLeaf(name, path, rootNode)
--table.insert(theLeaf.strings, aString)
theLeaf.strings = theLeaf.strings .. aString theLeaf.strings = theLeaf.strings .. aString
-- return aString
end end
-- reset the log -- reset the log
function nameStats.removeAllString(name, path, rootNode) function nameStats.removeAllString(name, path, rootNode)
if not name then return nil end if not name then return nil end
local theLeaf = nameStats.getLeaf(name, path, rootNode) local theLeaf = nameStats.getLeaf(name, path, rootNode)
-- theLeaf.strings = {}
theLeaf.strings = "" theLeaf.strings = ""
end end
@ -151,7 +148,7 @@ function nameStats.reset(name, path, rootNode)
if not name then return nil end if not name then return nil end
if not rootNode then rootNode = nameStats.stats end if not rootNode then rootNode = nameStats.stats end
local theEntry = rootNode[name] local theEntry = rootNode[name]
if not theEntry then if not theEntry then
-- does not yet exist, create a root entry -- does not yet exist, create a root entry
theEntry = nameStats.createRoot(name) theEntry = nameStats.createRoot(name)
rootNode[name] = theEntry rootNode[name] = theEntry
@ -166,7 +163,7 @@ function nameStats.reset(name, path, rootNode)
-- create new leaf and replace existing -- create new leaf and replace existing
theLeaf = nameStats.createLeaf() theLeaf = nameStats.createLeaf()
theEntry.data[path] = theLeaf theEntry.data[path] = theLeaf
rootNode[name] = theEntry
end end
-- --

View File

@ -1,5 +1,5 @@
cfxObjectSpawnZones = {} cfxObjectSpawnZones = {}
cfxObjectSpawnZones.version = "2.0.0" cfxObjectSpawnZones.version = "2.1.0"
cfxObjectSpawnZones.requiredLibs = { cfxObjectSpawnZones.requiredLibs = {
"dcsCommon", -- common is of course needed for everything "dcsCommon", -- common is of course needed for everything
-- pretty stupid to check for this since we -- pretty stupid to check for this since we
@ -14,6 +14,7 @@ cfxObjectSpawnZones.verbose = false
version history version history
2.0.0 - dmlZones 2.0.0 - dmlZones
2.1.0 - autoTurn attribute
--]]-- --]]--
@ -87,6 +88,8 @@ function cfxObjectSpawnZones.createSpawner(inZone)
theSpawner.autoLink = inZone:getBoolFromZoneProperty("autoLink", true) theSpawner.autoLink = inZone:getBoolFromZoneProperty("autoLink", true)
theSpawner.heading = inZone:getNumberFromZoneProperty("heading", 0) theSpawner.heading = inZone:getNumberFromZoneProperty("heading", 0)
-- note: currently expects rads. maybe change to degrees?
theSpawner.autoTurn = inZone:getBoolFromZoneProperty("autoTurn", false)
theSpawner.weight = inZone:getNumberFromZoneProperty("weight", 0) theSpawner.weight = inZone:getNumberFromZoneProperty("weight", 0)
if theSpawner.weight < 0 then theSpawner.weight = 0 end if theSpawner.weight < 0 then theSpawner.weight = 0 end
@ -265,8 +268,8 @@ function cfxObjectSpawnZones.spawnObjectNTimes(aSpawner, theType, n, container)
end end
local numObjects = n local numObjects = n
local degrees = 3.14157 / 180 local degrees = 3.14157 / 180 -- rads!
local degreeIncrement = (360 / numObjects) * degrees local degreeIncrement = (360 / numObjects) * degrees -- rads!
local currDegree = 0 local currDegree = 0
local missionObjects = {} local missionObjects = {}
for i=1, numObjects do for i=1, numObjects do
@ -274,11 +277,14 @@ function cfxObjectSpawnZones.spawnObjectNTimes(aSpawner, theType, n, container)
local ry = math.sin(currDegree) * aZone.radius local ry = math.sin(currDegree) * aZone.radius
local ox = center.x + rx local ox = center.x + rx
local oy = center.z + ry -- note: z! local oy = center.z + ry -- note: z!
local hdg = aSpawner.heading -- heading is in rads!
if aSpawner.autoTurn then
hdg = hdg + currDegree -- currDegree is in rads!
end
local theStaticData = dcsCommon.createStaticObjectData( local theStaticData = dcsCommon.createStaticObjectData(
aSpawner.baseName .. "-" .. aSpawner.count, aSpawner.baseName .. "-" .. aSpawner.count,
theType, theType,
aSpawner.heading, hdg ,--aSpawner.heading,
false, -- dead? false, -- dead?
aSpawner.isCargo, aSpawner.isCargo,
aSpawner.weight) aSpawner.weight)

View File

@ -1,5 +1,5 @@
cfxOwnedZones = {} cfxOwnedZones = {}
cfxOwnedZones.version = "2.1.0" cfxOwnedZones.version = "2.2.0"
cfxOwnedZones.verbose = false cfxOwnedZones.verbose = false
cfxOwnedZones.announcer = true cfxOwnedZones.announcer = true
cfxOwnedZones.name = "cfxOwnedZones" cfxOwnedZones.name = "cfxOwnedZones"
@ -29,6 +29,7 @@ cfxOwnedZones.name = "cfxOwnedZones"
- method support for individual owned zones - method support for individual owned zones
- method support for global (config) output - method support for global (config) output
- moved drawZone to cfxZones - moved drawZone to cfxZones
2.2.0 - excludedTypes option in config
--]]-- --]]--
cfxOwnedZones.requiredLibs = { cfxOwnedZones.requiredLibs = {
@ -317,13 +318,43 @@ function cfxOwnedZones.update()
-- we only check first unit that is alive -- we only check first unit that is alive
local theUnit = dcsCommon.getGroupUnit(aGroup) local theUnit = dcsCommon.getGroupUnit(aGroup)
if theUnit and (not theUnit:inAir()) and theZone:unitInZone(theUnit) then if theUnit and (not theUnit:inAir()) and theZone:unitInZone(theUnit) then
theZone.numRed = theZone.numRed + aGroup:getSize() if cfxOwnedZones.excludedTypes then
-- special carve-out for exclduding some
-- unit types to prevent them from capping
local uType = theUnit:getTypeName()
local forbidden = false
for idx, aType in pairs(cfxOwnedZones.excludedTypes) do
if uType == aType then
forbidden = true
else
end
end
if not forbidden then
theZone.numRed = theZone.numRed + aGroup:getSize()
end
else
theZone.numRed = theZone.numRed + aGroup:getSize()
end
end end
else else -- full eval
local allUnits = aGroup:getUnits() local allUnits = aGroup:getUnits()
for idy, theUnit in pairs(allUnits) do for idy, theUnit in pairs(allUnits) do
if (not theUnit:inAir()) and theZone:unitInZone(theUnit) then if (not theUnit:inAir()) and theZone:unitInZone(theUnit) then
theZone.numRed = theZone.numRed + 1 -- theZone.numRed = theZone.numRed + 1
if cfxOwnedZones.excludedTypes then
-- special carve-out for exclduding some
-- unit types to prevent them from capping
local uType = theUnit:getTypeName()
local forbidden = false
for idx, aType in pairs(cfxOwnedZones.excludedTypes) do
if uType == aType then forbidden = true end
end
if not forbidden then
theZone.numRed = theZone.numRed + aGroup:getSize()
end
else
theZone.numRed = theZone.numRed + aGroup:getSize()
end
end end
end end
end end
@ -337,13 +368,43 @@ function cfxOwnedZones.update()
-- we only check first unit that is alive -- we only check first unit that is alive
local theUnit = dcsCommon.getGroupUnit(aGroup) local theUnit = dcsCommon.getGroupUnit(aGroup)
if theUnit and (not theUnit:inAir()) and theZone:unitInZone(theUnit) then if theUnit and (not theUnit:inAir()) and theZone:unitInZone(theUnit) then
theZone.numBlue = theZone.numBlue + aGroup:getSize() if cfxOwnedZones.excludedTypes then
-- special carve-out for exclduding some
-- unit types to prevent them from capping
local uType = theUnit:getTypeName()
local forbidden = false
for idx, aType in pairs(cfxOwnedZones.excludedTypes) do
if uType == aType then
forbidden = true
else
end
end
if not forbidden then
theZone.numBlue = theZone.numBlue + aGroup:getSize()
end
else
theZone.numBlue = theZone.numBlue + aGroup:getSize()
end
end end
else else
local allUnits = aGroup:getUnits() local allUnits = aGroup:getUnits()
for idy, theUnit in pairs(allUnits) do for idy, theUnit in pairs(allUnits) do
if (not theUnit:inAir()) and theZone:unitInZone(theUnit) then if (not theUnit:inAir()) and theZone:unitInZone(theUnit) then
theZone.numBlue = theZone.numBlue + 1 -- theZone.numBlue = theZone.numBlue + 1
if cfxOwnedZones.excludedTypes then
-- special carve-out for exclduding some
-- unit types to prevent them from capping
local uType = theUnit:getTypeName()
local forbidden = false
for idx, aType in pairs(cfxOwnedZones.excludedTypes) do
if uType == aType then forbidden = true end
end
if not forbidden then
theZone.numBlue = theZone.numBlue + aGroup:getSize()
end
else
theZone.numBlue = theZone.numBlue + aGroup:getSize()
end
end end
end end
end end
@ -799,6 +860,13 @@ function cfxOwnedZones.readConfigZone(theZone)
cfxOwnedZones.neutralLine = theZone:getRGBAVectorFromZoneProperty("neutralLine", {0.8, 0.8, 0.8, 1.0}) cfxOwnedZones.neutralLine = theZone:getRGBAVectorFromZoneProperty("neutralLine", {0.8, 0.8, 0.8, 1.0})
cfxOwnedZones.neutralFill = theZone:getRGBAVectorFromZoneProperty("neutralFill", {0.8, 0.8, 0.8, 0.2}) cfxOwnedZones.neutralFill = theZone:getRGBAVectorFromZoneProperty("neutralFill", {0.8, 0.8, 0.8, 0.2})
if theZone:hasProperty("excludedTypes") then
local theTypes = theZone:getStringFromZoneProperty("excludedTypes", "none")
local typeArray = dcsCommon.splitString(theTypes, ",")
typeArray = dcsCommon.trimArray(typeArray)
cfxOwnedZones.excludedTypes = typeArray
end
cfxOwnedZones.method = theZone:getStringFromZoneProperty("method", "inc") cfxOwnedZones.method = theZone:getStringFromZoneProperty("method", "inc")
end end

View File

@ -1,5 +1,5 @@
persistence = {} persistence = {}
persistence.version = "2.0.0" persistence.version = "3.0.0"
persistence.ups = 1 -- once every 1 seconds persistence.ups = 1 -- once every 1 seconds
persistence.verbose = false persistence.verbose = false
persistence.active = false persistence.active = false
@ -17,6 +17,7 @@ persistence.requiredLibs = {
Version History Version History
2.0.0 - dml zones, OOP 2.0.0 - dml zones, OOP
cleanup cleanup
3.0.0 - shared data
PROVIDES LOAD/SAVE ABILITY TO MODULES PROVIDES LOAD/SAVE ABILITY TO MODULES
PROVIDES STANDALONE/HOSTED SERVER COMPATIBILITY PROVIDES STANDALONE/HOSTED SERVER COMPATIBILITY
@ -67,11 +68,34 @@ end
-- --
-- registered modules call this to get their data -- registered modules call this to get their data
-- --
function persistence.getSavedDataForModule(name) function persistence.getSavedDataForModule(name, sharedDataName)
if not persistence.active then return nil end if not persistence.active then return nil end
if not persistence.hasData then return nil end if not persistence.hasData then return nil end
if not persistence.missionData then return end if not persistence.missionData then return end
if not sharedDataName then sharedDataName = nil end
if sharedDataName then
-- we read from shared data and only revert to
-- common if we find nothing
local shFile = persistence.sharedDir .. sharedDataName .. ".txt"
if persistence.verbose then
trigger.action.outText("persistence: will try to load shared data from <" .. shFile .. ">", 30)
end
local theData = persistence.loadTable(shFile, true)
if theData then
if theData[name] then
return theData[name]
end
if persistence.verbose then
trigger.action.outText("persistence: shared data file <" .. sharedDataName .. "> exists but currently holds no data for <" .. name .. ">, reverting to main", 30)
end
else
if persistence.verbose then
trigger.action.outText("persistence: shared data file <" .. sharedDataName
.. "> does not yet exist, reverting to main", 30)
end
end
end
return persistence.missionData[name] -- simply get the modules data block return persistence.missionData[name] -- simply get the modules data block
end end
@ -162,9 +186,8 @@ function persistence.saveTable(theTable, fileName, shared, append)
local path = persistence.missionDir .. fileName local path = persistence.missionDir .. fileName
if shared then if shared then
-- we would now change the path -- we change the path to shared
trigger.action.outText("+++persistence: NYI: shared", 30) path = persistence.sharedDir .. fileName .. ".txt"
return
end end
local theFile = nil local theFile = nil
@ -186,11 +209,21 @@ function persistence.saveTable(theTable, fileName, shared, append)
return true return true
end end
function persistence.loadText(fileName) -- load file as text function persistence.loadText(fileName, hasPath) -- load file as text
if not persistence.active then return nil end if not persistence.active then return nil end
if not fileName then return nil end if not fileName then return nil end
local path = persistence.missionDir .. fileName local path
if hasPath then
path = fileName
else
path = persistence.missionDir .. fileName
end
if persistence.verbose then
trigger.action.outText("persistence: will load text file <" .. path .. ">", 30)
end
local theFile = io.open(path, "r") local theFile = io.open(path, "r")
if not theFile then return nil end if not theFile then return nil end
@ -201,11 +234,12 @@ function persistence.loadText(fileName) -- load file as text
return t return t
end end
function persistence.loadTable(fileName) -- load file as table function persistence.loadTable(fileName, hasPath) -- load file as table
if not persistence.active then return nil end if not persistence.active then return nil end
if not fileName then return nil end if not fileName then return nil end
if not hasPath then hasPath = false end
local t = persistence.loadText(fileName) local t = persistence.loadText(fileName, hasPath)
if not t then return nil end if not t then return nil end
@ -241,6 +275,12 @@ function persistence.initFlagsFromData(theFlags)
end end
function persistence.freshStart()
persistence.missionData = {}
persistence.hasData = true
trigger.action.setUserFlag("cfxPersistenceHasData", 1)
end
function persistence.missionStartDataLoad() function persistence.missionStartDataLoad()
-- check one: see if we have mission data -- check one: see if we have mission data
local theData = persistence.loadTable(persistence.saveFileName) local theData = persistence.loadTable(persistence.saveFileName)
@ -249,6 +289,7 @@ function persistence.missionStartDataLoad()
if persistence.verbose then if persistence.verbose then
trigger.action.outText("+++persistence: no saved data, fresh start.", 30) trigger.action.outText("+++persistence: no saved data, fresh start.", 30)
end end
persistence.freshStart()
return return
end -- there was no data to load end -- there was no data to load
@ -256,6 +297,7 @@ function persistence.missionStartDataLoad()
if persistence.verbose then if persistence.verbose then
trigger.action.outText("+++persistence: detected fresh start.", 30) trigger.action.outText("+++persistence: detected fresh start.", 30)
end end
persistence.freshStart()
return return
end end
@ -308,9 +350,14 @@ function persistence.collectFlagData()
return flagData return flagData
end end
function persistence.saveSharedData()
trigger.action.outText("WARNING: Persistence's saveSharedData invoked!", 30)
end
function persistence.saveMissionData() function persistence.saveMissionData()
local myData = {} local myData = {}
local allSharedData = {} -- organized by 'shared' name returned
-- first, handle versionID and freshMaker -- first, handle versionID and freshMaker
if persistence.freshMaker then if persistence.freshMaker then
myData["freshMaker"] = true myData["freshMaker"] = true
@ -325,8 +372,15 @@ function persistence.saveMissionData()
-- now handle all other modules -- now handle all other modules
for moduleName, callbacks in pairs(persistence.callbacks) do for moduleName, callbacks in pairs(persistence.callbacks) do
local moduleData = callbacks.persistData() local moduleData, sharedName = callbacks.persistData()
if moduleData then if moduleData then
if sharedName then -- save into shared bucket
-- allshared[specificShared[moduleName]]
local specificShared = allSharedData[sharedName]
if not specificShared then specificShared = {} end
specificShared[moduleName] = moduleData
allSharedData[sharedName] = specificShared -- write back
end
myData[moduleName] = moduleData myData[moduleName] = moduleData
if persistence.verbose then if persistence.verbose then
trigger.action.outText("+++persistence: gathered data from <" .. moduleName .. ">", 30) trigger.action.outText("+++persistence: gathered data from <" .. moduleName .. ">", 30)
@ -340,6 +394,23 @@ function persistence.saveMissionData()
-- now save data to file -- now save data to file
persistence.saveTable(myData, persistence.saveFileName) persistence.saveTable(myData, persistence.saveFileName)
-- now save all shared name data as separate files
for shareName, data in pairs (allSharedData) do
-- save into shared folder, by name that was returned from callback
-- read what was saved, and replace changed key/values from data
local shFile = persistence.sharedDir .. shareName .. ".txt"
local theData = persistence.loadTable(shFile, true) -- hasPath
if theData then
for k, v in pairs(data) do
theData[k] = v
end
else
theData = data
end
persistence.saveTable(theData, shareName, true) -- true --> shared
end
end end
-- --
@ -433,6 +504,7 @@ function persistence.readConfigZone()
end end
persistence.serverDir = theZone:getStringFromZoneProperty("serverDir", "Missions\\") persistence.serverDir = theZone:getStringFromZoneProperty("serverDir", "Missions\\")
persistence.sharedDir = "DML-Shared-Data\\" -- hard-wired!
if hasConfig then if hasConfig then
if theZone:hasProperty("saveDir") then if theZone:hasProperty("saveDir") then
@ -513,10 +585,12 @@ function persistence.start()
return false return false
end end
local mainDir = persistence.root .. persistence.serverDir local mainDir = persistence.root .. persistence.serverDir -- usually DCS/Missions
if not dcsCommon.stringEndsWith(mainDir, "\\") then if not dcsCommon.stringEndsWith(mainDir, "\\") then
mainDir = mainDir .. "\\" mainDir = mainDir .. "\\"
end end
local sharedDir = mainDir .. persistence.sharedDir -- ends on \\, hardwired
persistence.sharedDir = sharedDir
-- lets see if we can access the server's mission directory and -- lets see if we can access the server's mission directory and
-- save directory -- save directory
@ -531,11 +605,11 @@ function persistence.start()
return false return false
end end
persistence.mainDir = mainDir persistence.mainDir = mainDir
local missionDir = mainDir .. persistence.saveDir local missionDir = mainDir .. persistence.saveDir
if not dcsCommon.stringEndsWith(missionDir, "\\") then if not dcsCommon.stringEndsWith(missionDir, "\\") then
missionDir = missionDir .. "\\" missionDir = missionDir .. "\\"
end end
-- check if mission dir exists already -- check if mission dir exists already
local success, mode = persistence.hasFile(missionDir) local success, mode = persistence.hasFile(missionDir)
@ -565,6 +639,35 @@ function persistence.start()
trigger.action.outText("+++persistence: created <" .. missionDir .. "> successfully, will save mission data here", 30) trigger.action.outText("+++persistence: created <" .. missionDir .. "> successfully, will save mission data here", 30)
end end
end end
-- make sure that SHARED dir exists, create if not
local success, mode = persistence.hasFile(sharedDir)
if success and mode == "directory" then
-- has been allocated, and is dir
if persistence.verbose then
trigger.action.outText("+++persistence: saving SHARED data to <" .. sharedDir .. ">", 30)
end
elseif success then
if persistence.verbose then
trigger.action.outText("+++persistence: <" .. sharedDir .. "> is not a directory", 30)
end
return false
else
-- does not exist, try to allocate it
if persistence.verbose then
trigger.action.outText("+++persistence: will now create <" .. sharedDir .. ">", 30)
end
local ok, mkErr = lfs.mkdir(sharedDir)
if not ok then
if persistence.verbose then
trigger.action.outText("+++persistence: unable to create <" .. sharedDir .. ">: <" .. mkErr .. ">", 30)
end
return false
end
if persistence.verbose then
trigger.action.outText("+++persistence: created <" .. sharedDir .. "> successfully, will save SHARED data here", 30)
end
end
-- missionDir is root + serverDir + saveDir -- missionDir is root + serverDir + saveDir
persistence.missionDir = missionDir persistence.missionDir = missionDir

View File

@ -1,5 +1,5 @@
scribe = {} scribe = {}
scribe.version = "1.0.1" scribe.version = "1.1.0"
scribe.requiredLibs = { scribe.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
"cfxZones", -- Zones, of course "cfxZones", -- Zones, of course
@ -11,6 +11,7 @@ Player statistics package
VERSION HISTORY VERSION HISTORY
1.0.0 Initial Version 1.0.0 Initial Version
1.0.1 postponed land, postponed takeoff, unit_lost 1.0.1 postponed land, postponed takeoff, unit_lost
1.1.0 supports persistence's SHARED ability to share data across missions
--]]-- --]]--
scribe.verbose = true scribe.verbose = true
scribe.db = {} -- indexed by player name scribe.db = {} -- indexed by player name
@ -538,6 +539,11 @@ function scribe.readConfigZone()
scribe.lTime = theZone:getStringFromZoneProperty("lTime", "time:") scribe.lTime = theZone:getStringFromZoneProperty("lTime", "time:")
scribe.landingCD = theZone:getNumberFromZoneProperty("landingCD", 60) -- seconds between stake-off, landings, or either scribe.landingCD = theZone:getNumberFromZoneProperty("landingCD", 60) -- seconds between stake-off, landings, or either
-- shared data persistence interface
if theZone:hasProperty("sharedData") then
scribe.sharedData = theZone:getStringFromZoneProperty("sharedData", "cfxNameMissing")
end
end end
-- --
@ -555,12 +561,13 @@ function scribe.saveData()
local theLog = dcsCommon.clone(scribe.db) local theLog = dcsCommon.clone(scribe.db)
theData.theLog = theLog theData.theLog = theLog
return theData return theData, scribe.sharedData -- second val only if shared
end end
function scribe.loadData() function scribe.loadData()
if not persistence then return end if not persistence then return end
local theData = persistence.getSavedDataForModule("scribe") local theData = persistence.getSavedDataForModule("scribe", scribe.sharedData)
if not theData then if not theData then
if scribe.verbose then if scribe.verbose then
trigger.action.outText("+++scb: no save date received, skipping.", 30) trigger.action.outText("+++scb: no save date received, skipping.", 30)

View File

@ -1,5 +1,5 @@
stopGap = {} stopGap = {}
stopGap.version = "1.0.9 STANDALONE" stopGap.version = "1.1.0 STANDALONE"
stopGap.verbose = false stopGap.verbose = false
stopGap.ssbEnabled = true stopGap.ssbEnabled = true
stopGap.ignoreMe = "-sg" stopGap.ignoreMe = "-sg"
@ -7,6 +7,7 @@ stopGap.spIgnore = "-sp" -- only single-player ignored
stopGap.isMP = false stopGap.isMP = false
stopGap.running = true stopGap.running = true
stopGap.refreshInterval = -1 -- seconds to refresh all statics. -1 = never, 3600 = once every hour stopGap.refreshInterval = -1 -- seconds to refresh all statics. -1 = never, 3600 = once every hour
stopGap.kickTheDead = true -- kick players to spectators on death to prevent re-entry issues
--[[-- --[[--
Written and (c) 2023 by Christian Franz Written and (c) 2023 by Christian Franz
@ -34,6 +35,8 @@ stopGap.refreshInterval = -1 -- seconds to refresh all statics. -1 = never, 3600
1.0.7 - (DML-only internal cool stuff) 1.0.7 - (DML-only internal cool stuff)
1.0.8 - added refreshInterval option as requested 1.0.8 - added refreshInterval option as requested
1.0.9 - optimization when turning on stopgap 1.0.9 - optimization when turning on stopgap
1.1.0 - kickTheDead option
--]]-- --]]--
stopGap.standInGroups ={} stopGap.standInGroups ={}
@ -253,11 +256,11 @@ function stopGap:onEvent(event)
if not event.id then return end if not event.id then return end
if not event.initiator then return end if not event.initiator then return end
local theUnit = event.initiator local theUnit = event.initiator
if (not theUnit.getPlayerName) or (not theUnit:getPlayerName()) then
if event.id == 15 then return
if (not theUnit.getPlayerName) or (not theUnit:getPlayerName()) then end -- no player unit.
return local id = event.id
end -- no player unit. if id == 15 then
local uName = theUnit:getName() local uName = theUnit:getName()
local theGroup = theUnit:getGroup() local theGroup = theUnit:getGroup()
local gName = theGroup:getName() local gName = theGroup:getName()
@ -269,13 +272,31 @@ function stopGap:onEvent(event)
stopGap.removeStaticGapGroupNamed(gName) stopGap.removeStaticGapGroupNamed(gName)
end end
end end
-- erase stopGapGUI flag, no longer required, unit -- erase stopGapGUI flag, no longer required, unit
-- is now slotted into -- is now slotted into
trigger.action.setUserFlag("SG"..gName, 0) trigger.action.setUserFlag("SG"..gName, 0)
end end
if (id == 9) or (id == 30) or (id == 5) then -- dead, lost, crash
local pName = theUnit:getPlayerName()
timer.scheduleFunction(stopGap.kickplayer, pName, timer.getTime() + 1)
end
end end
stopGap.kicks = {}
function stopGap.kickplayer(args)
if not stopGap.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 stopGap.kicks[nn] then
if timer.getTime() < stopGap.kicks[nn] then return end
end
net.force_player_slot(slot, 0, '')
stopGap.kicks[nn] = timer.getTime() + 5 -- avoid too many kicks in 5 seconds
end
end
end
-- --
-- update -- update
-- --

View File

@ -1,5 +1,5 @@
stopGap = {} stopGap = {}
stopGap.version = "1.0.10" stopGap.version = "1.1.0"
stopGap.verbose = false stopGap.verbose = false
stopGap.ssbEnabled = true stopGap.ssbEnabled = true
stopGap.ignoreMe = "-sg" stopGap.ignoreMe = "-sg"
@ -7,7 +7,7 @@ stopGap.spIgnore = "-sp" -- only single-player ignored
stopGap.isMP = false stopGap.isMP = false
stopGap.running = true stopGap.running = true
stopGap.refreshInterval = -1 -- seconds to refresh all statics. -1 = never, 3600 = once every hour stopGap.refreshInterval = -1 -- seconds to refresh all statics. -1 = never, 3600 = once every hour
stopGap.kickTheDead = true -- kick players to spectators on death to prevent re-entry issues
stopGap.requiredLibs = { stopGap.requiredLibs = {
"dcsCommon", "dcsCommon",
@ -49,6 +49,7 @@ stopGap.requiredLibs = {
- refresh attribute config zone - refresh attribute config zone
1.0.9 - in line with standalone (optimization not required for DML) 1.0.9 - in line with standalone (optimization not required for DML)
1.0.10 - some more verbosity for spIgnore and sgIgnore zones (DML only) 1.0.10 - some more verbosity for spIgnore and sgIgnore zones (DML only)
1.1.0 - kickTheDead option
--]]-- --]]--
@ -233,11 +234,11 @@ function stopGap:onEvent(event)
if not event.id then return end if not event.id then return end
if not event.initiator then return end if not event.initiator then return end
local theUnit = event.initiator local theUnit = event.initiator
if (not theUnit.getPlayerName) or (not theUnit:getPlayerName()) then
if event.id == 15 then return
if (not theUnit.getPlayerName) or (not theUnit:getPlayerName()) then end -- no player unit.
return local id = event.id
end -- no player unit. if id == 15 then
local uName = theUnit:getName() local uName = theUnit:getName()
local theGroup = theUnit:getGroup() local theGroup = theUnit:getGroup()
local gName = theGroup:getName() local gName = theGroup:getName()
@ -248,12 +249,32 @@ function stopGap:onEvent(event)
if stopGap.standInGroups[gName] then if stopGap.standInGroups[gName] then
stopGap.removeStaticGapGroupNamed(gName) stopGap.removeStaticGapGroupNamed(gName)
end end
end end
-- erase stopGapGUI flag, no longer required, unit -- erase stopGapGUI flag, no longer required, unit
-- is now slotted into -- is now slotted into
trigger.action.setUserFlag("SG"..gName, 0) trigger.action.setUserFlag("SG"..gName, 0)
end end
if (id == 9) or (id == 30) or (id == 5) then -- dead, lost, crash
local pName = theUnit:getPlayerName()
timer.scheduleFunction(stopGap.kickplayer, pName, timer.getTime() + 1)
end
end
stopGap.kicks = {}
function stopGap.kickplayer(args)
if not stopGap.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 stopGap.kicks[nn] then
if timer.getTime() < stopGap.kicks[nn] then return end
end
net.force_player_slot(slot, 0, '')
stopGap.kicks[nn] = timer.getTime() + 5 -- avoid too many kicks in 5 seconds
end
end
end end
-- --
@ -403,6 +424,7 @@ function stopGap.readConfigZone(theZone)
end end
stopGap.refreshInterval = theZone:getNumberFromZoneProperty("refresh", -1) -- default: no refresh stopGap.refreshInterval = theZone:getNumberFromZoneProperty("refresh", -1) -- default: no refresh
stopGap.kickTheDead = theZone:getBoolFromZoneProperty("kickDead", true)
end end
-- --

Binary file not shown.

Binary file not shown.