Version 1.11

cloneZone onRoad, more persistence
This commit is contained in:
Christian Franz 2022-08-04 10:46:22 +02:00
parent 66686acbf5
commit 7ad32b89c4
16 changed files with 1829 additions and 1137 deletions

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,5 @@
FARPZones = {}
FARPZones.version = "1.1.0"
FARPZones.version = "1.2.0"
FARPZones.verbose = false
--[[--
Version History
@ -11,6 +11,9 @@ FARPZones.verbose = false
- rFormation attribute added
- verbose flag
- verbose cleanup ("FZ: something happened")
1.2.0 - persistence
- handles contested state
--]]--
@ -75,7 +78,7 @@ FARPZones.spinUpDelay = 30 -- seconds until FARP becomes operational after captu
FARPZones.allFARPZones = {}
FARPZones.startingUp = false
FARPZones.startingUp = false -- not needed / read anywhere
-- FARP ZONE ACCESS
function FARPZones.addFARPZone(aFARP)
@ -90,6 +93,15 @@ function FARPZones.getFARPForZone(aZone)
return FARPZones.allFARPZones[aZone]
end
function FARPZones.getFARPZoneByName(aName)
for aZone, aFarp in pairs(FARPZones.allFARPZones) do
if aZone.name == aName then return aFarp end
-- we assume zone.name == farp.name
end
trigger.action.outText("Unable to find FARP <" .. aName .. ">", 30)
return nil
end
function FARPZones.getFARPZoneForFARP(aFarp)
-- find the first FARP zone that associates with
-- aFARP (an airField)
@ -123,9 +135,9 @@ function FARPZones.createFARPFromZone(aZone)
if #mapFarps == 0 then
trigger.action.outText("***Farp Zones: no FARP found for zone " .. aZone.name, 30)
else
for idx, aFarp in pairs(mapFarps) do
--for idx, aFarp in pairs(mapFarps) do
-- trigger.action.outText("Associated FARP " .. aFarp:getName() .. " with FARP Zone " .. aZone.name, 30)
end
--end
theFarp.mainFarp = theFarp.myFarps[1]
theFarp.point = theFarp.mainFarp:getPoint() -- this is FARP, not zone!!!
@ -317,7 +329,7 @@ function FARPZones.produceVehicles(theFarp)
local theCoalition = theFarp.owner
if theTypes ~= "none" then
local theGroup = cfxZones.createGroundUnitsInZoneForCoalition (
local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
theCoalition,
theFarp.name .. "-D" .. theFarp.count, -- must be unique
theFarp.defZone,
@ -326,10 +338,11 @@ function FARPZones.produceVehicles(theFarp)
theFarp.defHeading)
-- we do not add these troops to ground troop management
theFarp.defenders = theGroup -- but we retain a handle just in case
theFarp.defenderData = theData
end
unitTypes = FARPZones.resourceTypes
local theGroup = cfxZones.createGroundUnitsInZoneForCoalition (
local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
theCoalition,
theFarp.name .. "-R" .. theFarp.count, -- must be unique
theFarp.resZone,
@ -337,7 +350,7 @@ function FARPZones.produceVehicles(theFarp)
"line_v",
theFarp.resHeading)
theFarp.resources = theGroup
theFarp.resourceData = theData
-- update unique counter
theFarp.count = theFarp.count + 1
end
@ -382,6 +395,23 @@ function FARPZones.somethingHappened(event)
end
local newOwner = aFarp:getCoalition()
-- now, because we can load from file, we may get a notice
-- that a newly loaded state disagrees with new game state
-- if so, we simply wink and exit
if newOwner == zonedFarp.owner then
trigger.action.outText("FARP <" .. zonedFarp.name .. "> aligned with persisted data", 30)
return
end
-- let's ignore the owner = 3 (contested). Usually does not
-- happen with an event, but let's be prepared
if newOwner == 3 then
if FARPZones.verbose then
trigger.action.outText("FARP <" .. zonedFarp.name .. "> has become contested", 30)
end
return
end
local blueRed = "Red"
if newOwner == 2 then blueRed = "Blue" end
trigger.action.outText("FARP " .. zonedFarp.zone.name .. " captured by " .. blueRed .."!", 30)
@ -408,6 +438,80 @@ function FARPZones.somethingHappened(event)
end
--
-- LOAD / SAVE
--
function FARPZones.saveData()
local theData = {}
if FARPZones.verbose then
trigger.action.outText("+++frpZ: enter saveData", 30)
end
local farps = {}
-- iterate all farp data and put them into a container each
for theZone, theFARP in pairs(FARPZones.allFARPZones) do
fName = theZone.name
trigger.action.outText("frpZ persistence: processing FARP <" .. fName .. ">", 30)
local fData = {}
fData.owner = theFARP.owner
fData.defenderData = dcsCommon.clone(theFARP.defenderData)
fData.resourceData = dcsCommon.clone(theFARP.resourceData)
dcsCommon.synchGroupData(fData.defenderData)
if fData.defenderData and #fData.defenderData.units<1 then
fData.defenderData = nil
end
dcsCommon.synchGroupData(fData.resourceData)
if fData.resourceData and #fData.resourceData.units<1 then
fData.resourceData = nil
end
farps[fName] = fData
end
theData.farps = farps
return theData
end
function FARPZones.loadMission()
local theData = persistence.getSavedDataForModule("FARPZones")
if not theData then
if FARPZones.verbose then
trigger.action.outText("frpZ: no save date received, skipping.", 30)
end
return
end
local farps = theData.farps
if farps then
for fName, fData in pairs(farps) do
local theFARP = FARPZones.getFARPZoneByName(fName)
if theFARP then
theFARP.owner = fData.owner
theFARP.zone.owner = fData.owner
theFARP.defenderData = dcsCommon.clone(fData.defenderData)
local groupData = fData.defenderData
if groupData and #groupData.units > 0 then
local cty = groupData.cty
local cat = groupData.cat
theFARP.defenders = coalition.addGroup(cty, cat, groupData)
end
groupData = fData.resourceData
if groupData and #groupData.units > 0 then
local cty = groupData.cty
local cat = groupData.cat
theFARP.resources = coalition.addGroup(cty, cat, groupData)
end
FARPZones.drawFARPCircleInMap(theFARP) -- mark in map
if (not theFARP.defenders) and (not theFARP.resources) then
-- we instigate a resource and defender drop
FARPZones.produceVehicles(theFARP)
end
else
trigger.action.outText("frpZ: persistence: FARP <" .. fName .. "> no longer exists in mission, skipping", 30)
end
end
end
end
--
-- Start
@ -439,7 +543,7 @@ function FARPZones.start()
return false
end
FARPZones.startingUp = true
FARPZones.startingUp = true -- not needed / read anywhere
-- read config zone
FARPZones.readConfig()
@ -449,19 +553,41 @@ function FARPZones.start()
FARPZones.preProcessor,
FARPZones.postProcessor)
-- set up persistence BEFORE we read zones, so weh know the
-- score during init phase
local hasSaveData = false
if persistence then
-- sign up for persistence
callbacks = {}
callbacks.persistData = FARPZones.saveData
persistence.registerModule("FARPZones", callbacks)
hasSaveData = persistence.hasData
end
-- collect all FARP Zones
local theZones = cfxZones.getZonesWithAttributeNamed("FARP")
for k, aZone in pairs(theZones) do
local aFARP = FARPZones.createFARPFromZone(aZone) -- read attributes from DCS
FARPZones.addFARPZone(aFARP) -- add to managed zones
FARPZones.drawFARPCircleInMap(aFARP) -- mark in map
FARPZones.produceVehicles(aFARP) -- allocate initial vehicles
-- moved FARPZones.drawFARPCircleInMap(aFARP) -- mark in map
-- moved FARPZones.produceVehicles(aFARP) -- allocate initial vehicles
if FARPZones.verbose then
trigger.action.outText("processed FARP <" .. aZone.name .. "> now owned by " .. aZone.owner, 30)
end
end
FARPZones.startingUp = false
-- now produce all vehicles - whether from
-- save, or clean from start
if hasSaveData then
FARPZones.loadMission()
else
for idx, aFARP in pairs (FARPZones.allFARPZones) do
FARPZones.drawFARPCircleInMap(aFARP) -- mark in map
FARPZones.produceVehicles(aFARP) -- allocate initial vehicles
end
end
FARPZones.startingUp = false -- not needed / read anywhere
trigger.action.outText("cf/x FARP Zones v" .. FARPZones.version .. " started", 30)
return true
@ -480,4 +606,6 @@ Improvements:
make hidden farps only appear for owning side
make farps repair their service vehicles after a time, or simply refresh them every x minutes, to make the algo simpler
--]]--

View File

@ -297,6 +297,10 @@ function cfxArtilleryZones.doFireAt(aZone, maxDistFromCenter)
cfxArtilleryZones.invokeCallbacksFor('fire', aZone, {})
end
--
-- API main entry call for firing at zone
-- invokes doFireAt()
--
function cfxArtilleryZones.simFireAtZone(aZone, aGroup, dist)
if not dist then dist = aZone.spotRange end

View File

@ -1,5 +1,5 @@
cfxMX = {}
cfxMX.version = "1.2.0"
cfxMX.version = "1.2.1"
cfxMX.verbose = false
--[[--
Mission data decoder. Access to ME-built mission structures
@ -16,14 +16,15 @@ cfxMX.verbose = false
1.2.0 - added group name reference table
- added group type reference
- added references for allFixed, allHelo, allGround, allSea, allStatic
1.2.1 - added countryByName
- added linkByName
--]]--
cfxMX.groupNamesByID = {}
cfxMX.groupIDbyName = {}
cfxMX.groupDataByName = {}
cfxMX.countryByName ={}
cfxMX.linkByName = {}
cfxMX.allFixedByName = {}
cfxMX.allHeloByName = {}
cfxMX.allGroundByName = {}
@ -193,11 +194,20 @@ function cfxMX.createCrossReferences()
local category = obj_type_name
if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then --there's at least one group!
for group_num, group_data in pairs(obj_type_data.group) do
local aName = group_data.name
local aID = group_data.groupId
-- get linkUnit info if it exists
local linkUnit = nil
if group_data and group_data.route and group_data.route and group_data.route.points[1] then
linkUnit = group_data.route.points[1].linkUnit
cfxMX.linkByName[aName] = linkUnit
end
cfxMX.groupNamesByID[aID] = aName
cfxMX.groupIDbyName[aName] = aID
cfxMX.groupDataByName[aName] = group_data
cfxMX.countryByName[aName] = cntry_id
-- now make the type-specific xrefs
if obj_type_name == "helicopter" then
cfxMX.allHeloByName[aName] = group_data

View File

@ -1,22 +1,24 @@
cfxObjectDestructDetector = {}
cfxObjectDestructDetector.version = "1.2.0"
cfxObjectDestructDetector.version = "1.3.0"
cfxObjectDestructDetector.verbose = false
cfxObjectDestructDetector.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
}
cfxObjectDestructDetector.verbose = false
--[[--
VERSION HISTORY
1.0.0 initial version, based on parashoo, arty zones
1.0.1 fixed bug: trigger.MISC.getUserFlag()
1.1.0 added support for method, f! and destroyed!
1.2.0 DML / Watchflag support
1.3.0 Persistence support
Detect when an object with OBJECT ID as assigned in ME dies
*** EXTENDS ZONES
--]]--
cfxObjectDestructDetector.objectZones = {}
--
@ -41,6 +43,14 @@ function cfxObjectDestructDetector.addObjectDetectZone(aZone)
table.insert(cfxObjectDestructDetector.objectZones, aZone)
end
function cfxObjectDestructDetector.getObjectDetectZoneByName(aName)
for idx, aZone in pairs(cfxObjectDestructDetector.objectZones) do
if aZone.name == aName then return aZone end
end
-- add landHeight to this zone
return nil
end
--
-- processing of zones
--
@ -48,6 +58,10 @@ function cfxObjectDestructDetector.processObjectDestructZone(aZone)
aZone.name = cfxZones.getStringFromZoneProperty(aZone, "NAME", aZone.name)
-- aZone.coalition = cfxZones.getCoalitionFromZoneProperty(aZone, "coalition", 0)
aZone.ID = cfxZones.getNumberFromZoneProperty(aZone, "OBJECT ID", 1) -- THIS!
-- persistence interface
aZone.isDestroyed = false
--[[-- old code, to be decom'd --]]--
if cfxZones.hasProperty(aZone, "setFlag") then
aZone.setFlag = cfxZones.getStringFromZoneProperty(aZone, "setFlag", "999")
end
@ -73,16 +87,16 @@ function cfxObjectDestructDetector.processObjectDestructZone(aZone)
aZone.decreaseFlag = cfxZones.getStringFromZoneProperty(aZone, "f-1", "999")
end
-- new method support
aZone.oddMethod = cfxZones.getStringFromZoneProperty(aZone, "method", "flip")
-- DML method support
aZone.oddMethod = cfxZones.getStringFromZoneProperty(aZone, "method", "inc")
if cfxZones.hasProperty(aZone, "oddMethod") then
aZone.oddMethod = cfxZones.getStringFromZoneProperty(aZone, "oddMethod", "flip")
aZone.oddMethod = cfxZones.getStringFromZoneProperty(aZone, "oddMethod", "inc")
end
if cfxZones.hasProperty(aZone, "f!") then
aZone.outDestroyFlag = cfxZones.getStringFromZoneProperty(aZone, "f!", "*none")
end
-- we now always have that property
aZone.outDestroyFlag = cfxZones.getStringFromZoneProperty(aZone, "f!", "*none")
if cfxZones.hasProperty(aZone, "destroyed!") then
aZone.outDestroyFlag = cfxZones.getStringFromZoneProperty(aZone, "destroyed!", "*none")
end
@ -102,7 +116,7 @@ function cfxObjectDestructDetector:onEvent(event)
if not id then return end
for idx, aZone in pairs(cfxObjectDestructDetector.objectZones) do
if aZone.ID == id then
if (not aZone.isDestroyed) and aZone.ID == id then
-- flag manipulation
-- OLD FLAG SUPPORT, SOON TO BE REMOVED
if aZone.setFlag then
@ -128,7 +142,7 @@ function cfxObjectDestructDetector:onEvent(event)
-- invoke callbacks
cfxObjectDestructDetector.invokeCallbacksFor(aZone)
if cfxObjectDestructDetector.verbose then
if aZone.verbose or cfxObjectDestructDetector.verbose then
trigger.action.outText("OBJECT KILL: " .. id, 30)
end
@ -136,6 +150,9 @@ function cfxObjectDestructDetector:onEvent(event)
-- for better performance since it cant
-- die twice
-- save state for persistence
aZone.isDestroyed = true
return
end
end
@ -143,8 +160,85 @@ function cfxObjectDestructDetector:onEvent(event)
end
end
-- add event handler
--
-- persistence: save and load data
--
function cfxObjectDestructDetector.saveData() -- invoked by persistence
local theData = {}
local zoneInfo = {}
for idx, aZone in pairs(cfxObjectDestructDetector.objectZones) do
-- save all pertinent info. in our case, it's just
-- the isDestroyed and flag info info
info = {}
info.isDestroyed = aZone.isDestroyed
info.outDestroyVal = cfxZones.getFlagValue(aZone.outDestroyFlag, aZone)
zoneInfo[aZone.name] = info
end
-- expasion proof: assign as own field
theData.zoneInfo = zoneInfo
return theData
end
function cfxObjectDestructDetector.loadMission()
if cfxObjectDestructDetector.verbose then
trigger.action.outText("+++oDDet: persistence - loading data", 30)
end
local theData = persistence.getSavedDataForModule("cfxObjectDestructDetector")
if not theData then
return
end
-- iterate the data, and fail graciously if
-- we can't find a zone. it's probably beed edited out
local zoneInfo = theData.zoneInfo
if not zoneInfo then return end
if cfxObjectDestructDetector.verbose then
trigger.action.outText("+++oDDet: persistence - processing data", 30)
end
for zName, info in pairs (zoneInfo) do
local theZone = cfxObjectDestructDetector.getObjectDetectZoneByName(zName)
if theZone then
theZone.isDestroyed = info.isDestroyed
cfxZones.setFlagValue(theZone.outDestroyFlag, info.outDestroyVal, theZone)
if cfxObjectDestructDetector.verbose or theZone.verbose then
trigger.action.outText("+++oDDet: persistence setting flag <" .. theZone.outDestroyFlag .. "> to <" .. info.outDestroyVal .. ">",30)
end
local theName = tostring(theZone.ID)
if info.isDestroyed then
-- We now get the scenery object in that zone
-- and remove it
-- note that dcsCommon methods use DCS zones, not cfx
local theObject = dcsCommon.getSceneryObjectInZoneByName(theName, theZone.dcsZone)
if theObject then
if cfxObjectDestructDetector.verbose or theZone.verbose then
trigger.action.outText("+++oDDet: persistence removing dead scenery object <" .. theName .. ">",30)
end
theObject:destroy()
else
if cfxObjectDestructDetector.verbose or theZone.verbose then
trigger.action.outText("+++oDDet: persistence - can't find scenery objects <" .. theName .. ">, skipped destruction",30)
end
end
else
if cfxObjectDestructDetector.verbose or theZone.verbose then
trigger.action.outText("+++oDDet: persistence - scenery objects <" .. theName .. "> is healthy",30)
end
end
else
trigger.action.outText("+++oDDet: persistence - can't find detector <" .. zName .. "> on load. skipping", 30)
end
end
if cfxObjectDestructDetector.verbose then
trigger.action.outText("+++oDDet: persistence - processing complete", 30)
end
end
--
-- start
--
function cfxObjectDestructDetector.start()
if not dcsCommon.libCheck("cfx Object Destruct Detector",
@ -152,20 +246,38 @@ function cfxObjectDestructDetector.start()
return false
end
-- collect all zones with 'smoke' attribute
-- collect all zones with 'OBJECT id' attribute
-- collect all spawn zones
local attrZones = cfxZones.getZonesWithAttributeNamed("OBJECT ID")
-- now create a spawner for all, add them to the spawner updater, and spawn for all zones that are not
-- paused
for k, aZone in pairs(attrZones) do
cfxObjectDestructDetector.processObjectDestructZone(aZone) -- process attribute and add to zone properties (extend zone)
cfxObjectDestructDetector.addObjectDetectZone(aZone) -- remember it so we can smoke it
cfxObjectDestructDetector.addObjectDetectZone(aZone)
end
-- add myself as event handler
world.addEventHandler(cfxObjectDestructDetector)
-- persistence: see if we have any data to process
-- for all our zones, and sign up for data saving
if persistence and persistence.active then
-- sign up for saves
callbacks = {}
callbacks.persistData = cfxObjectDestructDetector.saveData
persistence.registerModule("cfxObjectDestructDetector", callbacks)
if persistence.hasData then
cfxObjectDestructDetector.loadMission()
end
else
if cfxObjectDestructDetector.verbose then
trigger.action.outText("no persistence for cfxObjectDestructDetector", 30)
end
end
-- say hi
trigger.action.outText("cfx Object Destruct Zones v" .. cfxObjectDestructDetector.version .. " started.", 30)
return true

View File

@ -1,7 +1,8 @@
cfxOwnedZones = {}
cfxOwnedZones.version = "1.1.2"
cfxOwnedZones.version = "1.2.0"
cfxOwnedZones.verbose = false
cfxOwnedZones.announcer = true
cfxOwnedZones.name = "cfxOwnedZones"
--[[-- VERSION HISTORY
1.0.3 - added getNearestFriendlyZone
@ -21,7 +22,7 @@ cfxOwnedZones.announcer = true
- support of 'none' type string to indicate no attackers/defenders
- updated property access
- module check
- cfxOwnedTroop.usesDefenders(aZone)
- cfxOwnedZones.usesDefenders(aZone)
- verifyZone
1.0.8 - repairDefenders trims types to allow blanks in
type separator
@ -41,6 +42,9 @@ cfxOwnedZones.announcer = true
- announcer
1.1.1 - conq+1 flag
1.1.2 - corrected type bug in zoneConquered
1.2.0 - support for persistence
- conq+1 --> conquered!
- no cfxGroundTroop bug (no delay)
--]]--
cfxOwnedZones.requiredLibs = {
@ -60,6 +64,10 @@ cfxOwnedZones.attackingTime = 300 -- 300 seconds until new attackers are produce
cfxOwnedZones.shockTime = 200 -- 200 -- 'shocked' period of inactivity
cfxOwnedZones.repairTime = 200 -- 200 -- time until we raplace one lost unit, also repairs all other units to 100%
-- persistence: all attackers we ever sent out.
-- is regularly verified and cut to size
cfxOwnedZones.spawnedAttackers = {}
-- owned zones is a module that managers 'conquerable' zones and keeps a
-- record of who owns the zone
-- based on some simple rules that are regularly checked
@ -166,6 +174,12 @@ function cfxOwnedZones.drawZoneInMap(aZone)
end
function cfxOwnedZones.getOwnedZoneByName(zName)
for zKey, theZone in pairs (cfxOwnedZones.zones) do
if theZone.name == zName then return theZone end
end
return nil
end
function cfxOwnedZones.addOwnedZone(aZone)
local owner = cfxZones.getCoalitionFromZoneProperty(aZone, "owner", 0) -- is already readm read it again
@ -203,8 +217,9 @@ function cfxOwnedZones.addOwnedZone(aZone)
local paused = cfxZones.getBoolFromZoneProperty(aZone, "paused", false)
aZone.paused = paused
aZone.conqueredFlag = cfxZones.getStringFromZoneProperty(aZone, "conquered!", "*<cfxnone>")
if cfxZones.hasProperty(aZone, "conq+1") then
cfxOwnedZones.conqueredFlag = cfxZones.getNumberFromZoneProperty(theZone, "conq+1", -1)
aZone.conqueredFlag = cfxZones.getStringFromZoneProperty(aZone, "conq+1", "*<cfxnone>")
end
aZone.unbeatable = cfxZones.getBoolFromZoneProperty(aZone, "unbeatable", false)
@ -218,7 +233,7 @@ end
function cfxOwnedZones.verifyZone(aZone)
-- do some sanity checks
if not cfxGroundTroops and (aZone.attackersRED ~= "none" or aZone.attackersBLUE ~= "none") then
trigger.action.outText("+++owdZ: " .. aZone.name .. " attackers need cfxGroundTroops to function")
trigger.action.outText("+++owdZ: " .. aZone.name .. " attackers need cfxGroundTroops to function", 30)
end
end
@ -375,14 +390,14 @@ function cfxOwnedZones.spawnAttackTroops(theTypes, aZone, aCoalition, aFormation
local spawnZone = cfxZones.createSimpleZone("attkSpawnZone", spawnPoint, aZone.attackRadius)
local theGroup = cfxZones.createGroundUnitsInZoneForCoalition (
local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
aCoalition, -- theCountry,
aZone.name .. " (A) " .. dcsCommon.numberUUID(), -- must be unique
spawnZone,
unitTypes,
aFormation, -- outward facing
0)
return theGroup
return theGroup, theData
end
function cfxOwnedZones.spawnDefensiveTroops(theTypes, aZone, aCoalition, aFormation)
@ -400,13 +415,13 @@ function cfxOwnedZones.spawnDefensiveTroops(theTypes, aZone, aCoalition, aFormat
--local theCountry = dcsCommon.coalition2county(aCoalition)
local spawnZone = cfxZones.createSimpleZone("spawnZone", aZone.point, aZone.spawnRadius)
local theGroup = cfxZones.createGroundUnitsInZoneForCoalition (
local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
aCoalition, --theCountry,
aZone.name .. " (D) " .. dcsCommon.numberUUID(), -- must be unique
spawnZone, unitTypes,
aFormation, -- outward facing
0)
return theGroup
return theGroup, theData
end
--
-- U P D A T E
@ -432,7 +447,13 @@ function cfxOwnedZones.sendOutAttackers(aZone)
if attackers == "none" then return end
local theGroup = cfxOwnedZones.spawnAttackTroops(attackers, aZone, aZone.owner, aZone.attackFormation)
local theGroup, theData = cfxOwnedZones.spawnAttackTroops(attackers, aZone, aZone.owner, aZone.attackFormation)
local troopData = {}
troopData.groupData = theData
troopData.orders = "attackOwnedZone" -- lazy coding!
troopData.side = aZone.owner
cfxOwnedZones.spawnedAttackers[theData.name] = troopData
-- submit them to ground troops handler as zoneseekers
-- and our groundTroops module will handle the rest
@ -496,10 +517,11 @@ function cfxOwnedZones.zoneConquered(aZone, theSide, formerOwner) -- 0 = neutral
end
end
-- increase conq flag
if aZone.conqueredFlag then
local lastVal = trigger.misc.getUserFlag(aZone.conqueredFlag)
trigger.action.setUserFlag(aZone.conqueredFlag, lastVal + 1)
end
-- if aZone.conqueredFlag then
-- local lastVal = trigger.misc.getUserFlag(aZone.conqueredFlag)
-- trigger.action.setUserFlag(aZone.conqueredFlag, lastVal + 1)
cfxZones.pollFlag(aZone.conqueredFlag, "inc", aZone)
-- end
-- invoke callbacks now
cfxOwnedZones.invokeConqueredCallbacks(aZone, theSide, formerOwner)
@ -567,7 +589,7 @@ function cfxOwnedZones.repairDefenders(aZone)
-- now livingTypes holds the full array of units we need to spawn
local theCountry = dcsCommon.getACountryForCoalition(aZone.owner)
local spawnZone = cfxZones.createSimpleZone("spawnZone", aZone.point, aZone.spawnRadius)
local theGroup = cfxZones.createGroundUnitsInZoneForCoalition (
local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
aZone.owner, -- was wrongly: theCountry
aZone.name .. dcsCommon.numberUUID(), -- must be unique
spawnZone,
@ -599,15 +621,17 @@ function cfxOwnedZones.spawnDefenders(aZone)
-- if 'none', simply exit
if defenders == "none" then return end
local theGroup = cfxOwnedZones.spawnDefensiveTroops(defenders, aZone, aZone.owner, aZone.formation)
local theGroup, theData = cfxOwnedZones.spawnDefensiveTroops(defenders, aZone, aZone.owner, aZone.formation)
-- the troops reamin, so no orders to move, no handing off to ground troop manager
aZone.defenders = theGroup;
aZone.defenders = theGroup
aZone.defenderData = theData -- used for persistence
if theGroup then
aZone.defenderMax = theGroup:getInitialSize() -- so we can determine if some units were destroyed
aZone.lastDefenders = aZone.defenderMax -- if this is larger than current number, someone bit the dust
--aZone.defenderMax = theGroup:getInitialSize() -- so we can determine if some units were destroyed
aZone.lastDefenders = theGroup:getInitialSize() --- aZone.defenderMax -- if this is larger than current number, someone bit the dust
--trigger.action.outText("+++ spawned defenders for ".. aZone.name, 30)
else
trigger.action.outText("+++owdZ: WARNING: spawned no defenders for ".. aZone.name, 30)
aZone.defenderData = nil
end
end
@ -661,6 +685,10 @@ function cfxOwnedZones.updateZone(aZone)
-- we have defenders
if aZone.defenders:isExist() then
-- isee if group was damaged
if not aZone.lastDefenders then
-- fresh group, probably from persistence, needs init
aZone.lastDefenders = -1
end
if aZone.defenders:getSize() < aZone.lastDefenders then
-- yes, at least one unit destroyed
aZone.timeStamp = timer.getTime()
@ -671,6 +699,8 @@ function cfxOwnedZones.updateZone(aZone)
aZone.state = "shocked"
return
else
aZone.lastDefenders = aZone.defenders:getSize()
end
else
@ -725,17 +755,19 @@ function cfxOwnedZones.updateZone(aZone)
-- we are currently rebuilding defenders unit by unit
if timer.getTime() > aZone.timeStamp + cfxOwnedZones.repairTime then
aZone.timeStamp = timer.getTime()
-- wait's up, repair one defender, then check if full strength
cfxOwnedZones.repairDefenders(aZone)
if aZone.defenders:getSize() >= aZone.defenderMax then
--
-- see if we are full strenght and if so go to attack, else set timer to reair the next unit
if aZone.defenders and aZone.defenders:isExist() and aZone.defenders:getSize() >= aZone.defenders:getInitialSize() then
-- we are at max size, time to produce some attackers
-- progress to next state
nextState = "attacking"
aZone.timeStamp = timer.getTime()
if cfxOwnedZones.verbose then
trigger.action.outText("+++owdZ: State " .. aZone.state .. " to " .. nextState .. " for " .. aZone.name, 30)
end
end
-- see if we are full strenght and if so go to attack, else set timer to reair the next unit
end
elseif aZone.state == "shocked" then
@ -763,6 +795,20 @@ function cfxOwnedZones.updateZone(aZone)
aZone.state = nextState
end
function cfxOwnedZones.GC()
-- GC run. remove all my dead remembered troops
local filteredAttackers = {}
for gName, gData in pairs (cfxOwnedZones.spawnedAttackers) do
-- all we need to do is get the group of that name
-- and if it still returns units we are fine
local gameGroup = Group.getByName(gName)
if gameGroup and gameGroup:isExist() and gameGroup:getSize() > 0 then
filteredAttackers[gName] = gData
end
end
cfxOwnedZones.spawnedAttackers = filteredAttackers
end
function cfxOwnedZones.update()
cfxOwnedZones.updateSchedule = timer.scheduleFunction(cfxOwnedZones.update, {}, timer.getTime() + 1/cfxOwnedZones.ups)
@ -792,6 +838,11 @@ function cfxOwnedZones.update()
end
function cfxOwnedZones.houseKeeping()
timer.scheduleFunction(cfxOwnedZones.houseKeeping, {}, timer.getTime() + 5 * 60) -- every 5 minutes
cfxOwnedZones.GC()
end
function cfxOwnedZones.sideOwnsAll(theSide)
for key, aZone in pairs(cfxOwnedZones.zones) do
if aZone.owner ~= theSide then
@ -810,19 +861,150 @@ function cfxOwnedZones.hasOwnedZones()
return false
end
--
-- load / save data
--
function cfxOwnedZones.saveData()
-- this is called from persistence when it's time to
-- save data. returns a table with all my data
local theData = {}
local allZoneData = {}
-- iterate all my zones and create data
for idx, theZone in pairs(cfxOwnedZones.zones) do
local zoneData = {}
if theZone.defenderData then
zoneData.defenderData = dcsCommon.clone(theZone.defenderData)
dcsCommon.synchGroupData(zoneData.defenderData)
end
zoneData.conquered = cfxZones.getFlagValue(theZone.conqueredFlag, theZone)
zoneData.owner = theZone.owner
zoneData.state = theZone.state -- will prevent immediate spawn
-- since new zones are spawned with 'init'
allZoneData[theZone.name] = zoneData
end
-- now iterate all attack groups that we have spawned and that
-- (maybe) are still alive
cfxOwnedZones.GC() -- start with a GC run to remove all dead
local livingAttackers = {}
for gName, gData in pairs (cfxOwnedZones.spawnedAttackers) do
-- all we need to do is get the group of that name
-- and if it still returns units we are fine
-- spawnedAttackers is a [groupName] table with {.groupData, .orders, .side}
local gameGroup = Group.getByName(gName)
if gameGroup and gameGroup:isExist() then
if gameGroup:getSize() > 0 then
local sData = dcsCommon.clone(gData)
dcsCommon.synchGroupData(sData.groupData)
livingAttackers[gName] = sData
end
end
end
-- now write the info for the flags that we output for #red, etc
local flagInfo = {}
flagInfo.neutral = cfxZones.getFlagValue(cfxOwnedZones.neutralTriggerFlag, cfxOwnedZones)
flagInfo.red = cfxZones.getFlagValue(cfxOwnedZones.redTriggerFlag, cfxOwnedZones)
flagInfo.blue = cfxZones.getFlagValue(cfxOwnedZones.blueTriggerFlag, cfxOwnedZones)
-- assemble the data
theData.zoneData = allZoneData
theData.attackers = livingAttackers
theData.flagInfo = flagInfo
-- return it
return theData
end
function cfxOwnedZones.loadData()
-- remember to draw in map with new owner
if not persistence then return end
local theData = persistence.getSavedDataForModule("cfxOwnedZones")
if not theData then
if cfxOwnedZones.verbose then
trigger.action.outText("owdZ: no save date received, skipping.", 30)
end
return
end
-- theData contains the following tables:
-- zoneData: per-zone data
-- flagInfo: module-global flags
-- attackers: all spawned attackers that we feed to groundTroops
local allZoneData = theData.zoneData
for zName, zData in pairs(allZoneData) do
-- access zone
local theZone = cfxOwnedZones.getOwnedZoneByName(zName)
if theZone then
if zData.defenderData then
if theZone.defenders and theZone.defenders:isExist() then
-- should not happen, but so be it
theZone.defenders:destroy()
end
local gData = zData.defenderData
local cty = gData.cty
local cat = gData.cat
theZone.defenders = coalition.addGroup(cty, cat, gData)
theZone.defenderData = zData.defenderData
end
theZone.owner = zData.owner
theZone.state = zData.state
cfxZones.setFlagValue(theZone.conqueredFlag, zData.conquered, theZone)
-- update mark in map
cfxOwnedZones.drawZoneInMap(theZone)
else
trigger.action.outText("owdZ: load - data mismatch: cannot find zone <" .. zName .. ">, skipping zone.", 30)
end
end
-- now process all attackers
local allAttackers = theData.attackers
for gName, gdTroop in pairs(allAttackers) do
-- table is {.groupData, .orders, .side}
local gData = gdTroop.groupData
local orders = gdTroop.orders
local side = gdTroop.side
local cty = gData.cty
local cat = gData.cat
-- add to my own attacker queue so we can save later
local dClone = dcsCommon.clone(gData)
cfxOwnedZones.spawnedAttackers[gName] = dClone
local theGroup = coalition.addGroup(cty, cat, gData)
if cfxGroundTroops then
local troops = cfxGroundTroops.createGroundTroops(theGroup)
troops.orders = orders
troops.side = side
cfxGroundTroops.addGroundTroopsToPool(troops) -- hand off to ground troops
end
end
-- now process module global flags
local flagInfo = theData.flagInfo
if flagInfo then
cfxZones.setFlagValue(cfxOwnedZones.neutralTriggerFlag, flagInfo.neutral, cfxOwnedZones)
cfxZones.setFlagValue(cfxOwnedZones.redTriggerFlag, flagInfo.red, cfxOwnedZones)
cfxZones.setFlagValue(cfxOwnedZones.blueTriggerFlag, flagInfo.blue, cfxOwnedZones)
end
end
--
function cfxOwnedZones.readConfigZone(theZone)
if not theZone then theZone = cfxZones.createSimpleZone("ownedZonesConfig") end
cfxOwnedZones.name = "cfxOwnedZones" -- just in case, so we can access with cfxZones
cfxOwnedZones.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
cfxOwnedZones.announcer = cfxZones.getBoolFromZoneProperty(theZone, "announcer", true)
if cfxZones.hasProperty(theZone, "r!") then
cfxOwnedZones.redTriggerFlag = cfxZones.getStringFromZoneProperty(theZone, "r!", "<none>")
end
if cfxZones.hasProperty(theZone, "b!") then
cfxOwnedZones.blueTriggerFlag = cfxZones.getStringFromZoneProperty(theZone, "b!", "<none>")
end
if cfxZones.hasProperty(theZone, "n!") then
cfxOwnedZones.neutralTriggerFlag = cfxZones.getStringFromZoneProperty(theZone, "n!", "<none>")
end
-- if cfxZones.hasProperty(theZone, "r!") then
cfxOwnedZones.redTriggerFlag = cfxZones.getStringFromZoneProperty(theZone, "r!", "*<cfxnone>")
-- end
-- if cfxZones.hasProperty(theZone, "b!") then
cfxOwnedZones.blueTriggerFlag = cfxZones.getStringFromZoneProperty(theZone, "b!", "*<cfxnone>")
-- end
-- if cfxZones.hasProperty(theZone, "n!") then
cfxOwnedZones.neutralTriggerFlag = cfxZones.getStringFromZoneProperty(theZone, "n!", "*<cfxnone>")
-- end
cfxOwnedZones.defendingTime = cfxZones.getNumberFromZoneProperty(theZone, "defendingTime", 100)
cfxOwnedZones.attackingTime = cfxZones.getNumberFromZoneProperty(theZone, "attackingTime", 300)
cfxOwnedZones.shockTime = cfxZones.getNumberFromZoneProperty(theZone, "shockTime", 200)
@ -838,11 +1020,8 @@ function cfxOwnedZones.init()
-- read my config zone
local theZone = cfxZones.getZoneByName("ownedZonesConfig")
if not theZone then
trigger.action.outText("+++ownZ: no config", 30)
else
cfxOwnedZones.readConfigZone(theZone)
end
cfxOwnedZones.readConfigZone(theZone)
-- collect all owned zones by their 'owner' property
-- start the process
@ -854,9 +1033,21 @@ function cfxOwnedZones.init()
cfxOwnedZones.addOwnedZone(aZone)
end
if persistence then
-- sign up for persistence
callbacks = {}
callbacks.persistData = cfxOwnedZones.saveData
persistence.registerModule("cfxOwnedZones", callbacks)
-- now load my data
cfxOwnedZones.loadData()
end
initialized = true
cfxOwnedZones.updateSchedule = timer.scheduleFunction(cfxOwnedZones.update, {}, timer.getTime() + 1/cfxOwnedZones.ups)
-- start housekeeping
cfxOwnedZones.houseKeeping()
trigger.action.outText("cx/x owned zones v".. cfxOwnedZones.version .. " started", 30)
return true

View File

@ -1,5 +1,5 @@
cfxReconMode = {}
cfxReconMode.version = "2.1.0"
cfxReconMode.version = "2.1.1"
cfxReconMode.verbose = false -- set to true for debug info
cfxReconMode.reconSound = "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav" -- to be played when somethiong discovered
@ -77,6 +77,8 @@ VERSION HISTORY
2.1.0 - processZoneMessage uses group's position, not zone
- silent attribute for priority targets
- activate / deactivate by flags
2.1.1 - Lat Lon and MGRS also give Elevation
- cfxReconMode.reportTime
cfxReconMode is a script that allows units to perform reconnaissance
missions and, after detecting units, marks them on the map with
@ -421,13 +423,14 @@ function cfxReconMode.getLocation(theGroup)
local msg = ""
local theUnit = theGroup:getUnit(1)
local currPoint = theUnit:getPoint()
local ele = math.floor(land.getHeight({x = currPoint.x, y = currPoint.z}))
if cfxReconMode.mgrs then
local grid = coord.LLtoMGRS(coord.LOtoLL(currPoint))
msg = grid.UTMZone .. ' ' .. grid.MGRSDigraph .. ' ' .. grid.Easting .. ' ' .. grid.Northing
msg = grid.UTMZone .. ' ' .. grid.MGRSDigraph .. ' ' .. grid.Easting .. ' ' .. grid.Northing .. " Ele " .. ele .."m"
else
local lat, lon, alt = coord.LOtoLL(currPoint)
lat, lon = dcsCommon.latLon2Text(lat, lon)
msg = "Lat " .. lat .. " Lon " .. lon
msg = "Lat " .. lat .. " Lon " .. lon .. " Ele " .. ele .."m"
end
return msg
end
@ -516,7 +519,7 @@ function cfxReconMode.detectedGroup(mySide, theScout, theGroup, theLoc)
-- say something
if not silent and cfxReconMode.announcer then
local msg = cfxReconMode.generateSALT(theScout, theGroup)
trigger.action.outTextForCoalition(mySide, msg, 30)
trigger.action.outTextForCoalition(mySide, msg, cfxReconMode.reportTime)
-- trigger.action.outTextForCoalition(mySide, theScout:getName() .. " reports new ground contact " .. theGroup:getName(), 30)
if cfxReconMode.verbose then
trigger.action.outText("+++rcn: announced for side " .. mySide, 30)
@ -549,7 +552,7 @@ function cfxReconMode.detectedGroup(mySide, theScout, theGroup, theLoc)
-- AND EVEN WHEN SILENT!!!
local msg = zInfo.prioMessage
msg = cfxReconMode.processZoneMessage(msg, zInfo.theZone, theGroup)
trigger.action.outTextForCoalition(mySide, msg, 30)
trigger.action.outTextForCoalition(mySide, msg, cfxReconMode.reportTime)
if cfxReconMode.verbose or zInfo.theZone.verbose then
trigger.action.outText("+++rcn: prio message sent for prio target zone <" .. zInfo.theZone.name .. ">",30)
end
@ -965,6 +968,7 @@ function cfxReconMode.readConfigZone()
cfxReconMode.greyScouts = cfxZones.getBoolFromZoneProperty(theZone, "greyScouts", false)
cfxReconMode.playerOnlyRecon = cfxZones.getBoolFromZoneProperty(theZone, "playerOnlyRecon", false)
cfxReconMode.reportNumbers = cfxZones.getBoolFromZoneProperty(theZone, "reportNumbers", true)
cfxReconMode.reportTime = cfxZones.getNumberFromZoneProperty(theZone, "reportTime", 30)
cfxReconMode.detectionMinRange = cfxZones.getNumberFromZoneProperty(theZone, "detectionMinRange", 3000)
cfxReconMode.detectionMaxRange = cfxZones.getNumberFromZoneProperty(theZone, "detectionMaxRange", 12000)

View File

@ -1,5 +1,5 @@
cfxZones = {}
cfxZones.version = "2.8.4"
cfxZones.version = "2.8.5"
-- cf/x zone management module
-- reads dcs zones and makes them accessible and mutable
@ -89,6 +89,9 @@ cfxZones.version = "2.8.4"
- changed extractPropertyFromDCS() to also match attributes with blanks like "the Attr" to "theAttr"
- new expandFlagName()
- 2.8.4 - fixed bug in setFlagValue()
- 2.8.5 - createGroundUnitsInZoneForCoalition() now always passes back a copy of the group data
- data also contains cty = country and cat = category for easy spawn
- getFlagValue additional zone name guards
--]]--
cfxZones.verbose = false
@ -1047,8 +1050,17 @@ function cfxZones.createGroundUnitsInZoneForCoalition (theCoalition, groupName,
-- first we need to translate the coalition to a legal
-- country. we use UN for neutral, cjtf for red and blue
local theSideCJTF = dcsCommon.coalition2county(theCoalition)
return coalition.addGroup(theSideCJTF, Group.Category.GROUND, theGroup)
-- store cty and cat for later access. DCS doesn't need it, but we may
theGroup.cty = theSideCJTF
theGroup.cat = Group.Category.GROUND
-- create a copy of the group data for
-- later reference
local groupDataCopy = dcsCommon.clone(theGroup)
local newGroup = coalition.addGroup(theSideCJTF, Group.Category.GROUND, theGroup)
return newGroup, groupDataCopy
end
-- parsing zone names. The first part of the name until the first blank " "
@ -1311,8 +1323,8 @@ end
function cfxZones.getFlagValue(theFlag, theZone)
local zoneName = "<dummy>"
if not theZone then
trigger.action.outText("+++Zne: no zone on getFlagValue", 30)
if not theZone or not theZone.name then
trigger.action.outText("+++Zne: no zone or zone name on getFlagValue")
else
zoneName = theZone.name -- for flag wildcards
end

View File

@ -258,7 +258,7 @@ function changer.start()
-- read config
changer.readConfigZone()
-- process cloner Zones
-- process changer Zones
local attrZones = cfxZones.getZonesWithAttributeNamed("change?")
for k, aZone in pairs(attrZones) do
changer.createChangerWithZone(aZone) -- process attributes

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
dcsCommon = {}
dcsCommon.version = "2.6.8"
dcsCommon.version = "2.7.0"
--[[-- VERSION HISTORY
2.2.6 - compassPositionOfARelativeToB
- clockPositionOfARelativeToB
@ -85,6 +85,11 @@ dcsCommon.version = "2.6.8"
2.6.7 - new menu2text()
2.6.8 - new getMissionName()
- new flagArrayFromString()
2.6.9 - new getSceneryObjectsInZone()
- new getSceneryObjectInZoneByName()
2.7.0 - new synchGroupData()
clone, topClone and copyArray now all nil-trap
--]]--
-- dcsCommon is a library of common lua functions
@ -456,6 +461,7 @@ dcsCommon.version = "2.6.8"
return dcsCommon.dist(point1, point2)
end
-- distance between points
function dcsCommon.dist(point1, point2) -- returns distance between two points
-- supports xyz and xy notations
@ -882,6 +888,7 @@ dcsCommon.version = "2.6.8"
-- topClone is a shallow clone of orig, only top level is iterated,
-- all values are ref-copied
function dcsCommon.topClone(orig)
if not orig then return nil end
local orig_type = type(orig)
local copy
if orig_type == 'table' then
@ -898,6 +905,7 @@ dcsCommon.version = "2.6.8"
-- clone is a recursive clone which will also clone
-- deeper levels, as used in units
function dcsCommon.clone(orig)
if not orig then return nil end
local orig_type = type(orig)
local copy
if orig_type == 'table' then
@ -913,6 +921,8 @@ dcsCommon.version = "2.6.8"
end
function dcsCommon.copyArray(inArray)
if not inArray then return nil end
-- warning: this is a ref copy!
local theCopy = {}
for idx, element in pairs(inArray) do
@ -1687,6 +1697,28 @@ dcsCommon.version = "2.6.8"
end
function dcsCommon.synchGroupData(inGroupData) -- update group data block by
-- comparing it to spawned group and update units by x, y, heding and isExist
-- modifies inGroupData!
if not inGroupData then return end
-- groupdata from game, NOT MX DATA!
-- we synch the units and their coords
local livingUnits = {}
for idx, unitData in pairs(inGroupData.units) do
local theUnit = Unit.getByName(unitData.name)
if theUnit and theUnit:isExist() and theUnit:getLife()>1 then
-- update x and y and heading
local pos = theUnit:getPoint()
unitData.unitId = theUnit:getID()
unitData.x = pos.x
unitData.y = pos.z -- !!!!
unitData.heading = dcsCommon.getUnitHeading(gUnit)
table.insert(livingUnits, unitData)
end
end
inGroupData.units = livingUnits
end
--
--
-- M I S C M E T H O D S
@ -2440,6 +2472,42 @@ function dcsCommon.flagArrayFromString(inString, verbose)
end
return flags
end
function dcsCommon.objectHandler(theObject, theCollector)
table.insert(theCollector, theObject)
return true
end
function dcsCommon.getSceneryObjectsInZone(theZone) -- DCS ZONE!!!
local aCat = 5 -- scenery
-- WARNING: WE ARE USING DCS ZONES, NOT CFX!!!
local p = {x=theZone.x, y=0, z=theZone.y}
local lp = {x = p.x, y = p.z}
p.y = land.getHeight(lp)
local collector = {}
-- now build the search argument
local args = {
id = world.VolumeType.SPHERE,
params = {
point = p,
radius = theZone.radius
}
}
-- now call search
world.searchObjects(aCat, args, dcsCommon.objectHandler, collector)
return collector
end
function dcsCommon.getSceneryObjectInZoneByName(theName, theZone) -- DCS ZONE!!!
local allObs = dcsCommon.getSceneryObjectsInZone(theZone)
for idx, anObject in pairs(allObs) do
if tostring(anObject:getName()) == theName then return anObject end
end
return nil
end
--
--
-- INIT

View File

@ -1,5 +1,5 @@
persistence = {}
persistence.version = "1.0.0"
persistence.version = "1.0.1"
persistence.ups = 1 -- once every 1 seconds
persistence.verbose = false
persistence.active = false
@ -7,7 +7,7 @@ persistence.saveFileName = nil -- "mission data.txt"
persistence.sharedDir = nil -- not yet implemented
persistence.missionDir = nil -- set at start
persistence.saveDir = nil -- set at start
persistence.name = "persistence" -- for cfxZones
persistence.missionData = {} -- loaded from file
persistence.requiredLibs = {
"dcsCommon", -- always
@ -16,6 +16,12 @@ persistence.requiredLibs = {
--[[--
Version History
1.0.0 - initial version
1.0.1 - when available, module sets flag "cfxPersistence" to 1
- when data availabe, cfxPersistenceHasData is set to 1
- spelling
- cfxZones interface
- always output save location
PROVIDES LOAD/SAVE ABILITY TO MODULES
PROVIDES STANDALONE/HOSTED SERVER COMPATIOBILITY
@ -42,7 +48,7 @@ function persistence.registerModule(name, callbacks)
-- and must be the one given when you retrieve it later
persistence.callbacks[name] = callbacks
if persistence.verbose then
trigger.action.outText("+++persistence: module <" .. name .. "> registred itself", 30)
trigger.action.outText("+++persistence: module <" .. name .. "> registered itself", 30)
end
end
@ -120,7 +126,10 @@ end
--
function persistence.saveText(theString, fileName, shared, append)
if not persistence.active then return false end
if not fileName then return false end
if not fileName then
trigger.action.outText("+++persistence: saveText without fileName")
return false
end
if not shared then shared = flase end
if not theString then theString = "" end
@ -140,6 +149,7 @@ function persistence.saveText(theString, fileName, shared, append)
end
if not theFile then
trigger.action.outText("+++persistence: saveText - unable to open " .. path, 30)
return false
end
@ -283,6 +293,7 @@ function persistence.missionStartDataLoad()
-- can init from by data
persistence.missionData = theData
persistence.hasData = true
trigger.action.setUserFlag("cfxPersistenceHasData", 1)
-- init my flags from last save
local theFlags = theData["persistence.flagData"]
@ -359,9 +370,9 @@ function persistence.doSaveMission()
return
end
if persistence.verbose then
trigger.action.outText("+++persistence: mission saved", 30)
end
-- if persistence.verbose then
trigger.action.outText("+++persistence: mission saved to\n" .. persistence.missionDir .. persistence.saveFileName, 30)
-- end
end
function persistence.noteCleanRestart()
@ -546,6 +557,7 @@ function persistence.start()
persistence.missionDir = missionDir
persistence.active = true -- we can load and save data
trigger.action.setUserFlag("cfxPersistence", 1)
persistence.hasData = false -- we do not have save data
-- from here on we can read and write files in the missionDir

View File

@ -1,8 +1,8 @@
-- theDebugger
debugger = {}
debugger.version = "1.1.1"
debugger.version = "1.1.2"
debugDemon = {}
debugDemon.version = "1.1.1"
debugDemon.version = "1.1.2"
debugger.verbose = false
debugger.ups = 4 -- every 0.25 second
@ -22,6 +22,7 @@ debugger.log = ""
- persistence of logs
- save <name>
1.1.1 - warning when trying to set a flag to a non-int
1.1.2 - remove command
--]]--
@ -478,11 +479,6 @@ if not debugger.start() then
debugger = nil
end
--[[--
debug on and off. globally, not per zone
--]]--
--
-- DEBUG DEMON
@ -625,15 +621,6 @@ end
--
-- Helpers
--
--[[--
function debugDemon.isObserving(flagName)
-- for now, we simply scan out own
for idx, aName in pairs(debugDemon.observer.flagArray) do
if aName == flagName then return true end
end
return false
end
--]]--
function debugDemon.createObserver(aName)
local observer = cfxZones.createSimpleZone(aName)
@ -667,6 +654,7 @@ debugger.outText("*** debugger: commands are:" ..
"\n\n " .. debugDemon.markOfDemon .. "snap [<observername>] -- create new snapshot of flags" ..
"\n " .. debugDemon.markOfDemon .. "compare -- compare snapshot flag values with current" ..
"\n " .. debugDemon.markOfDemon .. "note <your note> -- add <your note> to the text log" ..
"\n\n " .. debugDemon.markOfDemon .. "remove <group/unit/object name> -- remove named item from mission" ..
"\n\n " .. debugDemon.markOfDemon .. "start -- starts debugger" ..
"\n " .. debugDemon.markOfDemon .. "stop -- stop debugger" ..
@ -1154,6 +1142,40 @@ function debugDemon.processSaveCommand(args, event)
debugger.saveLog(aName)
return true
end
function debugDemon.processRemoveCommand(args, event)
-- remove a group, unit or object
-- try group first
local aName = event.remainder
if not aName or aName:len() < 1 then
debugger.outText("*** remove: no remove target", 30)
return false
end
aName = dcsCommon.trim(aName)
local theGroup = Group.getByName(aName)
if theGroup and theGroup:isExist() then
theGroup:destroy()
debugger.outText("*** remove: removed group <" .. aName .. ">", 30)
return true
end
local theUnit = Unit.getByName(aName)
if theUnit and theUnit:isExist() then
theUnit:destroy()
debugger.outText("*** remove: removed unit <" .. aName .. ">", 30)
return true
end
local theStatic = StaticObject.getByName(aName)
if theStatic and theStatic:isExist() then
theStatic:destroy()
debugger.outText("*** remove: removed static object <" .. aName .. ">", 30)
return true
end
debugger.outText("*** remove: did not find anything called <" .. aName .. "> to remove", 30)
return true
end
--
-- init and start
--
@ -1223,6 +1245,7 @@ function debugDemon.init()
debugDemon.addCommndProcessor("?", debugDemon.processHelpCommand)
debugDemon.addCommndProcessor("help", debugDemon.processHelpCommand)
debugDemon.addCommndProcessor("remove", debugDemon.processRemoveCommand)
return true
end
@ -1259,6 +1282,11 @@ end
- inspect objects, dumping category, life, if it's tasking, latLon, alt, speed, direction
- exec files. save all commands and then run them from script
- remove units via delete and explode
- query objects: -q persistence.active returns boolean, true
-q x.y returns table, 12 elements
-q a.b.x returns number 12
-q d.e.f returns string "asdasda..."
-q sada reuturs <nil>
--]]--

View File

@ -1,6 +1,7 @@
unitPersistence = {}
unitPersistence.version = '1.0.0'
unitPersistence.version = '1.0.1'
unitPersistence.verbose = false
unitPersistence.updateTime = 60 -- seconds. Once every minute check statics
unitPersistence.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
@ -10,6 +11,10 @@ unitPersistence.requiredLibs = {
--[[--
Version History
1.0.0 - initial version
1.0.1 - handles late activation
- handles linked static objects
- does no longer mess with heliports
- update statics once a minute, not second
REQUIRES PERSISTENCE AND MX
@ -66,7 +71,7 @@ function unitPersistence.saveData()
-- process all static objects placed with ME
for oName, oData in pairs(unitPersistence.statics) do
if not oData.isDead then
if not oData.isDead or oData.lateActivation then
-- fetch the object and see if it's still alive
local theObject = StaticObject.getByName(oName)
if theObject and theObject:isExist() then
@ -75,17 +80,16 @@ function unitPersistence.saveData()
oData.x = pos.x
oData.y = pos.z -- (!!)
oData.isDead = theObject:getLife() < 1
-- trigger.action.outText("deadcheck: " .. oName .. " has health=" .. theObject:getLife(), 30)
oData.dead = oData.isDead
else
oData.isDead = true
oData.dead = true
-- trigger.action.outText("deadcheck: " .. oName .. " certified dead", 30)
end
end
if unitPersistence.verbose then
local note = "(ok)"
if oData.isDead then note = "(dead)" end
if oData.lateActivation then note = "(late active)" end
trigger.action.outText("unitPersistence: save - processed group <" .. oName .. ">. " .. note, 30)
end
end
@ -154,7 +158,7 @@ function unitPersistence.loadMission()
trigger.action.outText("+++ failed to add modified group <" .. groupName .. ">")
end
if unitPersistence.verbose then
trigger.action.outText("+++unitPersistence: updated group <" .. groupName .. "> of cat <" .. cat .. "> for cty <" .. cty .. ">", 30)
-- trigger.action.outText("+++unitPersistence: updated group <" .. groupName .. "> of cat <" .. cat .. "> for cty <" .. cty .. ">", 30)
end
end
end
@ -167,11 +171,27 @@ function unitPersistence.loadMission()
-- and now the same for static objects
if theData.statics then
for name, staticData in pairs(theData.statics) do
local theStatic = StaticObject.getByName(name)
if not theStatic then
mismatchWarning = true
--local theStatic = StaticObject.getByName(name)
if staticData.lateActivation then
-- this one will not be in the game now, skip
if unitPersistence.verbose then
trigger.action.outText("+++unitPersistence: static <" .. name .. "> is late activate, no update", 30)
end
--elseif not theStatic then
-- mismatchWarning = true
elseif staticData.category == "Heliports" then
-- FARPS are static objects that HATE to be
-- messed with, so we don't
if unitPersistence.verbose then
trigger.action.outText("+++unitPersistence: static <" .. name .. "> is Heliport, no update", 30)
end
else
local newStatic = dcsCommon.clone(staticData)
-- add link info if it exists
newStatic.linkUnit = cfxMX.linkByName[name]
if newStatic.linkUnit and unitPersistence.verbose then
trigger.action.outText("+++unitPersistence: linked static <" .. name .. "> to unit <" .. newStatic.linkUnit .. ">", 30)
end
local cty = staticData.cty
local cat = staticData.cat
-- spawn new one, replacing same.named old, dead if required
@ -182,7 +202,7 @@ function unitPersistence.loadMission()
if unitPersistence.verbose then
local note = ""
if newStatic.dead then note = " (dead)" end
trigger.action.outText("+++unitPersistence: updated static <" .. name .. "> for cty <" .. cty .. ">" .. note, 30)
-- trigger.action.outText("+++unitPersistence: updated static <" .. name .. "> for cty <" .. cty .. ">" .. note, 30)
end
end
end
@ -197,6 +217,31 @@ function unitPersistence.loadMission()
end
end
--
-- Update
--
function unitPersistence.update()
-- we check every minute
timer.scheduleFunction(unitPersistence.update, {}, timer.getTime() + unitPersistence.updateTime)
-- do a quick scan for all late activated static objects and if they
-- are suddently visible, remove their late activate state
--for groupName, groupdata in pairs(unitPersistence.groundTroops) do
-- currently not needed
--end
for objName, objData in pairs(unitPersistence.statics) do
if objData.lateActivation then
local theStatic = StaticObject.getByName(objData.name)
if theStatic then
objData.lateActivation = false
if unitPersistence.verbose then
trigger.action.outText("+++unitPersistence: <" .. objData.name .. "> has activated", 30)
end
end
end
end
end
--
-- Start
--
@ -250,13 +295,18 @@ function unitPersistence.start()
theStatic.isDead = false
theStatic.groupId = mxData.groupId
theStatic.cat = cfxMX.catText2ID("static")
theStatic.cty = cfxMX.countryByName[name]
local gameOb = StaticObject.getByName(theStatic.name)
if not gameOb then
trigger.action.outText("+++warning: static object <" .. theStatic.name .. "> does not exist in-game!?", 30)
if unitPersistence.verbose then
trigger.action.outText("+++unitPersistence: static object <" .. theStatic.name .. "> has late activation", 30)
end
theStatic.lateActivation = true
else
theStatic.cty = gameOb:getCountry()
unitPersistence.statics[theStatic.name] = theStatic
--theStatic.cty = gameOb:getCountry()
--unitPersistence.statics[theStatic.name] = theStatic
end
unitPersistence.statics[theStatic.name] = theStatic
end
end
@ -265,6 +315,9 @@ function unitPersistence.start()
unitPersistence.loadMission()
end
-- start update
unitPersistence.update()
return true
end
@ -274,3 +327,7 @@ if not unitPersistence.start() then
end
unitPersistence = nil
end
--[[--
ToDo: linked statics and linked units on restore
--]]--