diff --git a/Doc/DML Documentation.pdf b/Doc/DML Documentation.pdf index 8040a9c..7535ea0 100644 Binary files a/Doc/DML Documentation.pdf and b/Doc/DML Documentation.pdf differ diff --git a/Doc/DML Quick Reference.pdf b/Doc/DML Quick Reference.pdf index 767b7ff..fe9ba03 100644 Binary files a/Doc/DML Quick Reference.pdf and b/Doc/DML Quick Reference.pdf differ diff --git a/modules/cfxMX.lua b/modules/cfxMX.lua index d127f40..39ec941 100644 --- a/modules/cfxMX.lua +++ b/modules/cfxMX.lua @@ -1,10 +1,10 @@ cfxMX = {} -cfxMX.version = "1.2.4" +cfxMX.version = "1.2.5" cfxMX.verbose = false --[[-- 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 1.0.0 - initial version @@ -24,9 +24,11 @@ cfxMX.verbose = false 1.2.3 - groupTypeByName - groupCoalitionByName 1.2.4 - playerUnit2Group cross index + 1.2.5 - unitIDbyName index added --]]-- cfxMX.groupNamesByID = {} cfxMX.groupIDbyName = {} +cfxMX.unitIDbyName = {} cfxMX.groupDataByName = {} cfxMX.groupTypeByName = {} -- category of group: "helicopter", "plane", "ship"... cfxMX.groupCoalitionByName = {} @@ -234,7 +236,7 @@ function cfxMX.createCrossReferences() trigger.action.outText("+++MX: <" .. obj_type_name .. "> unknown type for <" .. aName .. ">", 30) end -- 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 if unit_data.skill 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 end -- if unit skill client end -- if has skill + cfxMX.unitIDbyName[unit_data.name] = unit_data.unitId end -- for all units end -- for all groups end --if has category data diff --git a/modules/cfxObjectSpawnZones.lua b/modules/cfxObjectSpawnZones.lua index bf2108b..d0bf760 100644 --- a/modules/cfxObjectSpawnZones.lua +++ b/modules/cfxObjectSpawnZones.lua @@ -1,5 +1,5 @@ cfxObjectSpawnZones = {} -cfxObjectSpawnZones.version = "1.3.0" +cfxObjectSpawnZones.version = "1.3.1" cfxObjectSpawnZones.requiredLibs = { "dcsCommon", -- common is of course needed for everything -- pretty stupid to check for this since we @@ -30,6 +30,8 @@ cfxObjectSpawnZones.verbose = false -- 1.3.0 - better synonym handling -- - useDelicates link to delicate when spawned -- - 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 cfxObjectSpawnZones.allSpawners = {} @@ -92,6 +94,10 @@ function cfxObjectSpawnZones.createSpawner(inZone) theSpawner.rawOwner = coalition.getCountryCoalition(theSpawner.country) 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") theSpawner.cooldown = cfxZones.getNumberFromZoneProperty(inZone, "cooldown", 60) theSpawner.lastSpawnTimeStamp = -10000 -- just init so it will always work diff --git a/modules/cfxSpawnZones.lua b/modules/cfxSpawnZones.lua index 675782a..e4f6832 100644 --- a/modules/cfxSpawnZones.lua +++ b/modules/cfxSpawnZones.lua @@ -1,5 +1,5 @@ cfxSpawnZones = {} -cfxSpawnZones.version = "1.7.1" +cfxSpawnZones.version = "1.7.2" cfxSpawnZones.requiredLibs = { "dcsCommon", -- common is of course needed for everything -- pretty stupid to check for this since we @@ -63,6 +63,7 @@ cfxSpawnZones.spawnedGroups = {} -- 1.7.0 - persistence support -- 1.7.1 - improved verbosity -- - spelling check +-- 1.7.2 - baseName now can can be set to zone name by issuing "*" -- -- new version requires cfxGroundTroops, where they are -- @@ -198,6 +199,10 @@ function cfxSpawnZones.createSpawner(inZone) theSpawner.rawOwner = coalition.getCountryCoalition(theSpawner.country) --theSpawner.baseName = cfxZones.getZoneProperty(inZone, "baseName") 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.autoRemove = cfxZones.getBoolFromZoneProperty(inZone, "autoRemove", false) diff --git a/modules/cfxZones.lua b/modules/cfxZones.lua index fea283a..21d91ec 100644 --- a/modules/cfxZones.lua +++ b/modules/cfxZones.lua @@ -116,6 +116,7 @@ cfxZones.version = "3.0.0" - 3.0.0 - support for DCS 2.8 linkUnit attribute, integration with linedUnit and warning. - initZoneVerbosity() +- 3.0.1 - updateMovingZones() better tracks linked units by name --]]-- @@ -2304,35 +2305,52 @@ end function cfxZones.updateMovingZones() 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 for aName,aZone in pairs(cfxZones.zones) do - if aZone.linkBroken then - -- try to relink - cfxZones.initLink(aZone) - end - 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) + -- only do this if ther is a linkName property, + -- else this zone isn't linked. link name is harmonized from + -- both linkUnit non-DML and linedUnit DML + if aZone.linkName then + if aZone.linkBroken then + -- try to relink + cfxZones.initLink(aZone) + else --if aZone.linkName then + -- always re-acquire linkedUnit via Unit.getByName() + -- this way we gloss over any replacements via spawns + aZone.linkedUnit = Unit.getByName(aZone.linkName) + end + + 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 - cfxZones.offsetZone(aZone, dx, dy) else - -- we lost link + -- we lost link (top level) aZone.linkBroken = true aZone.linkedUnit = nil end + else + -- this zone isn't linked end end end 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) theZone.linkBroken = true theZone.linkedUnit = nil diff --git a/modules/cloneZone.lua b/modules/cloneZone.lua index 9a15389..d0d0fea 100644 --- a/modules/cloneZone.lua +++ b/modules/cloneZone.lua @@ -1,5 +1,5 @@ cloneZones = {} -cloneZones.version = "1.6.3" +cloneZones.version = "1.7.0" cloneZones.verbose = false cloneZones.requiredLibs = { "dcsCommon", -- always @@ -17,9 +17,14 @@ cloneZones.callbacks = {} 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 +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.allCObjects = {} -- all clones objects + +cloneZones.respawnOnGroupID = true + --[[-- Clones Groups from ME mission data 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 uniqueNameGroupData has provisions 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 + - , , wildcards + - , , , 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 trigger.action.outText("+++clnZ: new cloner <" .. theZone.name ..">", 30) end - + + theZone.myUniqueCounter = cloneZones.lclUniqueCounter -- init local counter + local localZones = cloneZones.allGroupsInZoneByData(theZone) local localObjects = cfxZones.allStaticsInZone(theZone, true) -- true = use DCS origin, not moved zone if theZone.verbose then @@ -301,7 +319,14 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner" end if cfxZones.hasProperty(theZone, "masterOwner") then - theZone.masterOwner = cfxZones.getStringFromZoneProperty(theZone, "masterOwner", "") + 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 theZone.turn = cfxZones.getNumberFromZoneProperty(theZone, "turn", 0) @@ -326,6 +351,20 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner" 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", "-") -- default to [ "-" ] + 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 end @@ -509,17 +548,97 @@ function cloneZones.updateLocationsInGroupData(theData, zoneDelta, adjustAllWayp end +function cloneZones.nameFromSchema(schema, inName, theZone, sourceName, i) + -- default schema (classic) is "-" + local outName = schema + local iter = i + + -- replace all occurences of with original name + outName = outName:gsub("", inName) + + -- replace all occurences of with zone name + outName = outName:gsub("", theZone.name) + -- replace all occurences of with source zone name + outName = outName:gsub("", sourceName) + + -- uid (uuid) with auto-increment + local pos = string.find(outName, "") + while (pos and pos > 0) do + local uid = tostring(dcsCommon.numberUUID()) + outName = outName:gsub("", uid, 1) -- only first substitution + pos = string.find(outName, "") + end + + -- i (iter) with increment + pos = string.find(outName, "") + while (pos and pos > 0) do + local uid = tostring(iter) + outName = outName:gsub("", uid, 1) -- only first substitution + iter = iter + 1 + pos = string.find(outName, "") + end + + -- lcl local (zonal) count with increment + pos = string.find(outName, "") + while (pos and pos > 0) do + local uid = tostring(theZone.myUniqueCounter) + outName = outName:gsub("", uid, 1) -- only first substitution + theZone.myUniqueCounter = theZone.myUniqueCounter + 1 + pos = string.find(outName, "") + end + + -- g global (module) count with increment + pos = string.find(outName, "") + while (pos and pos > 0) do + local uid = tostring(cloneZones.globalCounter) + outName = outName:gsub("", uid, 1) -- only first substitution + cloneZones.globalCounter = cloneZones.globalCounter + 1 + pos = string.find(outName, "") + 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() local uid = cloneZones.uniqueCounter cloneZones.uniqueCounter = cloneZones.uniqueCounter + 1 return uid 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) local units = theData.units + local iterCount = 1 + local newName = "none" + local allNames = {} -- enforce unique names inside group 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 -- default naming scheme: - aUnit.name = dcsCommon.uuid(aUnit.name) @@ -527,9 +646,25 @@ function cloneZones.uniqueNameGroupData(theData, theCloneZone) end end -function cloneZones.uniqueNameStaticData(theData, spawnZone) - theData.name = dcsCommon.uuid(theData.name) +function cloneZones.uniqueNameStaticData(theData, theCloneZone, sourcename) + 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: - + theData.name = dcsCommon.uuid(theData.name) + end end function cloneZones.uniqueIDGroupData(theData) @@ -547,6 +682,16 @@ function cloneZones.uniqueIDUnitData(theData) 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) if not spawnZone.masterOwner then return ctry end local masterZone = cfxZones.getZoneByName(spawnZone.masterOwner) @@ -750,14 +895,14 @@ end function cloneZones.handoffTracking(theGroup, theZone) 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 end local trackerName = theZone.trackWith --if trackerName == "*" then trackerName = theZone.name end -- now assemble a list of all trackers 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 local trackerNames = {} @@ -771,22 +916,127 @@ function cloneZones.handoffTracking(theGroup, theZone) if theName == "*" then theName = theZone.name end local theTracker = groupTracker.getTrackerByName(theName) 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 groupTracker.addGroupToTracker(theGroup, theTracker) 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 +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) if cloneZones.verbose or spawnZone.verbose then trigger.action.outText("+++clnZ: spawning with template <" .. theZone.name .. "> for spawner <" .. spawnZone.name .. ">", 30) end - -- theZone is the cloner with the template (source) - -- spawnZone is the spawner with settings (target location) + -- theZone is the cloner with the TEMPLATE (source) + -- spawnZone is the spawner with SETTINGS and DESTINATION (target location) local newCenter = cfxZones.getPoint(spawnZone) -- includes zone following updates local oCenter = cfxZones.getDCSOrigin(theZone) -- get original coords on map for cloning offsets -- calculate zoneDelta, is added to all vectors @@ -811,8 +1061,14 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone) rawData.CZorigName = rawData.name -- save original group name local origID = rawData.groupId -- save original group ID rawData.CZorigID = origID - cloneZones.uniqueIDGroupData(rawData) -- assign unique ID we know - cloneZones.uniqueIDUnitData(rawData) -- assign unique ID for units -- saves old unitId as CZorigID + if spawnZone.identical then + 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 if rawData.name ~= aGroupName then 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 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 -- update routes when not spawning same location cloneZones.updateLocationsInGroupData(rawData, zoneDelta, spawnZone.moveRoute, rotCenter, spawnZone.turn / 57.2958 + @@ -925,8 +1181,11 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone) -- apply turning dcsCommon.rotateGroupData(rawData, spawnZone.turn + 57.2958 *dHeading, newCenter.x, newCenter.z) - -- make sure unit and group names are unique - cloneZones.uniqueNameGroupData(rawData, spawnZone) + -- make sure unit and group names are unique unless + -- we have identical active + if not spawnZone.identical then + cloneZones.uniqueNameGroupData(rawData, spawnZone, theZone.name) + end -- see what country we spawn for ctry = cloneZones.resolveOwnership(spawnZone, ctry) @@ -939,6 +1198,8 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone) cloneZones.resolveReferences(theZone, dataToSpawn) -- 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 -- now spawn and save to clones -- first norm and clone data for later save @@ -958,6 +1219,12 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone) local theData = dcsCommon.clone(rawData) 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) table.insert(spawnedGroups, theGroup) @@ -978,7 +1245,15 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone) if uID == aUnit.CZTargetID then -- all good 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 cloneZones.unitXlate[aUnit.CZorigID] = uID else @@ -987,11 +1262,28 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone) end -- check if our assigned ID matches the handed out by - -- DCS + -- DCS. Mismatches can happen, and are only noted if newGroupID == rawData.CZTargetID then -- we are good 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 cloneZones.invokeCallbacks(spawnZone, "did spawn group", theGroup) @@ -1019,10 +1311,7 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone) -- randomize if enabled 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 rawData.x = rawData.x + dx rawData.y = rawData.y + dy @@ -1044,10 +1333,12 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone) -- apply turning dcsCommon.rotateUnitData(rawData, spawnZone.turn + 57.2958 * dHeading, newCenter.x, newCenter.z) - -- make sure static name is unique and remember original - cloneZones.uniqueNameStaticData(rawData, spawnZone) - --rawData.name = dcsCommon.uuid(rawData.name) - rawData.unitId = cloneZones.uniqueID() + if not spawnZone.identical then + -- make sure static name is unique and remember original + cloneZones.uniqueNameStaticData(rawData, spawnZone, theZone.name) + --rawData.name = dcsCommon.uuid(rawData.name) + rawData.unitId = cloneZones.uniqueID() + end rawData.CZTargetID = rawData.unitId -- see what country we spawn for @@ -1075,6 +1366,11 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone) local theData = dcsCommon.clone(rawData) 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 newStaticID = tonumber(theStatic:getID()) table.insert(spawnedStatics, theStatic) @@ -1082,7 +1378,9 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone) -- statics if newStaticID == rawData.CZTargetID then 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 cloneZones.unitXlate[origID] = newStaticID -- same as units @@ -1096,11 +1394,11 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone) if cfxCargoManager then cfxCargoManager.addCargo(theStatic) 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 else 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 @@ -1298,7 +1596,7 @@ function cloneZones.doOnStart() if theZone.onStart then if theZone.isStarted 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 else if cloneZones.verbose or theZone.verbose then @@ -1418,6 +1716,8 @@ function cloneZones.saveData() for idx, theCloner in pairs(cloneZones.cloners) do local cData = {} local cName = theCloner.name + cData.myUniqueCounter = theCloner.myUniqueCounter + -- mySpawns: all groups i'm curently observing for empty! -- myStatics: dto for objects local mySpawns = {} @@ -1440,11 +1740,13 @@ function cloneZones.saveData() -- save globals theData.cuid = cloneZones.uniqueCounter -- replace whatever is larger theData.uuid = dcsCommon.simpleUUID -- replace whatever is larger + theData.globalCount = cloneZones.globalCount -- save to struct and pass back theData.clones = allCloneData theData.objects = allSOData theData.cloneZones = cloners + return theData end @@ -1503,6 +1805,11 @@ function cloneZones.loadData() local theCloner = cloneZones.getCloneZoneByName(cName) if theCloner then 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 = {} for idx, aName in pairs(cData.mySpawns) do local theGroup = Group.getByName(aName) @@ -1536,7 +1843,9 @@ function cloneZones.loadData() if theData.uuiD and theData.uuid > dcsCommon.simpleUUID then dcsCommon.simpleUUID = theData.uuid end - + if theData.globalCount and theData.globalCount > cloneZones.globalCount then + cloneZones.globalCount = theData.globalCount + end end -- @@ -1551,6 +1860,18 @@ function cloneZones.readConfigZone() return 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) if cloneZones.verbose then @@ -1622,4 +1943,9 @@ end - AFAC - FAC Assign group - 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 --]]-- \ No newline at end of file diff --git a/modules/dcsCommon.lua b/modules/dcsCommon.lua index 06db73b..d755f01 100644 --- a/modules/dcsCommon.lua +++ b/modules/dcsCommon.lua @@ -691,6 +691,31 @@ dcsCommon.version = "2.8.0" end 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 dz = B.z - A.z bearing = math.atan2(dz, dx) -- in radiants diff --git a/server modules/smrGUI.lua b/server modules/smrGUI.lua index c3f7f05..3dab988 100644 --- a/server modules/smrGUI.lua +++ b/server modules/smrGUI.lua @@ -32,7 +32,7 @@ function smr.restartMission() end end --- main update loop, checked once per secon +-- main update loop, checked once per second local lTime = DCS.getModelTime() function smr.onSimulationFrame() if lTime + 1 < DCS.getModelTime() then diff --git a/tutorial & demo missions/demo - Hope you guess my name (cloner).miz b/tutorial & demo missions/demo - Hope you guess my name (cloner).miz new file mode 100644 index 0000000..821842c Binary files /dev/null and b/tutorial & demo missions/demo - Hope you guess my name (cloner).miz differ