Version 1.2.1

CloneZones:
- nameScheme
- indentical
This commit is contained in:
Christian Franz 2023-01-12 16:40:50 +01:00
parent 46afa49c3a
commit 43d598f035
10 changed files with 440 additions and 57 deletions

Binary file not shown.

Binary file not shown.

View File

@ -1,10 +1,10 @@
cfxMX = {} cfxMX = {}
cfxMX.version = "1.2.4" cfxMX.version = "1.2.5"
cfxMX.verbose = false cfxMX.verbose = false
--[[-- --[[--
Mission data decoder. Access to ME-built mission structures Mission data decoder. Access to ME-built mission structures
Copyright (c) 2022 by Christian Franz and cf/x AG Copyright (c) 2022, 2023 by Christian Franz and cf/x AG
Version History Version History
1.0.0 - initial version 1.0.0 - initial version
@ -24,9 +24,11 @@ cfxMX.verbose = false
1.2.3 - groupTypeByName 1.2.3 - groupTypeByName
- groupCoalitionByName - groupCoalitionByName
1.2.4 - playerUnit2Group cross index 1.2.4 - playerUnit2Group cross index
1.2.5 - unitIDbyName index added
--]]-- --]]--
cfxMX.groupNamesByID = {} cfxMX.groupNamesByID = {}
cfxMX.groupIDbyName = {} cfxMX.groupIDbyName = {}
cfxMX.unitIDbyName = {}
cfxMX.groupDataByName = {} cfxMX.groupDataByName = {}
cfxMX.groupTypeByName = {} -- category of group: "helicopter", "plane", "ship"... cfxMX.groupTypeByName = {} -- category of group: "helicopter", "plane", "ship"...
cfxMX.groupCoalitionByName = {} cfxMX.groupCoalitionByName = {}
@ -234,7 +236,7 @@ function cfxMX.createCrossReferences()
trigger.action.outText("+++MX: <" .. obj_type_name .. "> unknown type for <" .. aName .. ">", 30) trigger.action.outText("+++MX: <" .. obj_type_name .. "> unknown type for <" .. aName .. ">", 30)
end end
-- now iterate all units in this group -- now iterate all units in this group
-- for player into -- for unit xref like player info and ID
for unit_num, unit_data in pairs(group_data.units) do for unit_num, unit_data in pairs(group_data.units) do
if unit_data.skill then if unit_data.skill then
if unit_data.skill == "Client" or unit_data.skill == "Player" then if unit_data.skill == "Client" or unit_data.skill == "Player" then
@ -244,6 +246,7 @@ function cfxMX.createCrossReferences()
cfxMX.playerUnit2Group[unit_data.name] = group_data cfxMX.playerUnit2Group[unit_data.name] = group_data
end -- if unit skill client end -- if unit skill client
end -- if has skill end -- if has skill
cfxMX.unitIDbyName[unit_data.name] = unit_data.unitId
end -- for all units end -- for all units
end -- for all groups end -- for all groups
end --if has category data end --if has category data

View File

@ -1,5 +1,5 @@
cfxObjectSpawnZones = {} cfxObjectSpawnZones = {}
cfxObjectSpawnZones.version = "1.3.0" cfxObjectSpawnZones.version = "1.3.1"
cfxObjectSpawnZones.requiredLibs = { cfxObjectSpawnZones.requiredLibs = {
"dcsCommon", -- common is of course needed for everything "dcsCommon", -- common is of course needed for everything
-- pretty stupid to check for this since we -- pretty stupid to check for this since we
@ -30,6 +30,8 @@ cfxObjectSpawnZones.verbose = false
-- 1.3.0 - better synonym handling -- 1.3.0 - better synonym handling
-- - useDelicates link to delicate when spawned -- - useDelicates link to delicate when spawned
-- - spawned single and multi-objects can be made delicates -- - spawned single and multi-objects can be made delicates
-- 1.3.1 - baseName can be set to zone's name by giving "*"
-- respawn currently happens after theSpawns is deleted and cooldown seconds have passed -- respawn currently happens after theSpawns is deleted and cooldown seconds have passed
cfxObjectSpawnZones.allSpawners = {} cfxObjectSpawnZones.allSpawners = {}
@ -92,6 +94,10 @@ function cfxObjectSpawnZones.createSpawner(inZone)
theSpawner.rawOwner = coalition.getCountryCoalition(theSpawner.country) theSpawner.rawOwner = coalition.getCountryCoalition(theSpawner.country)
theSpawner.baseName = cfxZones.getStringFromZoneProperty(inZone, "baseName", dcsCommon.uuid("objSpwn")) theSpawner.baseName = cfxZones.getStringFromZoneProperty(inZone, "baseName", dcsCommon.uuid("objSpwn"))
theSpawner.baseName = dcsCommon.trim(theSpawner.baseName)
if theSpawner.baseName == "*" then
theSpawner.baseName = inZone.name -- convenience shortcut
end
--cfxZones.getZoneProperty(inZone, "baseName") --cfxZones.getZoneProperty(inZone, "baseName")
theSpawner.cooldown = cfxZones.getNumberFromZoneProperty(inZone, "cooldown", 60) theSpawner.cooldown = cfxZones.getNumberFromZoneProperty(inZone, "cooldown", 60)
theSpawner.lastSpawnTimeStamp = -10000 -- just init so it will always work theSpawner.lastSpawnTimeStamp = -10000 -- just init so it will always work

View File

@ -1,5 +1,5 @@
cfxSpawnZones = {} cfxSpawnZones = {}
cfxSpawnZones.version = "1.7.1" cfxSpawnZones.version = "1.7.2"
cfxSpawnZones.requiredLibs = { cfxSpawnZones.requiredLibs = {
"dcsCommon", -- common is of course needed for everything "dcsCommon", -- common is of course needed for everything
-- pretty stupid to check for this since we -- pretty stupid to check for this since we
@ -63,6 +63,7 @@ cfxSpawnZones.spawnedGroups = {}
-- 1.7.0 - persistence support -- 1.7.0 - persistence support
-- 1.7.1 - improved verbosity -- 1.7.1 - improved verbosity
-- - spelling check -- - spelling check
-- 1.7.2 - baseName now can can be set to zone name by issuing "*"
-- --
-- new version requires cfxGroundTroops, where they are -- new version requires cfxGroundTroops, where they are
-- --
@ -198,6 +199,10 @@ function cfxSpawnZones.createSpawner(inZone)
theSpawner.rawOwner = coalition.getCountryCoalition(theSpawner.country) theSpawner.rawOwner = coalition.getCountryCoalition(theSpawner.country)
--theSpawner.baseName = cfxZones.getZoneProperty(inZone, "baseName") --theSpawner.baseName = cfxZones.getZoneProperty(inZone, "baseName")
theSpawner.baseName = cfxZones.getStringFromZoneProperty(inZone, "baseName", dcsCommon.uuid("SpwnDflt")) theSpawner.baseName = cfxZones.getStringFromZoneProperty(inZone, "baseName", dcsCommon.uuid("SpwnDflt"))
theSpawner.baseName = dcsCommon.trim(theSpawner.baseName)
if theSpawner.baseName == "*" then
theSpawner.baseName = inZone.name -- convenience shortcut
end
theSpawner.cooldown = cfxZones.getNumberFromZoneProperty(inZone, "cooldown", 60) theSpawner.cooldown = cfxZones.getNumberFromZoneProperty(inZone, "cooldown", 60)
theSpawner.autoRemove = cfxZones.getBoolFromZoneProperty(inZone, "autoRemove", false) theSpawner.autoRemove = cfxZones.getBoolFromZoneProperty(inZone, "autoRemove", false)

View File

@ -116,6 +116,7 @@ cfxZones.version = "3.0.0"
- 3.0.0 - support for DCS 2.8 linkUnit attribute, integration with - 3.0.0 - support for DCS 2.8 linkUnit attribute, integration with
linedUnit and warning. linedUnit and warning.
- initZoneVerbosity() - initZoneVerbosity()
- 3.0.1 - updateMovingZones() better tracks linked units by name
--]]-- --]]--
@ -2304,35 +2305,52 @@ end
function cfxZones.updateMovingZones() function cfxZones.updateMovingZones()
cfxZones.updateSchedule = timer.scheduleFunction(cfxZones.updateMovingZones, {}, timer.getTime() + 1/cfxZones.ups) cfxZones.updateSchedule = timer.scheduleFunction(cfxZones.updateMovingZones, {}, timer.getTime() + 1/cfxZones.ups)
-- simply scan all cfx zones for the linkedUnit property and if there -- simply scan all cfx zones for the linkName property, and if present
-- update the zone's points -- update the zone's points
for aName,aZone in pairs(cfxZones.zones) do for aName,aZone in pairs(cfxZones.zones) do
if aZone.linkBroken then -- only do this if ther is a linkName property,
-- try to relink -- else this zone isn't linked. link name is harmonized from
cfxZones.initLink(aZone) -- both linkUnit non-DML and linedUnit DML
end if aZone.linkName then
if aZone.linkedUnit then if aZone.linkBroken then
local theUnit = aZone.linkedUnit -- try to relink
-- has a link. is link existing? cfxZones.initLink(aZone)
if theUnit:isExist() then else --if aZone.linkName then
cfxZones.centerZoneOnUnit(aZone, theUnit) -- always re-acquire linkedUnit via Unit.getByName()
local dx = aZone.dx -- this way we gloss over any replacements via spawns
local dy = aZone.dy -- this is actually z aZone.linkedUnit = Unit.getByName(aZone.linkName)
if aZone.useHeading then end
dx, dy = cfxZones.calcHeadingOffset(aZone, theUnit)
if aZone.linkedUnit then
local theUnit = aZone.linkedUnit
-- has a link. is link existing?
if theUnit:isExist() then
cfxZones.centerZoneOnUnit(aZone, theUnit)
local dx = aZone.dx
local dy = aZone.dy -- this is actually z
if aZone.useHeading then
dx, dy = cfxZones.calcHeadingOffset(aZone, theUnit)
end
cfxZones.offsetZone(aZone, dx, dy)
else
-- we lost link (track level)
aZone.linkBroken = true
aZone.linkedUnit = nil
end end
cfxZones.offsetZone(aZone, dx, dy)
else else
-- we lost link -- we lost link (top level)
aZone.linkBroken = true aZone.linkBroken = true
aZone.linkedUnit = nil aZone.linkedUnit = nil
end end
else
-- this zone isn't linked
end end
end end
end end
function cfxZones.initLink(theZone) function cfxZones.initLink(theZone)
--trigger.action.outText("enter initlink for <" .. theZone.name .. ">", 30) --trigger.action.outText("enter initlink for <" .. theZone.name .. ">")
--if true then return end
--trigger.action.outText("entry verbose check: <" .. theZone.name .. "> is verbose = " .. dcsCommon.bool2YesNo(theZone.verbose), 30) --trigger.action.outText("entry verbose check: <" .. theZone.name .. "> is verbose = " .. dcsCommon.bool2YesNo(theZone.verbose), 30)
theZone.linkBroken = true theZone.linkBroken = true
theZone.linkedUnit = nil theZone.linkedUnit = nil

View File

@ -1,5 +1,5 @@
cloneZones = {} cloneZones = {}
cloneZones.version = "1.6.3" cloneZones.version = "1.7.0"
cloneZones.verbose = false cloneZones.verbose = false
cloneZones.requiredLibs = { cloneZones.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
@ -17,9 +17,14 @@ cloneZones.callbacks = {}
cloneZones.unitXlate = {} cloneZones.unitXlate = {}
cloneZones.groupXlate = {} -- used to translate original groupID to cloned. only holds last spawned group id cloneZones.groupXlate = {} -- used to translate original groupID to cloned. only holds last spawned group id
cloneZones.uniqueCounter = 9200000 -- we start group numbering here cloneZones.uniqueCounter = 9200000 -- we start group numbering here
cloneZones.lclUniqueCounter = 1 -- zone-local init value, can be config'dHeading
cloneZones.globalCounter = 1 -- module-global count
cloneZones.allClones = {} -- all clones spawned, regularly GC'd cloneZones.allClones = {} -- all clones spawned, regularly GC'd
cloneZones.allCObjects = {} -- all clones objects cloneZones.allCObjects = {} -- all clones objects
cloneZones.respawnOnGroupID = true
--[[-- --[[--
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
@ -75,6 +80,17 @@ cloneZones.allCObjects = {} -- all clones objects
1.6.3 - removed verbosity bug with rndLoc 1.6.3 - removed verbosity bug with rndLoc
uniqueNameGroupData has provisions for naming scheme uniqueNameGroupData has provisions for naming scheme
new uniqueNameStaticData() for naming scheme new uniqueNameStaticData() for naming scheme
1.6.4 - uniqueCounter is now configurable via config zone
new lclUniqueCounter config, also added to persistence
new globalCount
1.7.0 - wildcard "*" for masterOwner
- identical attribute: makes identical ID and name for unit and group as template
- new sameIDUnitData()
- nameScheme attribute: allow parametric names
- <o>, <z>, <s> wildcards
- <uid>, <lcl>, <i>, <g> wildcards
- identical=true overrides nameScheme
- masterOwner "*" convenience shortcut
--]]-- --]]--
@ -167,7 +183,9 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
if cloneZones.verbose or theZone.verbose then if cloneZones.verbose or theZone.verbose then
trigger.action.outText("+++clnZ: new cloner <" .. theZone.name ..">", 30) trigger.action.outText("+++clnZ: new cloner <" .. theZone.name ..">", 30)
end end
theZone.myUniqueCounter = cloneZones.lclUniqueCounter -- init local counter
local localZones = cloneZones.allGroupsInZoneByData(theZone) local localZones = cloneZones.allGroupsInZoneByData(theZone)
local localObjects = cfxZones.allStaticsInZone(theZone, true) -- true = use DCS origin, not moved zone local localObjects = cfxZones.allStaticsInZone(theZone, true) -- true = use DCS origin, not moved zone
if theZone.verbose then if theZone.verbose then
@ -301,7 +319,14 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
end end
if cfxZones.hasProperty(theZone, "masterOwner") then if cfxZones.hasProperty(theZone, "masterOwner") then
theZone.masterOwner = cfxZones.getStringFromZoneProperty(theZone, "masterOwner", "<none>") theZone.masterOwner = cfxZones.getStringFromZoneProperty(theZone, "masterOwner", "*")
theZone.masterOwner = dcsCommon.trim(theZone.masterOwner)
if theZone.masterOwner == "*" then
theZone.masterOwner = theZone.name
if theZone.verbose then
trigger.action.outText("+++clnZ: masterOwner for <" .. theZone.name .. "> successfully to to itself, currently owned by faction <" .. theZone.owner .. ">", 30)
end
end
end end
theZone.turn = cfxZones.getNumberFromZoneProperty(theZone, "turn", 0) theZone.turn = cfxZones.getNumberFromZoneProperty(theZone, "turn", 0)
@ -326,6 +351,20 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
theZone.onRoad = cfxZones.getBoolFromZoneProperty(theZone, "onRoad", false) theZone.onRoad = cfxZones.getBoolFromZoneProperty(theZone, "onRoad", false)
-- check for name scheme and / or identical
if cfxZones.hasProperty(theZone, "identical") then
theZone.identical = cfxZones.getBoolFromZoneProperty(theZone, "identical", false)
if theZone.identical == false then theZone.identical = nil end
end
if cfxZones.hasProperty(theZone, "nameScheme") then
theZone.nameScheme = cfxZones.getStringFromZoneProperty(theZone, "nameScheme", "<o>-<uid>") -- default to [<original name> "-" <uuid>]
end
if theZone.identical and theZone.nameScheme then
trigger.action.outText("+++clnZ: WARNING - clone zone <" .. theZone.name .. "> has both IDENTICAL and NAMESCHEME attributes. nameScheme is ignored.", 30)
theZone.nameScheme = nil
end
-- we end with clear plate -- we end with clear plate
end end
@ -509,17 +548,97 @@ function cloneZones.updateLocationsInGroupData(theData, zoneDelta, adjustAllWayp
end end
function cloneZones.nameFromSchema(schema, inName, theZone, sourceName, i)
-- default schema (classic) is "<o>-<uid>"
local outName = schema
local iter = i
-- replace all occurences of <o> with original name
outName = outName:gsub("<o>", inName)
-- replace all occurences of <z> with zone name
outName = outName:gsub("<z>", theZone.name)
-- replace all occurences of <s> with source zone name
outName = outName:gsub("<s>", sourceName)
-- uid (uuid) with auto-increment
local pos = string.find(outName, "<uid>")
while (pos and pos > 0) do
local uid = tostring(dcsCommon.numberUUID())
outName = outName:gsub("<uid>", uid, 1) -- only first substitution
pos = string.find(outName, "<uid>")
end
-- i (iter) with increment
pos = string.find(outName, "<i>")
while (pos and pos > 0) do
local uid = tostring(iter)
outName = outName:gsub("<i>", uid, 1) -- only first substitution
iter = iter + 1
pos = string.find(outName, "<i>")
end
-- lcl local (zonal) count with increment
pos = string.find(outName, "<lcl>")
while (pos and pos > 0) do
local uid = tostring(theZone.myUniqueCounter)
outName = outName:gsub("<lcl>", uid, 1) -- only first substitution
theZone.myUniqueCounter = theZone.myUniqueCounter + 1
pos = string.find(outName, "<lcl>")
end
-- g global (module) count with increment
pos = string.find(outName, "<g>")
while (pos and pos > 0) do
local uid = tostring(cloneZones.globalCounter)
outName = outName:gsub("<g>", uid, 1) -- only first substitution
cloneZones.globalCounter = cloneZones.globalCounter + 1
pos = string.find(outName, "<g>")
end
--if theZone.verbose then
-- trigger.action.outText("+++cln: schema [" .. schema .. "] for unit <" .. inName .. "> in zone <" .. theZone.name .. "> returns <" .. outName .. ">, iter = " .. iter, 30)
--end
return outName, iter
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, theCloneZone) function cloneZones.uniqueNameGroupData(theData, theCloneZone, sourceName)
if not sourceName then sourceName = theCloneZone.name end
theData.name = dcsCommon.uuid(theData.name) theData.name = dcsCommon.uuid(theData.name)
local units = theData.units local units = theData.units
local iterCount = 1
local newName = "none"
local allNames = {} -- enforce unique names inside group
for idx, aUnit in pairs(units) do for idx, aUnit in pairs(units) do
if theCloneZone and theCloneZone.namingScheme then if theCloneZone and theCloneZone.nameScheme then
local schema = theCloneZone.nameScheme
newName, iterCount = cloneZones.nameFromSchema(schema, aUnit.name, theCloneZone, sourceName, iterCount)
-- make sure that this name is has not been generated yet
-- inside the same group
local hasChanged = false
local schemeName = newName
while dcsCommon.arrayContainsString(allNames, newName) do
newName = newName .. "x"
hasChanged = true
end
if theCloneZone.verbose and hasChanged then
trigger.action.outText("cnlz: nameScheme [" .. theCloneZone.nameScheme .. "] failsafe: changed <" .. schemeName .. "> to <" .. newName .. ">", 30)
end
table.insert(allNames, newName)
if theCloneZone.verbose then
trigger.action.outText("clnZ: zone <" .. theCloneZone.name .. "> unit schema <" .. schema .. ">: <" .. aUnit.name .. "> --> <" .. newName .. ">", 30)
end
aUnit.name = newName -- dcsCommon.uuid(aUnit.name)
else else
-- default naming scheme: <name>-<uuid> -- default naming scheme: <name>-<uuid>
aUnit.name = dcsCommon.uuid(aUnit.name) aUnit.name = dcsCommon.uuid(aUnit.name)
@ -527,9 +646,25 @@ function cloneZones.uniqueNameGroupData(theData, theCloneZone)
end end
end end
function cloneZones.uniqueNameStaticData(theData, spawnZone) function cloneZones.uniqueNameStaticData(theData, theCloneZone, sourcename)
theData.name = dcsCommon.uuid(theData.name) if not sourceName then sourceName = theCloneZone.name end
-- WARNING: unlike GroupData enters with UNIT data
local iterCount = 1
local newName = "none"
if theCloneZone and theCloneZone.nameScheme then
local schema = theCloneZone.nameScheme
newName, iterCount = cloneZones.nameFromSchema(schema, theData.name, theCloneZone, sourceName, iterCount)
if theCloneZone.verbose then
trigger.action.outText("clnZ: zone <" .. theCloneZone.name .. "> static schema <" .. schema .. ">: <" .. theData.name .. "> --> <" .. newName .. ">", 30)
end
theData.name = newName -- dcsCommon.uuid(theData.name)
else
-- default naming scheme: <name>-<uuid>
theData.name = dcsCommon.uuid(theData.name)
end
end end
function cloneZones.uniqueIDGroupData(theData) function cloneZones.uniqueIDGroupData(theData)
@ -547,6 +682,16 @@ function cloneZones.uniqueIDUnitData(theData)
end end
end end
function cloneZones.sameIDUnitData(theData)
if not theData then return end
if not theData.units then return end
local units = theData.units
for idx, aUnit in pairs(units) do
aUnit.CZorigID = aUnit.unitId
aUnit.CZTargetID = aUnit.unitId
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)
@ -750,14 +895,14 @@ 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("+++clnZ: <" .. theZone.name .. "> trackWith requires groupTracker module", 30)
return return
end end
local trackerName = theZone.trackWith local trackerName = theZone.trackWith
--if trackerName == "*" then trackerName = theZone.name end --if trackerName == "*" then trackerName = theZone.name end
-- now assemble a list of all trackers -- now assemble a list of all trackers
if cloneZones.verbose or theZone.verbose then if cloneZones.verbose or theZone.verbose then
trigger.action.outText("+++clne: clone pass-off: " .. trackerName, 30) trigger.action.outText("+++clnZ: clone pass-off: " .. trackerName, 30)
end end
local trackerNames = {} local trackerNames = {}
@ -771,22 +916,127 @@ function cloneZones.handoffTracking(theGroup, theZone)
if theName == "*" then theName = theZone.name end if theName == "*" then theName = theZone.name end
local theTracker = groupTracker.getTrackerByName(theName) local theTracker = groupTracker.getTrackerByName(theName)
if not theTracker then if not theTracker then
trigger.action.outText("+++clne: <" .. theZone.name .. ">: cannot find tracker named <".. theName .. ">", 30) trigger.action.outText("+++clnZ: <" .. theZone.name .. ">: cannot find tracker named <".. theName .. ">", 30)
else else
groupTracker.addGroupToTracker(theGroup, theTracker) groupTracker.addGroupToTracker(theGroup, theTracker)
if cloneZones.verbose or theZone.verbose then if cloneZones.verbose or theZone.verbose then
trigger.action.outText("+++clne: added " .. theGroup:getName() .. " to tracker " .. theName, 30) trigger.action.outText("+++clnZ: added " .. theGroup:getName() .. " to tracker " .. theName, 30)
end end
end end
end end
end end
function cloneZones.validateSpawnUnitData(aUnit, theZone, unitNames)
-- entry with unit data construct
-- also used for static objects!
if not aUnit then return end
if not theZone then return end
-- we only verify replacement if identical or name sheme attribute
if not (theZone.identical or theZone.nameScheme) then
return
end
if unitNames[aUnit.name] then
trigger.action.outText("clnZ: <" .. theZone.name .. "> validation warning - Unit/Object name <" .. aUnit.name .. ">: duplicate name within spawn cycle, will be repaced", 30)
else
unitNames[aUnit.name] = true
end
local theUnit = Unit.getByName(aUnit.name)
if theUnit and Unit.isExist(theUnit) then
if cloneZones.verbose or theZone.verbose then
trigger.action.outText("+++clnZ: cloner <" .. theZone.name .. "> will replace existing UNIT <" .. aUnit.name .. ">", 30)
end
-- since we are about to replace a unit, we also steal the ID
local stolenID = theUnit:getID()
aUnit.unitId = stolenID
else
-- now check if we are about to grab an MX data ID
-- and ned to steal that
local stolenID = cfxMX.unitIDbyName[aUnit.name]
if stolenID then
if cloneZones.verbose or theZone.verbose then
trigger.action.outText("+++clnZ: cloner <" .. theZone.name .. "> will replace MX UNIT ID <" .. aUnit.name .. "> by appropriating ID <" .. stolenID .. ">", 30)
end
aUnit.unitId = stolenID
end
end
-- check against static objects.
local theStatic = StaticObject.getByName(aUnit.name)
if theStatic and StaticObject.isExist(theStatic) then
trigger.action.outText("+++clnZ: cloner <" .. theZone.name .. "> will replace existing STATIC <" .. aUnit.name .. ">", 30)
end
end
function cloneZones.validateSpawnGroupData(theData, theZone, groupNames, unitNames)
-- entry with group construct
if not theData then return end
if not theZone then return end
-- we only verify replacement if identical or name sheme attribute
if not (theZone.identical or theZone.nameScheme) then
return
end
if groupNames[theData.name] then
trigger.action.outText("clnZ: <" .. theZone.name .. "> validation warning - group name <" .. theData.name .. ">: duplicate within spawn, previous spawn will be removed", 30)
else
groupNames[theData.name] = true
end
local theGroup = Group.getByName(theData.name)
if theGroup and Group.isExist(theGroup) and theGroup:getSize() > 0 then
trigger.action.outText("+++clnZ: cloner <" .. theZone.name .. "> will replace existing GROUP <" .. theData.name .. ">", 30)
end
if not theData.units then return end
local units = theData.units
for idx, aUnit in pairs(units) do
cloneZones.validateSpawnUnitData(aUnit, theZone, unitNames)
end
end
function cloneZones.forcedRespawn(args)
local theData = args[1]
local spawnedGroups = args[2]
local pos = args[3]
local verbose = args[4]
local rawData = dcsCommon.clone(theData)
if verbose then
trigger.action.outText("clnZ: enter forced respawn of <" .. theData.name .. "> to meet ID " .. theData.CZTargetID .. " (currently set for <" .. theData.groupId .. ">)", 30)
end
-- we now try to spawn again, with hopes of receiving the
-- correct id
local theGroup = coalition.addGroup(rawData.CZctry, rawData.CZtheCat, rawData)
-- make sure that this time the id matches
local newGroupID = theGroup:getID()
if newGroupID == theData.CZTargetID then
if verbose then
trigger.action.outText("GOOD REPLACEMENT new ID <" .. newGroupID .. "> matches target <" .. theData.CZTargetID .. "> for <" .. theData.name .. ">", 30)
-- we can now remove the former group
-- and replace it with the new one
trigger.action.outText("will replace table entry at <" .. pos .. "> with new group", 30)
end
spawnedGroups[pos] = theGroup
else
-- we need to try again in one second
if verbose then
trigger.action.outText("FAIL: new ID <" .. newGroupID .. "> does not match target <" .. theData.CZTargetID .. "> for <" .. theData.name .. ">. Will re-try in 1s", 30)
end
spawnedGroups[pos] = theGroup -- replace so we don't fail checks
timer.scheduleFunction(cloneZones.forcedRespawn, args, timer.getTime() + 1)
end
end
function cloneZones.spawnWithTemplateForZone(theZone, spawnZone) function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
if cloneZones.verbose or spawnZone.verbose then if cloneZones.verbose or spawnZone.verbose then
trigger.action.outText("+++clnZ: spawning with template <" .. theZone.name .. "> for spawner <" .. spawnZone.name .. ">", 30) trigger.action.outText("+++clnZ: spawning with template <" .. theZone.name .. "> for spawner <" .. spawnZone.name .. ">", 30)
end end
-- theZone is the cloner with the template (source) -- theZone is the cloner with the TEMPLATE (source)
-- spawnZone is the spawner with settings (target location) -- spawnZone is the spawner with SETTINGS and DESTINATION (target location)
local newCenter = cfxZones.getPoint(spawnZone) -- includes zone following updates local newCenter = cfxZones.getPoint(spawnZone) -- includes zone following updates
local oCenter = cfxZones.getDCSOrigin(theZone) -- get original coords on map for cloning offsets local oCenter = cfxZones.getDCSOrigin(theZone) -- get original coords on map for cloning offsets
-- calculate zoneDelta, is added to all vectors -- calculate zoneDelta, is added to all vectors
@ -811,8 +1061,14 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
rawData.CZorigName = rawData.name -- save original group name rawData.CZorigName = rawData.name -- save original group name
local origID = rawData.groupId -- save original group ID local origID = rawData.groupId -- save original group ID
rawData.CZorigID = origID rawData.CZorigID = origID
cloneZones.uniqueIDGroupData(rawData) -- assign unique ID we know if spawnZone.identical then
cloneZones.uniqueIDUnitData(rawData) -- assign unique ID for units -- saves old unitId as CZorigID cloneZones.sameIDUnitData(rawData) -- set up CZTargetID for units to be same as in template
else
-- only assign new ids when 'identical' flag is not active
cloneZones.uniqueIDGroupData(rawData) -- assign unique ID we know
cloneZones.uniqueIDUnitData(rawData) -- assign unique ID for units -- saves old unitId as CZorigID
end
rawData.CZTargetID = rawData.groupId -- save rawData.CZTargetID = rawData.groupId -- save
if rawData.name ~= aGroupName then if rawData.name ~= aGroupName then
trigger.action.outText("Clone: FAILED name check", 30) trigger.action.outText("Clone: FAILED name check", 30)
@ -823,7 +1079,7 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
-- update their position if not spawning to exact same location -- update their position if not spawning to exact same location
if cloneZones.verbose or theZone.verbose or spawnZone.verbose then if cloneZones.verbose or theZone.verbose or spawnZone.verbose then
trigger.action.outText("+++clnZ: tmpl delta x = <" .. math.floor(zoneDelta.x) .. ">, y = <" .. math.floor(zoneDelta.z) .. "> for tmpl <" .. theZone.name .. "> to cloner <" .. spawnZone.name .. ">", 30) --trigger.action.outText("+++clnZ: tmpl delta x = <" .. math.floor(zoneDelta.x) .. ">, y = <" .. math.floor(zoneDelta.z) .. "> for tmpl <" .. theZone.name .. "> to cloner <" .. spawnZone.name .. ">", 30)
end end
-- update routes when not spawning same location -- update routes when not spawning same location
cloneZones.updateLocationsInGroupData(rawData, zoneDelta, spawnZone.moveRoute, rotCenter, spawnZone.turn / 57.2958 + cloneZones.updateLocationsInGroupData(rawData, zoneDelta, spawnZone.moveRoute, rotCenter, spawnZone.turn / 57.2958 +
@ -925,8 +1181,11 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
-- apply turning -- apply turning
dcsCommon.rotateGroupData(rawData, spawnZone.turn + 57.2958 *dHeading, newCenter.x, newCenter.z) dcsCommon.rotateGroupData(rawData, spawnZone.turn + 57.2958 *dHeading, newCenter.x, newCenter.z)
-- make sure unit and group names are unique -- make sure unit and group names are unique unless
cloneZones.uniqueNameGroupData(rawData, spawnZone) -- we have identical active
if not spawnZone.identical then
cloneZones.uniqueNameGroupData(rawData, spawnZone, theZone.name)
end
-- see what country we spawn for -- see what country we spawn for
ctry = cloneZones.resolveOwnership(spawnZone, ctry) ctry = cloneZones.resolveOwnership(spawnZone, ctry)
@ -939,6 +1198,8 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
cloneZones.resolveReferences(theZone, dataToSpawn) cloneZones.resolveReferences(theZone, dataToSpawn)
-- now spawn all raw data -- now spawn all raw data
local groupCollector = {} -- to detect cross-group conflicts
local unitCollector = {} -- to detect cross-group conflicts
for idx, rawData in pairs (dataToSpawn) do for idx, rawData in pairs (dataToSpawn) do
-- now spawn and save to clones -- now spawn and save to clones
-- first norm and clone data for later save -- first norm and clone data for later save
@ -958,6 +1219,12 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
local theData = dcsCommon.clone(rawData) local theData = dcsCommon.clone(rawData)
cloneZones.allClones[rawData.name] = theData cloneZones.allClones[rawData.name] = theData
if cloneZones.verbose or spawnZone.verbose then
-- optional spawn validation report before we spawn
cloneZones.validateSpawnGroupData(rawData, spawnZone, groupCollector, unitCollector)
end
-- SPAWN NOW!!!!
local theGroup = coalition.addGroup(rawData.CZctry, rawData.CZtheCat, rawData) local theGroup = coalition.addGroup(rawData.CZctry, rawData.CZtheCat, rawData)
table.insert(spawnedGroups, theGroup) table.insert(spawnedGroups, theGroup)
@ -978,7 +1245,15 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
if uID == aUnit.CZTargetID then if uID == aUnit.CZTargetID then
-- all good -- all good
else else
trigger.action.outText("clnZ: post-clone verification failed for unit <" .. uName .. ">: ÎD mismatch: " .. uID .. " -- " .. aUnit.CZTargetID, 30) -- mismatch. may happen when namingScheme causes
-- unit to be reallocated to existing unit.
if spawnZone.verbose then
if spawnZone.nameScheme then
trigger.action.outText("clnZ: nameScheme - unit <" .. uName .. ">: ÎD mapped to existing: " .. uID , 30)
else
trigger.action.outText("clnZ: post-clone verification failed for unit <" .. uName .. ">: ÎD mismatch: " .. uID .. " -- " .. aUnit.CZTargetID, 30)
end
end
end end
cloneZones.unitXlate[aUnit.CZorigID] = uID cloneZones.unitXlate[aUnit.CZorigID] = uID
else else
@ -987,11 +1262,28 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
end end
-- check if our assigned ID matches the handed out by -- check if our assigned ID matches the handed out by
-- DCS -- DCS. Mismatches can happen, and are only noted
if newGroupID == rawData.CZTargetID then if newGroupID == rawData.CZTargetID then
-- we are good -- we are good
else else
trigger.action.outText("clnZ: MISMATCH " .. rawData.name .. " target ID " .. rawData.CZTargetID .. " does not match " .. newGroupID, 30) if cloneZones.verbose or spawnZone.verbose then
trigger.action.outText("clnZ: Note: GROUP ID spawn changed for <" .. rawData.name .. ">: target ID " .. rawData.CZTargetID .. " (target) returns " .. newGroupID .. " (actual) in <" .. spawnZone.name .. ">", 30)
--trigger.action.outText("Note: theData.groupId is <" .. theData.groupId .. ">", 30)
--if spawnZone.identical then
-- trigger.action.outText("(Identical = true detected for this zone)", 30)
--end
end
if cloneZones.respawnOnGroupID then
-- remove last entry in table, will be added later
local pos = #spawnedGroups
timer.scheduleFunction(cloneZones.forcedRespawn, {theData, spawnedGroups, pos, spawnZone.verbose}, timer.getTime() + 2) -- initial gap: 2 seconds for DCS to sort itself out
else
-- we note it in the spawn data for the group so
-- persistence works fine
theData.groupId = newGroupID
end
end end
cloneZones.invokeCallbacks(spawnZone, "did spawn group", theGroup) cloneZones.invokeCallbacks(spawnZone, "did spawn group", theGroup)
@ -1019,10 +1311,7 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
-- randomize if enabled -- randomize if enabled
if spawnZone.rndLoc then if spawnZone.rndLoc then
--local r = math.random() * spawnZone.radius
--local phi = 6.2831 * math.random() -- that's 2Pi, folx
--local dx = r * math.cos(phi)
--local dy = r * math.sin(phi)
local loc, dx, dy = cfxZones.createRandomPointInZone(spawnZone) -- also supports polygonal zones local loc, dx, dy = cfxZones.createRandomPointInZone(spawnZone) -- also supports polygonal zones
rawData.x = rawData.x + dx rawData.x = rawData.x + dx
rawData.y = rawData.y + dy rawData.y = rawData.y + dy
@ -1044,10 +1333,12 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
-- apply turning -- apply turning
dcsCommon.rotateUnitData(rawData, spawnZone.turn + 57.2958 * dHeading, newCenter.x, newCenter.z) dcsCommon.rotateUnitData(rawData, spawnZone.turn + 57.2958 * dHeading, newCenter.x, newCenter.z)
-- make sure static name is unique and remember original if not spawnZone.identical then
cloneZones.uniqueNameStaticData(rawData, spawnZone) -- make sure static name is unique and remember original
--rawData.name = dcsCommon.uuid(rawData.name) cloneZones.uniqueNameStaticData(rawData, spawnZone, theZone.name)
rawData.unitId = cloneZones.uniqueID() --rawData.name = dcsCommon.uuid(rawData.name)
rawData.unitId = cloneZones.uniqueID()
end
rawData.CZTargetID = rawData.unitId rawData.CZTargetID = rawData.unitId
-- see what country we spawn for -- see what country we spawn for
@ -1075,6 +1366,11 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
local theData = dcsCommon.clone(rawData) local theData = dcsCommon.clone(rawData)
cloneZones.allCObjects[rawData.name] = theData cloneZones.allCObjects[rawData.name] = theData
if cloneZones.verbose or spawnZone.verbose then
-- optional spawn validation report before we spawn
cloneZones.validateSpawnUnitData(rawData, spawnZone, unitCollector)
end
local theStatic = coalition.addStaticObject(ctry, rawData) local theStatic = coalition.addStaticObject(ctry, rawData)
local newStaticID = tonumber(theStatic:getID()) local newStaticID = tonumber(theStatic:getID())
table.insert(spawnedStatics, theStatic) table.insert(spawnedStatics, theStatic)
@ -1082,7 +1378,9 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
-- statics -- statics
if newStaticID == rawData.CZTargetID then if newStaticID == rawData.CZTargetID then
else else
trigger.action.outText("Static ID mismatch: " .. newStaticID .. " vs (target) " .. rawData.CZTargetID .. " for " .. rawData.name, 30) if cloneZones.verbose or spawnZone.verbose then
trigger.action.outText("Static ID mismatch: " .. newStaticID .. " vs (target) " .. rawData.CZTargetID .. " for " .. rawData.name, 30)
end
end end
cloneZones.unitXlate[origID] = newStaticID -- same as units cloneZones.unitXlate[origID] = newStaticID -- same as units
@ -1096,11 +1394,11 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
if cfxCargoManager then if cfxCargoManager then
cfxCargoManager.addCargo(theStatic) cfxCargoManager.addCargo(theStatic)
if cloneZones.verbose or spawnZone.verbose then if cloneZones.verbose or spawnZone.verbose then
trigger.action.outText("+++clne: added CARGO " .. theStatic:getName() .. " to cargo manager ", 30) trigger.action.outText("+++clnZ: added CARGO " .. theStatic:getName() .. " to cargo manager ", 30)
end end
else else
if cloneZones.verbose or spawnZone.verbose then if cloneZones.verbose or spawnZone.verbose then
trigger.action.outText("+++clne: CARGO " .. theStatic:getName() .. " detected, not managerd", 30) trigger.action.outText("+++clnZ: CARGO " .. theStatic:getName() .. " detected, not managerd", 30)
end end
end end
end end
@ -1298,7 +1596,7 @@ function cloneZones.doOnStart()
if theZone.onStart then if theZone.onStart then
if theZone.isStarted then if theZone.isStarted then
if cloneZones.verbose or theZone.verbose then if cloneZones.verbose or theZone.verbose then
trigger.action.outText("+++clnz: onStart pre-empted for <" .. theZone.name .. "> by persistence", 30) trigger.action.outText("+++clnZ: onStart pre-empted for <" .. theZone.name .. "> by persistence", 30)
end end
else else
if cloneZones.verbose or theZone.verbose then if cloneZones.verbose or theZone.verbose then
@ -1418,6 +1716,8 @@ function cloneZones.saveData()
for idx, theCloner in pairs(cloneZones.cloners) do for idx, theCloner in pairs(cloneZones.cloners) do
local cData = {} local cData = {}
local cName = theCloner.name local cName = theCloner.name
cData.myUniqueCounter = theCloner.myUniqueCounter
-- mySpawns: all groups i'm curently observing for empty! -- mySpawns: all groups i'm curently observing for empty!
-- myStatics: dto for objects -- myStatics: dto for objects
local mySpawns = {} local mySpawns = {}
@ -1440,11 +1740,13 @@ function cloneZones.saveData()
-- save globals -- save globals
theData.cuid = cloneZones.uniqueCounter -- replace whatever is larger theData.cuid = cloneZones.uniqueCounter -- replace whatever is larger
theData.uuid = dcsCommon.simpleUUID -- replace whatever is larger theData.uuid = dcsCommon.simpleUUID -- replace whatever is larger
theData.globalCount = cloneZones.globalCount
-- save to struct and pass back -- save to struct and pass back
theData.clones = allCloneData theData.clones = allCloneData
theData.objects = allSOData theData.objects = allSOData
theData.cloneZones = cloners theData.cloneZones = cloners
return theData return theData
end end
@ -1503,6 +1805,11 @@ function cloneZones.loadData()
local theCloner = cloneZones.getCloneZoneByName(cName) local theCloner = cloneZones.getCloneZoneByName(cName)
if theCloner then if theCloner then
theCloner.isStarted = true -- ALWAYS TRUE WHEN WE COME HERE! cData.isStarted theCloner.isStarted = true -- ALWAYS TRUE WHEN WE COME HERE! cData.isStarted
-- init myUniqueCounter if it exists
if cData.myUniqueCounter then
theCloner.myUniqueCounter = cData.myUniqueCounter
end
local mySpawns = {} local mySpawns = {}
for idx, aName in pairs(cData.mySpawns) do for idx, aName in pairs(cData.mySpawns) do
local theGroup = Group.getByName(aName) local theGroup = Group.getByName(aName)
@ -1536,7 +1843,9 @@ function cloneZones.loadData()
if theData.uuiD and theData.uuid > dcsCommon.simpleUUID then if theData.uuiD and theData.uuid > dcsCommon.simpleUUID then
dcsCommon.simpleUUID = theData.uuid dcsCommon.simpleUUID = theData.uuid
end end
if theData.globalCount and theData.globalCount > cloneZones.globalCount then
cloneZones.globalCount = theData.globalCount
end
end end
-- --
@ -1551,6 +1860,18 @@ function cloneZones.readConfigZone()
return return
end end
if cfxZones.hasProperty(theZone, "uniqueCount") then
cloneZones.uniqueCounter = cfxZones.getNumberFromZoneProperty(theZone, "uniqueCount", cloneZone.uniqueCounter)
end
if cfxZones.hasProperty(theZone, "localCount") then
cloneZones.lclUniqueCounter = cfxZones.getNumberFromZoneProperty(theZone, "localCount", cloneZone.lclUniqueCounter)
end
if cfxZones.hasProperty(theZone, "globalCount") then
cloneZones.globalCounter = cfxZones.getNumberFromZoneProperty(theZone, "globalCount", cloneZone.globalCounter)
end
cloneZones.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false) cloneZones.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
if cloneZones.verbose then if cloneZones.verbose then
@ -1622,4 +1943,9 @@ end
- AFAC - AFAC
- FAC Assign group - FAC Assign group
- set freq for unit - set freq for unit
-- fixedName: immutable name, no safe renaming. always use ID and name without changing it. very special use case.
nameTest - optional safety / debug feature that will name-test each unit that is about to be spawned for replacement. Maybe auto turn on when verbose is set?
identical - make a clone of template and do not touch name nor id. will fully replace
make example where transport can be different plane types but have same name
--]]-- --]]--

View File

@ -691,6 +691,31 @@ dcsCommon.version = "2.8.0"
end end
function dcsCommon.bearingFromAtoB(A, B) -- coords in x, z function dcsCommon.bearingFromAtoB(A, B) -- coords in x, z
if not A then
trigger.action.outText("WARNING: no 'A' in bearingFromAtoB", 30)
return 0
end
if not B then
trigger.action.outText("WARNING: no 'A' in bearingFromAtoB", 30)
return 0
end
if not A.x then
trigger.action.outText("WARNING: no 'A.x' (type A =<" .. type(A) .. ">)in bearingFromAtoB", 30)
return 0
end
if not A.y then
trigger.action.outText("WARNING: no 'A.x' (type A =<" .. type(A) .. ">)in bearingFromAtoB", 30)
return 0
end
if not B.x then
trigger.action.outText("WARNING: no 'B.x' (type B =<" .. type(B) .. ">)in bearingFromAtoB", 30)
return 0
end
if not B.y then
trigger.action.outText("WARNING: no 'B.y' (type B =<" .. type(B) .. ">)in bearingFromAtoB", 30)
return 0
end
dx = B.x - A.x dx = B.x - A.x
dz = B.z - A.z dz = B.z - A.z
bearing = math.atan2(dz, dx) -- in radiants bearing = math.atan2(dz, dx) -- in radiants

View File

@ -32,7 +32,7 @@ function smr.restartMission()
end end
end end
-- main update loop, checked once per secon -- main update loop, checked once per second
local lTime = DCS.getModelTime() local lTime = DCS.getModelTime()
function smr.onSimulationFrame() function smr.onSimulationFrame()
if lTime + 1 < DCS.getModelTime() then if lTime + 1 < DCS.getModelTime() then