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 = {}
FARPZones.version = "1.1.0" FARPZones.version = "1.2.0"
FARPZones.verbose = false FARPZones.verbose = false
--[[-- --[[--
Version History Version History
@ -11,6 +11,9 @@ FARPZones.verbose = false
- rFormation attribute added - rFormation attribute added
- verbose flag - verbose flag
- verbose cleanup ("FZ: something happened") - 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.allFARPZones = {}
FARPZones.startingUp = false FARPZones.startingUp = false -- not needed / read anywhere
-- FARP ZONE ACCESS -- FARP ZONE ACCESS
function FARPZones.addFARPZone(aFARP) function FARPZones.addFARPZone(aFARP)
@ -90,6 +93,15 @@ function FARPZones.getFARPForZone(aZone)
return FARPZones.allFARPZones[aZone] return FARPZones.allFARPZones[aZone]
end 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) function FARPZones.getFARPZoneForFARP(aFarp)
-- find the first FARP zone that associates with -- find the first FARP zone that associates with
-- aFARP (an airField) -- aFARP (an airField)
@ -123,9 +135,9 @@ function FARPZones.createFARPFromZone(aZone)
if #mapFarps == 0 then if #mapFarps == 0 then
trigger.action.outText("***Farp Zones: no FARP found for zone " .. aZone.name, 30) trigger.action.outText("***Farp Zones: no FARP found for zone " .. aZone.name, 30)
else 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) -- trigger.action.outText("Associated FARP " .. aFarp:getName() .. " with FARP Zone " .. aZone.name, 30)
end --end
theFarp.mainFarp = theFarp.myFarps[1] theFarp.mainFarp = theFarp.myFarps[1]
theFarp.point = theFarp.mainFarp:getPoint() -- this is FARP, not zone!!! theFarp.point = theFarp.mainFarp:getPoint() -- this is FARP, not zone!!!
@ -317,7 +329,7 @@ function FARPZones.produceVehicles(theFarp)
local theCoalition = theFarp.owner local theCoalition = theFarp.owner
if theTypes ~= "none" then if theTypes ~= "none" then
local theGroup = cfxZones.createGroundUnitsInZoneForCoalition ( local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
theCoalition, theCoalition,
theFarp.name .. "-D" .. theFarp.count, -- must be unique theFarp.name .. "-D" .. theFarp.count, -- must be unique
theFarp.defZone, theFarp.defZone,
@ -326,10 +338,11 @@ function FARPZones.produceVehicles(theFarp)
theFarp.defHeading) theFarp.defHeading)
-- we do not add these troops to ground troop management -- we do not add these troops to ground troop management
theFarp.defenders = theGroup -- but we retain a handle just in case theFarp.defenders = theGroup -- but we retain a handle just in case
theFarp.defenderData = theData
end end
unitTypes = FARPZones.resourceTypes unitTypes = FARPZones.resourceTypes
local theGroup = cfxZones.createGroundUnitsInZoneForCoalition ( local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
theCoalition, theCoalition,
theFarp.name .. "-R" .. theFarp.count, -- must be unique theFarp.name .. "-R" .. theFarp.count, -- must be unique
theFarp.resZone, theFarp.resZone,
@ -337,7 +350,7 @@ function FARPZones.produceVehicles(theFarp)
"line_v", "line_v",
theFarp.resHeading) theFarp.resHeading)
theFarp.resources = theGroup theFarp.resources = theGroup
theFarp.resourceData = theData
-- update unique counter -- update unique counter
theFarp.count = theFarp.count + 1 theFarp.count = theFarp.count + 1
end end
@ -382,6 +395,23 @@ function FARPZones.somethingHappened(event)
end end
local newOwner = aFarp:getCoalition() 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" local blueRed = "Red"
if newOwner == 2 then blueRed = "Blue" end if newOwner == 2 then blueRed = "Blue" end
trigger.action.outText("FARP " .. zonedFarp.zone.name .. " captured by " .. blueRed .."!", 30) trigger.action.outText("FARP " .. zonedFarp.zone.name .. " captured by " .. blueRed .."!", 30)
@ -408,6 +438,80 @@ function FARPZones.somethingHappened(event)
end 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 -- Start
@ -439,7 +543,7 @@ function FARPZones.start()
return false return false
end end
FARPZones.startingUp = true FARPZones.startingUp = true -- not needed / read anywhere
-- read config zone -- read config zone
FARPZones.readConfig() FARPZones.readConfig()
@ -449,19 +553,41 @@ function FARPZones.start()
FARPZones.preProcessor, FARPZones.preProcessor,
FARPZones.postProcessor) 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 -- collect all FARP Zones
local theZones = cfxZones.getZonesWithAttributeNamed("FARP") local theZones = cfxZones.getZonesWithAttributeNamed("FARP")
for k, aZone in pairs(theZones) do for k, aZone in pairs(theZones) do
local aFARP = FARPZones.createFARPFromZone(aZone) -- read attributes from DCS local aFARP = FARPZones.createFARPFromZone(aZone) -- read attributes from DCS
FARPZones.addFARPZone(aFARP) -- add to managed zones FARPZones.addFARPZone(aFARP) -- add to managed zones
FARPZones.drawFARPCircleInMap(aFARP) -- mark in map -- moved FARPZones.drawFARPCircleInMap(aFARP) -- mark in map
FARPZones.produceVehicles(aFARP) -- allocate initial vehicles -- moved FARPZones.produceVehicles(aFARP) -- allocate initial vehicles
if FARPZones.verbose then if FARPZones.verbose then
trigger.action.outText("processed FARP <" .. aZone.name .. "> now owned by " .. aZone.owner, 30) trigger.action.outText("processed FARP <" .. aZone.name .. "> now owned by " .. aZone.owner, 30)
end end
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) trigger.action.outText("cf/x FARP Zones v" .. FARPZones.version .. " started", 30)
return true return true
@ -480,4 +606,6 @@ Improvements:
make hidden farps only appear for owning side 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, {}) cfxArtilleryZones.invokeCallbacksFor('fire', aZone, {})
end end
--
-- API main entry call for firing at zone
-- invokes doFireAt()
--
function cfxArtilleryZones.simFireAtZone(aZone, aGroup, dist) function cfxArtilleryZones.simFireAtZone(aZone, aGroup, dist)
if not dist then dist = aZone.spotRange end if not dist then dist = aZone.spotRange end

View File

@ -1,5 +1,5 @@
cfxMX = {} cfxMX = {}
cfxMX.version = "1.2.0" cfxMX.version = "1.2.1"
cfxMX.verbose = false cfxMX.verbose = false
--[[-- --[[--
Mission data decoder. Access to ME-built mission structures Mission data decoder. Access to ME-built mission structures
@ -16,14 +16,15 @@ cfxMX.verbose = false
1.2.0 - added group name reference table 1.2.0 - added group name reference table
- added group type reference - added group type reference
- added references for allFixed, allHelo, allGround, allSea, allStatic - added references for allFixed, allHelo, allGround, allSea, allStatic
1.2.1 - added countryByName
- added linkByName
--]]-- --]]--
cfxMX.groupNamesByID = {} cfxMX.groupNamesByID = {}
cfxMX.groupIDbyName = {} cfxMX.groupIDbyName = {}
cfxMX.groupDataByName = {} cfxMX.groupDataByName = {}
cfxMX.countryByName ={}
cfxMX.linkByName = {}
cfxMX.allFixedByName = {} cfxMX.allFixedByName = {}
cfxMX.allHeloByName = {} cfxMX.allHeloByName = {}
cfxMX.allGroundByName = {} cfxMX.allGroundByName = {}
@ -193,11 +194,20 @@ function cfxMX.createCrossReferences()
local category = obj_type_name 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! 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 for group_num, group_data in pairs(obj_type_data.group) do
local aName = group_data.name local aName = group_data.name
local aID = group_data.groupId 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.groupNamesByID[aID] = aName
cfxMX.groupIDbyName[aName] = aID cfxMX.groupIDbyName[aName] = aID
cfxMX.groupDataByName[aName] = group_data cfxMX.groupDataByName[aName] = group_data
cfxMX.countryByName[aName] = cntry_id
-- now make the type-specific xrefs -- now make the type-specific xrefs
if obj_type_name == "helicopter" then if obj_type_name == "helicopter" then
cfxMX.allHeloByName[aName] = group_data cfxMX.allHeloByName[aName] = group_data

View File

@ -1,22 +1,24 @@
cfxObjectDestructDetector = {} cfxObjectDestructDetector = {}
cfxObjectDestructDetector.version = "1.2.0" cfxObjectDestructDetector.version = "1.3.0"
cfxObjectDestructDetector.verbose = false
cfxObjectDestructDetector.requiredLibs = { cfxObjectDestructDetector.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
"cfxZones", -- Zones, of course "cfxZones", -- Zones, of course
} }
cfxObjectDestructDetector.verbose = false
--[[-- --[[--
VERSION HISTORY VERSION HISTORY
1.0.0 initial version, based on parashoo, arty zones 1.0.0 initial version, based on parashoo, arty zones
1.0.1 fixed bug: trigger.MISC.getUserFlag() 1.0.1 fixed bug: trigger.MISC.getUserFlag()
1.1.0 added support for method, f! and destroyed! 1.1.0 added support for method, f! and destroyed!
1.2.0 DML / Watchflag support 1.2.0 DML / Watchflag support
1.3.0 Persistence support
Detect when an object with OBJECT ID as assigned in ME dies Detect when an object with OBJECT ID as assigned in ME dies
*** EXTENDS ZONES *** EXTENDS ZONES
--]]-- --]]--
cfxObjectDestructDetector.objectZones = {} cfxObjectDestructDetector.objectZones = {}
-- --
@ -41,6 +43,14 @@ function cfxObjectDestructDetector.addObjectDetectZone(aZone)
table.insert(cfxObjectDestructDetector.objectZones, aZone) table.insert(cfxObjectDestructDetector.objectZones, aZone)
end 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 -- processing of zones
-- --
@ -48,6 +58,10 @@ function cfxObjectDestructDetector.processObjectDestructZone(aZone)
aZone.name = cfxZones.getStringFromZoneProperty(aZone, "NAME", aZone.name) aZone.name = cfxZones.getStringFromZoneProperty(aZone, "NAME", aZone.name)
-- aZone.coalition = cfxZones.getCoalitionFromZoneProperty(aZone, "coalition", 0) -- aZone.coalition = cfxZones.getCoalitionFromZoneProperty(aZone, "coalition", 0)
aZone.ID = cfxZones.getNumberFromZoneProperty(aZone, "OBJECT ID", 1) -- THIS! 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 if cfxZones.hasProperty(aZone, "setFlag") then
aZone.setFlag = cfxZones.getStringFromZoneProperty(aZone, "setFlag", "999") aZone.setFlag = cfxZones.getStringFromZoneProperty(aZone, "setFlag", "999")
end end
@ -73,16 +87,16 @@ function cfxObjectDestructDetector.processObjectDestructZone(aZone)
aZone.decreaseFlag = cfxZones.getStringFromZoneProperty(aZone, "f-1", "999") aZone.decreaseFlag = cfxZones.getStringFromZoneProperty(aZone, "f-1", "999")
end end
-- new method support -- DML method support
aZone.oddMethod = cfxZones.getStringFromZoneProperty(aZone, "method", "flip") aZone.oddMethod = cfxZones.getStringFromZoneProperty(aZone, "method", "inc")
if cfxZones.hasProperty(aZone, "oddMethod") then if cfxZones.hasProperty(aZone, "oddMethod") then
aZone.oddMethod = cfxZones.getStringFromZoneProperty(aZone, "oddMethod", "flip") aZone.oddMethod = cfxZones.getStringFromZoneProperty(aZone, "oddMethod", "inc")
end end
if cfxZones.hasProperty(aZone, "f!") then -- we now always have that property
aZone.outDestroyFlag = cfxZones.getStringFromZoneProperty(aZone, "f!", "*none") aZone.outDestroyFlag = cfxZones.getStringFromZoneProperty(aZone, "f!", "*none")
end
if cfxZones.hasProperty(aZone, "destroyed!") then if cfxZones.hasProperty(aZone, "destroyed!") then
aZone.outDestroyFlag = cfxZones.getStringFromZoneProperty(aZone, "destroyed!", "*none") aZone.outDestroyFlag = cfxZones.getStringFromZoneProperty(aZone, "destroyed!", "*none")
end end
@ -102,7 +116,7 @@ function cfxObjectDestructDetector:onEvent(event)
if not id then return end if not id then return end
for idx, aZone in pairs(cfxObjectDestructDetector.objectZones) do for idx, aZone in pairs(cfxObjectDestructDetector.objectZones) do
if aZone.ID == id then if (not aZone.isDestroyed) and aZone.ID == id then
-- flag manipulation -- flag manipulation
-- OLD FLAG SUPPORT, SOON TO BE REMOVED -- OLD FLAG SUPPORT, SOON TO BE REMOVED
if aZone.setFlag then if aZone.setFlag then
@ -128,7 +142,7 @@ function cfxObjectDestructDetector:onEvent(event)
-- invoke callbacks -- invoke callbacks
cfxObjectDestructDetector.invokeCallbacksFor(aZone) cfxObjectDestructDetector.invokeCallbacksFor(aZone)
if cfxObjectDestructDetector.verbose then if aZone.verbose or cfxObjectDestructDetector.verbose then
trigger.action.outText("OBJECT KILL: " .. id, 30) trigger.action.outText("OBJECT KILL: " .. id, 30)
end end
@ -136,6 +150,9 @@ function cfxObjectDestructDetector:onEvent(event)
-- for better performance since it cant -- for better performance since it cant
-- die twice -- die twice
-- save state for persistence
aZone.isDestroyed = true
return return
end end
end end
@ -143,8 +160,85 @@ function cfxObjectDestructDetector:onEvent(event)
end end
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() function cfxObjectDestructDetector.start()
if not dcsCommon.libCheck("cfx Object Destruct Detector", if not dcsCommon.libCheck("cfx Object Destruct Detector",
@ -152,20 +246,38 @@ function cfxObjectDestructDetector.start()
return false return false
end end
-- collect all zones with 'smoke' attribute -- collect all zones with 'OBJECT id' attribute
-- collect all spawn zones -- collect all spawn zones
local attrZones = cfxZones.getZonesWithAttributeNamed("OBJECT ID") 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 for k, aZone in pairs(attrZones) do
cfxObjectDestructDetector.processObjectDestructZone(aZone) -- process attribute and add to zone properties (extend zone) 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 end
-- add myself as event handler -- add myself as event handler
world.addEventHandler(cfxObjectDestructDetector) 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 -- say hi
trigger.action.outText("cfx Object Destruct Zones v" .. cfxObjectDestructDetector.version .. " started.", 30) trigger.action.outText("cfx Object Destruct Zones v" .. cfxObjectDestructDetector.version .. " started.", 30)
return true return true

View File

@ -1,7 +1,8 @@
cfxOwnedZones = {} cfxOwnedZones = {}
cfxOwnedZones.version = "1.1.2" cfxOwnedZones.version = "1.2.0"
cfxOwnedZones.verbose = false cfxOwnedZones.verbose = false
cfxOwnedZones.announcer = true cfxOwnedZones.announcer = true
cfxOwnedZones.name = "cfxOwnedZones"
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
1.0.3 - added getNearestFriendlyZone 1.0.3 - added getNearestFriendlyZone
@ -21,7 +22,7 @@ cfxOwnedZones.announcer = true
- support of 'none' type string to indicate no attackers/defenders - support of 'none' type string to indicate no attackers/defenders
- updated property access - updated property access
- module check - module check
- cfxOwnedTroop.usesDefenders(aZone) - cfxOwnedZones.usesDefenders(aZone)
- verifyZone - verifyZone
1.0.8 - repairDefenders trims types to allow blanks in 1.0.8 - repairDefenders trims types to allow blanks in
type separator type separator
@ -41,6 +42,9 @@ cfxOwnedZones.announcer = true
- announcer - announcer
1.1.1 - conq+1 flag 1.1.1 - conq+1 flag
1.1.2 - corrected type bug in zoneConquered 1.1.2 - corrected type bug in zoneConquered
1.2.0 - support for persistence
- conq+1 --> conquered!
- no cfxGroundTroop bug (no delay)
--]]-- --]]--
cfxOwnedZones.requiredLibs = { 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.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% 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 -- owned zones is a module that managers 'conquerable' zones and keeps a
-- record of who owns the zone -- record of who owns the zone
-- based on some simple rules that are regularly checked -- based on some simple rules that are regularly checked
@ -166,6 +174,12 @@ function cfxOwnedZones.drawZoneInMap(aZone)
end 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) function cfxOwnedZones.addOwnedZone(aZone)
local owner = cfxZones.getCoalitionFromZoneProperty(aZone, "owner", 0) -- is already readm read it again 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) local paused = cfxZones.getBoolFromZoneProperty(aZone, "paused", false)
aZone.paused = paused aZone.paused = paused
aZone.conqueredFlag = cfxZones.getStringFromZoneProperty(aZone, "conquered!", "*<cfxnone>")
if cfxZones.hasProperty(aZone, "conq+1") then if cfxZones.hasProperty(aZone, "conq+1") then
cfxOwnedZones.conqueredFlag = cfxZones.getNumberFromZoneProperty(theZone, "conq+1", -1) aZone.conqueredFlag = cfxZones.getStringFromZoneProperty(aZone, "conq+1", "*<cfxnone>")
end end
aZone.unbeatable = cfxZones.getBoolFromZoneProperty(aZone, "unbeatable", false) aZone.unbeatable = cfxZones.getBoolFromZoneProperty(aZone, "unbeatable", false)
@ -218,7 +233,7 @@ end
function cfxOwnedZones.verifyZone(aZone) function cfxOwnedZones.verifyZone(aZone)
-- do some sanity checks -- do some sanity checks
if not cfxGroundTroops and (aZone.attackersRED ~= "none" or aZone.attackersBLUE ~= "none") then 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
end end
@ -375,14 +390,14 @@ function cfxOwnedZones.spawnAttackTroops(theTypes, aZone, aCoalition, aFormation
local spawnZone = cfxZones.createSimpleZone("attkSpawnZone", spawnPoint, aZone.attackRadius) local spawnZone = cfxZones.createSimpleZone("attkSpawnZone", spawnPoint, aZone.attackRadius)
local theGroup = cfxZones.createGroundUnitsInZoneForCoalition ( local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
aCoalition, -- theCountry, aCoalition, -- theCountry,
aZone.name .. " (A) " .. dcsCommon.numberUUID(), -- must be unique aZone.name .. " (A) " .. dcsCommon.numberUUID(), -- must be unique
spawnZone, spawnZone,
unitTypes, unitTypes,
aFormation, -- outward facing aFormation, -- outward facing
0) 0)
return theGroup return theGroup, theData
end end
function cfxOwnedZones.spawnDefensiveTroops(theTypes, aZone, aCoalition, aFormation) function cfxOwnedZones.spawnDefensiveTroops(theTypes, aZone, aCoalition, aFormation)
@ -400,13 +415,13 @@ function cfxOwnedZones.spawnDefensiveTroops(theTypes, aZone, aCoalition, aFormat
--local theCountry = dcsCommon.coalition2county(aCoalition) --local theCountry = dcsCommon.coalition2county(aCoalition)
local spawnZone = cfxZones.createSimpleZone("spawnZone", aZone.point, aZone.spawnRadius) local spawnZone = cfxZones.createSimpleZone("spawnZone", aZone.point, aZone.spawnRadius)
local theGroup = cfxZones.createGroundUnitsInZoneForCoalition ( local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
aCoalition, --theCountry, aCoalition, --theCountry,
aZone.name .. " (D) " .. dcsCommon.numberUUID(), -- must be unique aZone.name .. " (D) " .. dcsCommon.numberUUID(), -- must be unique
spawnZone, unitTypes, spawnZone, unitTypes,
aFormation, -- outward facing aFormation, -- outward facing
0) 0)
return theGroup return theGroup, theData
end end
-- --
-- U P D A T E -- U P D A T E
@ -432,7 +447,13 @@ function cfxOwnedZones.sendOutAttackers(aZone)
if attackers == "none" then return end 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 -- submit them to ground troops handler as zoneseekers
-- and our groundTroops module will handle the rest -- and our groundTroops module will handle the rest
@ -496,10 +517,11 @@ function cfxOwnedZones.zoneConquered(aZone, theSide, formerOwner) -- 0 = neutral
end end
end end
-- increase conq flag -- increase conq flag
if aZone.conqueredFlag then -- if aZone.conqueredFlag then
local lastVal = trigger.misc.getUserFlag(aZone.conqueredFlag) -- local lastVal = trigger.misc.getUserFlag(aZone.conqueredFlag)
trigger.action.setUserFlag(aZone.conqueredFlag, lastVal + 1) -- trigger.action.setUserFlag(aZone.conqueredFlag, lastVal + 1)
end cfxZones.pollFlag(aZone.conqueredFlag, "inc", aZone)
-- end
-- invoke callbacks now -- invoke callbacks now
cfxOwnedZones.invokeConqueredCallbacks(aZone, theSide, formerOwner) 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 -- now livingTypes holds the full array of units we need to spawn
local theCountry = dcsCommon.getACountryForCoalition(aZone.owner) local theCountry = dcsCommon.getACountryForCoalition(aZone.owner)
local spawnZone = cfxZones.createSimpleZone("spawnZone", aZone.point, aZone.spawnRadius) local spawnZone = cfxZones.createSimpleZone("spawnZone", aZone.point, aZone.spawnRadius)
local theGroup = cfxZones.createGroundUnitsInZoneForCoalition ( local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
aZone.owner, -- was wrongly: theCountry aZone.owner, -- was wrongly: theCountry
aZone.name .. dcsCommon.numberUUID(), -- must be unique aZone.name .. dcsCommon.numberUUID(), -- must be unique
spawnZone, spawnZone,
@ -599,15 +621,17 @@ function cfxOwnedZones.spawnDefenders(aZone)
-- if 'none', simply exit -- if 'none', simply exit
if defenders == "none" then return end 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 -- 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 if theGroup then
aZone.defenderMax = theGroup:getInitialSize() -- so we can determine if some units were destroyed --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.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) --trigger.action.outText("+++ spawned defenders for ".. aZone.name, 30)
else else
trigger.action.outText("+++owdZ: WARNING: spawned no defenders for ".. aZone.name, 30) trigger.action.outText("+++owdZ: WARNING: spawned no defenders for ".. aZone.name, 30)
aZone.defenderData = nil
end end
end end
@ -661,6 +685,10 @@ function cfxOwnedZones.updateZone(aZone)
-- we have defenders -- we have defenders
if aZone.defenders:isExist() then if aZone.defenders:isExist() then
-- isee if group was damaged -- 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 if aZone.defenders:getSize() < aZone.lastDefenders then
-- yes, at least one unit destroyed -- yes, at least one unit destroyed
aZone.timeStamp = timer.getTime() aZone.timeStamp = timer.getTime()
@ -671,6 +699,8 @@ function cfxOwnedZones.updateZone(aZone)
aZone.state = "shocked" aZone.state = "shocked"
return return
else
aZone.lastDefenders = aZone.defenders:getSize()
end end
else else
@ -725,17 +755,19 @@ function cfxOwnedZones.updateZone(aZone)
-- we are currently rebuilding defenders unit by unit -- we are currently rebuilding defenders unit by unit
if timer.getTime() > aZone.timeStamp + cfxOwnedZones.repairTime then if timer.getTime() > aZone.timeStamp + cfxOwnedZones.repairTime then
aZone.timeStamp = timer.getTime() aZone.timeStamp = timer.getTime()
-- wait's up, repair one defender, then check if full strength
cfxOwnedZones.repairDefenders(aZone) 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 -- we are at max size, time to produce some attackers
-- progress to next state
nextState = "attacking" nextState = "attacking"
aZone.timeStamp = timer.getTime() aZone.timeStamp = timer.getTime()
if cfxOwnedZones.verbose then if cfxOwnedZones.verbose then
trigger.action.outText("+++owdZ: State " .. aZone.state .. " to " .. nextState .. " for " .. aZone.name, 30) trigger.action.outText("+++owdZ: State " .. aZone.state .. " to " .. nextState .. " for " .. aZone.name, 30)
end end
end end
-- see if we are full strenght and if so go to attack, else set timer to reair the next unit
end end
elseif aZone.state == "shocked" then elseif aZone.state == "shocked" then
@ -763,6 +795,20 @@ function cfxOwnedZones.updateZone(aZone)
aZone.state = nextState aZone.state = nextState
end 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() function cfxOwnedZones.update()
cfxOwnedZones.updateSchedule = timer.scheduleFunction(cfxOwnedZones.update, {}, timer.getTime() + 1/cfxOwnedZones.ups) cfxOwnedZones.updateSchedule = timer.scheduleFunction(cfxOwnedZones.update, {}, timer.getTime() + 1/cfxOwnedZones.ups)
@ -792,6 +838,11 @@ function cfxOwnedZones.update()
end end
function cfxOwnedZones.houseKeeping()
timer.scheduleFunction(cfxOwnedZones.houseKeeping, {}, timer.getTime() + 5 * 60) -- every 5 minutes
cfxOwnedZones.GC()
end
function cfxOwnedZones.sideOwnsAll(theSide) function cfxOwnedZones.sideOwnsAll(theSide)
for key, aZone in pairs(cfxOwnedZones.zones) do for key, aZone in pairs(cfxOwnedZones.zones) do
if aZone.owner ~= theSide then if aZone.owner ~= theSide then
@ -810,19 +861,150 @@ function cfxOwnedZones.hasOwnedZones()
return false return false
end 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) 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.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
cfxOwnedZones.announcer = cfxZones.getBoolFromZoneProperty(theZone, "announcer", true) cfxOwnedZones.announcer = cfxZones.getBoolFromZoneProperty(theZone, "announcer", true)
if cfxZones.hasProperty(theZone, "r!") then -- if cfxZones.hasProperty(theZone, "r!") then
cfxOwnedZones.redTriggerFlag = cfxZones.getStringFromZoneProperty(theZone, "r!", "<none>") cfxOwnedZones.redTriggerFlag = cfxZones.getStringFromZoneProperty(theZone, "r!", "*<cfxnone>")
end -- end
if cfxZones.hasProperty(theZone, "b!") then -- if cfxZones.hasProperty(theZone, "b!") then
cfxOwnedZones.blueTriggerFlag = cfxZones.getStringFromZoneProperty(theZone, "b!", "<none>") cfxOwnedZones.blueTriggerFlag = cfxZones.getStringFromZoneProperty(theZone, "b!", "*<cfxnone>")
end -- end
if cfxZones.hasProperty(theZone, "n!") then -- if cfxZones.hasProperty(theZone, "n!") then
cfxOwnedZones.neutralTriggerFlag = cfxZones.getStringFromZoneProperty(theZone, "n!", "<none>") cfxOwnedZones.neutralTriggerFlag = cfxZones.getStringFromZoneProperty(theZone, "n!", "*<cfxnone>")
end -- end
cfxOwnedZones.defendingTime = cfxZones.getNumberFromZoneProperty(theZone, "defendingTime", 100) cfxOwnedZones.defendingTime = cfxZones.getNumberFromZoneProperty(theZone, "defendingTime", 100)
cfxOwnedZones.attackingTime = cfxZones.getNumberFromZoneProperty(theZone, "attackingTime", 300) cfxOwnedZones.attackingTime = cfxZones.getNumberFromZoneProperty(theZone, "attackingTime", 300)
cfxOwnedZones.shockTime = cfxZones.getNumberFromZoneProperty(theZone, "shockTime", 200) cfxOwnedZones.shockTime = cfxZones.getNumberFromZoneProperty(theZone, "shockTime", 200)
@ -838,11 +1020,8 @@ function cfxOwnedZones.init()
-- read my config zone -- read my config zone
local theZone = cfxZones.getZoneByName("ownedZonesConfig") local theZone = cfxZones.getZoneByName("ownedZonesConfig")
if not theZone then
trigger.action.outText("+++ownZ: no config", 30)
else
cfxOwnedZones.readConfigZone(theZone) cfxOwnedZones.readConfigZone(theZone)
end
-- collect all owned zones by their 'owner' property -- collect all owned zones by their 'owner' property
-- start the process -- start the process
@ -854,9 +1033,21 @@ function cfxOwnedZones.init()
cfxOwnedZones.addOwnedZone(aZone) cfxOwnedZones.addOwnedZone(aZone)
end 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 initialized = true
cfxOwnedZones.updateSchedule = timer.scheduleFunction(cfxOwnedZones.update, {}, timer.getTime() + 1/cfxOwnedZones.ups) 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) trigger.action.outText("cx/x owned zones v".. cfxOwnedZones.version .. " started", 30)
return true return true

View File

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

View File

@ -1,5 +1,5 @@
cfxZones = {} cfxZones = {}
cfxZones.version = "2.8.4" cfxZones.version = "2.8.5"
-- cf/x zone management module -- cf/x zone management module
-- reads dcs zones and makes them accessible and mutable -- 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" - changed extractPropertyFromDCS() to also match attributes with blanks like "the Attr" to "theAttr"
- new expandFlagName() - new expandFlagName()
- 2.8.4 - fixed bug in setFlagValue() - 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 cfxZones.verbose = false
@ -1047,8 +1050,17 @@ function cfxZones.createGroundUnitsInZoneForCoalition (theCoalition, groupName,
-- first we need to translate the coalition to a legal -- first we need to translate the coalition to a legal
-- country. we use UN for neutral, cjtf for red and blue -- country. we use UN for neutral, cjtf for red and blue
local theSideCJTF = dcsCommon.coalition2county(theCoalition) 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 end
-- parsing zone names. The first part of the name until the first blank " " -- parsing zone names. The first part of the name until the first blank " "
@ -1311,8 +1323,8 @@ end
function cfxZones.getFlagValue(theFlag, theZone) function cfxZones.getFlagValue(theFlag, theZone)
local zoneName = "<dummy>" local zoneName = "<dummy>"
if not theZone then if not theZone or not theZone.name then
trigger.action.outText("+++Zne: no zone on getFlagValue", 30) trigger.action.outText("+++Zne: no zone or zone name on getFlagValue")
else else
zoneName = theZone.name -- for flag wildcards zoneName = theZone.name -- for flag wildcards
end end

View File

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

View File

@ -1,19 +1,23 @@
cloneZones = {} cloneZones = {}
cloneZones.version = "1.4.8" cloneZones.version = "1.4.9"
cloneZones.verbose = false cloneZones.verbose = false
cloneZones.requiredLibs = { cloneZones.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
"cfxZones", -- Zones, of course "cfxZones", -- Zones, of course
"cfxMX", "cfxMX",
} }
-- groupTracker is OPTIONAL! and required only with trackWith attribute cloneZones.minSep = 10 -- minimal separation for onRoad auto-pos
cloneZones.maxIter = 100 -- maximum number of attempts to resolve
-- a too-close separation
cloneZones.cloners = {} -- groupTracker is OPTIONAL! and required only with trackWith attribute
cloneZones.callbacks = {}
cloneZones.unitXlate = {} cloneZones.cloners = {}
cloneZones.groupXlate = {} -- used to translate original groupID to cloned. only holds last spawned group id cloneZones.callbacks = {}
cloneZones.uniqueCounter = 9200000 -- we start group numbering here cloneZones.unitXlate = {}
--[[-- cloneZones.groupXlate = {} -- used to translate original groupID to cloned. only holds last spawned group id
cloneZones.uniqueCounter = 9200000 -- we start group numbering here
--[[--
Clones Groups from ME mission data Clones Groups from ME mission data
Copyright (c) 2022 by Christian Franz and cf/x AG Copyright (c) 2022 by Christian Franz and cf/x AG
@ -47,18 +51,20 @@
1.4.6 - removed some verbosity for spawned aircraft with airfields on their routes 1.4.6 - removed some verbosity for spawned aircraft with airfields on their routes
1.4.7 - DML watchflag and DML Flag polish, method-->cloneMethod 1.4.7 - DML watchflag and DML Flag polish, method-->cloneMethod
1.4.8 - added 'wipe?' synonym 1.4.8 - added 'wipe?' synonym
1.4.9 - onRoad option
- rndHeading option
--]]-- --]]--
-- --
-- adding / removing from list -- adding / removing from list
-- --
function cloneZones.addCloneZone(theZone) function cloneZones.addCloneZone(theZone)
table.insert(cloneZones.cloners, theZone) table.insert(cloneZones.cloners, theZone)
end end
function cloneZones.getCloneZoneByName(aName) function cloneZones.getCloneZoneByName(aName)
for idx, aZone in pairs(cloneZones.cloners) do for idx, aZone in pairs(cloneZones.cloners) do
if aName == aZone.name then return aZone end if aName == aZone.name then return aZone end
end end
@ -67,28 +73,28 @@
end end
return nil return nil
end end
-- --
-- callbacks -- callbacks
-- --
function cloneZones.addCallback(theCallback) function cloneZones.addCallback(theCallback)
if not theCallback then return end if not theCallback then return end
table.insert(cloneZones.callbacks, theCallback) table.insert(cloneZones.callbacks, theCallback)
end end
-- reasons for callback -- reasons for callback
-- "will despawn group" - args is the group about to be despawned -- "will despawn group" - args is the group about to be despawned
-- "did spawn group" -- args is group that was spawned -- "did spawn group" -- args is group that was spawned
-- "will despawn static" -- "will despawn static"
-- "did spawn static" -- "did spawn static"
-- "spawned" -- completed spawn cycle. args contains .groups and .statics spawned -- "spawned" -- completed spawn cycle. args contains .groups and .statics spawned
-- "empty" -- all spawns have been killed, args is empty -- "empty" -- all spawns have been killed, args is empty
-- "wiped" -- preWipe executed -- "wiped" -- preWipe executed
-- "<none" -- something went wrong -- "<none" -- something went wrong
function cloneZones.invokeCallbacks(theZone, reason, args) function cloneZones.invokeCallbacks(theZone, reason, args)
if not theZone then return end if not theZone then return end
if not reason then reason = "<none>" end if not reason then reason = "<none>" end
if not args then args = {} end if not args then args = {} end
@ -98,14 +104,14 @@
for idx, cb in pairs(cloneZones.callbacks) do for idx, cb in pairs(cloneZones.callbacks) do
cb(theZone, reason, args) cb(theZone, reason, args)
end end
end end
-- group translation orig id -- group translation orig id
-- --
-- reading zones -- reading zones
-- --
function cloneZones.partOfGroupDataInZone(theZone, theUnits) function cloneZones.partOfGroupDataInZone(theZone, theUnits)
local zP = cfxZones.getPoint(theZone) local zP = cfxZones.getPoint(theZone)
zP.y = 0 zP.y = 0
@ -118,9 +124,9 @@
if dist <= theZone.radius then return true end if dist <= theZone.radius then return true end
end end
return false return false
end end
function cloneZones.allGroupsInZoneByData(theZone) function cloneZones.allGroupsInZoneByData(theZone)
local theGroupsInZone = {} local theGroupsInZone = {}
local radius = theZone.radius local radius = theZone.radius
for groupName, groupData in pairs(cfxMX.groupDataByName) do for groupName, groupData in pairs(cfxMX.groupDataByName) do
@ -132,9 +138,9 @@
end end
end end
return theGroupsInZone return theGroupsInZone
end end
function cloneZones.createClonerWithZone(theZone) -- has "Cloner" function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
if cloneZones.verbose then if cloneZones.verbose then
trigger.action.outText("+++clnZ: new cloner " .. theZone.name, 30) trigger.action.outText("+++clnZ: new cloner " .. theZone.name, 30)
end end
@ -170,8 +176,8 @@
-- resolving internal references -- resolving internal references
local rawData, cat, ctry = cfxMX.getGroupFromDCSbyName(gName) local rawData, cat, ctry = cfxMX.getGroupFromDCSbyName(gName)
local origID = rawData.groupId local origID = rawData.groupId
-- cloneZones.templateGroups[gName] = origID -- cloneZones.templateGroups[gName] = origID
-- cloneZones.templateGroupsReverse[origID] = gName -- cloneZones.templateGroupsReverse[origID] = gName
end end
end end
@ -278,18 +284,22 @@
if cfxZones.hasProperty(theZone, "rndLoc") then if cfxZones.hasProperty(theZone, "rndLoc") then
theZone.rndLoc = cfxZones.getBoolFromZoneProperty(theZone, "rndLoc", false) theZone.rndLoc = cfxZones.getBoolFromZoneProperty(theZone, "rndLoc", false)
end end
theZone.rndHeading = cfxZones.getBoolFromZoneProperty(theZone, "rndHeading", false)
theZone.onRoad = cfxZones.getBoolFromZoneProperty(theZone, "onRoad", false)
if theZone.rndLoc and theZone.verbose then if theZone.rndLoc and theZone.verbose then
trigger.action.outText("+++ rndloc on for " .. theZone.name, 30) trigger.action.outText("+++ rndloc on for " .. theZone.name, 30)
end end
-- we end with clear plate -- we end with clear plate
end end
-- --
-- spawning, despawning -- spawning, despawning
-- --
function cloneZones.despawnAll(theZone) function cloneZones.despawnAll(theZone)
if cloneZones.verbose then if cloneZones.verbose then
trigger.action.outText("wiping <" .. theZone.name .. ">", 30) trigger.action.outText("wiping <" .. theZone.name .. ">", 30)
end end
@ -314,9 +324,9 @@
end end
theZone.mySpawns = {} theZone.mySpawns = {}
theZone.myStatics = {} theZone.myStatics = {}
end end
function cloneZones.updateLocationsInGroupData(theData, zoneDelta, adjustAllWaypoints) function cloneZones.updateLocationsInGroupData(theData, zoneDelta, adjustAllWaypoints)
-- remember that zoneDelta's [z] modifies theData's y!! -- remember that zoneDelta's [z] modifies theData's y!!
theData.x = theData.x + zoneDelta.x theData.x = theData.x + zoneDelta.x
@ -376,26 +386,26 @@
end end
end end
end end
end end
function cloneZones.uniqueID() function cloneZones.uniqueID()
local uid = cloneZones.uniqueCounter local uid = cloneZones.uniqueCounter
cloneZones.uniqueCounter = cloneZones.uniqueCounter + 1 cloneZones.uniqueCounter = cloneZones.uniqueCounter + 1
return uid return uid
end end
function cloneZones.uniqueNameGroupData(theData) function cloneZones.uniqueNameGroupData(theData)
theData.name = dcsCommon.uuid(theData.name) theData.name = dcsCommon.uuid(theData.name)
local units = theData.units local units = theData.units
for idx, aUnit in pairs(units) do for idx, aUnit in pairs(units) do
aUnit.name = dcsCommon.uuid(aUnit.name) aUnit.name = dcsCommon.uuid(aUnit.name)
end end
end end
function cloneZones.uniqueIDGroupData(theData) function cloneZones.uniqueIDGroupData(theData)
theData.groupId = cloneZones.uniqueID() theData.groupId = cloneZones.uniqueID()
end end
function cloneZones.uniqueIDUnitData(theData) function cloneZones.uniqueIDUnitData(theData)
if not theData then return end if not theData then return end
if not theData.units then return end if not theData.units then return end
local units = theData.units local units = theData.units
@ -405,9 +415,9 @@
aUnit.CZTargetID = aUnit.unitId aUnit.CZTargetID = aUnit.unitId
end end
end end
function cloneZones.resolveOwnership(spawnZone, ctry) function cloneZones.resolveOwnership(spawnZone, ctry)
if not spawnZone.masterOwner then return ctry end if not spawnZone.masterOwner then return ctry end
local masterZone = cfxZones.getZoneByName(spawnZone.masterOwner) local masterZone = cfxZones.getZoneByName(spawnZone.masterOwner)
@ -422,13 +432,13 @@
ctry = dcsCommon.getACountryForCoalition(masterZone.owner) ctry = dcsCommon.getACountryForCoalition(masterZone.owner)
return ctry return ctry
end end
-- --
-- resolve external group references -- resolve external group references
-- --
function cloneZones.resolveGroupID(gID, rawData, dataTable, reason) function cloneZones.resolveGroupID(gID, rawData, dataTable, reason)
if not reason then reason = "<default>" end if not reason then reason = "<default>" end
local resolvedID = gID local resolvedID = gID
@ -459,10 +469,10 @@
-- if we get here, reference is not to a cloned item -- if we get here, reference is not to a cloned item
--trigger.action.outText("resolved " .. gID .. " to " .. resolvedID, 30) --trigger.action.outText("resolved " .. gID .. " to " .. resolvedID, 30)
return resolvedID return resolvedID
end end
function cloneZones.resolveUnitID(uID, rawData, dataTable, reason) function cloneZones.resolveUnitID(uID, rawData, dataTable, reason)
-- also resolves statics as they share ID with units -- also resolves statics as they share ID with units
local resolvedID = uID local resolvedID = uID
--trigger.action.outText("Resolve reference to unitId <" .. uID .. "> for <" .. reason.. "> task", 30) --trigger.action.outText("Resolve reference to unitId <" .. uID .. "> for <" .. reason.. "> task", 30)
@ -491,9 +501,9 @@
-- if we get here, reference is not to a cloned item -- if we get here, reference is not to a cloned item
--trigger.action.outText("resolved G-" .. uID .. " to " .. resolvedID, 30) --trigger.action.outText("resolved G-" .. uID .. " to " .. resolvedID, 30)
return resolvedID return resolvedID
end end
function cloneZones.resolveStaticLinkUnit(uID) function cloneZones.resolveStaticLinkUnit(uID)
local resolvedID = uID local resolvedID = uID
local lastClone = cloneZones.unitXlate[uID] local lastClone = cloneZones.unitXlate[uID]
if lastClone then if lastClone then
@ -502,11 +512,11 @@
return resolvedID return resolvedID
end end
return resolvedID return resolvedID
end end
function cloneZones.resolveWPReferences(rawData, theZone, dataTable) function cloneZones.resolveWPReferences(rawData, theZone, dataTable)
-- check to see if we really need data table, as we have theZone -- check to see if we really need data table, as we have theZone
-- perform a check of route for group or unit references -- perform a check of route for group or unit references
if not rawData then return end if not rawData then return end
local myOName = rawData.CZorigName local myOName = rawData.CZorigName
@ -604,9 +614,9 @@
end end
end end
end end
end end
function cloneZones.resolveReferences(theZone, dataTable) function cloneZones.resolveReferences(theZone, dataTable)
-- when an action refers to another group, we check if -- when an action refers to another group, we check if
-- the group referred to is also a clone, and update -- the group referred to is also a clone, and update
-- the reference to the newest incardnation -- the reference to the newest incardnation
@ -615,10 +625,10 @@
-- resolve references in waypoints -- resolve references in waypoints
cloneZones.resolveWPReferences(rawData, theZone, dataTable) cloneZones.resolveWPReferences(rawData, theZone, dataTable)
end end
end end
function cloneZones.handoffTracking(theGroup, theZone) function cloneZones.handoffTracking(theGroup, theZone)
if not groupTracker then if not groupTracker then
trigger.action.outText("+++clne: <" .. theZone.name .. "> trackWith requires groupTracker module", 30) trigger.action.outText("+++clne: <" .. theZone.name .. "> trackWith requires groupTracker module", 30)
return return
@ -649,12 +659,12 @@
end end
end end
end end
end end
function cloneZones.spawnWithTemplateForZone(theZone, spawnZone) function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
-- theZone is the cloner with the template -- theZone is the cloner with the template
-- spawnZone is the spawner with settings -- spawnZone is the spawner with settings
--if not spawnZone then spawnZone = theZone end -- if not spawnZone then spawnZone = theZone end
local newCenter = cfxZones.getPoint(spawnZone) local newCenter = cfxZones.getPoint(spawnZone)
-- calculate zoneDelta, is added to all vectors -- calculate zoneDelta, is added to all vectors
local zoneDelta = dcsCommon.vSub(newCenter, theZone.origin) local zoneDelta = dcsCommon.vSub(newCenter, theZone.origin)
@ -695,6 +705,49 @@
end end
end end
if spawnZone.rndHeading then
local units = rawData.units
for idx, aUnit in pairs(units) do
local phi = 6.2831 * math.random() -- that's 2Pi, folx
aUnit.heading = phi
end
end
-- apply onRoad option if selected
if spawnZone.onRoad then
local units = rawData.units
local iterCount = 0
local otherLocs = {} -- resolved locs
for idx, aUnit in pairs(units) do
local cx = aUnit.x
local cy = aUnit.y
-- we now iterate until there is enough separation or too many iters
local tooClose
local np, nx, ny
repeat
nx, ny = land.getClosestPointOnRoads("roads", cx, cy)
-- compare this with all other locs
np = {x=nx, y=ny}
tooClose = false
for idc, op in pairs(otherLocs) do
local d = dcsCommon.dist(np, op)
if d < cloneZones.minSep then
tooClose = true
cx = cx + cloneZones.minSep
cy = cy + cloneZones.minSep
iterCount = iterCount + 1
-- trigger.action.outText("d fail for <" .. aUnit.name.. ">: d= <" .. d .. ">, iters = <" .. iterCount .. ">", 30)
end
end
until (iterCount > cloneZones.maxIter) or (not tooClose)
-- trigger.action.outText("separation iters for <" .. aUnit.name.. ">:<" .. iterCount .. ">", 30)
table.insert(otherLocs, np)
aUnit.x = nx
aUnit.y = ny
end
end
-- apply turning -- apply turning
dcsCommon.rotateGroupData(rawData, spawnZone.turn, newCenter.x, newCenter.z) dcsCommon.rotateGroupData(rawData, spawnZone.turn, newCenter.x, newCenter.z)
@ -790,6 +843,19 @@
rawData.y = rawData.y + dy rawData.y = rawData.y + dy
end end
if spawnZone.rndHeading then
local phi = 6.2831 * math.random() -- that's 2Pi, folx
rawData.heading = phi
end
if spawnZone.onRoad then
local cx = rawData.x
local cy = rawData.y
local nx, ny = land.getClosestPointOnRoads("roads", cx, cy)
rawData.x = nx
rawData.y = ny
end
-- apply turning -- apply turning
dcsCommon.rotateUnitData(rawData, spawnZone.turn, newCenter.x, newCenter.z) dcsCommon.rotateUnitData(rawData, spawnZone.turn, newCenter.x, newCenter.z)
@ -818,7 +884,7 @@
rawData.offsets.x = rawData.offsets.x - zoneDelta.x rawData.offsets.x = rawData.offsets.x - zoneDelta.x
rawData.offsets.angle = rawData.offsets.angle + spawnZone.turn rawData.offsets.angle = rawData.offsets.angle + spawnZone.turn
rawData.linkOffset = true rawData.linkOffset = true
-- trigger.action.outText("zone deltas are " .. zoneDelta.x .. ", " .. zoneDelta.y, 30) -- trigger.action.outText("zone deltas are " .. zoneDelta.x .. ", " .. zoneDelta.y, 30)
end end
local isCargo = rawData.canCargo local isCargo = rawData.canCargo
@ -828,7 +894,7 @@
-- we don't mix groups with units, so no lookup tables for -- we don't mix groups with units, so no lookup tables for
-- statics -- statics
if newStaticID == rawData.CZTargetID then if newStaticID == rawData.CZTargetID then
-- trigger.action.outText("Static ID OK: " .. newStaticID .. " for " .. rawData.name, 30) -- trigger.action.outText("Static ID OK: " .. newStaticID .. " for " .. rawData.name, 30)
else else
trigger.action.outText("Static ID mismatch: " .. newStaticID .. " vs (target) " .. rawData.CZTargetID .. " for " .. rawData.name, 30) trigger.action.outText("Static ID mismatch: " .. newStaticID .. " vs (target) " .. rawData.CZTargetID .. " for " .. rawData.name, 30)
end end
@ -858,9 +924,9 @@
args.statics = spawnedStatics args.statics = spawnedStatics
cloneZones.invokeCallbacks(theZone, "spawned", args) cloneZones.invokeCallbacks(theZone, "spawned", args)
return spawnedGroups, spawnedStatics return spawnedGroups, spawnedStatics
end end
function cloneZones.spawnWithCloner(theZone) function cloneZones.spawnWithCloner(theZone)
if not theZone then if not theZone then
trigger.action.outText("+++clnZ: nil zone on spawnWithCloner", 30) trigger.action.outText("+++clnZ: nil zone on spawnWithCloner", 30)
return return
@ -925,9 +991,9 @@
theZone.mySpawns = {} theZone.mySpawns = {}
theZone.myStatics = {} theZone.myStatics = {}
end end
end end
function cloneZones.countLiveUnits(theZone) function cloneZones.countLiveUnits(theZone)
if not theZone then return 0 end if not theZone then return 0 end
local count = 0 local count = 0
-- count units -- count units
@ -953,9 +1019,9 @@
end end
end end
return count return count
end end
function cloneZones.hasLiveUnits(theZone) function cloneZones.hasLiveUnits(theZone)
if not theZone then return 0 end if not theZone then return 0 end
if theZone.mySpawns then if theZone.mySpawns then
for idx, aGroup in pairs(theZone.mySpawns) do for idx, aGroup in pairs(theZone.mySpawns) do
@ -979,12 +1045,12 @@
end end
return false return false
end end
-- --
-- UPDATE -- UPDATE
-- --
function cloneZones.update() function cloneZones.update()
timer.scheduleFunction(cloneZones.update, {}, timer.getTime() + 1) timer.scheduleFunction(cloneZones.update, {}, timer.getTime() + 1)
for idx, aZone in pairs(cloneZones.cloners) do for idx, aZone in pairs(cloneZones.cloners) do
@ -1031,9 +1097,9 @@
end end
end end
end end
function cloneZones.onStart() function cloneZones.onStart()
--trigger.action.outText("+++clnZ: Enter atStart", 30) --trigger.action.outText("+++clnZ: Enter atStart", 30)
for idx, theZone in pairs(cloneZones.cloners) do for idx, theZone in pairs(cloneZones.cloners) do
if theZone.onStart then if theZone.onStart then
@ -1044,12 +1110,12 @@
end end
end end
end end
-- --
-- START -- START
-- --
function cloneZones.readConfigZone() function cloneZones.readConfigZone()
local theZone = cfxZones.getZoneByName("cloneZonesConfig") local theZone = cfxZones.getZoneByName("cloneZonesConfig")
if not theZone then if not theZone then
if cloneZones.verbose then if cloneZones.verbose then
@ -1063,9 +1129,9 @@
if cloneZones.verbose then if cloneZones.verbose then
trigger.action.outText("+++clnZ: read config", 30) trigger.action.outText("+++clnZ: read config", 30)
end end
end end
function cloneZones.start() function cloneZones.start()
-- lib check -- lib check
if not dcsCommon.libCheck then if not dcsCommon.libCheck then
trigger.action.outText("cfx Clone Zones requires dcsCommon", 30) trigger.action.outText("cfx Clone Zones requires dcsCommon", 30)
@ -1101,20 +1167,20 @@
trigger.action.outText("cfx Clone Zones v" .. cloneZones.version .. " started.", 30) trigger.action.outText("cfx Clone Zones v" .. cloneZones.version .. " started.", 30)
return true return true
end end
-- let's go! -- let's go!
if not cloneZones.start() then if not cloneZones.start() then
trigger.action.outText("cf/x Clone Zones aborted: missing libraries", 30) trigger.action.outText("cf/x Clone Zones aborted: missing libraries", 30)
cloneZones = nil cloneZones = nil
end end
--[[-- --[[--
to resolve tasks to resolve tasks
- AFAC - AFAC
- FAC Assign group - FAC Assign group
- set freq for unit - set freq for unit
--]]-- --]]--

View File

@ -1,5 +1,5 @@
dcsCommon = {} dcsCommon = {}
dcsCommon.version = "2.6.8" dcsCommon.version = "2.7.0"
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
2.2.6 - compassPositionOfARelativeToB 2.2.6 - compassPositionOfARelativeToB
- clockPositionOfARelativeToB - clockPositionOfARelativeToB
@ -85,6 +85,11 @@ dcsCommon.version = "2.6.8"
2.6.7 - new menu2text() 2.6.7 - new menu2text()
2.6.8 - new getMissionName() 2.6.8 - new getMissionName()
- new flagArrayFromString() - 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 -- dcsCommon is a library of common lua functions
@ -456,6 +461,7 @@ dcsCommon.version = "2.6.8"
return dcsCommon.dist(point1, point2) return dcsCommon.dist(point1, point2)
end end
-- distance between points -- distance between points
function dcsCommon.dist(point1, point2) -- returns distance between two points function dcsCommon.dist(point1, point2) -- returns distance between two points
-- supports xyz and xy notations -- 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, -- topClone is a shallow clone of orig, only top level is iterated,
-- all values are ref-copied -- all values are ref-copied
function dcsCommon.topClone(orig) function dcsCommon.topClone(orig)
if not orig then return nil end
local orig_type = type(orig) local orig_type = type(orig)
local copy local copy
if orig_type == 'table' then if orig_type == 'table' then
@ -898,6 +905,7 @@ dcsCommon.version = "2.6.8"
-- clone is a recursive clone which will also clone -- clone is a recursive clone which will also clone
-- deeper levels, as used in units -- deeper levels, as used in units
function dcsCommon.clone(orig) function dcsCommon.clone(orig)
if not orig then return nil end
local orig_type = type(orig) local orig_type = type(orig)
local copy local copy
if orig_type == 'table' then if orig_type == 'table' then
@ -913,6 +921,8 @@ dcsCommon.version = "2.6.8"
end end
function dcsCommon.copyArray(inArray) function dcsCommon.copyArray(inArray)
if not inArray then return nil end
-- warning: this is a ref copy! -- warning: this is a ref copy!
local theCopy = {} local theCopy = {}
for idx, element in pairs(inArray) do for idx, element in pairs(inArray) do
@ -1687,6 +1697,28 @@ dcsCommon.version = "2.6.8"
end 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 -- M I S C M E T H O D S
@ -2440,6 +2472,42 @@ function dcsCommon.flagArrayFromString(inString, verbose)
end end
return flags return flags
end 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 -- INIT

View File

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

View File

@ -1,8 +1,8 @@
-- theDebugger -- theDebugger
debugger = {} debugger = {}
debugger.version = "1.1.1" debugger.version = "1.1.2"
debugDemon = {} debugDemon = {}
debugDemon.version = "1.1.1" debugDemon.version = "1.1.2"
debugger.verbose = false debugger.verbose = false
debugger.ups = 4 -- every 0.25 second debugger.ups = 4 -- every 0.25 second
@ -22,6 +22,7 @@ debugger.log = ""
- persistence of logs - persistence of logs
- save <name> - save <name>
1.1.1 - warning when trying to set a flag to a non-int 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 debugger = nil
end end
--[[--
debug on and off. globally, not per zone
--]]--
-- --
-- DEBUG DEMON -- DEBUG DEMON
@ -625,15 +621,6 @@ end
-- --
-- Helpers -- 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) function debugDemon.createObserver(aName)
local observer = cfxZones.createSimpleZone(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\n " .. debugDemon.markOfDemon .. "snap [<observername>] -- create new snapshot of flags" ..
"\n " .. debugDemon.markOfDemon .. "compare -- compare snapshot flag values with current" .. "\n " .. debugDemon.markOfDemon .. "compare -- compare snapshot flag values with current" ..
"\n " .. debugDemon.markOfDemon .. "note <your note> -- add <your note> to the text log" .. "\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\n " .. debugDemon.markOfDemon .. "start -- starts debugger" ..
"\n " .. debugDemon.markOfDemon .. "stop -- stop debugger" .. "\n " .. debugDemon.markOfDemon .. "stop -- stop debugger" ..
@ -1154,6 +1142,40 @@ function debugDemon.processSaveCommand(args, event)
debugger.saveLog(aName) debugger.saveLog(aName)
return true return true
end 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 -- init and start
-- --
@ -1223,6 +1245,7 @@ function debugDemon.init()
debugDemon.addCommndProcessor("?", debugDemon.processHelpCommand) debugDemon.addCommndProcessor("?", debugDemon.processHelpCommand)
debugDemon.addCommndProcessor("help", debugDemon.processHelpCommand) debugDemon.addCommndProcessor("help", debugDemon.processHelpCommand)
debugDemon.addCommndProcessor("remove", debugDemon.processRemoveCommand)
return true return true
end end
@ -1259,6 +1282,11 @@ end
- inspect objects, dumping category, life, if it's tasking, latLon, alt, speed, direction - inspect objects, dumping category, life, if it's tasking, latLon, alt, speed, direction
- exec files. save all commands and then run them from script - 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 = {}
unitPersistence.version = '1.0.0' unitPersistence.version = '1.0.1'
unitPersistence.verbose = false unitPersistence.verbose = false
unitPersistence.updateTime = 60 -- seconds. Once every minute check statics
unitPersistence.requiredLibs = { unitPersistence.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
"cfxZones", -- Zones, of course "cfxZones", -- Zones, of course
@ -10,6 +11,10 @@ unitPersistence.requiredLibs = {
--[[-- --[[--
Version History Version History
1.0.0 - initial version 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 REQUIRES PERSISTENCE AND MX
@ -66,7 +71,7 @@ function unitPersistence.saveData()
-- process all static objects placed with ME -- process all static objects placed with ME
for oName, oData in pairs(unitPersistence.statics) do 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 -- fetch the object and see if it's still alive
local theObject = StaticObject.getByName(oName) local theObject = StaticObject.getByName(oName)
if theObject and theObject:isExist() then if theObject and theObject:isExist() then
@ -75,17 +80,16 @@ function unitPersistence.saveData()
oData.x = pos.x oData.x = pos.x
oData.y = pos.z -- (!!) oData.y = pos.z -- (!!)
oData.isDead = theObject:getLife() < 1 oData.isDead = theObject:getLife() < 1
-- trigger.action.outText("deadcheck: " .. oName .. " has health=" .. theObject:getLife(), 30)
oData.dead = oData.isDead oData.dead = oData.isDead
else else
oData.isDead = true oData.isDead = true
oData.dead = true oData.dead = true
-- trigger.action.outText("deadcheck: " .. oName .. " certified dead", 30)
end end
end end
if unitPersistence.verbose then if unitPersistence.verbose then
local note = "(ok)" local note = "(ok)"
if oData.isDead then note = "(dead)" end 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) trigger.action.outText("unitPersistence: save - processed group <" .. oName .. ">. " .. note, 30)
end end
end end
@ -154,7 +158,7 @@ function unitPersistence.loadMission()
trigger.action.outText("+++ failed to add modified group <" .. groupName .. ">") trigger.action.outText("+++ failed to add modified group <" .. groupName .. ">")
end end
if unitPersistence.verbose then 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 end
end end
@ -167,11 +171,27 @@ function unitPersistence.loadMission()
-- and now the same for static objects -- and now the same for static objects
if theData.statics then if theData.statics then
for name, staticData in pairs(theData.statics) do for name, staticData in pairs(theData.statics) do
local theStatic = StaticObject.getByName(name) --local theStatic = StaticObject.getByName(name)
if not theStatic then if staticData.lateActivation then
mismatchWarning = true -- 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 else
local newStatic = dcsCommon.clone(staticData) 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 cty = staticData.cty
local cat = staticData.cat local cat = staticData.cat
-- spawn new one, replacing same.named old, dead if required -- spawn new one, replacing same.named old, dead if required
@ -182,7 +202,7 @@ function unitPersistence.loadMission()
if unitPersistence.verbose then if unitPersistence.verbose then
local note = "" local note = ""
if newStatic.dead then note = " (dead)" end 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 end
end end
@ -197,6 +217,31 @@ function unitPersistence.loadMission()
end end
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 -- Start
-- --
@ -250,13 +295,18 @@ function unitPersistence.start()
theStatic.isDead = false theStatic.isDead = false
theStatic.groupId = mxData.groupId theStatic.groupId = mxData.groupId
theStatic.cat = cfxMX.catText2ID("static") theStatic.cat = cfxMX.catText2ID("static")
theStatic.cty = cfxMX.countryByName[name]
local gameOb = StaticObject.getByName(theStatic.name) local gameOb = StaticObject.getByName(theStatic.name)
if not gameOb then if not gameOb then
trigger.action.outText("+++warning: static object <" .. theStatic.name .. "> does not exist in-game!?", 30) if unitPersistence.verbose then
else trigger.action.outText("+++unitPersistence: static object <" .. theStatic.name .. "> has late activation", 30)
theStatic.cty = gameOb:getCountry()
unitPersistence.statics[theStatic.name] = theStatic
end end
theStatic.lateActivation = true
else
--theStatic.cty = gameOb:getCountry()
--unitPersistence.statics[theStatic.name] = theStatic
end
unitPersistence.statics[theStatic.name] = theStatic
end end
end end
@ -265,6 +315,9 @@ function unitPersistence.start()
unitPersistence.loadMission() unitPersistence.loadMission()
end end
-- start update
unitPersistence.update()
return true return true
end end
@ -274,3 +327,7 @@ if not unitPersistence.start() then
end end
unitPersistence = nil unitPersistence = nil
end end
--[[--
ToDo: linked statics and linked units on restore
--]]--