diff --git a/Doc/DML Documentation.pdf b/Doc/DML Documentation.pdf index 0869ef0..c14558c 100644 Binary files a/Doc/DML Documentation.pdf and b/Doc/DML Documentation.pdf differ diff --git a/Doc/DML Quick Reference.pdf b/Doc/DML Quick Reference.pdf index 6ef3d07..c9c4eca 100644 Binary files a/Doc/DML Quick Reference.pdf and b/Doc/DML Quick Reference.pdf differ diff --git a/modules/bombRange.lua b/modules/bombRange.lua index 504a5ac..90be393 100644 --- a/modules/bombRange.lua +++ b/modules/bombRange.lua @@ -1,5 +1,5 @@ bombRange = {} -bombRange.version = "1.1.0" +bombRange.version = "1.1.1" bombRange.dh = 1 -- meters above ground level burst bombRange.requiredLibs = { diff --git a/modules/cargoSuper.lua b/modules/cargoSuper.lua index a3eec5c..8c97f09 100644 --- a/modules/cargoSuper.lua +++ b/modules/cargoSuper.lua @@ -1,5 +1,5 @@ cargoSuper = {} -cargoSuper.version = "1.1.1" +cargoSuper.version = "1.1.2" --[[-- version history 1.0.0 - initial version @@ -14,7 +14,9 @@ version history - getManifestForCategory alias for getManifestFor - removeAllMassFor() 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 to arbitrary categories like 'passengers', 'cargo' or "whatever". In order to add weight to a unit, first create a massObject through createMassObject @@ -102,14 +104,14 @@ function cargoSuper.removeMassObjectFrom(name, category, theMassObject, forget) end -- 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 nameStats.reset(name, category, cargoSuper.cargos) end -- alias for removeAllMassForCargo -function cargoSuper.removeAllMassForCategory(name, catergory) - cargoSuper.removeAllMassForCargo(name, catergory) +function cargoSuper.removeAllMassForCategory(name, category) + cargoSuper.removeAllMassForCargo(name, category) end function cargoSuper.removeAllMassFor(name) diff --git a/modules/csarManager2.lua b/modules/csarManager2.lua index 52b5d8f..401c9aa 100644 --- a/modules/csarManager2.lua +++ b/modules/csarManager2.lua @@ -1,5 +1,5 @@ csarManager = {} -csarManager.version = "3.2.0" +csarManager.version = "3.2.2" csarManager.ups = 1 --[[-- VERSION HISTORY @@ -32,6 +32,12 @@ csarManager.ups = 1 - 3.2.0 - inPopulated csar option - clearance 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 WITH LIMITED AIRFRAMES @@ -102,8 +108,11 @@ function csarManager.createDownedPilot(theMission, existingUnit) aHeading = math.random(360)/360 * 2 * 3.1415 end 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, - "Soldier M4 GRG", -- "Soldier M4 GRG", + rType, -- "Soldier M4 GRG", aLocation.x, aLocation.z, -aHeading + 1.5) -- + 1.5 to turn inwards @@ -181,7 +190,6 @@ function csarManager.createCSARMissionData(point, theSide, freq, name, numCrew, -- set timeLimit if enabled if timeLimit then 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 end @@ -192,7 +200,6 @@ function csarManager.createCSARMissionData(point, theSide, freq, name, numCrew, end function csarManager.addMission(theMission) --- trigger.action.outText("enter addMission", 30) table.insert(csarManager.openMissions, theMission) csarManager.invokeNewMissionCallbacks(theMission) end @@ -318,9 +325,16 @@ function csarManager:onEvent(event) -- we also need to make sure that there are no -- more troopsOnBoard + local myName = theUnit:getName() local conf = csarManager.getUnitConfig(theUnit) conf.unit = theUnit 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 @@ -448,11 +462,12 @@ function csarManager.heloLanded(theUnit) "Evacuees", theMassObject) msn = theMassObject.ref - end + -- reset weight local totalMass = cargoSuper.calculateTotalMassFor(myName) trigger.action.setUnitInternalCargo(myName, totalMass) -- super recalcs + conf.troopsOnBoard = {} -- empty out troops on board -- we do *not* return so we can pick up troops on -- a CSARBASE if they were dropped there @@ -523,6 +538,7 @@ function csarManager.heloLanded(theUnit) -- reset unit's weight based on people on board local totalMass = cargoSuper.calculateTotalMassFor(myName) trigger.action.setUnitInternalCargo(myName, totalMass) -- 10 kg as empty + per-unit time people + end @@ -733,6 +749,8 @@ function csarManager.doListCSARRequests(args) local conf = args[1] local param = args[2] local theUnit = conf.unit + if not theUnit then return end -- ?? + if not Unit.isExist(theUnit) then return end local point = theUnit:getPoint() local theSide = theUnit:getCoalition() @@ -794,6 +812,8 @@ function csarManager.doStatusCarrying(args) local conf = args[1] local param = args[2] local theUnit = conf.unit + if not theUnit then return end -- ?? + if not Unit.isExist(theUnit) then return end local now = timer.getTime() -- build status report @@ -837,6 +857,9 @@ function csarManager.unloadOne(args) local conf = args[1] local param = args[2] local theUnit = conf.unit + if not theUnit then return end -- ?? + if not Unit.isExist(theUnit) then return end + local myName = theUnit:getName() local report = "NYI: unload one" @@ -869,7 +892,9 @@ function csarManager.unloadOne(args) trigger.action.outSoundForCoalition(theSide, csarManager.actionSound) -- "Quest Snare 3.wav") -- 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 @@ -882,6 +907,8 @@ function csarManager.directions(args) local conf = args[1] local param = args[2] local theUnit = conf.unit + if not theUnit then return end -- ?? + if not Unit.isExist(theUnit) then return end local myName = theUnit:getName() local theSide = theUnit:getCoalition() local report = "Nothing to report." @@ -997,12 +1024,23 @@ function csarManager.updateCSARMissions() if stillRunning and stillAlive then table.insert(newMissions, aMission) 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." - trigger.action.outTextForCoalition(aMission.side, msg, 30) - trigger.action.outSoundForCoalition(aMission.side, csarManager.lostSound) + for idx, theUnit in pairs (thePlayers) do + 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) if aMission.group and Group.isExist(aMission.group) then --- trigger.action.outText("removing group", 30) Group.destroy(aMission.group) end else @@ -1150,18 +1188,17 @@ function csarManager.update() -- every second theMassObject) local totalMass = cargoSuper.calculateTotalMassFor(uName) trigger.action.setUnitInternalCargo(uName, totalMass) - + if csarManager.verbose then 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) end - --trigger.action.outTextForGroup(uID, hoverMsg, 30, true) trigger.action.outSoundForGroup(uID, csarManager.pickupSound) --return -- we only ever rescue one end -- hovered long enough - --trigger.action.outTextForGroup(uID, hoverMsg, 30, true) + -- return -- only ever one winch op 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" @@ -1328,13 +1365,11 @@ function csarManager.readCSARZone(theZone) theZone.csarMapMarker = nil if theZone:hasProperty("timeLimit") then local tmin, tmax = theZone:getPositiveRangeFromZoneProperty("timeLimit", 1) --- trigger.action.outText("Read time limit for <" .. theZone.name .. ">: <" .. tmin .. ">, <" .. tmax .. ">", 30) + theZone.timeLimit = {tmin, tmax} else theZone.timeLimit = nil - end --- theZone.timeLimit = theZone:getNumberFromZoneProperty("timeLimit", 0) --- if theZone.timeLimit == 0 then theZone.timeLimit = nil else theZone.timeLimit = timeLimit * 60 end + end local deferred = theZone:getBoolFromZoneProperty("deferred", false) @@ -1389,7 +1424,6 @@ function csarManager.readCSARZone(theZone) -- add to list of startable csar if theZone.startCSAR then csarManager.addCSARZone(theZone) --- trigger.action.outText("csar: added <".. theZone.name .."> to deferred csar missions", 30) end if deferred and not theZone.startCSAR then @@ -1495,6 +1529,7 @@ function csarManager.readConfigZone() csarManager.pickupSound = theZone:getStringFromZoneProperty("pickupSound", csarManager.actionSound) csarManager.vectoring = theZone:getBoolFromZoneProperty("vectoring", true) csarManager.lostSound = theZone:getStringFromZoneProperty("lostSound", csarManager.actionSound) + csarManager.comsRange = theZone:getNumberFromZoneProperty("comsRange", 40000) -- add own troop carriers if theZone:hasProperty("troopCarriers") then @@ -1512,6 +1547,13 @@ function csarManager.readConfigZone() csarManager.addPrefix = theZone:getBoolFromZoneProperty("addPrefix", true) 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 trigger.action.outText("+++csar: read config", 30) end @@ -1535,8 +1577,6 @@ function csarManager.start() -- and populate the available mission. csarManager.processCSARZones() - -- install callbacks for helo-relevant events - --dcsCommon.addEventHandler(csarManager.somethingHappened, csarManager.preProcessor, csarManager.postProcessor) world.addEventHandler(csarManager) -- now iterate through all player groups and install the CSAR Menu diff --git a/modules/factoryZone.lua b/modules/factoryZone.lua index 0f67610..4e3ea75 100644 --- a/modules/factoryZone.lua +++ b/modules/factoryZone.lua @@ -17,6 +17,7 @@ factoryZone.name = "factoryZone" - productionTime config synonyme - defendMe? attribute - triggered 'shocked' mode via defendMe +3.1.1 - fixed a big with persistence --]]-- factoryZone.requiredLibs = { @@ -734,7 +735,7 @@ function factoryZone.loadData() local allZoneData = theData.zoneData for zName, zData in pairs(allZoneData) do -- access zone - local theZone = factoryZone.getOwnedZoneByName(zName) + local theZone = factoryZone.getFactoryZoneByName(zName)-- was: factoryZone.getOwnedZoneByName(zName) if theZone then if zData.defenderData then if theZone.defenders and theZone.defenders:isExist() then diff --git a/modules/nameStats.lua b/modules/nameStats.lua index 5bd8679..3dabc0b 100644 --- a/modules/nameStats.lua +++ b/modules/nameStats.lua @@ -93,16 +93,13 @@ function nameStats.addString(name, aString, path, rootNode) if not name then return nil end if not aString then return nil end local theLeaf = nameStats.getLeaf(name, path, rootNode) - --table.insert(theLeaf.strings, aString) theLeaf.strings = theLeaf.strings .. aString --- return aString end -- reset the log function nameStats.removeAllString(name, path, rootNode) if not name then return nil end local theLeaf = nameStats.getLeaf(name, path, rootNode) --- theLeaf.strings = {} theLeaf.strings = "" end @@ -151,7 +148,7 @@ function nameStats.reset(name, path, rootNode) if not name then return nil end if not rootNode then rootNode = nameStats.stats end local theEntry = rootNode[name] - if not theEntry then + if not theEntry then -- does not yet exist, create a root entry theEntry = nameStats.createRoot(name) rootNode[name] = theEntry @@ -166,7 +163,7 @@ function nameStats.reset(name, path, rootNode) -- create new leaf and replace existing theLeaf = nameStats.createLeaf() theEntry.data[path] = theLeaf - + rootNode[name] = theEntry end -- diff --git a/modules/objectSpawnZones.lua b/modules/objectSpawnZones.lua index 7024db1..9197009 100644 --- a/modules/objectSpawnZones.lua +++ b/modules/objectSpawnZones.lua @@ -1,5 +1,5 @@ cfxObjectSpawnZones = {} -cfxObjectSpawnZones.version = "2.0.0" +cfxObjectSpawnZones.version = "2.1.0" cfxObjectSpawnZones.requiredLibs = { "dcsCommon", -- common is of course needed for everything -- pretty stupid to check for this since we @@ -14,6 +14,7 @@ cfxObjectSpawnZones.verbose = false version history 2.0.0 - dmlZones + 2.1.0 - autoTurn attribute --]]-- @@ -87,6 +88,8 @@ function cfxObjectSpawnZones.createSpawner(inZone) theSpawner.autoLink = inZone:getBoolFromZoneProperty("autoLink", true) 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) if theSpawner.weight < 0 then theSpawner.weight = 0 end @@ -265,8 +268,8 @@ function cfxObjectSpawnZones.spawnObjectNTimes(aSpawner, theType, n, container) end local numObjects = n - local degrees = 3.14157 / 180 - local degreeIncrement = (360 / numObjects) * degrees + local degrees = 3.14157 / 180 -- rads! + local degreeIncrement = (360 / numObjects) * degrees -- rads! local currDegree = 0 local missionObjects = {} for i=1, numObjects do @@ -274,11 +277,14 @@ function cfxObjectSpawnZones.spawnObjectNTimes(aSpawner, theType, n, container) local ry = math.sin(currDegree) * aZone.radius local ox = center.x + rx 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( aSpawner.baseName .. "-" .. aSpawner.count, theType, - aSpawner.heading, + hdg ,--aSpawner.heading, false, -- dead? aSpawner.isCargo, aSpawner.weight) diff --git a/modules/ownedZones.lua b/modules/ownedZones.lua index 4cabbc4..252a1c5 100644 --- a/modules/ownedZones.lua +++ b/modules/ownedZones.lua @@ -1,5 +1,5 @@ cfxOwnedZones = {} -cfxOwnedZones.version = "2.1.0" +cfxOwnedZones.version = "2.2.0" cfxOwnedZones.verbose = false cfxOwnedZones.announcer = true cfxOwnedZones.name = "cfxOwnedZones" @@ -29,6 +29,7 @@ cfxOwnedZones.name = "cfxOwnedZones" - method support for individual owned zones - method support for global (config) output - moved drawZone to cfxZones +2.2.0 - excludedTypes option in config --]]-- cfxOwnedZones.requiredLibs = { @@ -317,13 +318,43 @@ function cfxOwnedZones.update() -- we only check first unit that is alive local theUnit = dcsCommon.getGroupUnit(aGroup) 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 - else + else -- full eval local allUnits = aGroup:getUnits() for idy, theUnit in pairs(allUnits) do 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 @@ -337,13 +368,43 @@ function cfxOwnedZones.update() -- we only check first unit that is alive local theUnit = dcsCommon.getGroupUnit(aGroup) 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 else local allUnits = aGroup:getUnits() for idy, theUnit in pairs(allUnits) do 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 @@ -799,6 +860,13 @@ function cfxOwnedZones.readConfigZone(theZone) 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}) + 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") end diff --git a/modules/persistence.lua b/modules/persistence.lua index 600118a..5668cd9 100644 --- a/modules/persistence.lua +++ b/modules/persistence.lua @@ -1,5 +1,5 @@ persistence = {} -persistence.version = "2.0.0" +persistence.version = "3.0.0" persistence.ups = 1 -- once every 1 seconds persistence.verbose = false persistence.active = false @@ -17,6 +17,7 @@ persistence.requiredLibs = { Version History 2.0.0 - dml zones, OOP cleanup + 3.0.0 - shared data PROVIDES LOAD/SAVE ABILITY TO MODULES PROVIDES STANDALONE/HOSTED SERVER COMPATIBILITY @@ -67,11 +68,34 @@ end -- -- 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.hasData then return nil 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 end @@ -162,9 +186,8 @@ function persistence.saveTable(theTable, fileName, shared, append) local path = persistence.missionDir .. fileName if shared then - -- we would now change the path - trigger.action.outText("+++persistence: NYI: shared", 30) - return + -- we change the path to shared + path = persistence.sharedDir .. fileName .. ".txt" end local theFile = nil @@ -186,11 +209,21 @@ function persistence.saveTable(theTable, fileName, shared, append) return true 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 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") if not theFile then return nil end @@ -201,11 +234,12 @@ function persistence.loadText(fileName) -- load file as text return t 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 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 @@ -241,6 +275,12 @@ function persistence.initFlagsFromData(theFlags) end +function persistence.freshStart() + persistence.missionData = {} + persistence.hasData = true + trigger.action.setUserFlag("cfxPersistenceHasData", 1) +end + function persistence.missionStartDataLoad() -- check one: see if we have mission data local theData = persistence.loadTable(persistence.saveFileName) @@ -249,6 +289,7 @@ function persistence.missionStartDataLoad() if persistence.verbose then trigger.action.outText("+++persistence: no saved data, fresh start.", 30) end + persistence.freshStart() return end -- there was no data to load @@ -256,6 +297,7 @@ function persistence.missionStartDataLoad() if persistence.verbose then trigger.action.outText("+++persistence: detected fresh start.", 30) end + persistence.freshStart() return end @@ -308,9 +350,14 @@ function persistence.collectFlagData() return flagData end +function persistence.saveSharedData() + trigger.action.outText("WARNING: Persistence's saveSharedData invoked!", 30) +end + function persistence.saveMissionData() local myData = {} - + local allSharedData = {} -- organized by 'shared' name returned + -- first, handle versionID and freshMaker if persistence.freshMaker then myData["freshMaker"] = true @@ -325,8 +372,15 @@ function persistence.saveMissionData() -- now handle all other modules for moduleName, callbacks in pairs(persistence.callbacks) do - local moduleData = callbacks.persistData() + local moduleData, sharedName = callbacks.persistData() 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 if persistence.verbose then trigger.action.outText("+++persistence: gathered data from <" .. moduleName .. ">", 30) @@ -340,6 +394,23 @@ function persistence.saveMissionData() -- now save data to file 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 -- @@ -433,6 +504,7 @@ function persistence.readConfigZone() end persistence.serverDir = theZone:getStringFromZoneProperty("serverDir", "Missions\\") + persistence.sharedDir = "DML-Shared-Data\\" -- hard-wired! if hasConfig then if theZone:hasProperty("saveDir") then @@ -513,10 +585,12 @@ function persistence.start() return false end - local mainDir = persistence.root .. persistence.serverDir + local mainDir = persistence.root .. persistence.serverDir -- usually DCS/Missions if not dcsCommon.stringEndsWith(mainDir, "\\") then mainDir = mainDir .. "\\" end + local sharedDir = mainDir .. persistence.sharedDir -- ends on \\, hardwired + persistence.sharedDir = sharedDir -- lets see if we can access the server's mission directory and -- save directory @@ -531,11 +605,11 @@ function persistence.start() return false end persistence.mainDir = mainDir - local missionDir = mainDir .. persistence.saveDir if not dcsCommon.stringEndsWith(missionDir, "\\") then missionDir = missionDir .. "\\" end + -- check if mission dir exists already 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) 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 persistence.missionDir = missionDir diff --git a/modules/scribe.lua b/modules/scribe.lua index f5b8888..8ae1bfb 100644 --- a/modules/scribe.lua +++ b/modules/scribe.lua @@ -1,5 +1,5 @@ scribe = {} -scribe.version = "1.0.1" +scribe.version = "1.1.0" scribe.requiredLibs = { "dcsCommon", -- always "cfxZones", -- Zones, of course @@ -11,6 +11,7 @@ Player statistics package VERSION HISTORY 1.0.0 Initial Version 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.db = {} -- indexed by player name @@ -538,6 +539,11 @@ function scribe.readConfigZone() scribe.lTime = theZone:getStringFromZoneProperty("lTime", "time:") 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 -- @@ -555,12 +561,13 @@ function scribe.saveData() local theLog = dcsCommon.clone(scribe.db) theData.theLog = theLog - return theData + return theData, scribe.sharedData -- second val only if shared end function scribe.loadData() if not persistence then return end - local theData = persistence.getSavedDataForModule("scribe") + local theData = persistence.getSavedDataForModule("scribe", scribe.sharedData) + if not theData then if scribe.verbose then trigger.action.outText("+++scb: no save date received, skipping.", 30) diff --git a/modules/stopGaps standalone.lua b/modules/stopGaps standalone.lua index 75996dc..86f009f 100644 --- a/modules/stopGaps standalone.lua +++ b/modules/stopGaps standalone.lua @@ -1,5 +1,5 @@ stopGap = {} -stopGap.version = "1.0.9 STANDALONE" +stopGap.version = "1.1.0 STANDALONE" stopGap.verbose = false stopGap.ssbEnabled = true stopGap.ignoreMe = "-sg" @@ -7,6 +7,7 @@ stopGap.spIgnore = "-sp" -- only single-player ignored stopGap.isMP = false stopGap.running = true 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 @@ -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.8 - added refreshInterval option as requested 1.0.9 - optimization when turning on stopgap + 1.1.0 - kickTheDead option + --]]-- stopGap.standInGroups ={} @@ -253,11 +256,11 @@ function stopGap:onEvent(event) if not event.id then return end if not event.initiator then return end local theUnit = event.initiator - - if event.id == 15 then - if (not theUnit.getPlayerName) or (not theUnit:getPlayerName()) then - return - end -- no player unit. + if (not theUnit.getPlayerName) or (not theUnit:getPlayerName()) then + return + end -- no player unit. + local id = event.id + if id == 15 then local uName = theUnit:getName() local theGroup = theUnit:getGroup() local gName = theGroup:getName() @@ -269,13 +272,31 @@ function stopGap:onEvent(event) stopGap.removeStaticGapGroupNamed(gName) end end - -- erase stopGapGUI flag, no longer required, unit -- is now slotted into trigger.action.setUserFlag("SG"..gName, 0) 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 -- -- update -- diff --git a/modules/stopGaps.lua b/modules/stopGaps.lua index cee033e..46984d3 100644 --- a/modules/stopGaps.lua +++ b/modules/stopGaps.lua @@ -1,5 +1,5 @@ stopGap = {} -stopGap.version = "1.0.10" +stopGap.version = "1.1.0" stopGap.verbose = false stopGap.ssbEnabled = true stopGap.ignoreMe = "-sg" @@ -7,7 +7,7 @@ stopGap.spIgnore = "-sp" -- only single-player ignored stopGap.isMP = false stopGap.running = true 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 = { "dcsCommon", @@ -49,6 +49,7 @@ stopGap.requiredLibs = { - refresh attribute config zone 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.1.0 - kickTheDead option --]]-- @@ -233,11 +234,11 @@ function stopGap:onEvent(event) if not event.id then return end if not event.initiator then return end local theUnit = event.initiator - - if event.id == 15 then - if (not theUnit.getPlayerName) or (not theUnit:getPlayerName()) then - return - end -- no player unit. + if (not theUnit.getPlayerName) or (not theUnit:getPlayerName()) then + return + end -- no player unit. + local id = event.id + if id == 15 then local uName = theUnit:getName() local theGroup = theUnit:getGroup() local gName = theGroup:getName() @@ -248,12 +249,32 @@ function stopGap:onEvent(event) if stopGap.standInGroups[gName] then stopGap.removeStaticGapGroupNamed(gName) end - end - + end -- erase stopGapGUI flag, no longer required, unit -- is now slotted into trigger.action.setUserFlag("SG"..gName, 0) 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 -- @@ -403,6 +424,7 @@ function stopGap.readConfigZone(theZone) end stopGap.refreshInterval = theZone:getNumberFromZoneProperty("refresh", -1) -- default: no refresh + stopGap.kickTheDead = theZone:getBoolFromZoneProperty("kickDead", true) end -- diff --git a/tutorial & demo missions/demo - rvb BLUE.miz b/tutorial & demo missions/demo - rvb BLUE.miz new file mode 100644 index 0000000..688e279 Binary files /dev/null and b/tutorial & demo missions/demo - rvb BLUE.miz differ diff --git a/tutorial & demo missions/demo - rvb RED.miz b/tutorial & demo missions/demo - rvb RED.miz new file mode 100644 index 0000000..8363e66 Binary files /dev/null and b/tutorial & demo missions/demo - rvb RED.miz differ