mirror of
https://github.com/weyne85/DML.git
synced 2025-10-29 16:57:49 +00:00
Version 1.3.1
stopGaps owned zones factory zones flag documentation
This commit is contained in:
parent
83f9478f86
commit
3d058a12f2
Binary file not shown.
Binary file not shown.
@ -1,5 +1,5 @@
|
|||||||
cfxGroundTroops = {}
|
cfxGroundTroops = {}
|
||||||
cfxGroundTroops.version = "1.7.7"
|
cfxGroundTroops.version = "1.7.8"
|
||||||
cfxGroundTroops.ups = 1
|
cfxGroundTroops.ups = 1
|
||||||
cfxGroundTroops.verbose = false
|
cfxGroundTroops.verbose = false
|
||||||
cfxGroundTroops.requiredLibs = {
|
cfxGroundTroops.requiredLibs = {
|
||||||
@ -67,7 +67,7 @@ cfxGroundTroops.deployedTroops = {} -- indexed by group name
|
|||||||
- makeTroopsEngageZone() sets 'moving' status to true
|
- makeTroopsEngageZone() sets 'moving' status to true
|
||||||
- createGroundTroops() sets moving status to false
|
- createGroundTroops() sets moving status to false
|
||||||
- updateZoneAttackers() uses moving
|
- updateZoneAttackers() uses moving
|
||||||
|
1.7.8 - better guards before invoking ownedZones
|
||||||
|
|
||||||
an entry into the deployed troop table has the following attributes
|
an entry into the deployed troop table has the following attributes
|
||||||
- group - the group
|
- group - the group
|
||||||
@ -296,6 +296,10 @@ end
|
|||||||
|
|
||||||
function cfxGroundTroops.updateZoneAttackers(troop)
|
function cfxGroundTroops.updateZoneAttackers(troop)
|
||||||
if not troop then return end
|
if not troop then return end
|
||||||
|
if not cfxOwnedZones then
|
||||||
|
trigger.action.outText("+++gndT: update zone attackers requires ownedZones", 30)
|
||||||
|
return
|
||||||
|
end
|
||||||
troop.insideDestination = false -- mark as not inside
|
troop.insideDestination = false -- mark as not inside
|
||||||
|
|
||||||
local newTargetZone = cfxGroundTroops.getClosestEnemyZone(troop)
|
local newTargetZone = cfxGroundTroops.getClosestEnemyZone(troop)
|
||||||
|
|||||||
@ -32,7 +32,7 @@ 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 = {}
|
||||||
cfxMX.countryByName ={}
|
cfxMX.countryByName ={} -- county of group named
|
||||||
cfxMX.linkByName = {}
|
cfxMX.linkByName = {}
|
||||||
cfxMX.allFixedByName = {}
|
cfxMX.allFixedByName = {}
|
||||||
cfxMX.allHeloByName = {}
|
cfxMX.allHeloByName = {}
|
||||||
|
|||||||
@ -6,20 +6,33 @@ cfxOwnedZones.name = "cfxOwnedZones"
|
|||||||
--[[-- VERSION HISTORY
|
--[[-- VERSION HISTORY
|
||||||
|
|
||||||
2.0.0 - factored from cfxOwnedZones 1.x, separating out production
|
2.0.0 - factored from cfxOwnedZones 1.x, separating out production
|
||||||
|
- moved to flag# semantic
|
||||||
|
- xxxOwned# for all
|
||||||
|
- ownedBy# supports multFlag
|
||||||
|
- xxxOwned#
|
||||||
|
- redLine, blueLine
|
||||||
|
- redFill, blueFill
|
||||||
|
- neutralLine, neutralFill
|
||||||
|
- global and per-zone colors
|
||||||
|
- auto-defaulting colors from config
|
||||||
|
- supports poly zone
|
||||||
|
- groundCap option
|
||||||
|
- navalCap option
|
||||||
|
- heloCap option
|
||||||
|
- fixWingCap option
|
||||||
|
- filter water owned zones for groundTroops
|
||||||
|
|
||||||
--]]--
|
--]]--
|
||||||
cfxOwnedZones.requiredLibs = {
|
cfxOwnedZones.requiredLibs = {
|
||||||
"dcsCommon", -- common is of course needed for everything
|
"dcsCommon",
|
||||||
-- pretty stupid to check for this since we
|
"cfxZones",
|
||||||
-- need common to invoke the check, but anyway
|
|
||||||
"cfxZones", -- Zones, of course
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cfxOwnedZones.zones = {}
|
cfxOwnedZones.zones = {}
|
||||||
cfxOwnedZones.ups = 1
|
cfxOwnedZones.ups = 1
|
||||||
cfxOwnedZones.initialized = false
|
cfxOwnedZones.initialized = false
|
||||||
--[[--
|
--[[--
|
||||||
owned zones is a module that managers conquerable zones and keeps a record
|
owned zones is a module that manages conquerable zones and keeps a record
|
||||||
of who owns the zone based on rules
|
of who owns the zone based on rules
|
||||||
|
|
||||||
|
|
||||||
@ -83,23 +96,28 @@ function cfxOwnedZones.drawZoneInMap(aZone)
|
|||||||
end
|
end
|
||||||
if aZone.hidden then return end
|
if aZone.hidden then return end
|
||||||
|
|
||||||
local lineColor = {1.0, 0, 0, 1.0} -- red
|
local lineColor = aZone.redLine -- {1.0, 0, 0, 1.0} -- red
|
||||||
local fillColor = {1.0, 0, 0, 0.2} -- red
|
local fillColor = aZone.redFill -- {1.0, 0, 0, 0.2} -- red
|
||||||
local owner = aZone.owner -- cfxOwnedZones.getOwnerForZone(aZone)
|
local owner = aZone.owner -- cfxOwnedZones.getOwnerForZone(aZone)
|
||||||
if owner == 2 then
|
if owner == 2 then
|
||||||
lineColor = {0.0, 0, 1.0, 1.0}
|
lineColor = aZone.blueLine -- {0.0, 0, 1.0, 1.0}
|
||||||
fillColor = {0.0, 0, 1.0, 0.2}
|
fillColor = aZone.blueFill -- {0.0, 0, 1.0, 0.2}
|
||||||
elseif owner == 0 then
|
elseif owner == 0 then
|
||||||
lineColor = {0.8, 0.8, 0.8, 1.0}
|
lineColor = aZone.neutralLine -- {0.8, 0.8, 0.8, 1.0}
|
||||||
fillColor = {0.8, 0.8, 0.8, 0.2}
|
fillColor = aZone.neutralFill -- {0.8, 0.8, 0.8, 0.2}
|
||||||
end
|
end
|
||||||
|
|
||||||
local theShape = 2 -- circle
|
-- local theShape = 2 -- circle
|
||||||
local markID = dcsCommon.numberUUID()
|
local markID = dcsCommon.numberUUID()
|
||||||
|
|
||||||
trigger.action.circleToAll(-1, markID, aZone.point, aZone.radius, lineColor, fillColor, 1, true, "")
|
if aZone.isCircle then
|
||||||
aZone.markID = markID
|
trigger.action.circleToAll(-1, markID, aZone.point, aZone.radius, lineColor, fillColor, 1, true, "")
|
||||||
|
else
|
||||||
|
local poly = aZone.poly
|
||||||
|
trigger.action.quadToAll(-1, markID, poly[4], poly[3], poly[2], poly[1], lineColor, fillColor, 1, true, "") -- note: left winding to get fill color
|
||||||
|
end
|
||||||
|
|
||||||
|
aZone.markID = markID
|
||||||
end
|
end
|
||||||
|
|
||||||
function cfxOwnedZones.getOwnedZoneByName(zName)
|
function cfxOwnedZones.getOwnedZoneByName(zName)
|
||||||
@ -110,7 +128,7 @@ function cfxOwnedZones.getOwnedZoneByName(zName)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function cfxOwnedZones.addOwnedZone(aZone)
|
function cfxOwnedZones.addOwnedZone(aZone)
|
||||||
local owner = aZone.owner --cfxZones.getCoalitionFromZoneProperty(aZone, "owner", 0) -- is already readm read it again
|
local owner = aZone.owner --cfxZones.getCoalitionFromZoneProperty(aZone, "owner", 0) -- is already read
|
||||||
|
|
||||||
if cfxZones.hasProperty(aZone, "conquered!") then
|
if cfxZones.hasProperty(aZone, "conquered!") then
|
||||||
aZone.conqueredFlag = cfxZones.getStringFromZoneProperty(aZone, "conquered!", "*<cfxnone>")
|
aZone.conqueredFlag = cfxZones.getStringFromZoneProperty(aZone, "conquered!", "*<cfxnone>")
|
||||||
@ -130,18 +148,25 @@ function cfxOwnedZones.addOwnedZone(aZone)
|
|||||||
if cfxZones.hasProperty(aZone, "neutral!") then
|
if cfxZones.hasProperty(aZone, "neutral!") then
|
||||||
aZone.neutralCap = cfxZones.getStringFromZoneProperty(aZone, "neutral!", "none")
|
aZone.neutralCap = cfxZones.getStringFromZoneProperty(aZone, "neutral!", "none")
|
||||||
end
|
end
|
||||||
if cfxZones.hasProperty(aZone, "ownedBy") then
|
if cfxZones.hasProperty(aZone, "ownedBy#") then
|
||||||
|
aZone.ownedBy = cfxZones.getStringFromZoneProperty(aZone, "ownedBy#", "none")
|
||||||
|
elseif cfxZones.hasProperty(aZone, "ownedBy") then
|
||||||
aZone.ownedBy = cfxZones.getStringFromZoneProperty(aZone, "ownedBy", "none")
|
aZone.ownedBy = cfxZones.getStringFromZoneProperty(aZone, "ownedBy", "none")
|
||||||
end
|
end
|
||||||
|
|
||||||
aZone.ownedTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "triggerMethod", "change")
|
|
||||||
if cfxZones.hasProperty(aZone, "ownedTriggerMethod") then
|
|
||||||
aZone.ownedTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "ownedTriggerMethod", "change")
|
|
||||||
end
|
|
||||||
|
|
||||||
aZone.unbeatable = cfxZones.getBoolFromZoneProperty(aZone, "unbeatable", false)
|
aZone.unbeatable = cfxZones.getBoolFromZoneProperty(aZone, "unbeatable", false)
|
||||||
|
aZone.untargetable = cfxZones.getBoolFromZoneProperty(aZone, "untargetable", false)
|
||||||
|
|
||||||
aZone.hidden = cfxZones.getBoolFromZoneProperty(aZone, "hidden", false)
|
aZone.hidden = cfxZones.getBoolFromZoneProperty(aZone, "hidden", false)
|
||||||
|
|
||||||
|
-- individual colors, else default from config
|
||||||
|
aZone.redLine = cfxZones.getRGBAVectorFromZoneProperty(aZone, "redLine", cfxOwnedZones.redLine)
|
||||||
|
aZone.redFill = cfxZones.getRGBAVectorFromZoneProperty(aZone, "redFill", cfxOwnedZones.redFill)
|
||||||
|
aZone.blueLine = cfxZones.getRGBAVectorFromZoneProperty(aZone, "blueLine", cfxOwnedZones.blueLine)
|
||||||
|
aZone.blueFill = cfxZones.getRGBAVectorFromZoneProperty(aZone, "blueFill", cfxOwnedZones.blueFill)
|
||||||
|
aZone.neutralLine = cfxZones.getRGBAVectorFromZoneProperty(aZone, "neutralLine", cfxOwnedZones.neutralLine)
|
||||||
|
aZone.neutralFill = cfxZones.getRGBAVectorFromZoneProperty(aZone, "neutralFill", cfxOwnedZones.neutralFill)
|
||||||
|
|
||||||
cfxOwnedZones.zones[aZone] = aZone
|
cfxOwnedZones.zones[aZone] = aZone
|
||||||
cfxOwnedZones.drawZoneInMap(aZone)
|
cfxOwnedZones.drawZoneInMap(aZone)
|
||||||
end
|
end
|
||||||
@ -246,24 +271,54 @@ function cfxOwnedZones.update()
|
|||||||
-- new. unit counting update
|
-- new. unit counting update
|
||||||
cfxOwnedZones.updateSchedule = timer.scheduleFunction(cfxOwnedZones.update, {}, timer.getTime() + 1/cfxOwnedZones.ups)
|
cfxOwnedZones.updateSchedule = timer.scheduleFunction(cfxOwnedZones.update, {}, timer.getTime() + 1/cfxOwnedZones.ups)
|
||||||
-- iterate all groups and their units to count how many
|
-- iterate all groups and their units to count how many
|
||||||
-- units are in each zone
|
-- units are in each zone, also count how many zones each side has
|
||||||
|
local totalZoneNum = 0
|
||||||
|
local blueZoneNum = 0
|
||||||
|
local redZoneNum = 0
|
||||||
|
local greyZoneNum = 0
|
||||||
|
|
||||||
|
-- assemble all units in allRed and allBlue according to
|
||||||
|
-- cap options (boots, ships, rotors, wings)
|
||||||
|
local allRed = {}
|
||||||
|
if cfxOwnedZones.groundCap then allRed = coalition.getGroups(1, Group.Category.GROUND) end
|
||||||
|
if cfxOwnedZones.navalCap then
|
||||||
|
allRed = dcsCommon.combineTables(allRed, coalition.getGroups(1, Group.Category.SHIP))
|
||||||
|
end
|
||||||
|
if cfxOwnedZones.heloCap then
|
||||||
|
allRed = dcsCommon.combineTables(allRed, coalition.getGroups(1, Group.Category.HELICOPTER))
|
||||||
|
end
|
||||||
|
if cfxOwnedZones.fixWingCap then
|
||||||
|
allRed = dcsCommon.combineTables(allRed, coalition.getGroups(1, Group.Category.AIRPLANE))
|
||||||
|
end
|
||||||
|
|
||||||
|
local allBlue = {}
|
||||||
|
if cfxOwnedZones.groundCap then allBlue = coalition.getGroups(2, Group.Category.GROUND) end
|
||||||
|
if cfxOwnedZones.navalCap then
|
||||||
|
allBlue = dcsCommon.combineTables(allBlue, coalition.getGroups(2, Group.Category.SHIP))
|
||||||
|
end
|
||||||
|
if cfxOwnedZones.heloCap then
|
||||||
|
allBlue = dcsCommon.combineTables(allBlue, coalition.getGroups(2, Group.Category.HELICOPTER))
|
||||||
|
end
|
||||||
|
if cfxOwnedZones.fixWingCap then
|
||||||
|
allBlue = dcsCommon.combineTables(allBlue, coalition.getGroups(2, Group.Category.AIRPLANE))
|
||||||
|
end
|
||||||
|
|
||||||
for idz, theZone in pairs(cfxOwnedZones.zones) do
|
for idz, theZone in pairs(cfxOwnedZones.zones) do
|
||||||
theZone.numRed = 0
|
theZone.numRed = 0
|
||||||
theZone.numBlue = 0
|
theZone.numBlue = 0
|
||||||
-- count red units
|
-- count red units in zone
|
||||||
local allRed = coalition.getGroups(1, Group.Category.GROUND)
|
|
||||||
for idx, aGroup in pairs(allRed) do
|
for idx, aGroup in pairs(allRed) do
|
||||||
if Group.isExist(aGroup) then
|
if Group.isExist(aGroup) then
|
||||||
if cfxOwnedZones.fastEval then
|
if cfxOwnedZones.fastEval then
|
||||||
-- we only check first unit that is alive
|
-- we only check first unit that is alive
|
||||||
local theUnit = dcsCommon.getGroupUnit(aGroup)
|
local theUnit = dcsCommon.getGroupUnit(aGroup)
|
||||||
if theUnit and cfxZones.unitInZone(theUnit, theZone) then
|
if theUnit and (not theUnit:inAir()) and cfxZones.unitInZone(theUnit, theZone) then
|
||||||
theZone.numRed = theZone.numRed + aGroup:getSize()
|
theZone.numRed = theZone.numRed + aGroup:getSize()
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
local allUnits = aGroup:getUnits()
|
local allUnits = aGroup:getUnits()
|
||||||
for idy, theUnit in pairs(allUnits) do
|
for idy, theUnit in pairs(allUnits) do
|
||||||
if cfxZones.unitInZone(theUnit, theZone) then
|
if (not theUnit:inAir()) and cfxZones.unitInZone(theUnit, theZone) then
|
||||||
theZone.numRed = theZone.numRed + 1
|
theZone.numRed = theZone.numRed + 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -271,19 +326,18 @@ function cfxOwnedZones.update()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
-- count blue units
|
-- count blue units
|
||||||
local allBlue = coalition.getGroups(2, Group.Category.GROUND)
|
|
||||||
for idx, aGroup in pairs(allBlue) do
|
for idx, aGroup in pairs(allBlue) do
|
||||||
if Group.isExist(aGroup) then
|
if Group.isExist(aGroup) then
|
||||||
if cfxOwnedZones.fastEval then
|
if cfxOwnedZones.fastEval then
|
||||||
-- we only check first unit that is alive
|
-- we only check first unit that is alive
|
||||||
local theUnit = dcsCommon.getGroupUnit(aGroup)
|
local theUnit = dcsCommon.getGroupUnit(aGroup)
|
||||||
if theUnit and cfxZones.unitInZone(theUnit, theZone) then
|
if theUnit and (not theUnit:inAir()) and cfxZones.unitInZone(theUnit, theZone) then
|
||||||
theZone.numBlue = theZone.numBlue + aGroup:getSize()
|
theZone.numBlue = theZone.numBlue + aGroup:getSize()
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
local allUnits = aGroup:getUnits()
|
local allUnits = aGroup:getUnits()
|
||||||
for idy, theUnit in pairs(allUnits) do
|
for idy, theUnit in pairs(allUnits) do
|
||||||
if cfxZones.unitInZone(theUnit, theZone) then
|
if (not theUnit:inAir()) and cfxZones.unitInZone(theUnit, theZone) then
|
||||||
theZone.numBlue = theZone.numBlue + 1
|
theZone.numBlue = theZone.numBlue + 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -368,11 +422,51 @@ function cfxOwnedZones.update()
|
|||||||
|
|
||||||
-- update ownership flag if exists
|
-- update ownership flag if exists
|
||||||
if theZone.ownedBy then
|
if theZone.ownedBy then
|
||||||
cfxZones.setFlagValue(theZone.ownedBy, theZone.owner, theZone)
|
cfxZones.setFlagValueMult(theZone.ownedBy, theZone.owner, theZone)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- now add this zone to relevant side
|
||||||
|
totalZoneNum = totalZoneNum + 1
|
||||||
|
if newOwner == 0 then
|
||||||
|
greyZoneNum = greyZoneNum + 1
|
||||||
|
elseif newOwner == 1 then
|
||||||
|
redZoneNum = redZoneNum + 1
|
||||||
|
else
|
||||||
|
blueZoneNum = blueZoneNum + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
end -- iterating all zones
|
end -- iterating all zones
|
||||||
|
|
||||||
|
-- update totals
|
||||||
|
if cfxOwnedZones.redOwned then
|
||||||
|
cfxZones.setFlagValueMult(cfxOwnedZones.redOwned, redZoneNum, cfxOwnedZones)
|
||||||
|
end
|
||||||
|
if cfxOwnedZones.blueOwned then
|
||||||
|
cfxZones.setFlagValueMult(cfxOwnedZones.blueOwned, blueZoneNum, cfxOwnedZones)
|
||||||
|
end
|
||||||
|
if cfxOwnedZones.neutralOwned then
|
||||||
|
cfxZones.setFlagValueMult(cfxOwnedZones.neutralOwned, greyZoneNum, cfxOwnedZones)
|
||||||
|
end
|
||||||
|
|
||||||
|
if cfxOwnedZones.totalOwnedZones then
|
||||||
|
cfxZones.setFlagValueMult(cfxOwnedZones.totalOwnedZones, totalZoneNum, cfxOwnedZones)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- see if one side owns all and bang the flags if requiredLibs
|
||||||
|
if cfxOwnedZones.allBlue and not cfxOwnedZones.hasAllBlue then
|
||||||
|
if cfxOwnedZones.sideOwnsAll(2) then
|
||||||
|
cfxZones.pollFlag(cfxOwnedZones.allBlue, "inc", cfxOwnedZones)
|
||||||
|
cfxOwnedZones.hasAllBlue = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if cfxOwnedZones.allRed and not cfxOwnedZones.hasAllRed then
|
||||||
|
if cfxOwnedZones.sideOwnsAll(1) then
|
||||||
|
cfxZones.pollFlag(cfxOwnedZones.allRed, "inc", cfxOwnedZones)
|
||||||
|
cfxOwnedZones.hasAllRed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function cfxOwnedZones.sideOwnsAll(theSide)
|
function cfxOwnedZones.sideOwnsAll(theSide)
|
||||||
@ -393,6 +487,162 @@ function cfxOwnedZones.hasOwnedZones()
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- getting closest owned zones etc
|
||||||
|
-- required for groundTroops and factory attackers
|
||||||
|
-- methods provided only for other modules (e.g. cfxGroundTroops or
|
||||||
|
-- factoryZone
|
||||||
|
--
|
||||||
|
|
||||||
|
-- collect zones can filter owned zones.
|
||||||
|
-- by default it filters all zones that are in water
|
||||||
|
function cfxOwnedZones.collectZones(mode)
|
||||||
|
if not mode then mode = "land" end
|
||||||
|
if mode == "land" then
|
||||||
|
local landZones = {}
|
||||||
|
for idx, theZone in pairs(cfxOwnedZones.zones) do
|
||||||
|
p = cfxZones.getPoint(theZone)
|
||||||
|
p.y = p.z
|
||||||
|
local surfType = land.getSurfaceType(p)
|
||||||
|
if surfType == 3 then
|
||||||
|
else
|
||||||
|
table.insert(landZones, theZone)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return landZones
|
||||||
|
end
|
||||||
|
|
||||||
|
-- return all zones
|
||||||
|
return cfxOwnedZones.zones
|
||||||
|
--if not mode then mode = "OWNED" end
|
||||||
|
-- Note: since cfxGroundTroops currently simply uses owner flag
|
||||||
|
-- we cannot migrate to a differentiation between factory and
|
||||||
|
-- owned. All produced attackers always attack owned zones.
|
||||||
|
end
|
||||||
|
|
||||||
|
function cfxOwnedZones.getEnemyZonesFor(aCoalition)
|
||||||
|
local enemyZones = {}
|
||||||
|
local allZones = cfxOwnedZones.collectZones()
|
||||||
|
local ourEnemy = dcsCommon.getEnemyCoalitionFor(aCoalition)
|
||||||
|
for zKey, aZone in pairs(allZones) do
|
||||||
|
if aZone.owner == ourEnemy then -- only check enemy owned zones
|
||||||
|
-- note: will include untargetable zones
|
||||||
|
table.insert(enemyZones, aZone)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return enemyZones
|
||||||
|
end
|
||||||
|
|
||||||
|
function cfxOwnedZones.getNearestOwnedZoneToPoint(aPoint)
|
||||||
|
local shortestDist = math.huge
|
||||||
|
local closestZone = nil
|
||||||
|
local allZones = cfxOwnedZones.collectZones()
|
||||||
|
|
||||||
|
for zKey, aZone in pairs(allZones) do
|
||||||
|
local zPoint = cfxZones.getPoint(aZone)
|
||||||
|
currDist = dcsCommon.dist(zPoint, aPoint)
|
||||||
|
if aZone.untargetable ~= true and
|
||||||
|
currDist < shortestDist then
|
||||||
|
shortestDist = currDist
|
||||||
|
closestZone = aZone
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return closestZone, shortestDist
|
||||||
|
end
|
||||||
|
|
||||||
|
function cfxOwnedZones.getNearestOwnedZone(theZone)
|
||||||
|
local shortestDist = math.huge
|
||||||
|
local closestZone = nil
|
||||||
|
local aPoint = cfxZones.getPoint(theZone)
|
||||||
|
local allZones = cfxOwnedZones.collectZones()
|
||||||
|
for zKey, aZone in pairs(allZones) do
|
||||||
|
local zPoint = cfxZones.getPoint(aZone)
|
||||||
|
currDist = dcsCommon.dist(zPoint, aPoint)
|
||||||
|
if aZone.untargetable ~= true and currDist < shortestDist then
|
||||||
|
shortestDist = currDist
|
||||||
|
closestZone = aZone
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return closestZone, shortestDist
|
||||||
|
end
|
||||||
|
|
||||||
|
function cfxOwnedZones.getNearestEnemyOwnedZone(theZone, targetNeutral)
|
||||||
|
if not targetNeutral then targetNeutral = false else targetNeutral = true end
|
||||||
|
local shortestDist = math.huge
|
||||||
|
local closestZone = nil
|
||||||
|
local allZones = cfxOwnedZones.collectZones()
|
||||||
|
local ourEnemy = dcsCommon.getEnemyCoalitionFor(theZone.owner)
|
||||||
|
if not ourEnemy then return nil end -- we called for a neutral zone. they have no enemies
|
||||||
|
local zPoint = cfxZones.getPoint(theZone)
|
||||||
|
|
||||||
|
for zKey, aZone in pairs(allZones) do
|
||||||
|
if targetNeutral then
|
||||||
|
-- return all zones that do not belong to us
|
||||||
|
if aZone.owner ~= theZone.owner then
|
||||||
|
local aPoint = cfxZones.getPoint(aZone)
|
||||||
|
currDist = dcsCommon.dist(aPoint, zPoint)
|
||||||
|
if aZone.untargetable ~= true and currDist < shortestDist then
|
||||||
|
shortestDist = currDist
|
||||||
|
closestZone = aZone
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- return zones that are taken by the Enenmy
|
||||||
|
if aZone.owner == ourEnemy then -- only check own zones
|
||||||
|
local aPoint = cfxZones.getPoint(aZone)
|
||||||
|
currDist = dcsCommon.dist(zPoint, aPoint)
|
||||||
|
if aZone.untargetable ~= true and currDist < shortestDist then
|
||||||
|
shortestDist = currDist
|
||||||
|
closestZone = aZone
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return closestZone, shortestDist
|
||||||
|
end
|
||||||
|
|
||||||
|
function cfxOwnedZones.getNearestFriendlyZone(theZone, targetNeutral)
|
||||||
|
if not targetNeutral then targetNeutral = false else targetNeutral = true end
|
||||||
|
local shortestDist = math.huge
|
||||||
|
local closestZone = nil
|
||||||
|
local ourEnemy = dcsCommon.getEnemyCoalitionFor(theZone.owner)
|
||||||
|
if not ourEnemy then return nil end -- we called for a neutral zone. they have no enemies nor friends, all zones would be legal.
|
||||||
|
local zPoint = cfxZones.getPoint(theZone)
|
||||||
|
local allZones = cfxOwnedZones.collectZones()
|
||||||
|
|
||||||
|
for zKey, aZone in pairs(allZones) do
|
||||||
|
if targetNeutral then
|
||||||
|
-- target all zones that do not belong to the enemy
|
||||||
|
if aZone.owner ~= ourEnemy then
|
||||||
|
local aPoint = cfxZones.getPoint(aZone)
|
||||||
|
currDist = dcsCommon.dist(zPoint, aPoint)
|
||||||
|
if aZone.untargetable ~= true and currDist < shortestDist then
|
||||||
|
shortestDist = currDist
|
||||||
|
closestZone = aZone
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- only target zones that are taken by us
|
||||||
|
if aZone.owner == theZone.owner then -- only check own zones
|
||||||
|
local aPoint = cfxZones.getPoint(aZone)
|
||||||
|
currDist = dcsCommon.dist(zPoint, aPoint)
|
||||||
|
if aZone.untargetable ~= true and currDist < shortestDist then
|
||||||
|
shortestDist = currDist
|
||||||
|
closestZone = aZone
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return closestZone, shortestDist
|
||||||
|
end
|
||||||
|
|
||||||
|
function cfxOwnedZones.enemiesRemaining(aZone)
|
||||||
|
if cfxOwnedZones.getNearestEnemyOwnedZone(aZone) then return true end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
--
|
--
|
||||||
-- load / save data
|
-- load / save data
|
||||||
@ -415,8 +665,6 @@ function cfxOwnedZones.saveData()
|
|||||||
allZoneData[theZone.name] = zoneData
|
allZoneData[theZone.name] = zoneData
|
||||||
end
|
end
|
||||||
|
|
||||||
-- now iterate all attack groups that we have spawned and that
|
|
||||||
-- (maybe) are still alive
|
|
||||||
-- now write the info for the flags that we output for #red, etc
|
-- now write the info for the flags that we output for #red, etc
|
||||||
local flagInfo = {}
|
local flagInfo = {}
|
||||||
flagInfo.neutral = cfxZones.getFlagValue(cfxOwnedZones.neutralTriggerFlag, cfxOwnedZones)
|
flagInfo.neutral = cfxZones.getFlagValue(cfxOwnedZones.neutralTriggerFlag, cfxOwnedZones)
|
||||||
@ -478,10 +726,46 @@ function cfxOwnedZones.readConfigZone(theZone)
|
|||||||
cfxOwnedZones.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
|
cfxOwnedZones.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
|
||||||
cfxOwnedZones.announcer = cfxZones.getBoolFromZoneProperty(theZone, "announcer", true)
|
cfxOwnedZones.announcer = cfxZones.getBoolFromZoneProperty(theZone, "announcer", true)
|
||||||
|
|
||||||
cfxOwnedZones.redTriggerFlag = cfxZones.getStringFromZoneProperty(theZone, "r!", "*<cfxnone>")
|
if cfxZones.hasProperty(theZone, "r!") then
|
||||||
cfxOwnedZones.blueTriggerFlag = cfxZones.getStringFromZoneProperty(theZone, "b!", "*<cfxnone>")
|
cfxOwnedZones.redTriggerFlag = cfxZones.getStringFromZoneProperty(theZone, "r!", "*<cfxnone>")
|
||||||
cfxOwnedZones.neutralTriggerFlag = cfxZones.getStringFromZoneProperty(theZone, "n!", "*<cfxnone>")
|
else
|
||||||
|
cfxOwnedZones.redTriggerFlag = cfxZones.getStringFromZoneProperty(theZone, "r#", "*<cfxnone>")
|
||||||
|
end
|
||||||
|
if cfxZones.hasProperty(theZone, "b!") then
|
||||||
|
cfxOwnedZones.redTriggerFlag = cfxZones.getStringFromZoneProperty(theZone, "b!", "*<cfxnone>")
|
||||||
|
else
|
||||||
|
cfxOwnedZones.blueTriggerFlag = cfxZones.getStringFromZoneProperty(theZone, "b#", "*<cfxnone>")
|
||||||
|
end
|
||||||
|
|
||||||
|
if cfxZones.hasProperty(theZone, "n!") then
|
||||||
|
cfxOwnedZones.redTriggerFlag = cfxZones.getStringFromZoneProperty(theZone, "n!", "*<cfxnone>")
|
||||||
|
else
|
||||||
|
cfxOwnedZones.neutralTriggerFlag = cfxZones.getStringFromZoneProperty(theZone, "n#", "*<cfxnone>")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- allXXX flags
|
||||||
|
if cfxZones.hasProperty(theZone, "allBlue!") then
|
||||||
|
cfxOwnedZones.allBlue = cfxZones.getStringFromZoneProperty(theZone, "allBlue!", "*<cfxnone>")
|
||||||
|
cfxOwnedZones.hasAllBlue = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if cfxZones.hasProperty(theZone, "allRed!") then
|
||||||
|
cfxOwnedZones.allRed = cfxZones.getStringFromZoneProperty(theZone, "allRed!", "*<cfxnone>")
|
||||||
|
cfxOwnedZones.hasAllRed = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if cfxZones.hasProperty(theZone, "redOwned#") then
|
||||||
|
cfxOwnedZones.redOwned = cfxZones.getStringFromZoneProperty(theZone, "redOwned#", "*<cfxnone>")
|
||||||
|
end
|
||||||
|
if cfxZones.hasProperty(theZone, "blueOwned#") then
|
||||||
|
cfxOwnedZones.blueOwned = cfxZones.getStringFromZoneProperty(theZone, "blueOwned#", "*<cfxnone>")
|
||||||
|
end
|
||||||
|
if cfxZones.hasProperty(theZone, "neutralOwned#") then
|
||||||
|
cfxOwnedZones.neutralOwned = cfxZones.getStringFromZoneProperty(theZone, "neutralOwned#", "*<cfxnone>")
|
||||||
|
end
|
||||||
|
if cfxZones.hasProperty(theZone, "totalZones#") then
|
||||||
|
cfxOwnedZones.totalOwnedZones = cfxZones.getStringFromZoneProperty(theZone, "totalZones#", "*<cfxnone>")
|
||||||
|
end
|
||||||
-- numKeep, numCap, fastEval, easyContest
|
-- numKeep, numCap, fastEval, easyContest
|
||||||
cfxOwnedZones.numCap = cfxZones.getNumberFromZoneProperty(theZone, "numCap", 1) -- minimal number of units required to cap zone
|
cfxOwnedZones.numCap = cfxZones.getNumberFromZoneProperty(theZone, "numCap", 1) -- minimal number of units required to cap zone
|
||||||
cfxOwnedZones.numKeep = cfxZones.getNumberFromZoneProperty(theZone, "numKeep", 0) -- number required to keep zone
|
cfxOwnedZones.numKeep = cfxZones.getNumberFromZoneProperty(theZone, "numKeep", 0) -- number required to keep zone
|
||||||
@ -490,6 +774,21 @@ function cfxOwnedZones.readConfigZone(theZone)
|
|||||||
-- winSound, loseSound
|
-- winSound, loseSound
|
||||||
cfxOwnedZones.winSound = cfxZones.getStringFromZoneProperty(theZone, "winSound", "Quest Snare 3.wav" )
|
cfxOwnedZones.winSound = cfxZones.getStringFromZoneProperty(theZone, "winSound", "Quest Snare 3.wav" )
|
||||||
cfxOwnedZones.loseSound = cfxZones.getStringFromZoneProperty(theZone, "loseSound", "Death BRASS.wav")
|
cfxOwnedZones.loseSound = cfxZones.getStringFromZoneProperty(theZone, "loseSound", "Death BRASS.wav")
|
||||||
|
|
||||||
|
-- capture options
|
||||||
|
cfxOwnedZones.groundCap = cfxZones.getBoolFromZoneProperty(theZone, "groundCap", true)
|
||||||
|
cfxOwnedZones.navalCap = cfxZones.getBoolFromZoneProperty(theZone, "navalCap", false)
|
||||||
|
cfxOwnedZones.heloCap = cfxZones.getBoolFromZoneProperty(theZone, "heloCap")
|
||||||
|
cfxOwnedZones.fixWingCap = cfxZones.getBoolFromZoneProperty(theZone, "fixWingCap")
|
||||||
|
|
||||||
|
-- colors for line and fill
|
||||||
|
cfxOwnedZones.redLine = cfxZones.getRGBAVectorFromZoneProperty(theZone, "redLine", {1.0, 0, 0, 1.0})
|
||||||
|
cfxOwnedZones.redFill = cfxZones.getRGBAVectorFromZoneProperty(theZone, "redFill", {1.0, 0, 0, 0.2})
|
||||||
|
cfxOwnedZones.blueLine = cfxZones.getRGBAVectorFromZoneProperty(theZone, "blueLine", {0.0, 0, 1.0, 1.0})
|
||||||
|
cfxOwnedZones.blueFill = cfxZones.getRGBAVectorFromZoneProperty(theZone, "blueFill", {0.0, 0, 1.0, 0.2})
|
||||||
|
cfxOwnedZones.neutralLine = cfxZones.getRGBAVectorFromZoneProperty(theZone, "neutralLine", {0.8, 0.8, 0.8, 1.0})
|
||||||
|
cfxOwnedZones.neutralFill = cfxZones.getRGBAVectorFromZoneProperty(theZone, "neutralFill", {0.8, 0.8, 0.8, 0.2})
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function cfxOwnedZones.init()
|
function cfxOwnedZones.init()
|
||||||
@ -535,5 +834,12 @@ if not cfxOwnedZones.init() then
|
|||||||
cfxOwnedZones = nil
|
cfxOwnedZones = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[[--
|
||||||
|
masterOwner input for zones, overrides all else when not neutral
|
||||||
|
|
||||||
|
dont count zones that cant be conquered for allBlue/allRed
|
||||||
|
|
||||||
|
define color with #FF008080
|
||||||
|
|
||||||
|
--]]--
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
cfxZones = {}
|
cfxZones = {}
|
||||||
cfxZones.version = "3.0.9"
|
cfxZones.version = "3.1.0"
|
||||||
|
|
||||||
-- cf/x zone management module
|
-- cf/x zone management module
|
||||||
-- reads dcs zones and makes them accessible and mutable
|
-- reads dcs zones and makes them accessible and mutable
|
||||||
@ -127,6 +127,8 @@ cfxZones.version = "3.0.9"
|
|||||||
- 3.0.7 - getPoint() can also get land y when passing true as second param
|
- 3.0.7 - getPoint() can also get land y when passing true as second param
|
||||||
- 3.0.8 - new cfxZones.pointInOneOfZones(thePoint, zoneArray, useOrig)
|
- 3.0.8 - new cfxZones.pointInOneOfZones(thePoint, zoneArray, useOrig)
|
||||||
- 3.0.9 - new getFlareColorStringFromZoneProperty()
|
- 3.0.9 - new getFlareColorStringFromZoneProperty()
|
||||||
|
- 3.1.0 - new getRGBVectorFromZoneProperty()
|
||||||
|
new getRGBAVectorFromZoneProperty()
|
||||||
|
|
||||||
--]]--
|
--]]--
|
||||||
cfxZones.verbose = false
|
cfxZones.verbose = false
|
||||||
@ -2221,6 +2223,56 @@ function cfxZones.getVectorFromZoneProperty(theZone, theProperty, minDims, defau
|
|||||||
return nVec
|
return nVec
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function cfxZones.getRGBVectorFromZoneProperty(theZone, theProperty, defaultVal)
|
||||||
|
if not defaultVal then defaultVal = {1.0, 1.0, 1.0} end
|
||||||
|
if #defaultVal ~=3 then defaultVal = {1.0, 1.0, 1.0} end
|
||||||
|
local s = cfxZones.getStringFromZoneProperty(theZone, theProperty, "")
|
||||||
|
local sVec = dcsCommon.splitString(s, ",")
|
||||||
|
local nVec = {}
|
||||||
|
for i = 1, 3 do
|
||||||
|
n = sVec[i]
|
||||||
|
if n then n = tonumber(n) end
|
||||||
|
if not n then n = defaultVal[i] end
|
||||||
|
if n > 1.0 then n = 1.0 end
|
||||||
|
if n < 0 then n = 0 end
|
||||||
|
nVec[i] = n
|
||||||
|
end
|
||||||
|
|
||||||
|
return nVec
|
||||||
|
end
|
||||||
|
|
||||||
|
function cfxZones.getRGBAVectorFromZoneProperty(theZone, theProperty, defaultVal)
|
||||||
|
if not defaultVal then defaultVal = {1.0, 1.0, 1.0, 1.0} end
|
||||||
|
if #defaultVal ~=4 then defaultVal = {1.0, 1.0, 1.0, 1.0} end
|
||||||
|
local s = cfxZones.getStringFromZoneProperty(theZone, theProperty, "")
|
||||||
|
local sVec = dcsCommon.splitString(s, ",")
|
||||||
|
local nVec = {}
|
||||||
|
for i = 1, 4 do
|
||||||
|
n = sVec[i]
|
||||||
|
if n then n = tonumber(n) end
|
||||||
|
if not n then n = defaultVal[i] end
|
||||||
|
if n > 1.0 then n = 1.0 end
|
||||||
|
if n < 0 then n = 0 end
|
||||||
|
nVec[i] = n
|
||||||
|
end
|
||||||
|
|
||||||
|
return nVec
|
||||||
|
end
|
||||||
|
|
||||||
|
function cfxZones.getRGBFromZoneProperty(theZone, theProperty, default)
|
||||||
|
--if not default then default = {1.0, 1.0, 1.0} end -- white
|
||||||
|
local rawRGB = cfxZones.getVectorFromZoneProperty(theZone, theProperty, 3, 1.0)
|
||||||
|
local retVal = {}
|
||||||
|
for i = 1, 3 do
|
||||||
|
local cp = rawRGB[i]
|
||||||
|
if cp > 1.0 then cp = 1.0 end
|
||||||
|
if cp < 0 then cp = 0 end
|
||||||
|
retVal[i] = cp
|
||||||
|
end
|
||||||
|
return retVal
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function cfxZones.getSmokeColorStringFromZoneProperty(theZone, theProperty, default) -- smoke as 'red', 'green', or 1..5
|
function cfxZones.getSmokeColorStringFromZoneProperty(theZone, theProperty, default) -- smoke as 'red', 'green', or 1..5
|
||||||
if not default then default = "red" end
|
if not default then default = "red" end
|
||||||
local s = cfxZones.getStringFromZoneProperty(theZone, theProperty, default)
|
local s = cfxZones.getStringFromZoneProperty(theZone, theProperty, default)
|
||||||
|
|||||||
@ -1,19 +1,22 @@
|
|||||||
factoryZone = {}
|
factoryZone = {}
|
||||||
factoryZone.version = "1.0.0"
|
factoryZone.version = "2.0.0"
|
||||||
factoryZone.verbose = false
|
factoryZone.verbose = false
|
||||||
factoryZone.name = "factoryZone"
|
factoryZone.name = "factoryZone"
|
||||||
|
|
||||||
--[[-- VERSION HISTORY
|
--[[-- VERSION HISTORY
|
||||||
|
|
||||||
1.0.0 - refactored production part from cfxOwnedZones 1.xpcall
|
2.0.0 - refactored production part from cfxOwnedZones 1.xpcall
|
||||||
|
- "production" and "defenders" simplification
|
||||||
|
- now optional specification for red/blue
|
||||||
|
- use maxRadius from zone for spawning to support quad zones
|
||||||
|
|
||||||
--]]--
|
--]]--
|
||||||
factoryZone.requiredLibs = {
|
factoryZone.requiredLibs = {
|
||||||
"dcsCommon", -- common is of course needed for everything
|
"dcsCommon",
|
||||||
-- pretty stupid to check for this since we
|
"cfxZones",
|
||||||
-- need common to invoke the check, but anyway
|
|
||||||
"cfxZones", -- Zones, of course
|
|
||||||
"cfxCommander", -- to make troops do stuff
|
"cfxCommander", -- to make troops do stuff
|
||||||
|
"cfxGroundTroops", -- all produced troops rely on this
|
||||||
|
"cfxOwnedZones",
|
||||||
}
|
}
|
||||||
|
|
||||||
factoryZone.zones = {} -- my factory zones
|
factoryZone.zones = {} -- my factory zones
|
||||||
@ -28,7 +31,7 @@ factoryZone.repairTime = 200 -- time until we raplace one lost unit, also repair
|
|||||||
-- is regularly verified and cut to size
|
-- is regularly verified and cut to size
|
||||||
factoryZone.spawnedAttackers = {}
|
factoryZone.spawnedAttackers = {}
|
||||||
|
|
||||||
-- factoryZone is a module that managers production of units
|
-- factoryZone is a module that manages production of units
|
||||||
-- inside zones and can switch production based on who owns the
|
-- inside zones and can switch production based on who owns the
|
||||||
-- zone. Zone ownership can by dynamic (by using OwnedZones or
|
-- zone. Zone ownership can by dynamic (by using OwnedZones or
|
||||||
-- using scripts to change the 'owner' flag
|
-- using scripts to change the 'owner' flag
|
||||||
@ -48,10 +51,6 @@ factoryZone.spawnedAttackers = {}
|
|||||||
-- attackDelta - polar coord: r from zone center where attackers are spawned
|
-- attackDelta - polar coord: r from zone center where attackers are spawned
|
||||||
-- attackPhi - polar degrees where attackers are to be spawned
|
-- attackPhi - polar degrees where attackers are to be spawned
|
||||||
-- paused - will not spawn. default is false
|
-- paused - will not spawn. default is false
|
||||||
-- unbeatable - can't be conquered by other side. default is false
|
|
||||||
-- untargetable - will not be targeted by either side. make unbeatable
|
|
||||||
-- owned zones untargetable, or they'll become a troop magnet for
|
|
||||||
-- zoneAttackers
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- M I S C
|
-- M I S C
|
||||||
@ -66,29 +65,39 @@ function factoryZone.getFactoryZoneByName(zName)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function factoryZone.addFactoryZone(aZone)
|
function factoryZone.addFactoryZone(aZone)
|
||||||
aZone.worksFor = cfxZones.getCoalitionFromZoneProperty(aZone, "factory", 0) -- currently unused, have RED/BLUE separate types
|
--aZone.worksFor = cfxZones.getCoalitionFromZoneProperty(aZone, "factory", 0) -- currently unused, have RED/BLUE separate types
|
||||||
aZone.state = "init"
|
aZone.state = "init"
|
||||||
aZone.timeStamp = timer.getTime()
|
aZone.timeStamp = timer.getTime()
|
||||||
aZone.defendersRED = cfxZones.getStringFromZoneProperty(aZone, "defendersRED", "none")
|
|
||||||
aZone.defendersBLUE = cfxZones.getStringFromZoneProperty(aZone, "defendersBLUE", "none")
|
-- set up production default
|
||||||
|
local factory = cfxZones.getStringFromZoneProperty(aZone, "factory", "none")
|
||||||
|
|
||||||
|
local production = cfxZones.getStringFromZoneProperty(aZone, "production", factory)
|
||||||
|
|
||||||
|
local defenders = cfxZones.getStringFromZoneProperty(aZone, "defenders", factory)
|
||||||
|
|
||||||
if cfxZones.hasProperty(aZone, "attackersRED") then
|
if cfxZones.hasProperty(aZone, "attackersRED") then
|
||||||
-- legacy support
|
-- legacy support
|
||||||
aZone.attackersRED = cfxZones.getStringFromZoneProperty(aZone, "attackersRED", "none")
|
aZone.attackersRED = cfxZones.getStringFromZoneProperty(aZone, "attackersRED", production)
|
||||||
else
|
else
|
||||||
aZone.attackersRED = cfxZones.getStringFromZoneProperty(aZone, "productionRED", "none")
|
aZone.attackersRED = cfxZones.getStringFromZoneProperty(aZone, "productionRED", production)
|
||||||
end
|
end
|
||||||
|
|
||||||
if cfxZones.hasProperty(aZone, "attackersBLUE") then
|
if cfxZones.hasProperty(aZone, "attackersBLUE") then
|
||||||
-- legacy support
|
-- legacy support
|
||||||
aZone.attackersBLUE = cfxZones.getStringFromZoneProperty(aZone, "attackersBLUE", "none")
|
aZone.attackersBLUE = cfxZones.getStringFromZoneProperty(aZone, "attackersBLUE", production)
|
||||||
else
|
else
|
||||||
aZone.attackersBLUE = cfxZones.getStringFromZoneProperty(aZone, "productionBLUE", "none")
|
aZone.attackersBLUE = cfxZones.getStringFromZoneProperty(aZone, "productionBLUE", production)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- set up defenders default, or use production / factory
|
||||||
|
aZone.defendersRED = cfxZones.getStringFromZoneProperty(aZone, "defendersRED", defenders)
|
||||||
|
aZone.defendersBLUE = cfxZones.getStringFromZoneProperty(aZone, "defendersBLUE", defenders)
|
||||||
|
|
||||||
aZone.formation = cfxZones.getStringFromZoneProperty(aZone, "formation", "circle_out")
|
aZone.formation = cfxZones.getStringFromZoneProperty(aZone, "formation", "circle_out")
|
||||||
aZone.attackFormation = cfxZones.getStringFromZoneProperty(aZone, "attackFormation", "circle_out") -- cfxZones.getZoneProperty(aZone, "attackFormation")
|
aZone.attackFormation = cfxZones.getStringFromZoneProperty(aZone, "attackFormation", "circle_out") -- cfxZones.getZoneProperty(aZone, "attackFormation")
|
||||||
aZone.spawnRadius = cfxZones.getNumberFromZoneProperty(aZone, "spawnRadius", aZone.radius-5) -- "-5" so they remaininside radius
|
aZone.spawnRadius = cfxZones.getNumberFromZoneProperty(aZone, "spawnRadius", aZone.maxRadius-5) -- "-5" so they remaininside radius
|
||||||
aZone.attackRadius = cfxZones.getNumberFromZoneProperty(aZone, "attackRadius", aZone.radius)
|
aZone.attackRadius = cfxZones.getNumberFromZoneProperty(aZone, "attackRadius", aZone.maxRadius)
|
||||||
aZone.attackDelta = cfxZones.getNumberFromZoneProperty(aZone, "attackDelta", 10) -- aZone.radius)
|
aZone.attackDelta = cfxZones.getNumberFromZoneProperty(aZone, "attackDelta", 10) -- aZone.radius)
|
||||||
aZone.attackPhi = cfxZones.getNumberFromZoneProperty(aZone, "attackPhi", 0)
|
aZone.attackPhi = cfxZones.getNumberFromZoneProperty(aZone, "attackPhi", 0)
|
||||||
|
|
||||||
@ -111,8 +120,6 @@ function factoryZone.addFactoryZone(aZone)
|
|||||||
aZone.factoryTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "factoryTriggerMethod", "change")
|
aZone.factoryTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "factoryTriggerMethod", "change")
|
||||||
end
|
end
|
||||||
|
|
||||||
aZone.untargetable = cfxZones.getBoolFromZoneProperty(aZone, "untargetable", false)
|
|
||||||
|
|
||||||
factoryZone.zones[aZone.name] = aZone
|
factoryZone.zones[aZone.name] = aZone
|
||||||
factoryZone.verifyZone(aZone)
|
factoryZone.verifyZone(aZone)
|
||||||
end
|
end
|
||||||
@ -125,154 +132,6 @@ function factoryZone.verifyZone(aZone)
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function factoryZone.getEnemyZonesFor(aCoalition)
|
|
||||||
-- when cfxOwnedZones is present, or it will return only those
|
|
||||||
-- else it scans all zones from cfxZones
|
|
||||||
local enemyZones = {}
|
|
||||||
local allZones = cfxZones.zones
|
|
||||||
if cfxOwnedZones then
|
|
||||||
allZones = cfxOwnedZones.zones
|
|
||||||
end
|
|
||||||
local ourEnemy = dcsCommon.getEnemyCoalitionFor(aCoalition)
|
|
||||||
for zKey, aZone in pairs(allZones) do
|
|
||||||
if aZone.owner == ourEnemy then -- only check enemy owned zones
|
|
||||||
-- note: will include untargetable zones
|
|
||||||
table.insert(enemyZones, aZone)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return enemyZones
|
|
||||||
end
|
|
||||||
|
|
||||||
function factoryZone.getNearestOwnedZoneToPoint(aPoint)
|
|
||||||
local shortestDist = math.huge
|
|
||||||
-- when cfxOwnedZones is present, or it will return only those
|
|
||||||
-- else it scans all zones from cfxZones
|
|
||||||
local closestZone = nil
|
|
||||||
local allZones = cfxZones.zones
|
|
||||||
if cfxOwnedZones then
|
|
||||||
allZones = cfxOwnedZones.zones
|
|
||||||
end
|
|
||||||
for zKey, aZone in pairs(allZones) do
|
|
||||||
local zPoint = cfxZones.getPoint(aZone)
|
|
||||||
currDist = dcsCommon.dist(zPoint, aPoint)
|
|
||||||
if aZone.untargetable ~= true and
|
|
||||||
currDist < shortestDist then
|
|
||||||
shortestDist = currDist
|
|
||||||
closestZone = aZone
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return closestZone, shortestDist
|
|
||||||
end
|
|
||||||
|
|
||||||
function factoryZone.getNearestOwnedZone(theZone)
|
|
||||||
local shortestDist = math.huge
|
|
||||||
-- when cfxOwnedZones is present, or it will return only those
|
|
||||||
-- else it scans all zones from cfxZones
|
|
||||||
local closestZone = nil
|
|
||||||
local aPoint = cfxZones.getPoint(theZone)
|
|
||||||
local allZones = cfxZones.zones
|
|
||||||
if cfxOwnedZones then
|
|
||||||
allZones = cfxOwnedZones.zones
|
|
||||||
end
|
|
||||||
for zKey, aZone in pairs(allZones) do
|
|
||||||
local zPoint = cfxZones.getPoint(aZone)
|
|
||||||
currDist = dcsCommon.dist(zPoint, aPoint)
|
|
||||||
if aZone.untargetable ~= true and currDist < shortestDist then
|
|
||||||
shortestDist = currDist
|
|
||||||
closestZone = aZone
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return closestZone, shortestDist
|
|
||||||
end
|
|
||||||
|
|
||||||
function factoryZone.getNearestEnemyOwnedZone(theZone, targetNeutral)
|
|
||||||
if not targetNeutral then targetNeutral = false else targetNeutral = true end
|
|
||||||
local shortestDist = math.huge
|
|
||||||
local closestZone = nil
|
|
||||||
-- when cfxOwnedZones is present, or it will return only those
|
|
||||||
-- else it scans all zones from cfxZones
|
|
||||||
local allZones = cfxZones.zones
|
|
||||||
if cfxOwnedZones then
|
|
||||||
allZones = cfxOwnedZones.zones
|
|
||||||
end
|
|
||||||
local ourEnemy = dcsCommon.getEnemyCoalitionFor(theZone.owner)
|
|
||||||
if not ourEnemy then return nil end -- we called for a neutral zone. they have no enemies
|
|
||||||
local zPoint = cfxZones.getPoint(theZone)
|
|
||||||
|
|
||||||
for zKey, aZone in pairs(allZones) do
|
|
||||||
if targetNeutral then
|
|
||||||
-- return all zones that do not belong to us
|
|
||||||
if aZone.owner ~= theZone.owner then
|
|
||||||
local aPoint = cfxZones.getPoint(aZone)
|
|
||||||
currDist = dcsCommon.dist(aPoint, zPoint)
|
|
||||||
if aZone.untargetable ~= true and currDist < shortestDist then
|
|
||||||
shortestDist = currDist
|
|
||||||
closestZone = aZone
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
-- return zones that are taken by the Enenmy
|
|
||||||
if aZone.owner == ourEnemy then -- only check own zones
|
|
||||||
local aPoint = cfxZones.getPoint(aZone)
|
|
||||||
currDist = dcsCommon.dist(zPoint, aPoint)
|
|
||||||
if aZone.untargetable ~= true and currDist < shortestDist then
|
|
||||||
shortestDist = currDist
|
|
||||||
closestZone = aZone
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return closestZone, shortestDist
|
|
||||||
end
|
|
||||||
|
|
||||||
function factoryZone.getNearestFriendlyZone(theZone, targetNeutral)
|
|
||||||
if not targetNeutral then targetNeutral = false else targetNeutral = true end
|
|
||||||
local shortestDist = math.huge
|
|
||||||
local closestZone = nil
|
|
||||||
local ourEnemy = dcsCommon.getEnemyCoalitionFor(theZone.owner)
|
|
||||||
if not ourEnemy then return nil end -- we called for a neutral zone. they have no enemies nor friends, all zones would be legal.
|
|
||||||
local zPoint = cfxZones.getPoint(theZone)
|
|
||||||
-- when cfxOwnedZones is present, or it will return only those
|
|
||||||
-- else it scans all zones from cfxZones
|
|
||||||
local allZones = cfxZones.zones
|
|
||||||
if cfxOwnedZones then
|
|
||||||
allZones = cfxOwnedZones.zones
|
|
||||||
end
|
|
||||||
for zKey, aZone in pairs(allZones) do
|
|
||||||
if targetNeutral then
|
|
||||||
-- target all zones that do not belong to the enemy
|
|
||||||
if aZone.owner ~= ourEnemy then
|
|
||||||
local aPoint = cfxZones.getPoint(aZone)
|
|
||||||
currDist = dcsCommon.dist(zPoint, aPoint)
|
|
||||||
if aZone.untargetable ~= true and currDist < shortestDist then
|
|
||||||
shortestDist = currDist
|
|
||||||
closestZone = aZone
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
-- only target zones that are taken by us
|
|
||||||
if aZone.owner == theZone.owner then -- only check own zones
|
|
||||||
local aPoint = cfxZones.getPoint(aZone)
|
|
||||||
currDist = dcsCommon.dist(zPoint, aPoint)
|
|
||||||
if aZone.untargetable ~= true and currDist < shortestDist then
|
|
||||||
shortestDist = currDist
|
|
||||||
closestZone = aZone
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return closestZone, shortestDist
|
|
||||||
end
|
|
||||||
|
|
||||||
function factoryZone.enemiesRemaining(aZone)
|
|
||||||
if factoryZone.getNearestEnemyOwnedZone(aZone) then return true end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function factoryZone.spawnAttackTroops(theTypes, aZone, aCoalition, aFormation)
|
function factoryZone.spawnAttackTroops(theTypes, aZone, aCoalition, aFormation)
|
||||||
local unitTypes = {} -- build type names
|
local unitTypes = {} -- build type names
|
||||||
-- split theTypes into an array of types
|
-- split theTypes into an array of types
|
||||||
@ -337,7 +196,7 @@ end
|
|||||||
--
|
--
|
||||||
|
|
||||||
function factoryZone.sendOutAttackers(aZone)
|
function factoryZone.sendOutAttackers(aZone)
|
||||||
-- sanity check: never done for non-neutral zones
|
-- sanity check: never done for neutral zones
|
||||||
if aZone.owner == 0 then
|
if aZone.owner == 0 then
|
||||||
if aZone.verbose or factoryZone.verbose then
|
if aZone.verbose or factoryZone.verbose then
|
||||||
trigger.action.outText("+++factZ: SendAttackers invoked for NEUTRAL zone <" .. aZone.name .. ">", 30)
|
trigger.action.outText("+++factZ: SendAttackers invoked for NEUTRAL zone <" .. aZone.name .. ">", 30)
|
||||||
@ -346,7 +205,7 @@ function factoryZone.sendOutAttackers(aZone)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- only spawn if there are zones to attack
|
-- only spawn if there are zones to attack
|
||||||
if not factoryZone.enemiesRemaining(aZone) then
|
if not cfxOwnedZones.enemiesRemaining(aZone) then
|
||||||
if factoryZone.verbose then
|
if factoryZone.verbose then
|
||||||
trigger.action.outText("+++factZ - no enemies, resting ".. aZone.name, 30)
|
trigger.action.outText("+++factZ - no enemies, resting ".. aZone.name, 30)
|
||||||
end
|
end
|
||||||
@ -832,11 +691,13 @@ function factoryZone.readConfigZone(theZone)
|
|||||||
factoryZone.attackingTime = cfxZones.getNumberFromZoneProperty(theZone, "attackingTime", 300)
|
factoryZone.attackingTime = cfxZones.getNumberFromZoneProperty(theZone, "attackingTime", 300)
|
||||||
factoryZone.shockTime = cfxZones.getNumberFromZoneProperty(theZone, "shockTime", 200)
|
factoryZone.shockTime = cfxZones.getNumberFromZoneProperty(theZone, "shockTime", 200)
|
||||||
factoryZone.repairTime = cfxZones.getNumberFromZoneProperty(theZone, "repairTime", 200)
|
factoryZone.repairTime = cfxZones.getNumberFromZoneProperty(theZone, "repairTime", 200)
|
||||||
|
factoryZone.targetZones = "OWNED"
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function factoryZone.init()
|
function factoryZone.init()
|
||||||
-- check libs
|
-- check libs
|
||||||
if not dcsCommon.libCheck("cfx Owned Zones",
|
if not dcsCommon.libCheck("cfx Factory Zones",
|
||||||
factoryZone.requiredLibs) then
|
factoryZone.requiredLibs) then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
@ -845,7 +706,7 @@ function factoryZone.init()
|
|||||||
local theZone = cfxZones.getZoneByName("factoryZoneConfig")
|
local theZone = cfxZones.getZoneByName("factoryZoneConfig")
|
||||||
factoryZone.readConfigZone(theZone)
|
factoryZone.readConfigZone(theZone)
|
||||||
|
|
||||||
-- collect all owned zones by their 'factory' property
|
-- collect all zones by their 'factory' property
|
||||||
-- start the process
|
-- start the process
|
||||||
local pZones = cfxZones.zonesWithProperty("factory")
|
local pZones = cfxZones.zonesWithProperty("factory")
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
limitedAirframes = {}
|
limitedAirframes = {}
|
||||||
limitedAirframes.version = "1.5.3"
|
limitedAirframes.version = "1.5.4"
|
||||||
limitedAirframes.verbose = false
|
limitedAirframes.verbose = false
|
||||||
limitedAirframes.enabled = true -- can be turned off
|
limitedAirframes.enabled = true -- can be turned off
|
||||||
limitedAirframes.userCanToggle = true -- F10 menu?
|
limitedAirframes.userCanToggle = true -- F10 menu?
|
||||||
@ -56,6 +56,7 @@ limitedAirframes.requiredLibs = {
|
|||||||
when autoCSAR is active
|
when autoCSAR is active
|
||||||
- 1.5.3 - ... but do allow it if not coming from 'ejected' so ditching
|
- 1.5.3 - ... but do allow it if not coming from 'ejected' so ditching
|
||||||
a plane will again create CSAR missions
|
a plane will again create CSAR missions
|
||||||
|
1.5.4 - red# and blue# instead of #red and #blue
|
||||||
|
|
||||||
--]]--
|
--]]--
|
||||||
|
|
||||||
@ -147,8 +148,16 @@ function limitedAirframes.readConfigZone()
|
|||||||
|
|
||||||
limitedAirframes.maxBlue = cfxZones.getNumberFromZoneProperty(theZone, "maxBlue", -1)
|
limitedAirframes.maxBlue = cfxZones.getNumberFromZoneProperty(theZone, "maxBlue", -1)
|
||||||
|
|
||||||
limitedAirframes.numRed = cfxZones.getStringFromZoneProperty(theZone, "#red", "*none")
|
if cfxZones.hasProperty(theZone, "#red") then
|
||||||
limitedAirframes.numBlue = cfxZones.getStringFromZoneProperty(theZone, "#blue", "*none")
|
limitedAirframes.numRed = cfxZones.getStringFromZoneProperty(theZone, "#red", "*none")
|
||||||
|
else
|
||||||
|
limitedAirframes.numRed = cfxZones.getStringFromZoneProperty(theZone, "red#", "*none")
|
||||||
|
end
|
||||||
|
if cfxZones.hasProperty(theZone, "#blue") then
|
||||||
|
limitedAirframes.numBlue = cfxZones.getStringFromZoneProperty(theZone, "#blue", "*none")
|
||||||
|
else
|
||||||
|
limitedAirframes.numBlue = cfxZones.getStringFromZoneProperty(theZone, "blue#", "*none")
|
||||||
|
end
|
||||||
|
|
||||||
limitedAirframes.redWinsFlag = cfxZones.getStringFromZoneProperty(theZone, "redWins!", "*none")
|
limitedAirframes.redWinsFlag = cfxZones.getStringFromZoneProperty(theZone, "redWins!", "*none")
|
||||||
|
|
||||||
|
|||||||
52
modules/mxObjects.lua
Normal file
52
modules/mxObjects.lua
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
mxObjects = {}
|
||||||
|
|
||||||
|
function mxObjects.getObjectFreePoly(layerName, polyName, rel) -- omit rel to get absolute points, else pass 'true' to get relative to first point.
|
||||||
|
if not rel then rel = false end -- relative or absolute
|
||||||
|
if not env.mission.drawings then
|
||||||
|
trigger.action.outText("+++mxO: Mission has no drawings.", 30)
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
|
||||||
|
local drawings = env.mission.drawings
|
||||||
|
local layers = drawings["layers"]
|
||||||
|
if not layers then
|
||||||
|
trigger.action.outText("+++mxO: Mission has no layers in drawing", 30)
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
local theLayer = nil
|
||||||
|
for idx, aLayer in pairs(layers) do
|
||||||
|
if aLayer.name == layerName then
|
||||||
|
theLayer = aLayer
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not theLayer then
|
||||||
|
trigger.action.outText("+++mxO: No layer named <" .. layerName .. "> in Mission", 30)
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
|
||||||
|
local objects = theLayer.objects
|
||||||
|
if not objects then
|
||||||
|
trigger.action.outText("+++mxO: No objects in layer <" .. layerName .. ">", 30)
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
-- scan objects for a "free" mode poly with name polyName
|
||||||
|
for idx, theObject in pairs(objects) do
|
||||||
|
if theObject.polygonMode == "free" and theObject.name == polyName then
|
||||||
|
local poly = {}
|
||||||
|
for idp, thePoint in pairs(theObject.points) do
|
||||||
|
local p = {}
|
||||||
|
p.x = thePoint.x
|
||||||
|
p.y = thePoint.y
|
||||||
|
if not rel then
|
||||||
|
p.x = p.x + theObject.mapX
|
||||||
|
p.y = p.y + theObject.mapY
|
||||||
|
end
|
||||||
|
poly[idp] = p
|
||||||
|
end
|
||||||
|
return poly
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
trigger.action.outText("+++mxO: no polygon named <" .. polyName .. "> in layer <" ..layerName .. ">", 30)
|
||||||
|
return {}
|
||||||
|
end
|
||||||
@ -8,6 +8,7 @@ playerZone.playerZones = {}
|
|||||||
--[[--
|
--[[--
|
||||||
Version History
|
Version History
|
||||||
1.0.0 - Initial version
|
1.0.0 - Initial version
|
||||||
|
1.0.1 - pNum --> pNum#
|
||||||
|
|
||||||
--]]--
|
--]]--
|
||||||
|
|
||||||
@ -22,7 +23,9 @@ function playerZone.createPlayerZone(theZone)
|
|||||||
theZone.pzMethod = cfxZones.getStringFromZoneProperty(theZone, "pwMethod", "inc")
|
theZone.pzMethod = cfxZones.getStringFromZoneProperty(theZone, "pwMethod", "inc")
|
||||||
end
|
end
|
||||||
|
|
||||||
if cfxZones.hasProperty(theZone, "pNum") then
|
if cfxZones.hasProperty(theZone, "pNum#") then
|
||||||
|
theZone.pNum = cfxZones.getStringFromZoneProperty(theZone, "pNum#", "none")
|
||||||
|
elseif cfxZones.hasProperty(theZone, "pNum") then
|
||||||
theZone.pNum = cfxZones.getStringFromZoneProperty(theZone, "pNum", "none")
|
theZone.pNum = cfxZones.getStringFromZoneProperty(theZone, "pNum", "none")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
320
modules/stopGaps standalone.lua
Normal file
320
modules/stopGaps standalone.lua
Normal file
@ -0,0 +1,320 @@
|
|||||||
|
stopGap = {}
|
||||||
|
stopGap.version = "1.0.4 STANDALONE"
|
||||||
|
stopGap.verbose = false
|
||||||
|
stopGap.ssbEnabled = true
|
||||||
|
stopGap.ignoreMe = "-sg"
|
||||||
|
--[[--
|
||||||
|
Written and (c) 2023 by Christian Franz
|
||||||
|
|
||||||
|
Replace all player units with static aircraft until the first time
|
||||||
|
that a player slots into that plane. Static is then replaced with live
|
||||||
|
player unit. For Multiplayer the small (server-only) script "StopGapGUI" is required
|
||||||
|
|
||||||
|
For aircraft/helo carriers, no player planes are replaced with statics
|
||||||
|
|
||||||
|
STRONGLY RECOMMENDED:
|
||||||
|
- Use single-unit player groups.
|
||||||
|
- Use 'start from ground hot/cold' to be able to control initial aircraft orientation
|
||||||
|
|
||||||
|
To selectively exempt player units from stopGap, add a '-sg' to their name
|
||||||
|
|
||||||
|
Version History
|
||||||
|
1.0.0 - Initial version
|
||||||
|
1.0.1 - update / replace statics after slots become free
|
||||||
|
1.0.3 - server plug-in logic for SSB, sgGUI
|
||||||
|
1.0.4 - player units or groups that end in '-sg' are not stop-gapped
|
||||||
|
|
||||||
|
--]]--
|
||||||
|
|
||||||
|
stopGap.standInGroups ={}
|
||||||
|
stopGap.myGroups = {} -- for fast look-up of mx orig data
|
||||||
|
--
|
||||||
|
-- one-time start-up processing
|
||||||
|
--
|
||||||
|
-- in DCS, a group with one or more players only allocates when
|
||||||
|
-- the first player in the group enters the game.
|
||||||
|
--
|
||||||
|
cfxMX = {} -- local copy of cfxMX mission data cross reference tool
|
||||||
|
cfxMX.playerGroupByName = {} -- returns data only if a player is in group
|
||||||
|
cfxMX.countryByName ={} -- county of group named
|
||||||
|
|
||||||
|
function cfxMX.createCrossReferences()
|
||||||
|
-- tip o' hat to Mist for scanning mission struct.
|
||||||
|
for coa_name_miz, coa_data in pairs(env.mission.coalition) do -- iterate all coalitions
|
||||||
|
local coa_name = coa_name_miz
|
||||||
|
if string.lower(coa_name_miz) == 'neutrals' then -- remove 's' at neutralS
|
||||||
|
coa_name = 'neutral'
|
||||||
|
end
|
||||||
|
-- directly convert coalition into number for easier access later
|
||||||
|
local coaNum = 0
|
||||||
|
if coa_name == "red" then coaNum = 1 end
|
||||||
|
if coa_name == "blue" then coaNum = 2 end
|
||||||
|
|
||||||
|
if type(coa_data) == 'table' then -- coalition = {bullseye, nav_points, name, county},
|
||||||
|
-- with county being an array
|
||||||
|
if coa_data.country then -- make sure there a country table for this coalition
|
||||||
|
for cntry_id, cntry_data in pairs(coa_data.country) do -- iterate all countries for this
|
||||||
|
-- per country = {id, name, vehicle, helicopter, plane, ship, static}
|
||||||
|
local countryName = string.lower(cntry_data.name)
|
||||||
|
local countryID = cntry_data.id
|
||||||
|
if type(cntry_data) == 'table' then -- filter strings .id and .name
|
||||||
|
for obj_type_name, obj_type_data in pairs(cntry_data) do
|
||||||
|
if obj_type_name == "helicopter" or
|
||||||
|
obj_type_name == "ship" or
|
||||||
|
obj_type_name == "plane" or
|
||||||
|
obj_type_name == "vehicle" or
|
||||||
|
obj_type_name == "static" -- what about "cargo"?
|
||||||
|
then -- (so it's not id or name)
|
||||||
|
local category = obj_type_name
|
||||||
|
if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then --there's at least one group!
|
||||||
|
for group_num, group_data in pairs(obj_type_data.group) do
|
||||||
|
|
||||||
|
local aName = group_data.name
|
||||||
|
cfxMX.countryByName[aName] = countryID
|
||||||
|
-- now iterate all units in this group
|
||||||
|
-- for 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
|
||||||
|
cfxMX.playerGroupByName[aName] = group_data -- inefficient, but works
|
||||||
|
end -- if unit skill client
|
||||||
|
end -- if is player/client skill
|
||||||
|
end -- for all units
|
||||||
|
end -- for all groups
|
||||||
|
end --if has category data
|
||||||
|
end --if plane, helo etc... category
|
||||||
|
end --for all objects in country
|
||||||
|
end --if has country data
|
||||||
|
end --for all countries in coalition
|
||||||
|
end --if coalition has country table
|
||||||
|
end -- if there is coalition data
|
||||||
|
end --for all coalitions in mission
|
||||||
|
end
|
||||||
|
|
||||||
|
function stopGap.staticMXFromUnitMX(theGroup, theUnit)
|
||||||
|
-- enter with MX data blocks
|
||||||
|
-- build a static object from mx unit data
|
||||||
|
local theStatic = {}
|
||||||
|
theStatic.x = theUnit.x
|
||||||
|
theStatic.y = theUnit.y
|
||||||
|
theStatic.livery_id = theUnit.livery_id -- if exists
|
||||||
|
theStatic.heading = theUnit.heading -- may need some attention
|
||||||
|
theStatic.type = theUnit.type
|
||||||
|
theStatic.name = theUnit.name -- will magically be replaced with player unit
|
||||||
|
theStatic.cty = cfxMX.countryByName[theGroup.name]
|
||||||
|
|
||||||
|
return theStatic
|
||||||
|
end
|
||||||
|
|
||||||
|
function stopGap.isGroundStart(theGroup)
|
||||||
|
-- look at route
|
||||||
|
if not theGroup.route then return false end
|
||||||
|
local route = theGroup.route
|
||||||
|
local points = route.points
|
||||||
|
if not points then return false end
|
||||||
|
local ip = points[1]
|
||||||
|
if not ip then return false end
|
||||||
|
local action = ip.action
|
||||||
|
if action == "Fly Over Point" then return false end
|
||||||
|
if action == "Turning Point" then return false end
|
||||||
|
if action == "Landing" then return false end
|
||||||
|
-- looks like aircraft is on the ground
|
||||||
|
-- but is it in water (carrier)?
|
||||||
|
local u1 = theGroup.units[1]
|
||||||
|
local sType = land.getSurfaceType(u1) -- has fields x and y
|
||||||
|
if sType == 3 then return false end
|
||||||
|
|
||||||
|
if stopGap.verbose then
|
||||||
|
trigger.action.outText("Player Group <" .. theGroup.name .. "> GROUND BASED: " .. action .. " land type " .. sType, 30)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function stopGap.createStandInsForMXGroup(group)
|
||||||
|
local allUnits = group.units
|
||||||
|
if group.name:sub(-#stopGap.ignoreMe) == stopGap.ignoreMe then
|
||||||
|
if stopGap.verbose then
|
||||||
|
trigger.action.outText("<<skipping group " .. group.name .. ">>", 30)
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local theStaticGroup = {}
|
||||||
|
for idx, theUnit in pairs (allUnits) do
|
||||||
|
if (theUnit.skill == "Client" or theUnit.skill == "Player")
|
||||||
|
and (theUnit.name:sub(-#stopGap.ignoreMe) ~= stopGap.ignoreMe)
|
||||||
|
then
|
||||||
|
local theStaticMX = stopGap.staticMXFromUnitMX(group, theUnit)
|
||||||
|
local theStatic = coalition.addStaticObject(theStaticMX.cty, theStaticMX)
|
||||||
|
theStaticGroup[theUnit.name] = theStatic -- remember me
|
||||||
|
if stopGap.verbose then
|
||||||
|
trigger.action.outText("Stop-gap-ing <" .. theUnit.name .. ">", 30)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if stopGap.verbose then
|
||||||
|
trigger.action.outText("<<skipping unit " .. theUnit.name .. ">>", 30)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return theStaticGroup
|
||||||
|
end
|
||||||
|
|
||||||
|
function stopGap.initGaps()
|
||||||
|
-- when we enter, all slots are emptry
|
||||||
|
-- and we populate all slots
|
||||||
|
-- with their static representations
|
||||||
|
for name, group in pairs (cfxMX.playerGroupByName) do
|
||||||
|
-- check to see if this group is on the ground at parking
|
||||||
|
-- by looking at the first waypoint
|
||||||
|
if stopGap.isGroundStart(group) then
|
||||||
|
-- this is one of ours!
|
||||||
|
group.sgName = "SG"..group.name -- flag name for MP
|
||||||
|
trigger.action.setUserFlag(group.sgName, 0) -- mark unengaged
|
||||||
|
stopGap.myGroups[name] = group
|
||||||
|
|
||||||
|
-- see if this group exists in-game already
|
||||||
|
local existing = Group.getByName(name)
|
||||||
|
if existing and Group.isExist(existing) then
|
||||||
|
if stopGap.verbose then
|
||||||
|
trigger.action.outText("+++stopG: group <" .. name .. "> already slotted, skipping", 30)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- replace all groups entirely with static objects
|
||||||
|
---local allUnits = group.units
|
||||||
|
local theStaticGroup = stopGap.createStandInsForMXGroup(group)
|
||||||
|
-- remember this static group by its real name
|
||||||
|
stopGap.standInGroups[group.name] = theStaticGroup
|
||||||
|
end
|
||||||
|
end -- if groundtstart
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- event handling
|
||||||
|
--
|
||||||
|
function stopGap.removeStaticGapGroupNamed(gName)
|
||||||
|
for name, theStatic in pairs(stopGap.standInGroups[gName]) do
|
||||||
|
StaticObject.destroy(theStatic)
|
||||||
|
end
|
||||||
|
stopGap.standInGroups[gName] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function stopGap:onEvent(event)
|
||||||
|
if not event then return end
|
||||||
|
if not event.id then return end
|
||||||
|
if not event.initiator then return end
|
||||||
|
local theUnit = event.initiator
|
||||||
|
|
||||||
|
if event.id == 15 then
|
||||||
|
if (not theUnit.getPlayerName) or (not theUnit:getPlayerName()) then
|
||||||
|
return
|
||||||
|
end -- no player unit.
|
||||||
|
local uName = theUnit:getName()
|
||||||
|
local theGroup = theUnit:getGroup()
|
||||||
|
local gName = theGroup:getName()
|
||||||
|
|
||||||
|
if stopGap.myGroups[gName] then
|
||||||
|
-- in case there were more than one units in this group,
|
||||||
|
-- also clear out the others. better safe than sorry
|
||||||
|
if stopGap.standInGroups[gName] then
|
||||||
|
stopGap.removeStaticGapGroupNamed(gName)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- erase stopGapGUI flag, no longer required, unit
|
||||||
|
-- is now slotted into
|
||||||
|
trigger.action.setUserFlag("SG"..gName, 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- update
|
||||||
|
--
|
||||||
|
function stopGap.update()
|
||||||
|
-- check every 1 second
|
||||||
|
timer.scheduleFunction(stopGap.update, {}, timer.getTime() + 1)
|
||||||
|
|
||||||
|
-- check if slots can be refilled or need to be vacated (MP)
|
||||||
|
for name, theGroup in pairs(stopGap.myGroups) do
|
||||||
|
if not stopGap.standInGroups[name] then
|
||||||
|
-- if there is no stand-in group, that group was slotted
|
||||||
|
-- or removed for ssb
|
||||||
|
local busy = true
|
||||||
|
local pGroup = Group.getByName(name)
|
||||||
|
if pGroup then
|
||||||
|
if Group.isExist(pGroup) then
|
||||||
|
else
|
||||||
|
busy = false -- no longer exists
|
||||||
|
end
|
||||||
|
else
|
||||||
|
busy = false -- nil group
|
||||||
|
end
|
||||||
|
|
||||||
|
-- now conduct ssb checks if enabled
|
||||||
|
if stopGap.ssbEnabled then
|
||||||
|
local ssbState = trigger.misc.getUserFlag(name)
|
||||||
|
if ssbState > 0 then
|
||||||
|
busy = true -- keep busy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- check if StopGapGUI wants a word
|
||||||
|
local sgState = trigger.misc.getUserFlag(theGroup.sgName)
|
||||||
|
if sgState < 0 then
|
||||||
|
busy = true
|
||||||
|
-- count up for auto-release after n seconds
|
||||||
|
trigger.action.setUserFlag(theGroup.sgName, sgState + 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
if busy then
|
||||||
|
-- players active in this group
|
||||||
|
else
|
||||||
|
local theStaticGroup = stopGap.createStandInsForMXGroup(theGroup)
|
||||||
|
stopGap.standInGroups[name] = theStaticGroup
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- plane is currently static and visible
|
||||||
|
-- check if this needs to change
|
||||||
|
local removeMe = false
|
||||||
|
if stopGap.ssbEnabled then
|
||||||
|
local ssbState = trigger.misc.getUserFlag(name)
|
||||||
|
if ssbState > 0 then removeMe = true end
|
||||||
|
end
|
||||||
|
local sgState = trigger.misc.getUserFlag(theGroup.sgName)
|
||||||
|
if sgState < 0 then removeMe = true end
|
||||||
|
if removeMe then
|
||||||
|
stopGap.removeStaticGapGroupNamed(name) -- also nils entry
|
||||||
|
if stopGap.verbose then
|
||||||
|
trigger.action.outText("+++StopG: [server command] remove static group <" .. name .. "> for SSB/SG server", 30)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- get going
|
||||||
|
--
|
||||||
|
function stopGap.start()
|
||||||
|
-- run a cross reference on all mission data for palyer info
|
||||||
|
cfxMX.createCrossReferences()
|
||||||
|
-- fill player slots with static objects
|
||||||
|
stopGap.initGaps()
|
||||||
|
|
||||||
|
-- connect event handler
|
||||||
|
world.addEventHandler(stopGap)
|
||||||
|
|
||||||
|
-- start update in 10 seconds
|
||||||
|
timer.scheduleFunction(stopGap.update, {}, timer.getTime() + 1)
|
||||||
|
|
||||||
|
-- say hi!
|
||||||
|
trigger.action.outText("stopGap v" .. stopGap.version .. " running", 30)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
if not stopGap.start() then
|
||||||
|
trigger.action.outText("+++ aborted stopGap v" .. stopGap.version .. " -- start failed", 30)
|
||||||
|
stopGap = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
370
modules/stopGaps.lua
Normal file
370
modules/stopGaps.lua
Normal file
@ -0,0 +1,370 @@
|
|||||||
|
stopGap = {}
|
||||||
|
stopGap.version = "1.0.4"
|
||||||
|
stopGap.verbose = false
|
||||||
|
stopGap.ssbEnabled = true
|
||||||
|
stopGap.ignoreMe = "-sg"
|
||||||
|
stopGap.requiredLibs = {
|
||||||
|
"dcsCommon",
|
||||||
|
"cfxZones",
|
||||||
|
"cfxMX",
|
||||||
|
}
|
||||||
|
--[[--
|
||||||
|
Written and (c) 2023 by Christian Franz
|
||||||
|
|
||||||
|
Replace all player units with static aircraft until the first time
|
||||||
|
that a player slots into that plane. Static is then replaced with live player unit.
|
||||||
|
|
||||||
|
For aircraft/helo carriers, no player planes are replaced with statics
|
||||||
|
|
||||||
|
For multiplayer, StopGapGUI must run on the server (only server)
|
||||||
|
|
||||||
|
STRONGLY RECOMMENDED FOR MISSION DESIGNERS:
|
||||||
|
- Use single-unit player groups.
|
||||||
|
- Use 'start from ground hot/cold' to be able to control initial aircraft orientation
|
||||||
|
|
||||||
|
To selectively exempt player units from stopGap, add a '-sg' to their name. Alternatively, use stopGap zones
|
||||||
|
|
||||||
|
Version History
|
||||||
|
1.0.0 - Initial version
|
||||||
|
1.0.1 - update / replace statics after slots become free again
|
||||||
|
1.0.2 - DML integration
|
||||||
|
- SSB integration
|
||||||
|
- on?
|
||||||
|
- off?
|
||||||
|
- onStart
|
||||||
|
- stopGap Zones
|
||||||
|
1.0.3 - server plug-in logic
|
||||||
|
1.0.4 - player units or groups that end in '-sg' are not stop-gapped
|
||||||
|
--]]--
|
||||||
|
|
||||||
|
stopGap.standInGroups = {}
|
||||||
|
stopGap.myGroups = {} -- for fast look-up of mx orig data
|
||||||
|
stopGap.stopGapZones = {} -- DML only
|
||||||
|
|
||||||
|
--
|
||||||
|
-- one-time start-up processing
|
||||||
|
--
|
||||||
|
-- in DCS, a group with one or more players only allocates when
|
||||||
|
-- the first player in the group enters the game.
|
||||||
|
--
|
||||||
|
|
||||||
|
function stopGap.staticMXFromUnitMX(theGroup, theUnit)
|
||||||
|
-- enter with MX data blocks
|
||||||
|
-- build a static object from mx unit data
|
||||||
|
local theStatic = {}
|
||||||
|
theStatic.x = theUnit.x
|
||||||
|
theStatic.y = theUnit.y
|
||||||
|
theStatic.livery_id = theUnit.livery_id -- if exists
|
||||||
|
theStatic.heading = theUnit.heading -- may need some attention
|
||||||
|
theStatic.type = theUnit.type
|
||||||
|
theStatic.name = theUnit.name -- will magically be replaced with player unit
|
||||||
|
theStatic.cty = cfxMX.countryByName[theGroup.name]
|
||||||
|
return theStatic
|
||||||
|
end
|
||||||
|
|
||||||
|
function stopGap.isGroundStart(theGroup)
|
||||||
|
-- look at route
|
||||||
|
if not theGroup.route then return false end
|
||||||
|
local route = theGroup.route
|
||||||
|
local points = route.points
|
||||||
|
if not points then return false end
|
||||||
|
local ip = points[1]
|
||||||
|
if not ip then return false end
|
||||||
|
local action = ip.action
|
||||||
|
if action == "Fly Over Point" then return false end
|
||||||
|
if action == "Turning Point" then return false end
|
||||||
|
if action == "Landing" then return false end
|
||||||
|
-- looks like aircraft is on the ground
|
||||||
|
-- but is it in water (carrier)?
|
||||||
|
local u1 = theGroup.units[1]
|
||||||
|
local sType = land.getSurfaceType(u1) -- has fields x and y
|
||||||
|
if sType == 3 then return false end
|
||||||
|
if stopGap.verbose then
|
||||||
|
trigger.action.outText("StopG: Player Group <" .. theGroup.name .. "> GROUND BASED: " .. action .. ", land type " .. sType, 30)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function stopGap.ignoreMXUnit(theUnit)
|
||||||
|
local p = {x=theUnit.x, y=0, z=theUnit.y}
|
||||||
|
for idx, theZone in pairs(stopGap.stopGapZones) do
|
||||||
|
if theZone.sgIgnore and cfxZones.pointInZone(p, theZone) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function stopGap.createStandInsForMXGroup(group)
|
||||||
|
local allUnits = group.units
|
||||||
|
if group.name:sub(-#stopGap.ignoreMe) == stopGap.ignoreMe then
|
||||||
|
if stopGap.verbose then
|
||||||
|
trigger.action.outText("+++StopG: <<skipping group " .. group.name .. ">>", 30)
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local theStaticGroup = {}
|
||||||
|
for idx, theUnit in pairs (allUnits) do
|
||||||
|
if (theUnit.skill == "Client" or theUnit.skill == "Player")
|
||||||
|
and (theUnit.name:sub(-#stopGap.ignoreMe) ~= stopGap.ignoreMe)
|
||||||
|
and (not stopGap.ignoreMXUnit(theUnit))
|
||||||
|
then
|
||||||
|
local theStaticMX = stopGap.staticMXFromUnitMX(group, theUnit)
|
||||||
|
local theStatic = coalition.addStaticObject(theStaticMX.cty, theStaticMX)
|
||||||
|
theStaticGroup[theUnit.name] = theStatic -- remember me
|
||||||
|
if stopGap.verbose then
|
||||||
|
trigger.action.outText("+++StopG: adding static for <" .. theUnit.name .. ">", 30)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if stopGap.verbose then
|
||||||
|
trigger.action.outText("+++StopG: <<skipping unit " .. theUnit.name .. ">>", 30)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return theStaticGroup
|
||||||
|
end
|
||||||
|
|
||||||
|
function stopGap.initGaps()
|
||||||
|
-- turn on. May turn on any time, even during game
|
||||||
|
-- when we enter, all slots should be emptry
|
||||||
|
-- and we populate all slots. If slot in use, don't populate
|
||||||
|
-- with their static representations
|
||||||
|
for name, group in pairs (cfxMX.playerGroupByName) do
|
||||||
|
-- check to see if this group is on the ground at parking
|
||||||
|
-- by looking at the first waypoint
|
||||||
|
if stopGap.isGroundStart(group) then
|
||||||
|
-- this is one of ours!
|
||||||
|
group.sgName = "SG"..group.name -- flag name for MP
|
||||||
|
trigger.action.setUserFlag(group.sgName, 0) -- mark unengaged
|
||||||
|
stopGap.myGroups[name] = group
|
||||||
|
|
||||||
|
-- see if this group exists in-game already
|
||||||
|
local existing = Group.getByName(name)
|
||||||
|
if existing and Group.isExist(existing) then
|
||||||
|
if stopGap.verbose then
|
||||||
|
trigger.action.outText("+++stopG: group <" .. name .. "> already slotted, skipping", 30)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- replace all groups entirely with static objects
|
||||||
|
---local allUnits = group.units
|
||||||
|
local theStaticGroup = stopGap.createStandInsForMXGroup(group)
|
||||||
|
-- remember this static group by its real name
|
||||||
|
stopGap.standInGroups[group.name] = theStaticGroup
|
||||||
|
end
|
||||||
|
end -- if groundtstart
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function stopGap.turnOff()
|
||||||
|
-- remove all stand-ins
|
||||||
|
for gName, standIn in pairs (stopGap.standInGroups) do
|
||||||
|
for name, theStatic in pairs(standIn) do
|
||||||
|
StaticObject.destroy(theStatic)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
stopGap.standInGroups = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
function stopGap.turnOn()
|
||||||
|
-- populate all empty (non-taken) slots with stand-ins
|
||||||
|
stopGap.initGaps()
|
||||||
|
end
|
||||||
|
--
|
||||||
|
-- event handling
|
||||||
|
--
|
||||||
|
function stopGap.removeStaticGapGroupNamed(gName)
|
||||||
|
for name, theStatic in pairs(stopGap.standInGroups[gName]) do
|
||||||
|
StaticObject.destroy(theStatic)
|
||||||
|
end
|
||||||
|
stopGap.standInGroups[gName] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function stopGap:onEvent(event)
|
||||||
|
if not event then return end
|
||||||
|
if not event.id then return end
|
||||||
|
if not event.initiator then return end
|
||||||
|
local theUnit = event.initiator
|
||||||
|
|
||||||
|
if event.id == 15 then
|
||||||
|
if (not theUnit.getPlayerName) or (not theUnit:getPlayerName()) then
|
||||||
|
return
|
||||||
|
end -- no player unit.
|
||||||
|
local uName = theUnit:getName()
|
||||||
|
local theGroup = theUnit:getGroup()
|
||||||
|
local gName = theGroup:getName()
|
||||||
|
|
||||||
|
if stopGap.myGroups[gName] then
|
||||||
|
-- in case there were more than one units in this group,
|
||||||
|
-- also clear out the others. better safe than sorry
|
||||||
|
if stopGap.standInGroups[gName] then
|
||||||
|
stopGap.removeStaticGapGroupNamed(gName)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- erase stopGapGUI flag, no longer required, unit
|
||||||
|
-- is now slotted into
|
||||||
|
trigger.action.setUserFlag("SG"..gName, 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- update, includes MP client check code
|
||||||
|
--
|
||||||
|
function stopGap.update()
|
||||||
|
-- check every second.
|
||||||
|
timer.scheduleFunction(stopGap.update, {}, timer.getTime() + 1)
|
||||||
|
|
||||||
|
-- check if signal for on? or off?
|
||||||
|
if stopGap.turnOn and cfxZones.testZoneFlag(stopGap, stopGap.turnOnFlag, "change", "lastTurnOnFlag") then
|
||||||
|
if not stopGap.enabled then
|
||||||
|
stopGap.turnOn()
|
||||||
|
end
|
||||||
|
stopGap.enabled = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if stopGap.turnOff and cfxZones.testZoneFlag(stopGap, stopGap.turnOffFlag, "change", "lastTurnOffFlag") then
|
||||||
|
if stopGap.enabled then
|
||||||
|
stopGap.turnOff()
|
||||||
|
end
|
||||||
|
stopGap.enabled = false
|
||||||
|
end
|
||||||
|
|
||||||
|
if not stopGap.enabled then return end
|
||||||
|
|
||||||
|
-- check if slots can be refilled or need to be vacated (MP)
|
||||||
|
for name, theGroup in pairs(stopGap.myGroups) do
|
||||||
|
if not stopGap.standInGroups[name] then
|
||||||
|
-- if there is no stand-in group, that group was slotted
|
||||||
|
-- or removed for ssb
|
||||||
|
local busy = true
|
||||||
|
local pGroup = Group.getByName(name)
|
||||||
|
if pGroup then
|
||||||
|
if Group.isExist(pGroup) then
|
||||||
|
else
|
||||||
|
busy = false -- no longer exists
|
||||||
|
end
|
||||||
|
else
|
||||||
|
busy = false -- nil group
|
||||||
|
end
|
||||||
|
|
||||||
|
-- now conduct ssb checks if enabled
|
||||||
|
if stopGap.ssbEnabled then
|
||||||
|
local ssbState = trigger.misc.getUserFlag(name)
|
||||||
|
if ssbState > 0 then
|
||||||
|
busy = true -- keep busy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- check if StopGapGUI wants a word
|
||||||
|
local sgState = trigger.misc.getUserFlag(theGroup.sgName)
|
||||||
|
if sgState < 0 then
|
||||||
|
busy = true
|
||||||
|
-- count up for auto-release after n seconds
|
||||||
|
trigger.action.setUserFlag(theGroup.sgName, sgState + 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
if busy then
|
||||||
|
-- players active in this group
|
||||||
|
else
|
||||||
|
local theStaticGroup = stopGap.createStandInsForMXGroup(theGroup)
|
||||||
|
stopGap.standInGroups[name] = theStaticGroup
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- plane is currently static and visible
|
||||||
|
-- check if this needs to change
|
||||||
|
local removeMe = false
|
||||||
|
if stopGap.ssbEnabled then
|
||||||
|
local ssbState = trigger.misc.getUserFlag(name)
|
||||||
|
if ssbState > 0 then removeMe = true end
|
||||||
|
end
|
||||||
|
local sgState = trigger.misc.getUserFlag(theGroup.sgName)
|
||||||
|
if sgState < 0 then removeMe = true end
|
||||||
|
if removeMe then
|
||||||
|
stopGap.removeStaticGapGroupNamed(name) -- also nils entry
|
||||||
|
if stopGap.verbose then
|
||||||
|
trigger.action.outText("+++StopG: [server command] remove static group <" .. name .. "> for SSB/SG server", 30)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- read stopGapZone
|
||||||
|
--
|
||||||
|
function stopGap.createStopGapZone(theZone)
|
||||||
|
local sg = cfxZones.getBoolFromZoneProperty(theZone, "stopGap", true)
|
||||||
|
if sg then theZone.sgIgnore = false else theZone.sgIgnore = true end
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Read Config Zone
|
||||||
|
--
|
||||||
|
stopGap.name = "stopGapConfig" -- cfxZones compatibility here
|
||||||
|
function stopGap.readConfigZone(theZone)
|
||||||
|
-- currently nothing to do
|
||||||
|
stopGap.verbose = theZone.verbose
|
||||||
|
stopGap.ssbEnabled = cfxZones.getBoolFromZoneProperty(theZone, "sbb", true)
|
||||||
|
stopGap.enabled = cfxZones.getBoolFromZoneProperty(theZone, "onStart", true)
|
||||||
|
if cfxZones.hasProperty(theZone, "on?") then
|
||||||
|
stopGap.turnOnFlag = cfxZones.getStringFromZoneProperty(theZone, "on?", "*<none>")
|
||||||
|
stopGap.lastTurnOnFlag = trigger.misc.getUserFlag(stopGap.turnOnFlag)
|
||||||
|
end
|
||||||
|
if cfxZones.hasProperty(theZone, "off?") then
|
||||||
|
stopGap.turnOffFlag = cfxZones.getStringFromZoneProperty(theZone, "off?", "*<none>")
|
||||||
|
stopGap.lastTurnOffFlag = trigger.misc.getUserFlag(stopGap.turnOffFlag)
|
||||||
|
end
|
||||||
|
|
||||||
|
if stopGap.verbose then
|
||||||
|
trigger.action.outText("+++StopG: config read, verbose = YES", 30)
|
||||||
|
if stopGap.enabled then
|
||||||
|
trigger.action.outText("+++StopG: enabled", 30)
|
||||||
|
else
|
||||||
|
trigger.action.outText("+++StopG: turned off", 30)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- get going
|
||||||
|
--
|
||||||
|
function stopGap.start()
|
||||||
|
if not dcsCommon.libCheck("cfx StopGap",
|
||||||
|
stopGap.requiredLibs)
|
||||||
|
then return false end
|
||||||
|
|
||||||
|
local theZone = cfxZones.getZoneByName("stopGapConfig")
|
||||||
|
if not theZone then
|
||||||
|
theZone = cfxZones.createSimpleZone("stopGapConfig")
|
||||||
|
end
|
||||||
|
stopGap.readConfigZone(theZone)
|
||||||
|
|
||||||
|
-- collect exclusion zones
|
||||||
|
local pZones = cfxZones.zonesWithProperty("stopGap")
|
||||||
|
for k, aZone in pairs(pZones) do
|
||||||
|
stopGap.createStopGapZone(aZone)
|
||||||
|
stopGap.stopGapZones[aZone.name] = aZone
|
||||||
|
end
|
||||||
|
|
||||||
|
-- fill player slots with static objects
|
||||||
|
if stopGap.enabled then
|
||||||
|
stopGap.initGaps()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- connect event handler
|
||||||
|
world.addEventHandler(stopGap)
|
||||||
|
|
||||||
|
-- start update in 10 seconds
|
||||||
|
timer.scheduleFunction(stopGap.update, {}, timer.getTime() + 1)
|
||||||
|
|
||||||
|
-- say hi!
|
||||||
|
trigger.action.outText("stopGap v" .. stopGap.version .. " running", 30)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
if not stopGap.start() then
|
||||||
|
trigger.action.outText("+++ aborted stopGap v" .. stopGap.version .. " -- startup failed", 30)
|
||||||
|
stopGap = nil
|
||||||
|
end
|
||||||
|
|
||||||
22
server modules/stopGapGUI.lua
Normal file
22
server modules/stopGapGUI.lua
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
stopGapGUI = {}
|
||||||
|
stopGapGUI.version = "1.0.0"
|
||||||
|
stopGapGUI.fVal = -300 -- 5 minutes max block
|
||||||
|
--
|
||||||
|
-- Server Plug-In for StopGap mission script, only required for server
|
||||||
|
-- Put into (main DCS save folder)/Scripts/Hooks/ and restart DCS
|
||||||
|
--
|
||||||
|
function stopGapGUI.onPlayerTryChangeSlot(playerID, side, slotID)
|
||||||
|
if not slotID then return end
|
||||||
|
if slotID == "" then return end
|
||||||
|
if not DCS.isServer() then return end
|
||||||
|
if not DCS.isMultiplayer() then return end
|
||||||
|
|
||||||
|
local gName = DCS.getUnitProperty(slotID, DCS.UNIT_GROUPNAME)
|
||||||
|
if not gName then return end
|
||||||
|
local sgName = "SG" .. gName
|
||||||
|
-- tell all clients to remove this group's statics if they are deployed
|
||||||
|
net.dostring_in("server", " trigger.action.setUserFlag(\""..sgName.."\", " .. stopGapGUI.fVal .. "); ")
|
||||||
|
net.send_chat("+++SG: readying group <" .. sgName .. "> for slotting")
|
||||||
|
end
|
||||||
|
|
||||||
|
DCS.setUserCallbacks(stopGapGUI)
|
||||||
BIN
tutorial & demo missions/demo - My First Factory.miz
Normal file
BIN
tutorial & demo missions/demo - My First Factory.miz
Normal file
Binary file not shown.
BIN
tutorial & demo missions/demo - No gap, no glory.miz
Normal file
BIN
tutorial & demo missions/demo - No gap, no glory.miz
Normal file
Binary file not shown.
BIN
tutorial & demo missions/demo - Owned Zones and Factories.miz
Normal file
BIN
tutorial & demo missions/demo - Owned Zones and Factories.miz
Normal file
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user