mirror of
https://github.com/weyne85/DML.git
synced 2025-10-29 16:57:49 +00:00
Version 0.97
Clone Zones MX delayFlags Attack of the CloneZ miz
This commit is contained in:
451
modules/cloneZone.lua
Normal file
451
modules/cloneZone.lua
Normal file
@@ -0,0 +1,451 @@
|
||||
cloneZones = {}
|
||||
cloneZones.version = "1.0.0"
|
||||
cloneZones.verbose = false
|
||||
cloneZones.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
"cfxZones", -- Zones, of course
|
||||
"cfxMX",
|
||||
}
|
||||
cloneZones.cloners = {}
|
||||
|
||||
--[[--
|
||||
Clones Groups from ME mission data
|
||||
Copyright (c) 2022 by Christian Franz and cf/x AG
|
||||
|
||||
Version History
|
||||
1.0.0 - initial version
|
||||
|
||||
--]]--
|
||||
|
||||
--
|
||||
-- adding / removing from list
|
||||
--
|
||||
function cloneZones.addCloneZone(theZone)
|
||||
table.insert(cloneZones.cloners, theZone)
|
||||
end
|
||||
|
||||
function cloneZones.getCloneZoneByName(aName)
|
||||
for idx, aZone in pairs(cloneZones.cloners) do
|
||||
if aName == aZone.name then return aZone end
|
||||
end
|
||||
if cloneZones.verbose then
|
||||
trigger.action.outText("+++clnZ: no clone with name <" .. aName ..">", 30)
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
--
|
||||
-- reading zones
|
||||
--
|
||||
|
||||
|
||||
function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
|
||||
local localZones = cfxZones.allGroupsInZone(theZone)
|
||||
theZone.cloner = true -- this is a cloner zoner
|
||||
theZone.mySpawns = {}
|
||||
--theZone.groupVectors = {}
|
||||
theZone.origin = cfxZones.getPoint(theZone) -- save reference point for all groupVectors
|
||||
|
||||
-- source tells us which template to use. it can be the following:
|
||||
-- nothing (no attribute) - then we use whatever groups are in zone to
|
||||
-- spawn as template
|
||||
-- name of another spawner that provides the template
|
||||
-- we can't simply use a group name as we lack the reference
|
||||
-- location for delta
|
||||
if cfxZones.hasProperty(theZone, "source") then
|
||||
theZone.source = cfxZones.getStringFromZoneProperty(theZone, "source", "<none>")
|
||||
if theZone.source == "<none>" then theZone.source = nil end
|
||||
end
|
||||
|
||||
if not theZone.source then
|
||||
theZone.cloneNames = {} -- names of the groups. only present in template spawners
|
||||
for idx, aGroup in pairs(localZones) do
|
||||
local gName = aGroup:getName()
|
||||
if gName then
|
||||
table.insert(theZone.cloneNames, gName)
|
||||
table.insert(theZone.mySpawns, aGroup) -- collect them for initial despawn
|
||||
end
|
||||
|
||||
end
|
||||
cloneZones.despawnAll(theZone)
|
||||
if #theZone.cloneNames < 1 then
|
||||
if cloneZones.verbose then
|
||||
trigger.action.outText("+++clnZ: WARNING - Template in clone zone <" .. theZone.name .. "> is empty", 30)
|
||||
end
|
||||
theZone.cloneNames = nil
|
||||
end
|
||||
if cloneZones.verbose then
|
||||
trigger.action.outText(theZone.name .. " clone template saved", 30)
|
||||
end
|
||||
end
|
||||
|
||||
-- f? and spawn? map to the same
|
||||
if cfxZones.hasProperty(theZone, "f?") then
|
||||
theZone.spawnFlag = cfxZones.getStringFromZoneProperty(theZone, "f?", "none")
|
||||
theZone.lastSpawnValue = trigger.misc.getUserFlag(theZone.spawnFlag) -- save last value
|
||||
end
|
||||
if cfxZones.hasProperty(theZone, "spawn?") then
|
||||
theZone.spawnFlag = cfxZones.getStringFromZoneProperty(theZone, "spawn?", "none")
|
||||
theZone.lastSpawnValue = trigger.misc.getUserFlag(theZone.spawnFlag) -- save last value
|
||||
end
|
||||
|
||||
theZone.onStart = cfxZones.getBoolFromZoneProperty(theZone, "onStart", false)
|
||||
|
||||
theZone.moveRoute = cfxZones.getBoolFromZoneProperty(theZone, "moveRoute", false)
|
||||
|
||||
if cfxZones.hasProperty(theZone, "empty+1") then
|
||||
theZone.emptyFlag = cfxZones.getNumberFromZoneProperty(theZone, "empty+1", "<None>") -- note string on number default
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "masterOwner") then
|
||||
theZone.masterOwner = cfxZones.getStringFromZoneProperty(theZone, "masterOwner", "<none>")
|
||||
end
|
||||
|
||||
--cloneZones.spawnWithCloner(theZone)
|
||||
theZone.turn = cfxZones.getNumberFromZoneProperty(theZone, "turn", 0)
|
||||
|
||||
-- make sure we spawn at least once
|
||||
-- bad idea, since we may want to simply create a template
|
||||
-- if not theZone.spawnFlag then theZone.onStart = true end
|
||||
end
|
||||
|
||||
--
|
||||
-- spawning, despawning
|
||||
--
|
||||
|
||||
function cloneZones.despawnAll(theZone)
|
||||
for idx, aGroup in pairs(theZone.mySpawns) do
|
||||
Group.destroy(aGroup)
|
||||
end
|
||||
theZone.mySpawns = {}
|
||||
end
|
||||
|
||||
function cloneZones.updateLocationsInGroupData(theData, zoneDelta, adjustAllWaypoints)
|
||||
--trigger.action.outText("Update loc - zone delta: [" .. zoneDelta.x .. "," .. zoneDelta.z .. "]", 30)
|
||||
-- remember that zoneDelta's [z] modifies theData's y!!
|
||||
theData.x = theData.x + zoneDelta.x
|
||||
theData.y = theData.y + zoneDelta.z -- !!!
|
||||
local units = theData.units
|
||||
for idx, aUnit in pairs(units) do
|
||||
aUnit.x = aUnit.x + zoneDelta.x
|
||||
aUnit.y = aUnit.y + zoneDelta.z -- again!!!!
|
||||
end
|
||||
-- now modifiy waypoints. we ALWAYS adjust the
|
||||
-- first waypoint, but only all others if asked
|
||||
-- to
|
||||
local theRoute = theData.route
|
||||
-- TODO: vehicles can have 'spans' - may need to program for
|
||||
-- those as well. we currently only go for points
|
||||
if theRoute then
|
||||
local thePoints = theRoute.points
|
||||
if thePoints and #thePoints > 0 then
|
||||
if adjustAllWaypoints then
|
||||
for i=1, #thePoints do
|
||||
thePoints[i].x = thePoints[i].x + zoneDelta.x
|
||||
thePoints[i].y = thePoints[i].y + zoneDelta.z -- (!!)
|
||||
|
||||
end
|
||||
else
|
||||
-- only first point
|
||||
thePoints[1].x = thePoints[1].x + zoneDelta.x
|
||||
thePoints[1].y = thePoints[1].y + zoneDelta.z -- (!!)
|
||||
end
|
||||
|
||||
-- if there is an airodrome id given in first waypoint,
|
||||
-- adjust for closest location
|
||||
local firstPoint = thePoints[1]
|
||||
if firstPoint.airdromeId then
|
||||
trigger.action.outText("first: airdrome adjust for " .. theData.name .. " now is " .. firstPoint.airdromeId, 30)
|
||||
local loc = {}
|
||||
loc.x = firstPoint.x
|
||||
loc.y = 0
|
||||
loc.z = firstPoint.y
|
||||
local bestAirbase = dcsCommon.getClosestAirbaseTo(loc)
|
||||
firstPoint.airdromeId = bestAirbase:getID()
|
||||
trigger.action.outText("first: adjusted to " .. firstPoint.airdromeId, 30)
|
||||
end
|
||||
|
||||
-- adjust last point (landing)
|
||||
if #thePoints > 1 then
|
||||
local lastPoint = thePoints[#thePoints]
|
||||
if firstPoint.airdromeId then
|
||||
trigger.action.outText("last: airdrome adjust for " .. theData.name .. " now is " .. lastPoint.airdromeId, 30)
|
||||
local loc = {}
|
||||
loc.x = lastPoint.x
|
||||
loc.y = 0
|
||||
loc.z = lastPoint.y
|
||||
local bestAirbase = dcsCommon.getClosestAirbaseTo(loc)
|
||||
lastPoint.airdromeId = bestAirbase:getID()
|
||||
trigger.action.outText("last: adjusted to " .. lastPoint.airdromeId, 30)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function cloneZones.uniqueNameGroupData(theData)
|
||||
theData.name = dcsCommon.uuid(theData.name)
|
||||
local units = theData.units
|
||||
for idx, aUnit in pairs(units) do
|
||||
aUnit.name = dcsCommon.uuid(aUnit.name)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function cloneZones.resolveOwnership(spawnZone, ctry)
|
||||
if not spawnZone.masterOwner then return ctry end
|
||||
|
||||
local masterZone = cfxZones.getZoneByName(spawnZone.masterOwner)
|
||||
if not masterZone then
|
||||
trigger.action.outText("+++clnZ: cloner " .. spawnZone.name .. " could not fine master owner <" .. spawnZone.masterOwner .. ">", 30)
|
||||
return ctry
|
||||
end
|
||||
|
||||
if not masterZone.owner then
|
||||
return ctry
|
||||
end
|
||||
|
||||
ctry = dcsCommon.getACountryForCoalition(masterZone.owner)
|
||||
return ctry
|
||||
end
|
||||
|
||||
function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
|
||||
--trigger.action.outText("ENTER: Spawn with template " .. theZone.name .. " for spawnZone " .. spawnZone.name, 30)
|
||||
-- theZone is the zoner with the template
|
||||
-- spawnZone is the spawner with settings
|
||||
--if not spawnZone then spawnZone = theZone end
|
||||
local newCenter = cfxZones.getPoint(spawnZone)
|
||||
-- calculate zoneDelta, is added to all vectors
|
||||
local zoneDelta = dcsCommon.vSub(newCenter, theZone.origin)
|
||||
|
||||
local spawnedGroups = {}
|
||||
|
||||
for idx, aGroupName in pairs(theZone.cloneNames) do
|
||||
local rawData, cat, ctry = cfxMX.getGroupFromDCSbyName(aGroupName)
|
||||
|
||||
if rawData.name == aGroupName then
|
||||
else
|
||||
trigger.action.outText("Clone: FAILED name check", 30)
|
||||
end
|
||||
|
||||
-- now use raw data to spawn and see if it works outabox
|
||||
local theCat = cfxMX.catText2ID(cat)
|
||||
--TODO: if theCat == -1 the group is static, may need to code for that
|
||||
|
||||
-- update their position if not spawning to exact same location
|
||||
cloneZones.updateLocationsInGroupData(rawData, zoneDelta, spawnZone.moveRoute)
|
||||
|
||||
-- apply turning
|
||||
dcsCommon.rotateGroupData(rawData, spawnZone.turn, newCenter.x, newCenter.z)
|
||||
|
||||
-- make sure all names (group and units) are unique
|
||||
cloneZones.uniqueNameGroupData(rawData)
|
||||
|
||||
-- see waht country we spawn for
|
||||
ctry = cloneZones.resolveOwnership(spawnZone, ctry)
|
||||
|
||||
local theGroup = coalition.addGroup(ctry, theCat, rawData)
|
||||
table.insert(spawnedGroups, theGroup)
|
||||
end
|
||||
|
||||
return spawnedGroups
|
||||
end
|
||||
|
||||
function cloneZones.spawnWithCloner(theZone)
|
||||
if not theZone then
|
||||
trigger.action.outText("+++clnZ: nil zone on spawnWithCloner", 30)
|
||||
return
|
||||
end
|
||||
if not theZone.cloner then
|
||||
trigger.action.outText("+++clnZ: spawnWithCloner invoked with non-cloner <" .. theZone.name .. ">", 30)
|
||||
return
|
||||
end
|
||||
|
||||
-- force spawn with this spawner
|
||||
local templateZone = theZone
|
||||
if theZone.source then
|
||||
-- we use a different zone for templates
|
||||
-- souce can be a comma separated list
|
||||
local templateName = theZone.source
|
||||
if dcsCommon.containsString(templateName, ",") then
|
||||
local allNames = templateName
|
||||
local templates = dcsCommon.splitString(templateName, ",")
|
||||
templateName = dcsCommon.pickRandom(templates)
|
||||
templateName = dcsCommon.trim(templateName)
|
||||
if cloneZones.verbose then
|
||||
trigger.action.outText("+++clnZ: picked random template <" .. templateName .."> for from <" .. allNames .. "> for cloner " .. theZone.name, 30)
|
||||
end
|
||||
end
|
||||
|
||||
local newTemplate = cloneZones.getCloneZoneByName(templateName)
|
||||
if not newTemplate then
|
||||
if cloneZones.verbose then
|
||||
trigger.action.outText("+++clnZ: no clone source with name <" .. templateName .."> for cloner " .. theZone.name, 30)
|
||||
end
|
||||
return
|
||||
end
|
||||
templateZone = newTemplate
|
||||
end
|
||||
|
||||
-- make sure our template is filled
|
||||
if not templateZone.cloneNames then
|
||||
if cloneZones.verbose then
|
||||
trigger.action.outText("+++clnZ: clone source template <".. templateZone.name .. "> for clone zone <" .. theZone.name .."> is empty", 30)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
-- local myLoc = cfxZones.getPoint(theZone)
|
||||
local theClones = cloneZones.spawnWithTemplateForZone(templateZone, theZone)
|
||||
-- reset hasClones so we know our spawns are full and we can
|
||||
-- detect complete destruction
|
||||
if theClones and #theClones > 0 then
|
||||
theZone.hasClones = true
|
||||
theZone.mySpawns = theClones
|
||||
end
|
||||
end
|
||||
|
||||
function cloneZones.countLiveUnits(theZone)
|
||||
if not theZone then return 0 end
|
||||
if not theZone.mySpawns then return 0 end
|
||||
local count = 0
|
||||
for idx, aGroup in pairs(theZone.mySpawns) do
|
||||
if aGroup:isExist() then
|
||||
local allUnits = aGroup:getUnits()
|
||||
for idy, aUnit in pairs(allUnits) do
|
||||
if aUnit:isExist() and aUnit:getLife() >= 1 then
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
function cloneZones.hasLiveUnits(theZone)
|
||||
if not theZone then return 0 end
|
||||
if not theZone.mySpawns then return 0 end
|
||||
for idx, aGroup in pairs(theZone.mySpawns) do
|
||||
if aGroup:isExist() then
|
||||
local allUnits = aGroup:getUnits()
|
||||
for idy, aUnit in pairs(allUnits) do
|
||||
if aUnit:isExist() and aUnit:getLife() >= 1 then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function cloneZones.pollFlag(flagNum, method)
|
||||
-- we currently ignore method
|
||||
local num = trigger.misc.getUserFlag(flagNum)
|
||||
trigger.action.setUserFlag(flagNum, num+1)
|
||||
end
|
||||
--
|
||||
-- UPDATE
|
||||
--
|
||||
function cloneZones.update()
|
||||
timer.scheduleFunction(cloneZones.update, {}, timer.getTime() + 1)
|
||||
|
||||
for idx, aZone in pairs(cloneZones.cloners) do
|
||||
-- see if pulse is running
|
||||
|
||||
-- see if we got spawn? command
|
||||
if aZone.spawnFlag then
|
||||
local currTriggerVal = trigger.misc.getUserFlag(aZone.spawnFlag)
|
||||
if currTriggerVal ~= aZone.lastSpawnValue
|
||||
then
|
||||
if cloneZones.verbose then
|
||||
trigger.action.outText("+++clnZ: spawn triggered for <" .. aZone.name .. ">", 30)
|
||||
end
|
||||
cloneZones.spawnWithCloner(aZone)
|
||||
aZone.lastSpawnValue = currTriggerVal
|
||||
end
|
||||
end
|
||||
|
||||
-- see if we are empty and should signal
|
||||
if aZone.emptyFlag and aZone.hasClones then
|
||||
if cloneZones.countLiveUnits(aZone) < 1 then
|
||||
-- we are depleted. poll flag once, then remember we have
|
||||
-- polled
|
||||
cloneZones.pollFlag(aZone.emptyFlag)
|
||||
aZone.hasClones = false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function cloneZones.onStart()
|
||||
--trigger.action.outText("+++clnZ: Enter atStart", 30)
|
||||
for idx, theZone in pairs(cloneZones.cloners) do
|
||||
if theZone.onStart then
|
||||
if cloneZones.verbose then
|
||||
trigger.action.outText("+++clnZ: atStart will spawn for <"..theZone.name .. ">", 30)
|
||||
end
|
||||
cloneZones.spawnWithCloner(theZone)
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- START
|
||||
--
|
||||
function cloneZones.readConfigZone()
|
||||
local theZone = cfxZones.getZoneByName("cloneZonesConfig")
|
||||
if not theZone then
|
||||
if cloneZones.verbose then
|
||||
trigger.action.outText("+++clnZ: NO config zone!", 30)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
cloneZones.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
|
||||
|
||||
if cloneZones.verbose then
|
||||
trigger.action.outText("+++clnZ: read config", 30)
|
||||
end
|
||||
end
|
||||
|
||||
function cloneZones.start()
|
||||
-- lib check
|
||||
if not dcsCommon.libCheck then
|
||||
trigger.action.outText("cfx Clone Zones requires dcsCommon", 30)
|
||||
return false
|
||||
end
|
||||
if not dcsCommon.libCheck("cfx Clone Zones",
|
||||
cloneZones.requiredLibs) then
|
||||
return false
|
||||
end
|
||||
|
||||
-- read config
|
||||
cloneZones.readConfigZone()
|
||||
|
||||
-- process cloner Zones
|
||||
local attrZones = cfxZones.getZonesWithAttributeNamed("cloner")
|
||||
|
||||
-- now create an rnd gen for each one and add them
|
||||
-- to our watchlist
|
||||
for k, aZone in pairs(attrZones) do
|
||||
cloneZones.createClonerWithZone(aZone) -- process attribute and add to zone
|
||||
cloneZones.addCloneZone(aZone) -- remember it so we can smoke it
|
||||
end
|
||||
|
||||
-- run through onStart
|
||||
cloneZones.onStart()
|
||||
|
||||
-- start update
|
||||
cloneZones.update()
|
||||
|
||||
trigger.action.outText("cfx Clone Zones v" .. cloneZones.version .. " started.", 30)
|
||||
return true
|
||||
end
|
||||
|
||||
-- let's go!
|
||||
if not cloneZones.start() then
|
||||
trigger.action.outText("cf/x Clone Zones aborted: missing libraries", 30)
|
||||
cloneZones = nil
|
||||
end
|
||||
Reference in New Issue
Block a user