mirror of
https://github.com/weyne85/DML.git
synced 2025-10-29 16:57:49 +00:00
Version 2.0.0
Version II goes live!
This commit is contained in:
@@ -1,25 +1,22 @@
|
||||
cfxSSBSingleUse = {}
|
||||
cfxSSBSingleUse.version = "1.1.0"
|
||||
|
||||
--[[--
|
||||
Version History
|
||||
1.0.0 - Initial version
|
||||
1.1.0 - importing dcsCommon, cfxGroup for simplicity
|
||||
- save unit name on player enter unit as look-up
|
||||
- determining ground-start
|
||||
- place wreck in slot
|
||||
1.1.1 - guarding against nil playerName
|
||||
- using 15 (birth) instead of 20 (player enter)
|
||||
WHAT IT IS
|
||||
SSB Single Use is a script that blocks a player slot
|
||||
after that plane crashes.
|
||||
|
||||
|
||||
--]]--
|
||||
|
||||
cfxSSBSingleUse.enabledFlagValue = 0 -- DO NOT CHANGE, MUST MATCH SSB
|
||||
cfxSSBSingleUse.disabledFlagValue = cfxSSBSingleUse.enabledFlagValue + 100 -- DO NOT CHANGE
|
||||
|
||||
cfxSSBSingleUse.version = "2.0.0"
|
||||
cfxSSBSingleUse.verbose = true
|
||||
cfxSSBSingleUse.useDebris = false
|
||||
cfxSSBSingleUse.requiredLibs = {
|
||||
"dcsCommon", -- no cfxZones. a rarity!
|
||||
"cfxMX",
|
||||
}
|
||||
--[[--
|
||||
Version History
|
||||
2.0.0 - cfxMX homologization
|
||||
- startup checks
|
||||
- minimal verbosity
|
||||
- useDebris switch, currently set off
|
||||
- clean-up
|
||||
--]]--
|
||||
|
||||
cfxSSBSingleUse.playerUnits = {}
|
||||
cfxSSBSingleUse.slotGroundActions = {
|
||||
@@ -29,7 +26,6 @@ cfxSSBSingleUse.slotGroundActions = {
|
||||
"From Ground Area",
|
||||
"From Ground Area Hot",
|
||||
}
|
||||
|
||||
cfxSSBSingleUse.groundSlots = {} -- players that start on the ground
|
||||
|
||||
function cfxSSBSingleUse:onEvent(event)
|
||||
@@ -40,7 +36,6 @@ function cfxSSBSingleUse:onEvent(event)
|
||||
-- if we get here, initiator is set
|
||||
local theUnit = event.initiator -- we know this exists
|
||||
|
||||
|
||||
-- write down player names and planes
|
||||
if event.id == 15 then
|
||||
local uName = theUnit:getName()
|
||||
@@ -69,7 +64,9 @@ function cfxSSBSingleUse:onEvent(event)
|
||||
|
||||
if not thePilot then
|
||||
-- ignore. not a player plane
|
||||
trigger.action.outText("+++singleUse: ignored crash for NPC unit <" .. uName .. ">", 30)
|
||||
if cfxSSBSingleUse.verbose then
|
||||
trigger.action.outText("+++singleUse: ignored crash for NPC unit <" .. uName .. ">", 30)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
@@ -80,19 +77,24 @@ function cfxSSBSingleUse:onEvent(event)
|
||||
local theGroundSlot = cfxSSBSingleUse.groundSlots[gName]
|
||||
if theGroundSlot then
|
||||
local unitType = theUnit:getTypeName()
|
||||
trigger.action.outText("+++singleUse: <" .. uName .. "> starts on Ground. Will place debris for " .. unitType .. " NOW!!!", 30)
|
||||
if cfxSSBSingleUse.verbose then
|
||||
trigger.action.outText("+++singleUse: <" .. uName .. "> starts on Ground. Will place debris for " .. unitType .. " NOW!!!", 30)
|
||||
end
|
||||
cfxSSBSingleUse.placeDebris(unitType, theGroundSlot)
|
||||
end
|
||||
|
||||
-- block this slot.
|
||||
trigger.action.setUserFlag(gName, cfxSSBSingleUse.disabledFlagValue)
|
||||
|
||||
trigger.action.outText("+++singleUse: blocked <" .. gName .. "> after " .. thePilot .. " crashed it.", 30)
|
||||
if cfxSSBSingleUse.verbose then
|
||||
trigger.action.outText("+++singleUse: blocked <" .. gName .. "> after " .. thePilot .. " crashed it.", 30)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function cfxSSBSingleUse.placeDebris(unitType, theGroundSlot)
|
||||
if not unitType then return end
|
||||
if not cfxSSBSingleUse.useDebris then return end
|
||||
|
||||
-- access location one, we assume single-unit groups
|
||||
-- or at least that the player sits in unit one
|
||||
local playerData = theGroundSlot.playerUnits
|
||||
@@ -106,12 +108,14 @@ function cfxSSBSingleUse.placeDebris(unitType, theGroundSlot)
|
||||
wreckData.type = unitType
|
||||
|
||||
coalition.addStaticObject(theGroundSlot.coaNum, wreckData )
|
||||
trigger.action.outText("+++singleUse: wreck <" .. unitType .. "> at " .. wreckData.x .. ", " .. wreckData.y .. " for " .. wreckData.name, 30)
|
||||
if cfxSSBSingleUse.verbose then
|
||||
trigger.action.outText("+++singleUse: wreck <" .. unitType .. "> at " .. wreckData.x .. ", " .. wreckData.y .. " for " .. wreckData.name, 30)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function cfxSSBSingleUse.populateAirfieldSlots()
|
||||
local pGroups = cfxGroups.getPlayerGroup()
|
||||
local pGroups = cfxMX.getPlayerGroup()
|
||||
local groundStarters = {}
|
||||
for idx, theGroup in pairs(pGroups) do
|
||||
-- we always use the first player's plane as referenced
|
||||
@@ -122,33 +126,35 @@ function cfxSSBSingleUse.populateAirfieldSlots()
|
||||
if dcsCommon.arrayContainsString(cfxSSBSingleUse.slotGroundActions, action ) then
|
||||
-- ground starter, not from runway
|
||||
groundStarters[theGroup.name] = theGroup
|
||||
trigger.action.outText("+++singleUse: <" .. theGroup.name .. "> is ground starter", 30)
|
||||
if cfxSSBSingleUse.verbose then
|
||||
trigger.action.outText("+++singleUse: <" .. theGroup.name .. "> is ground starter", 30)
|
||||
end
|
||||
end
|
||||
end
|
||||
cfxSSBSingleUse.groundSlots = groundStarters
|
||||
end
|
||||
|
||||
function cfxSSBSingleUse.start()
|
||||
-- check libs
|
||||
if not dcsCommon.libCheck("cfx SSB Single Use",
|
||||
cfxSSBSingleUse.requiredLibs) then
|
||||
return false
|
||||
end
|
||||
|
||||
-- install event monitor
|
||||
world.addEventHandler(cfxSSBSingleUse)
|
||||
|
||||
-- get all groups and process them to find
|
||||
-- all planes that are on the ground for
|
||||
-- eye candy
|
||||
cfxSSBSingleUse.populateAirfieldSlots()
|
||||
|
||||
-- turn on ssb
|
||||
trigger.action.setUserFlag("SSB",100)
|
||||
|
||||
trigger.action.outText("SSB Single use v" .. cfxSSBSingleUse.version .. " running", 30)
|
||||
return true
|
||||
end
|
||||
|
||||
-- let's go!
|
||||
cfxSSBSingleUse.start()
|
||||
|
||||
--[[--
|
||||
Additional features (later):
|
||||
- place a wreck in slot when blocking for eye candy
|
||||
- record player when they enter a unit and only block player planes
|
||||
|
||||
--]]--
|
||||
if not cfxSSBSingleUse.start()then
|
||||
trigger.action.outText("SSB Single Use failed to start up.", 30)
|
||||
cfxSSBSingleUse = nil
|
||||
end
|
||||
@@ -1,5 +1,5 @@
|
||||
tdz = {}
|
||||
tdz.version = "1.0.1"
|
||||
tdz.version = "1.0.2"
|
||||
tdz.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
"cfxZones", -- Zones, of course
|
||||
@@ -15,6 +15,8 @@ VERSION HISTORY
|
||||
multiple zone support
|
||||
hops detection improvement
|
||||
helo attribute
|
||||
1.0.2 - manual placement option
|
||||
filters FARPs
|
||||
|
||||
--]]--
|
||||
|
||||
@@ -80,24 +82,41 @@ end
|
||||
--
|
||||
function tdz.createTDZ(theZone)
|
||||
local p = theZone:getPoint()
|
||||
local theBase = dcsCommon.getClosestAirbaseTo(p) -- never get FARPS
|
||||
local theBase = dcsCommon.getClosestAirbaseTo(p, 0) -- never get FARPS
|
||||
theZone.base = theBase
|
||||
theZone.baseName = theBase:getName()
|
||||
theZone.helos = false
|
||||
|
||||
-- get closest runway to TDZ
|
||||
-- may get a bit hairy, so let's find a good way
|
||||
local allRwys = theBase:getRunways()
|
||||
local nearestRwy = nil
|
||||
local minDist = math.huge
|
||||
for idx, aRwy in pairs(allRwys) do
|
||||
local rp = aRwy.position
|
||||
local dist = dcsCommon.distFlat(p, rp)
|
||||
if dist < minDist then
|
||||
nearestRwy = aRwy
|
||||
minDist = dist
|
||||
-- see if this is a manually placed runway
|
||||
if theZone:getBoolFromZoneProperty("manual", true) then
|
||||
-- construct runway from trigger zone attributes
|
||||
if theZone.verbose or tdz.verbose then
|
||||
trigger.action.outText("+++TDZ: runway for <" .. theZone.name .. "> is manually placed", 30)
|
||||
end
|
||||
nearestRwy = {}
|
||||
nearestRwy.length = theZone:getNumberFromZoneProperty("length", 2500)
|
||||
nearestRwy.width = theZone:getNumberFromZoneProperty("width", 60)
|
||||
local hdgRaw = theZone:getNumberFromZoneProperty("course", 0) -- in degrees
|
||||
hdgRaw = hdgRaw * 0.0174533 -- rads
|
||||
nearestRwy.course = hdgRaw * (-1) -- why? because DCS.
|
||||
nearestRwy.position = theZone:getPoint(true)
|
||||
theZone.baseName = theZone.name .. "(manual placement)"
|
||||
else
|
||||
-- get closest runway to TDZ
|
||||
-- may get a bit hairy, so let's find a good way
|
||||
local allRwys = theBase:getRunways()
|
||||
|
||||
local minDist = math.huge
|
||||
for idx, aRwy in pairs(allRwys) do
|
||||
local rp = aRwy.position
|
||||
local dist = dcsCommon.distFlat(p, rp)
|
||||
if dist < minDist then
|
||||
nearestRwy = aRwy
|
||||
minDist = dist
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
local bearing = nearestRwy.course * (-1)
|
||||
if bearing < 0 then bearing = bearing + math.pi * 2 end
|
||||
theZone.bearing = bearing
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
airfield = {}
|
||||
airfield.version = "1.1.2"
|
||||
airfield.version = "2.0.0"
|
||||
airfield.requiredLibs = {
|
||||
"dcsCommon",
|
||||
"cfxZones",
|
||||
}
|
||||
airfield.verbose = false
|
||||
airfield.myAirfields = {} -- indexed by name
|
||||
airfield.farps = false
|
||||
airfield.gracePeriod = 3
|
||||
airfield.allAirfields = {} -- inexed by name
|
||||
|
||||
--[[--
|
||||
This module generates signals when the nearest airfield changes hands,
|
||||
can force the coalition of an airfield, and always provides the
|
||||
@@ -24,17 +24,47 @@ airfield.gracePeriod = 3
|
||||
1.1.2 - 'show' attribute
|
||||
line color attributes per zone
|
||||
line color defaults in config
|
||||
2.0.0 - show all airfields option
|
||||
- fully reworked show options
|
||||
- unmanaged airfields are automatically updated
|
||||
- full color support
|
||||
-- support for FARPS as well
|
||||
|
||||
--]]--
|
||||
|
||||
-- init all airfields DB
|
||||
function airfield.collectAll()
|
||||
local allBases = world.getAirbases() -- get all
|
||||
local count = 0
|
||||
local dropped = 0
|
||||
for idx, aBase in pairs(allBases) do
|
||||
local entry = {}
|
||||
local cat = Airbase.getCategory(aBase) -- DCS 2.9 hardened
|
||||
-- cats: 0 = airfield, 1 = farp, 2 = ship
|
||||
if (cat == 0) or (cat == 1) then
|
||||
local name = aBase:getName()
|
||||
entry.base = aBase
|
||||
entry.cat = cat
|
||||
-- entry.linkedTo holds zone if linked to. that how we know
|
||||
airfield.allAirfields[name] = entry
|
||||
count = count + 1
|
||||
else
|
||||
dropped = dropped + 1
|
||||
end
|
||||
end
|
||||
if airfield.verbose then
|
||||
trigger.action.outText("+++airF: init - count = <" .. count .. ">, dropped = <" .. dropped .. ">", 30)
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- setting up airfield
|
||||
--
|
||||
function airfield.createAirFieldFromZone(theZone)
|
||||
--trigger.action.outText("Enter airfield for <" .. theZone.name .. ">", 30)
|
||||
theZone.farps = theZone:getBoolFromZoneProperty("farps", false)
|
||||
|
||||
local filterCat = 0
|
||||
if (airfield.farps or theZone.farps) then filterCat = {0, 1} end -- bases and farps
|
||||
if (theZone.farps) then filterCat = {0, 1} end -- bases and farps
|
||||
local p = theZone:getPoint()
|
||||
local theBase = dcsCommon.getClosestAirbaseTo(p, filterCat)
|
||||
theZone.airfield = theBase
|
||||
@@ -113,9 +143,29 @@ function airfield.createAirFieldFromZone(theZone)
|
||||
theZone.neutralFill = theZone:getRGBAVectorFromZoneProperty("neutralFill", airfield.neutralFill)
|
||||
|
||||
airfield.showAirfield(theZone)
|
||||
|
||||
-- now mark this zone as handled
|
||||
local entry = airfield.allAirfields[theZone.afName]
|
||||
entry.linkedTo = theZone -- only remember last, but that's enough.
|
||||
|
||||
--trigger.action.outText("Exit airfield for <" .. theZone.name .. ">", 30)
|
||||
|
||||
end
|
||||
|
||||
function airfield.markAirfieldOnMap(theAirfield, lineColor, fillColor)
|
||||
local markID = dcsCommon.numberUUID()
|
||||
local radius = 2000 -- meters
|
||||
local p = theAirfield:getPoint()
|
||||
-- if there are runways, we center on first runway
|
||||
local rws = theAirfield:getRunways()
|
||||
if rws then -- all airfields and farps have runways defined, but that array isnt filled for FARPS
|
||||
local rw1 = rws[1]
|
||||
if rw1 then
|
||||
p.x = rw1.position.x
|
||||
p.z = rw1.position.z
|
||||
end
|
||||
end
|
||||
p.y = 0
|
||||
trigger.action.circleToAll(-1, markID, p, radius, lineColor, fillColor, 1, true, "")
|
||||
return markID
|
||||
end
|
||||
|
||||
function airfield.showAirfield(theZone)
|
||||
@@ -138,35 +188,9 @@ function airfield.showAirfield(theZone)
|
||||
fillColor = theZone.neutralFill -- {0.8, 0.8, 0.8, 0.2}
|
||||
end
|
||||
|
||||
-- always center on airfield, always 2km radius
|
||||
local markID = dcsCommon.numberUUID()
|
||||
local radius = 2000 -- meters
|
||||
local p = theZone.airfield:getPoint()
|
||||
-- if there are runways, we center on first runway
|
||||
local rws = theZone.airfield:getRunways()
|
||||
if rws then -- all airfields and farps have runways, but that array isnt filled for FARPS
|
||||
local rw1 = rws[1]
|
||||
if rw1 then
|
||||
p.x = rw1.position.x
|
||||
p.z = rw1.position.z
|
||||
if airfield.verbose or theZone.verbose then
|
||||
trigger.action.outText("+++airF: zone <" .. theZone.name .. "> assoc airfield <" .. theZone.afName .. "> has rw1, x=" .. p.x .. ", z=" .. p.z, 30)
|
||||
end
|
||||
else
|
||||
if airfield.verbose or theZone.verbose then
|
||||
trigger.action.outText("+++airF: zone <" .. theZone.name .. "> assoc airfield <" .. theZone.afName .. "> has no rw1", 30)
|
||||
end
|
||||
end
|
||||
else
|
||||
if airfield.verbose or theZone.verbose then
|
||||
trigger.action.outText("+++airF: zone <" .. theZone.name .. "> assoc airfield <" .. theZone.afName .. "> has no runways", 30)
|
||||
end
|
||||
end
|
||||
p.y = 0
|
||||
|
||||
trigger.action.circleToAll(-1, markID, p, radius, lineColor, fillColor, 1, true, "")
|
||||
theZone.ownerMark = markID
|
||||
theZone.ownerMark = airfield.markAirfieldOnMap(theZone.airfield, lineColor, fillColor)
|
||||
|
||||
|
||||
end
|
||||
|
||||
function airfield.assumeControl(theZone)
|
||||
@@ -188,12 +212,40 @@ end
|
||||
--
|
||||
-- event handling
|
||||
--
|
||||
function airfield.untendedCapture(theName, theBase)
|
||||
if airfield.showAll and airfield.allAirfields[theName] then
|
||||
-- we draw and handle all airfields, even those
|
||||
-- without an attached handler zone
|
||||
local theEntry = airfield.allAirfields[theName]
|
||||
if not theEntry.linkedTo then -- merely safety
|
||||
if theEntry.ownerMark then
|
||||
-- remove previous mark
|
||||
trigger.action.removeMark(theEntry.ownerMark)
|
||||
theEntry.ownerMark = nil
|
||||
end
|
||||
local owner = theBase:getCoalition()
|
||||
local lineColor = airfield.redLine
|
||||
local fillColor = airfield.redFill
|
||||
if owner == 2 then
|
||||
lineColor = airfield.blueLine
|
||||
fillColor = airfield.blueFill
|
||||
elseif owner == 0 or owner == 3 then
|
||||
lineColor = airfield.neutralLine
|
||||
fillColor = airfield.neutralFill
|
||||
end
|
||||
theEntry.ownerMark = airfield.markAirfieldOnMap(theBase, lineColor, fillColor)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function airfield.airfieldCaptured(theBase)
|
||||
-- retrieve the zone that controls this airfield
|
||||
local bName = theBase:getName()
|
||||
local theZone = airfield.myAirfields[bName]
|
||||
if not theZone then return end -- not handled
|
||||
if not theZone then
|
||||
airfield.untendedCapture(bName, theBase)
|
||||
return
|
||||
end -- not attached to a zone
|
||||
local newCoa = theBase:getCoalition()
|
||||
theZone.owner = newCoa
|
||||
|
||||
@@ -227,15 +279,8 @@ function airfield:onEvent(event)
|
||||
-- get category
|
||||
local desc = theBase:getDesc()
|
||||
local bName = theBase:getName()
|
||||
local cat = desc.category -- never get cat directly!
|
||||
--[[-- if cat == 1 then
|
||||
if not airfield.farps then
|
||||
if airfield.verbose then
|
||||
trigger.action.outText("+++airF: ignored cap event for FARP <" .. bName .. ">", 30)
|
||||
end
|
||||
return
|
||||
end
|
||||
end --]]
|
||||
local cat = desc.category -- never get cat directly! DCS 2.0 safe
|
||||
|
||||
if airfield.verbose then
|
||||
trigger.action.outText("+++airF: cap event for <" .. bName .. ">, cat = (" .. cat .. ")", 30)
|
||||
end
|
||||
@@ -391,7 +436,7 @@ function airfield.readConfig()
|
||||
theZone = cfxZones.createSimpleZone("airfieldConfig")
|
||||
end
|
||||
airfield.verbose = theZone.verbose
|
||||
airfield.farps = theZone:getBoolFromZoneProperty("farps", false)
|
||||
-- airfield.farps = theZone:getBoolFromZoneProperty("farps", false)
|
||||
|
||||
-- colors for line and fill
|
||||
airfield.redLine = theZone:getRGBAVectorFromZoneProperty("redLine", {1.0, 0, 0, 1.0})
|
||||
@@ -400,11 +445,24 @@ function airfield.readConfig()
|
||||
airfield.blueFill = theZone:getRGBAVectorFromZoneProperty("blueFill", {0.0, 0, 1.0, 0.2})
|
||||
airfield.neutralLine = theZone:getRGBAVectorFromZoneProperty("neutralLine", {0.8, 0.8, 0.8, 1.0})
|
||||
airfield.neutralFill = theZone:getRGBAVectorFromZoneProperty("neutralFill", {0.8, 0.8, 0.8, 0.2})
|
||||
|
||||
airfield.showAll = theZone:getBoolFromZoneProperty("show", false)
|
||||
end
|
||||
|
||||
function airfield.showUnlinked()
|
||||
for name, entry in pairs(airfield.allAirfields) do
|
||||
if not entry.linkedTo then
|
||||
airfield.untendedCapture(name, entry.base)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function airfield.start()
|
||||
if not dcsCommon.libCheck("cfx airfield", airfield.requiredLibs)
|
||||
then return false end
|
||||
|
||||
-- set up DB
|
||||
airfield.collectAll()
|
||||
|
||||
-- read config
|
||||
airfield.readConfig()
|
||||
@@ -415,6 +473,9 @@ function airfield.start()
|
||||
airfield.createAirFieldFromZone(aZone)
|
||||
end
|
||||
|
||||
-- show all unlinked
|
||||
if airfield.showAll then airfield.showUnlinked() end
|
||||
|
||||
-- connect event handler
|
||||
world.addEventHandler(airfield)
|
||||
|
||||
@@ -442,7 +503,3 @@ if not airfield.start() then
|
||||
trigger.action.outText("+++ aborted airfield v" .. airfield.version .. " -- startup failed", 30)
|
||||
airfield = nil
|
||||
end
|
||||
|
||||
--[[--
|
||||
ideas: what if we made this airfield movable?
|
||||
--]]--
|
||||
@@ -1,5 +1,5 @@
|
||||
cfxArtilleryUI = {}
|
||||
cfxArtilleryUI.version = "1.1.0"
|
||||
cfxArtilleryUI.version = "2.0.0"
|
||||
cfxArtilleryUI.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
"cfxPlayer", -- get all players
|
||||
@@ -8,7 +8,7 @@ cfxArtilleryUI.requiredLibs = {
|
||||
}
|
||||
--
|
||||
-- UI for ArtilleryZones module, implements LOS, Observer, SMOKE
|
||||
-- Copyright (c) 2021, 2022 by Christian Franz and cf/x AG
|
||||
-- Copyright (c) 2021 - 2024 by Christian Franz and cf/x AG
|
||||
|
||||
--[[-- VERSION HISTORY
|
||||
- 1.0.0 - based on jtacGrpUI
|
||||
@@ -21,6 +21,8 @@ cfxArtilleryUI.requiredLibs = {
|
||||
- collect zones recognizes moving zones, updates landHeight
|
||||
- allSeeing god mode attribute: always observing.
|
||||
- allRanging god mode attribute: always in range.
|
||||
- 2.0.0 - dmlZones, OOP
|
||||
cleanup
|
||||
|
||||
|
||||
--]]--
|
||||
@@ -272,21 +274,18 @@ function cfxArtilleryUI.populateTargetMenu(conf)
|
||||
-- now compare old control string with new, and only
|
||||
-- re-populate if the old is different
|
||||
if tgtCheckSum == conf.tgtCheckSum then
|
||||
-- trigger.action.outText("*** yeah old targets", 30)
|
||||
return
|
||||
elseif not conf.tgtCheckSum then
|
||||
-- trigger.action.outText("+++ new target menu", 30)
|
||||
|
||||
else
|
||||
trigger.action.outTextForGroup(conf.id, "Artillery target updates", 30)
|
||||
trigger.action.outSoundForGroup(conf.id, cfxArtilleryUI.updateSound)
|
||||
-- trigger.action.outText("!!! target update ", 30)
|
||||
|
||||
end
|
||||
|
||||
-- we need to re-populate. erase old values
|
||||
cfxArtilleryUI.clearCommsTargets(conf)
|
||||
conf.tgtCheckSum = tgtCheckSum -- remember for last time
|
||||
--trigger.action.outText("new targets", 30)
|
||||
|
||||
conf.tgtCheckSum = tgtCheckSum -- remember for last time
|
||||
if #filteredTargets < 1 then
|
||||
-- simply put one-line dummy in there
|
||||
local commandTxt = "(No unobscured target areas)"
|
||||
@@ -307,7 +306,6 @@ function cfxArtilleryUI.populateTargetMenu(conf)
|
||||
for i=1, numTargets do
|
||||
-- make a target command for each
|
||||
local aTarget = filteredTargets[i]
|
||||
|
||||
commandTxt = "Fire at: <" .. aTarget.name .. ">"
|
||||
theCommand = missionCommands.addCommandForGroup(
|
||||
conf.id,
|
||||
@@ -444,7 +442,6 @@ function cfxArtilleryUI.doCommandListTargets(args)
|
||||
local conf = args[1] -- < conf in here
|
||||
local what = args[2] -- < second argument in here
|
||||
local theGroup = conf.theGroup
|
||||
-- trigger.action.outTextForGroup(conf.id, "+++ groupUI: processing comms menu for <" .. what .. ">", 30)
|
||||
local targetList = cfxArtilleryUI.collectArtyTargets(conf)
|
||||
-- iterate the list
|
||||
if #targetList < 1 then
|
||||
@@ -453,7 +450,6 @@ function cfxArtilleryUI.doCommandListTargets(args)
|
||||
end
|
||||
|
||||
local desc = "Artillery Targets:\n"
|
||||
-- trigger.action.outTextForGroup(conf.id, "Target Report:", 30)
|
||||
for i=1, #targetList do
|
||||
local aTarget = targetList[i]
|
||||
local inRange = cfxArtilleryUI.allRanging or aTarget.range * 1000 < aTarget.spotRange
|
||||
@@ -475,7 +471,6 @@ end
|
||||
function cfxArtilleryUI.collectArtyTargets(conf)
|
||||
-- iterate all target zones, for those that are on my side
|
||||
-- calculate range, bearing, and then order by distance
|
||||
|
||||
local theTargets = {}
|
||||
for idx, aZone in pairs(cfxArtilleryZones.artilleryZones) do
|
||||
if aZone.coalition == conf.coalition then
|
||||
@@ -498,7 +493,7 @@ function cfxArtilleryUI.collectArtyTargets(conf)
|
||||
--aTarget.targetName = aZone.name
|
||||
aTarget.spotRange = aZone.spotRange
|
||||
-- get the target we are lazing
|
||||
local zP = cfxZones.getPoint(aZone) -- zone can move!
|
||||
local zP = aZone:getPoint() -- zone can move!
|
||||
aZone.landHeight = land.getHeight({x = zP.x, y= zP.z})
|
||||
local there = {x = zP.x, y = aZone.landHeight + 1, z=zP.z}
|
||||
aTarget.there = there
|
||||
@@ -654,18 +649,17 @@ function cfxArtilleryUI.readConfigZone()
|
||||
-- note: must match exactly!!!!
|
||||
local theZone = cfxZones.getZoneByName("ArtilleryUIConfig")
|
||||
if not theZone then
|
||||
trigger.action.outText("+++A-UI: no config zone!", 30)
|
||||
return
|
||||
--trigger.action.outText("+++A-UI: no config zone!", 30)
|
||||
--return
|
||||
theZone = cfxZones.createSimpleZone("ArtilleryUIConfig")
|
||||
end
|
||||
|
||||
trigger.action.outText("+++A-UI: found config zone!", 30)
|
||||
|
||||
cfxArtilleryUI.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
|
||||
cfxArtilleryUI.allowPlanes = cfxZones.getBoolFromZoneProperty(theZone, "allowPlanes", false)
|
||||
cfxArtilleryUI.smokeColor = cfxZones.getSmokeColorStringFromZoneProperty(theZone, "smokeColor", "red")
|
||||
cfxArtilleryUI.allSeeing = cfxZones.getBoolFromZoneProperty(theZone, "allSeeing", false)
|
||||
cfxArtilleryUI.allRanging = cfxZones.getBoolFromZoneProperty(theZone, "allRanging", false)
|
||||
cfxArtilleryUI.allTiming = cfxZones.getBoolFromZoneProperty(theZone, "allTiming", false)
|
||||
|
||||
cfxArtilleryUI.verbose = theZone.verbose
|
||||
cfxArtilleryUI.allowPlanes = theZone:getBoolFromZoneProperty("allowPlanes", false)
|
||||
cfxArtilleryUI.smokeColor = theZone:getSmokeColorStringFromZoneProperty("smokeColor", "red")
|
||||
cfxArtilleryUI.allSeeing = theZone:getBoolFromZoneProperty("allSeeing", false)
|
||||
cfxArtilleryUI.allRanging = theZone:getBoolFromZoneProperty("allRanging", false)
|
||||
cfxArtilleryUI.allTiming = theZone:getBoolFromZoneProperty("allTiming", false)
|
||||
end
|
||||
|
||||
--
|
||||
@@ -681,7 +675,6 @@ function cfxArtilleryUI.start()
|
||||
cfxArtilleryUI.readConfigZone()
|
||||
|
||||
-- iterate existing groups so we have a start situation
|
||||
-- now iterate through all player groups and install the Assault Troop Menu
|
||||
local allPlayerGroups = cfxPlayerGroups -- cfxPlayerGroups is a global, don't fuck with it!
|
||||
-- contains per group player record. Does not resolve on unit level!
|
||||
for gname, pgroup in pairs(allPlayerGroups) do
|
||||
@@ -710,5 +703,5 @@ end
|
||||
|
||||
--[[--
|
||||
TODO: transition times based on distance - requires real bound arty first
|
||||
DONE: ui for smoking target zone: list ten closest zones, and provide menu to smoke zone
|
||||
TODO: remove dependency on cfxPlayer
|
||||
--]]--
|
||||
@@ -1,5 +1,5 @@
|
||||
cfxArtilleryZones = {}
|
||||
cfxArtilleryZones.version = "2.2.2"
|
||||
cfxArtilleryZones.version = "3.0.0"
|
||||
cfxArtilleryZones.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
"cfxZones", -- Zones, of course
|
||||
@@ -31,28 +31,12 @@ cfxArtilleryZones.verbose = false
|
||||
2.2.0 - DML Watchflag integration
|
||||
2.2.1 - minor code clean-up
|
||||
2.2.2 - new doParametricFireAt()
|
||||
|
||||
3.0.0 - dmlZones, OOP
|
||||
- cleanup
|
||||
|
||||
Artillery Target Zones *** EXTENDS ZONES ***
|
||||
Target Zones for artillery. Can determine which zones are in range and visible and then handle artillery barrage to this zone
|
||||
Copyright (c) 2021, 2022 by Christian Franz and cf/x AG
|
||||
|
||||
USAGE
|
||||
Via ME: Add the relevant attributes to the zone
|
||||
Via Script: Use createArtilleryTarget()
|
||||
|
||||
|
||||
Callbacks
|
||||
when fire at target is invoked, a callback can be
|
||||
invoked so your code knows that fire control has been
|
||||
given a command or that projectiles are impacting.
|
||||
Signature
|
||||
callback(rason, zone, data), with
|
||||
reason: 'firing' - fire command given for zone
|
||||
'impact' a projectile has hit
|
||||
zone: artilleryZone
|
||||
data: empty on 'fire'
|
||||
.point where impact point
|
||||
.strength power of explosion
|
||||
Copyright (c) 2021 - 2024 by Christian Franz and cf/x AG
|
||||
|
||||
--]]--
|
||||
cfxArtilleryZones.artilleryZones = {}
|
||||
@@ -117,42 +101,39 @@ function cfxArtilleryZones.createArtilleryTarget(name, point, coalition, spotRan
|
||||
end
|
||||
|
||||
function cfxArtilleryZones.processArtilleryZone(aZone)
|
||||
aZone.artilleryTarget = cfxZones.getStringFromZoneProperty(aZone, "artilleryTarget", aZone.name)
|
||||
aZone.coalition = cfxZones.getCoalitionFromZoneProperty(aZone, "coalition", 0) -- side that marks it on map, and who fires arty
|
||||
aZone.spotRange = cfxZones.getNumberFromZoneProperty(aZone, "spotRange", 3000) -- FO max range to direct fire
|
||||
aZone.shellStrength = cfxZones.getNumberFromZoneProperty(aZone, "shellStrength", 500) -- power of shells (strength)
|
||||
aZone.artilleryTarget = aZone:getStringFromZoneProperty( "artilleryTarget", aZone.name)
|
||||
aZone.coalition = aZone:getCoalitionFromZoneProperty("coalition", 0) -- side that marks it on map, and who fires arty
|
||||
aZone.spotRange = aZone:getNumberFromZoneProperty("spotRange", 3000) -- FO max range to direct fire
|
||||
aZone.shellStrength = aZone:getNumberFromZoneProperty("shellStrength", 500) -- power of shells (strength)
|
||||
|
||||
aZone.shellNum = cfxZones.getNumberFromZoneProperty(aZone, "shellNum", 17) -- number of shells in bombardment
|
||||
aZone.transitionTime = cfxZones.getNumberFromZoneProperty(aZone, "transitionTime", 20) -- average time of travel for projectiles
|
||||
aZone.addMark = cfxZones.getBoolFromZoneProperty(aZone, "addMark", true) -- note: defaults to true
|
||||
aZone.shellVariance = cfxZones.getNumberFromZoneProperty(aZone, "shellVariance", 0.2) -- strength of explosion can vary by +/- this amount
|
||||
aZone.shellNum = aZone:getNumberFromZoneProperty("shellNum", 17) -- number of shells in bombardment
|
||||
aZone.transitionTime = aZone:getNumberFromZoneProperty( "transitionTime", 20) -- average time of travel for projectiles
|
||||
aZone.addMark = aZone:getBoolFromZoneProperty("addMark", true) -- note: defaults to true
|
||||
aZone.shellVariance = aZone:getNumberFromZoneProperty( "shellVariance", 0.2) -- strength of explosion can vary by +/- this amount
|
||||
|
||||
-- watchflag:
|
||||
-- triggerMethod
|
||||
aZone.artyTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "artyTriggerMethod", "change")
|
||||
aZone.artyTriggerMethod = aZone:getStringFromZoneProperty( "artyTriggerMethod", "change")
|
||||
|
||||
if cfxZones.hasProperty(aZone, "triggerMethod") then
|
||||
aZone.artyTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "triggerMethod", "change")
|
||||
if aZone:hasProperty("triggerMethod") then
|
||||
aZone.artyTriggerMethod = aZone:getStringFromZoneProperty("triggerMethod", "change")
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(aZone, "f?") then
|
||||
aZone.artyTriggerFlag = cfxZones.getStringFromZoneProperty(aZone, "f?", "none")
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(aZone, "artillery?") then
|
||||
aZone.artyTriggerFlag = cfxZones.getStringFromZoneProperty(aZone, "artillery?", "none")
|
||||
end
|
||||
if cfxZones.hasProperty(aZone, "in?") then
|
||||
aZone.artyTriggerFlag = cfxZones.getStringFromZoneProperty(aZone, "in?", "none")
|
||||
if aZone:hasProperty("f?") then
|
||||
aZone.artyTriggerFlag = cfxZones.getStringFromZoneProperty("f?", "none")
|
||||
elseif aZone:hasProperty("artillery?") then
|
||||
aZone.artyTriggerFlag = aZone:getStringFromZoneProperty("artillery?", "none")
|
||||
elseif aZone:hasProperty("in?") then
|
||||
aZone.artyTriggerFlag = aZone:getStringFromZoneProperty("in?", "none")
|
||||
end
|
||||
|
||||
if aZone.artyTriggerFlag then
|
||||
aZone.lastTriggerValue = trigger.misc.getUserFlag(aZone.artyTriggerFlag) -- save last value
|
||||
end
|
||||
aZone.cooldown =cfxZones.getNumberFromZoneProperty(aZone, "cooldown", 120) -- seconds
|
||||
aZone.baseAccuracy = cfxZones.getNumberFromZoneProperty(aZone, "baseAccuracy", aZone.radius) -- meters from center radius shell impact
|
||||
aZone.cooldown = aZone:getNumberFromZoneProperty("cooldown", 120) -- seconds
|
||||
aZone.baseAccuracy = aZone:getNumberFromZoneProperty( "baseAccuracy", aZone.radius) -- meters from center radius shell impact
|
||||
-- use zone radius as mase accuracy for simple placement
|
||||
aZone.silent = cfxZones.getBoolFromZoneProperty(aZone, "silent", false)
|
||||
aZone.silent = aZone:getBoolFromZoneProperty("silent", false)
|
||||
end
|
||||
|
||||
function cfxArtilleryZones.addArtilleryZone(aZone)
|
||||
@@ -200,7 +181,7 @@ function cfxArtilleryZones.artilleryZonesInRangeOfUnit(theUnit)
|
||||
-- is it one of mine?
|
||||
if aZone.coalition == myCoalition then
|
||||
-- is it close enough?
|
||||
local zP = cfxZones.getPoint(aZone)
|
||||
local zP = aZone:getPoint()
|
||||
aZone.landHeight = land.getHeight({x = zP.x, y= zP.z})
|
||||
local zonePoint = {x = zP.x, y = aZone.landHeight, z = zP.z}
|
||||
local d = dcsCommon.dist(p,zonePoint)
|
||||
@@ -249,7 +230,7 @@ end
|
||||
--
|
||||
|
||||
--
|
||||
-- BOOM command
|
||||
-- BOOM method
|
||||
--
|
||||
function cfxArtilleryZones.doBoom(args)
|
||||
trigger.action.explosion(args.point, args.strength)
|
||||
@@ -356,7 +337,6 @@ function cfxArtilleryZones.simFireAtZone(aZone, aGroup, dist)
|
||||
|
||||
trigger.action.outTextForCoalition(aGroup:getCoalition(), "Artillery firing on ".. aZone.name .. addInfo, 30)
|
||||
end
|
||||
--trigger.action.smoke(center, 2) -- mark location visually
|
||||
end
|
||||
|
||||
function cfxArtilleryZones.simSmokeZone(aZone, aGroup, aColor)
|
||||
@@ -367,7 +347,7 @@ function cfxArtilleryZones.simSmokeZone(aZone, aGroup, aColor)
|
||||
if type(aColor) == "string" then
|
||||
aColor = dcsCommon.smokeColor2Num(aColor)
|
||||
end
|
||||
local zP = cfxZones.getPoint(aZone)
|
||||
local zP = aZone:getPoint(aZone)
|
||||
aZone.landHeight = land.getHeight({x = zP.x, y= zP.z})
|
||||
|
||||
local transitionTime = aZone.transitionTime --17 -- seconds until phosphor lands
|
||||
@@ -404,7 +384,7 @@ function cfxArtilleryZones.update()
|
||||
|
||||
-- iterate all zones to see if a trigger has changed
|
||||
for idx, aZone in pairs(cfxArtilleryZones.artilleryZones) do
|
||||
if cfxZones.testZoneFlag(aZone, aZone.artyTriggerFlag, aZone.artyTriggerMethod, "lastTriggerValue") then
|
||||
if aZone:testZoneFlag(aZone.artyTriggerFlag, aZone.artyTriggerMethod, "lastTriggerValue") then
|
||||
-- a triggered release!
|
||||
cfxArtilleryZones.doFireAt(aZone) -- all from zone vars!
|
||||
if cfxArtilleryZones.verbose then
|
||||
@@ -413,10 +393,10 @@ function cfxArtilleryZones.update()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- old code
|
||||
--[[--
|
||||
if aZone.artyTriggerFlag then
|
||||
local currTriggerVal = cfxZones.getFlagValue(aZone.artyTriggerFlag, aZone) -- trigger.misc.getUserFlag(aZone.artyTriggerFlag)
|
||||
local currTriggerVal = cfxZones.getFlagValue(aZone.artyTriggerFlag, aZone)
|
||||
if currTriggerVal ~= aZone.lastTriggerValue
|
||||
then
|
||||
-- a triggered release!
|
||||
@@ -430,6 +410,7 @@ function cfxArtilleryZones.update()
|
||||
end
|
||||
|
||||
end
|
||||
--]]--
|
||||
end
|
||||
end
|
||||
|
||||
@@ -446,8 +427,6 @@ function cfxArtilleryZones.start()
|
||||
-- collect all spawn zones
|
||||
local attrZones = cfxZones.getZonesWithAttributeNamed("artilleryTarget")
|
||||
|
||||
-- now create a spawner for all, add them to the spawner updater, and spawn for all zones that are not
|
||||
-- paused
|
||||
for k, aZone in pairs(attrZones) do
|
||||
cfxArtilleryZones.processArtilleryZone(aZone) -- process attribute and add to zone
|
||||
cfxArtilleryZones.addArtilleryZone(aZone) -- remember it so we can smoke it
|
||||
@@ -1,25 +1,17 @@
|
||||
cfxCargoReceiver = {}
|
||||
cfxCargoReceiver.version = "1.2.2"
|
||||
cfxCargoReceiver.version = "2.0.0"
|
||||
cfxCargoReceiver.ups = 1 -- once a second
|
||||
cfxCargoReceiver.maxDirectionRange = 500 -- in m. distance when cargo manager starts talking to pilots who are carrying that cargo
|
||||
cfxCargoReceiver.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
"cfxPlayer", -- for directions
|
||||
"cfxZones", -- Zones, of course
|
||||
"cfxCargoManager", -- will notify me on a cargo event
|
||||
}
|
||||
--[[--
|
||||
Version history
|
||||
- 1.0.0 initial vbersion
|
||||
- 1.1.0 added flag manipulation options
|
||||
no negative agl on announcement
|
||||
silent attribute
|
||||
- 1.2.0 method
|
||||
f!, cargoReceived!
|
||||
- 1.2.1 cargoMethod
|
||||
- 1.2.2 removed deprecated functions
|
||||
corrected pollFlag bug (not passing zone along)
|
||||
distance to receiver is given as distance to zone boundary
|
||||
- 2.0.0 no more cfxPlayer Dependency
|
||||
dmlZones, OOP
|
||||
clean-up
|
||||
|
||||
|
||||
CargoReceiver is a zone enhancement you use to be automatically
|
||||
@@ -27,14 +19,7 @@ cfxCargoReceiver.requiredLibs = {
|
||||
It also provides BRA when in range to a cargo receiver
|
||||
|
||||
*** EXTENDS ZONES
|
||||
|
||||
Callback signature:
|
||||
cb(event, obj, name, zone) with
|
||||
- event being string, currently defined: 'deliver'
|
||||
- obj being the cargo object
|
||||
- name being cargo object name
|
||||
- zone in which cargo was dropped (if dropped)
|
||||
|
||||
|
||||
--]]--
|
||||
cfxCargoReceiver.receiverZones = {}
|
||||
function cfxCargoReceiver.processReceiverZone(aZone) -- process attribute and add to zone
|
||||
@@ -42,50 +27,22 @@ function cfxCargoReceiver.processReceiverZone(aZone) -- process attribute and ad
|
||||
-- isCargoReceiver flag and we are good
|
||||
aZone.isCargoReceiver = true
|
||||
-- we can add additional processing here
|
||||
aZone.autoRemove = cfxZones.getBoolFromZoneProperty(aZone, "autoRemove", false) -- maybe add a removeDelay
|
||||
aZone.removeDelay = cfxZones.getNumberFromZoneProperty(aZone, "removeDelay", 1)
|
||||
aZone.autoRemove = aZone:getBoolFromZoneProperty("autoRemove", false) -- maybe add a removeDelay
|
||||
aZone.removeDelay = aZone:getNumberFromZoneProperty("removeDelay", 1)
|
||||
if aZone.removeDelay < 1 then aZone.removeDelay = 1 end
|
||||
aZone.silent = cfxZones.getBoolFromZoneProperty(aZone, "silent", false)
|
||||
aZone.silent = aZone:getBoolFromZoneProperty("silent", false)
|
||||
|
||||
--trigger.action.outText("+++rcv: recognized receiver zone: " .. aZone.name , 30)
|
||||
|
||||
-- same integration as object destruct detector for flags
|
||||
--[[--
|
||||
if cfxZones.hasProperty(aZone, "setFlag") then
|
||||
aZone.setFlag = cfxZones.getStringFromZoneProperty(aZone, "setFlag", "999")
|
||||
end
|
||||
if cfxZones.hasProperty(aZone, "f=1") then
|
||||
aZone.setFlag = cfxZones.getStringFromZoneProperty(aZone, "f=1", "999")
|
||||
end
|
||||
if cfxZones.hasProperty(aZone, "clearFlag") then
|
||||
aZone.clearFlag = cfxZones.getStringFromZoneProperty(aZone, "clearFlag", "999")
|
||||
end
|
||||
if cfxZones.hasProperty(aZone, "f=0") then
|
||||
aZone.clearFlag = cfxZones.getStringFromZoneProperty(aZone, "f=0", "999")
|
||||
end
|
||||
if cfxZones.hasProperty(aZone, "increaseFlag") then
|
||||
aZone.increaseFlag = cfxZones.getStringFromZoneProperty(aZone, "increaseFlag", "999")
|
||||
end
|
||||
if cfxZones.hasProperty(aZone, "f+1") then
|
||||
aZone.increaseFlag = cfxZones.getStringFromZoneProperty(aZone, "f+1", "999")
|
||||
end
|
||||
if cfxZones.hasProperty(aZone, "decreaseFlag") then
|
||||
aZone.decreaseFlag = cfxZones.getStringFromZoneProperty(aZone, "decreaseFlag", "999")
|
||||
end
|
||||
if cfxZones.hasProperty(aZone, "f-1") then
|
||||
aZone.decreaseFlag = cfxZones.getStringFromZoneProperty(aZone, "f-1", "999")
|
||||
end
|
||||
--]]--
|
||||
-- new method support
|
||||
aZone.cargoMethod = cfxZones.getStringFromZoneProperty(aZone, "method", "inc")
|
||||
if cfxZones.hasProperty(aZone, "cargoMethod") then
|
||||
aZone.cargoMethod = cfxZones.getStringFromZoneProperty(aZone, "cargoMethod", "inc")
|
||||
aZone.cargoMethod = aZone:getStringFromZoneProperty("method", "inc")
|
||||
if aZone:hasProperty("cargoMethod") then
|
||||
aZone.cargoMethod = aZone:getStringFromZoneProperty("cargoMethod", "inc")
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(aZone, "f!") then
|
||||
aZone.outReceiveFlag = cfxZones.getStringFromZoneProperty(aZone, "f!", "*<none>")
|
||||
elseif cfxZones.hasProperty(aZone, "cargoReceived!") then
|
||||
aZone.outReceiveFlag = cfxZones.getStringFromZoneProperty(aZone, "cargoReceived!", "*<none>")
|
||||
if aZone:hasProperty("f!") then
|
||||
aZone.outReceiveFlag = aZone:getStringFromZoneProperty("f!", "*<none>")
|
||||
elseif aZone:hasProperty("cargoReceived!") then
|
||||
aZone.outReceiveFlag = aZone:getStringFromZoneProperty( "cargoReceived!", "*<none>")
|
||||
end
|
||||
|
||||
end
|
||||
@@ -134,17 +91,14 @@ end
|
||||
|
||||
function cfxCargoReceiver.cargoEvent(event, object, name)
|
||||
-- usually called from cargomanager
|
||||
--trigger.action.outText("Cargo Receiver: event <" .. event .. "> for " .. name, 30)
|
||||
|
||||
if not event then return end
|
||||
if event == "grounded" then
|
||||
--trigger.action.outText("+++rcv: grounded for " .. name, 30)
|
||||
-- this is actually the only one that interests us
|
||||
if not object then
|
||||
--trigger.action.outText("+++rcv: " .. name .. " has null object", 30)
|
||||
return
|
||||
end
|
||||
if not object:isExist() then
|
||||
--trigger.action.outText("+++rcv: " .. name .. " no longer exists", 30)
|
||||
if not Object.isExist(object) then
|
||||
return
|
||||
end
|
||||
loc = object:getPoint()
|
||||
@@ -156,28 +110,10 @@ function cfxCargoReceiver.cargoEvent(event, object, name)
|
||||
cfxCargoReceiver.invokeCallback("deliver", object, name, aZone)
|
||||
|
||||
-- set flags as indicated
|
||||
--[[--
|
||||
if aZone.setFlag then
|
||||
trigger.action.setUserFlag(aZone.setFlag, 1)
|
||||
end
|
||||
if aZone.clearFlag then
|
||||
trigger.action.setUserFlag(aZone.clearFlag, 0)
|
||||
end
|
||||
if aZone.increaseFlag then
|
||||
local val = trigger.misc.getUserFlag(aZone.increaseFlag) + 1
|
||||
trigger.action.setUserFlag(aZone.increaseFlag, val)
|
||||
end
|
||||
if aZone.decreaseFlag then
|
||||
local val = trigger.misc.getUserFlag(aZone.decreaseFlag) - 1
|
||||
trigger.action.setUserFlag(aZone.decreaseFlag, val)
|
||||
end
|
||||
--]]--
|
||||
if aZone.outReceiveFlag then
|
||||
cfxZones.pollFlag(aZone.outReceiveFlag, aZone.cargoMethod, aZone)
|
||||
end
|
||||
|
||||
--trigger.action.outText("+++rcv: " .. name .. " delivered in zone " .. aZone.name, 30)
|
||||
--trigger.action.outSound("Quest Snare 3.wav")
|
||||
if aZone.autoRemove then
|
||||
-- schedule this for in a few seconds?
|
||||
local args = {}
|
||||
@@ -218,12 +154,12 @@ function cfxCargoReceiver.update()
|
||||
-- this cargo can be talked down.
|
||||
-- find the player unit that is closest to in in hopes
|
||||
-- that that is the one carrying it
|
||||
local allPlayers = cfxPlayer.getAllPlayers() -- idx by name
|
||||
for pname, info in pairs(allPlayers) do
|
||||
local allPlayers = dcsCommon.getAllExistingPlayersAndUnits() -- idx by name
|
||||
for pname, theUnit in pairs(allPlayers) do
|
||||
-- iterate all player units
|
||||
local closestUnit = nil
|
||||
local minDelta = math.huge
|
||||
local theUnit = info.unit
|
||||
--local theUnit = info.unit
|
||||
if theUnit:isExist() then
|
||||
local uPoint = theUnit:getPoint()
|
||||
local currDelta = dcsCommon.distFlat(thePoint, uPoint)
|
||||
@@ -305,4 +241,4 @@ if not cfxCargoReceiver.start() then
|
||||
end
|
||||
|
||||
-- TODO: config zone for talking down pilots
|
||||
-- TODO: f+/f-/f=1/f=0
|
||||
-- detect all pilots in zone (not clear: are all detected or only one)
|
||||
@@ -1,411 +0,0 @@
|
||||
cfxArtilleryDemon = {}
|
||||
cfxArtilleryDemon.version = "1.0.3"
|
||||
-- based on cfx stage demon v 1.0.2
|
||||
--[[--
|
||||
Version History
|
||||
1.0.2 - taken from stageDemon
|
||||
1.0.3 - corrected 'messageOut' bug
|
||||
|
||||
--]]--
|
||||
cfxArtilleryDemon.messageToAll = true -- set to false if messages should be sent only to the group that set the mark
|
||||
cfxArtilleryDemon.messageTime = 30 -- how long a message stays on the sceeen
|
||||
|
||||
-- cfxArtillery hooks into DCS's mark system to intercept user
|
||||
-- transactions with the mark system and uses that for arty targeting
|
||||
-- used to interactively add ArtilleryZones during gameplay
|
||||
|
||||
-- Copyright (c) 2021 by Christian Franz and cf/x AG
|
||||
|
||||
cfxArtilleryDemon.autostart = true -- start automatically
|
||||
|
||||
-- whenever you begin a Mark with the string below, it will be taken as a command
|
||||
-- and run through the command parser, stripping the mark, and then splitting
|
||||
-- by blanks
|
||||
cfxArtilleryDemon.markOfDemon = "-" -- all commands must start with this sequence
|
||||
cfxArtilleryDemon.splitDelimiter = " "
|
||||
|
||||
cfxArtilleryDemon.unitFilterMethod = nil -- optional user filtering redirection. currently
|
||||
-- set to allow all users use cfxArtillery
|
||||
cfxArtilleryDemon.processCommandMethod = nil -- optional initial command processing redirection
|
||||
-- currently set to cfxArtillery's own processor
|
||||
cfxArtilleryDemon.commandTable = {} -- key, value pair for command processing per keyword
|
||||
-- all commands cfxArtillery understands are used as keys and
|
||||
-- the functions that process them are used as values
|
||||
-- making the parser a trivial table :)
|
||||
|
||||
cfxArtilleryDemon.demonID = nil -- used only for suspending the event callback
|
||||
|
||||
-- unit authorization. You return false to disallow this unit access
|
||||
-- to commands
|
||||
-- simple authorization checks would be to allow only players
|
||||
-- on neutral side, or players in range of location with Lino of sight
|
||||
-- to that point
|
||||
--
|
||||
function cfxArtilleryDemon.authorizeAllUnits(event)
|
||||
-- units/groups that are allowed to give a command can be filtered.
|
||||
-- return true if the unit/group may give commands
|
||||
-- cfxArtillery allows anyone to give it commands
|
||||
return true
|
||||
end
|
||||
|
||||
function cfxArtilleryDemon.hasMark(theString)
|
||||
-- check if the string begins with the sequece to identify commands
|
||||
if not theString then return false end
|
||||
return theString:find(cfxArtilleryDemon.markOfDemon) == 1
|
||||
end
|
||||
|
||||
function cfxArtilleryDemon.splitString(inputstr, sep)
|
||||
if sep == nil then
|
||||
sep = "%s"
|
||||
end
|
||||
if inputstr == nil then
|
||||
inputstr = ""
|
||||
end
|
||||
|
||||
local t={}
|
||||
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
|
||||
table.insert(t, str)
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
function cfxArtilleryDemon.str2num(inVal, default)
|
||||
if not default then default = 0 end
|
||||
if not inVal then return default end
|
||||
if type(inVal) == "number" then return inVal end
|
||||
local num = nil
|
||||
if type(inVal) == "string" then num = tonumber(inVal) end
|
||||
if not num then return default end
|
||||
return num
|
||||
end
|
||||
|
||||
--
|
||||
-- output method. can be customized, so we have a central place where we
|
||||
-- can control how output is handled. Is currently outText and outTextToGroup
|
||||
--
|
||||
function cfxArtilleryDemon.outMessage(theMessage, args)
|
||||
if not args then
|
||||
args = {}
|
||||
end
|
||||
local toAll = args.toAll -- will only be true if defined and set to true
|
||||
|
||||
if not args.group then
|
||||
toAll = true
|
||||
else
|
||||
if not args.group:isExist() then
|
||||
toAll = true
|
||||
end
|
||||
end
|
||||
toAll = toAll or cfxArtilleryDemon.messageToAll
|
||||
if not toAll then
|
||||
trigger.action.outTextToGroup(args.group, theMessage, cfxArtilleryDemon.messageTime)
|
||||
else
|
||||
trigger.action.outText(theMessage, cfxArtilleryDemon.messageTime)
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- get all player groups - since there is no getGroupByIndex in DCS (yet)
|
||||
-- we simply collect all player groups (since only palyers can place marks)
|
||||
-- and try to match their group ID to the one given by mark
|
||||
function cfxArtilleryDemon.getAllPayerGroups()
|
||||
local coalitionSides = {0, 1, 2} -- we currently have neutral, red, blue
|
||||
local playerGroups = {}
|
||||
for i=1, #coalitionSides do
|
||||
local theSide = coalitionSides[i]
|
||||
-- get all players for this side
|
||||
local thePlayers = coalition.getPlayers(theSide)
|
||||
for p=1, #thePlayers do
|
||||
aPlayerUnit = thePlayers[p] -- docs say this is a unit table, not a person!
|
||||
if aPlayerUnit:isExist() then
|
||||
local theGroup = aPlayerUnit:getGroup()
|
||||
if theGroup:isExist() then
|
||||
local gID = theGroup:getID()
|
||||
playerGroups[gID] = theGroup -- multiple players per group results in one group
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return playerGroups
|
||||
end
|
||||
|
||||
function cfxArtilleryDemon.retrieveGroupFromEvent(theEvent)
|
||||
-- DEBUG CODE
|
||||
if theEvent.initiator then
|
||||
trigger.action.outText("EVENT: initiator set to " .. theEvent.initiator:getName(), 30)
|
||||
else
|
||||
trigger.action.outText("EVENT: NO INITIATOR", 30)
|
||||
end
|
||||
|
||||
trigger.action.outText("EVENT: groupID = " .. theEvent.groupID, 30)
|
||||
|
||||
-- trivial case: initiator is set, and we can access the group
|
||||
if theEvent.initiator then
|
||||
if theEvent.initiator:isExist() then
|
||||
return theEvent.initiator:getGroup()
|
||||
end
|
||||
end
|
||||
|
||||
-- ok, bad news: initiator wasn't filled. let's try the fallback: event.groupID
|
||||
if theEvent.groupID and theEvent.groupID > 0 then
|
||||
local playerGroups = cfxArtilleryDemon.getAllPayerGroups()
|
||||
if playerGroups[theEvent.groupID] then
|
||||
return palyerGroups[theEvent.groupID]
|
||||
end
|
||||
end
|
||||
|
||||
-- nope, return nil
|
||||
return nil
|
||||
end
|
||||
|
||||
-- main hook into DCS. Called whenever a Mark-related event happens
|
||||
-- very simple: look if text begins with special sequence, and if so,
|
||||
-- call the command processor. Note that you can hook your own command
|
||||
-- processor in by changing the value of processCommandMethod
|
||||
function cfxArtilleryDemon:onEvent(theEvent)
|
||||
-- while we can hook into any of the three events,
|
||||
-- we curently only utilize CHANGE Mark
|
||||
if not (theEvent.id == world.event.S_EVENT_MARK_ADDED) and
|
||||
not (theEvent.id == world.event.S_EVENT_MARK_CHANGE) and
|
||||
not (theEvent.id == world.event.S_EVENT_MARK_REMOVED) then
|
||||
-- not of interest for us, bye bye
|
||||
return
|
||||
end
|
||||
|
||||
-- build the messageOut() arg table
|
||||
local args = {}
|
||||
args.toAll = cfxArtilleryDemon.toAll
|
||||
--
|
||||
--
|
||||
args.toAll = false -- FORCE GROUPS FOR DEBUGGING OF NEW CODE
|
||||
--
|
||||
--
|
||||
if not args.toAll then
|
||||
-- we want group-targeted messaging
|
||||
-- so we need to retrieve the group
|
||||
local theGroup = cfxArtilleryDemon.retrieveGroupFromEvent(theEvent)
|
||||
if not theGroup then
|
||||
args.toAll = true
|
||||
trigger.action.outText("*** WARNING: cfxArtilleryDemon can't find group for command", 30)
|
||||
else
|
||||
args.group = theGroup
|
||||
end
|
||||
end
|
||||
cfxArtilleryDemon.args = args -- copy reference so we can easily use it in messageOut
|
||||
|
||||
-- when we get here, we have a mark event
|
||||
-- see if the unit filter lets it pass
|
||||
if not cfxArtilleryDemon.unitFilterMethod(theEvent) then
|
||||
return -- unit is not allowed to give demon orders. bye bye
|
||||
end
|
||||
|
||||
if theEvent.id == world.event.S_EVENT_MARK_ADDED then
|
||||
-- add mark is quite useless for us as we are called when the user clicks, with no
|
||||
-- text in the description yet. Later abilities may want to use it though
|
||||
end
|
||||
|
||||
if theEvent.id == world.event.S_EVENT_MARK_CHANGE then
|
||||
-- when changed, the mark's text is examined for a command
|
||||
-- if it starts with the 'mark' string ("*" by default) it is processed
|
||||
-- by the command processor
|
||||
-- if it is processed succesfully, the mark is immediately removed
|
||||
-- else an error is displayed and the mark remains.
|
||||
if cfxArtilleryDemon.hasMark(theEvent.text) then
|
||||
-- strip the mark
|
||||
local commandString = theEvent.text:sub(1+cfxArtilleryDemon.markOfDemon:len())
|
||||
-- break remainder apart into <command> <arg1> ... <argn>
|
||||
local commands = cfxArtilleryDemon.splitString(commandString, cfxArtilleryDemon.splitDelimiter)
|
||||
|
||||
-- this is a command. process it and then remove it if it was executed successfully
|
||||
local success = cfxArtilleryDemon.processCommandMethod(commands, theEvent)
|
||||
|
||||
-- remove this mark after successful execution
|
||||
if success then
|
||||
trigger.action.removeMark(theEvent.idx)
|
||||
cfxArtilleryDemon.outMessage("executed command <" .. commandString .. "> from unit" .. theEvent.initiator:getName(), args)
|
||||
else
|
||||
-- we could play some error sound
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if theEvent.id == world.event.S_EVENT_MARK_REMOVED then
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- add / remove commands to/from cfxArtillerys vocabulary
|
||||
--
|
||||
function cfxArtilleryDemon.addCommndProcessor(command, processor)
|
||||
cfxArtilleryDemon.commandTable[command:upper()] = processor
|
||||
end
|
||||
|
||||
function cfxArtilleryDemon.removeCommandProcessor(command)
|
||||
cfxArtilleryDemon.commandTable[command:upper()] = nil
|
||||
end
|
||||
|
||||
--
|
||||
-- process input arguments. Here we simply move them
|
||||
-- up by one.
|
||||
--
|
||||
function cfxArtilleryDemon.getArgs(theCommands)
|
||||
local args = {}
|
||||
for i=2, #theCommands do
|
||||
table.insert(args, theCommands[i])
|
||||
end
|
||||
return args
|
||||
end
|
||||
|
||||
--
|
||||
-- stage demon's main command interpreter.
|
||||
-- magic lies in using the keywords as keys into a
|
||||
-- function table that holds all processing functions
|
||||
-- I wish we had that back in the Oberon days.
|
||||
--
|
||||
function cfxArtilleryDemon.executeCommand(theCommands, event)
|
||||
-- trigger.action.outText("executor: *" .. theCommands[1] .. "*", 30)
|
||||
-- see if theCommands[1] exists in the command table
|
||||
local cmd = theCommands[1]
|
||||
local arguments = cfxArtilleryDemon.getArgs(theCommands)
|
||||
if not cmd then return false end
|
||||
|
||||
-- use the command as index into the table of functions
|
||||
-- that handle them.
|
||||
if cfxArtilleryDemon.commandTable[cmd:upper()] then
|
||||
local theInvoker = cfxArtilleryDemon.commandTable[cmd:upper()]
|
||||
local success = theInvoker(arguments, event)
|
||||
return success
|
||||
else
|
||||
trigger.action.outText("***error: unknown command <".. cmd .. ">", 30)
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--
|
||||
-- SMOKE COMMAND
|
||||
--
|
||||
|
||||
-- known commands and their processors
|
||||
function cfxArtilleryDemon.smokeColor2Index (theColor)
|
||||
local color = theColor:lower()
|
||||
if color == "red" then return 1 end
|
||||
if color == "white" then return 2 end
|
||||
if color == "orange" then return 3 end
|
||||
if color == "blue" then return 4 end
|
||||
return 0
|
||||
end
|
||||
|
||||
-- this is the command processing template for your own commands
|
||||
-- when you add a command processor via addCommndProcessor()
|
||||
-- smoke command syntax: '-smoke <color>' with optional color, color being red, green, blue, white or orange
|
||||
function cfxArtilleryDemon.processSmokeCommand(args, event)
|
||||
if not args[1] then args[1] = "red" end -- default to red color
|
||||
local thePoint = event.pos
|
||||
thePoint.y = land.getHeight({x = thePoint.x, y = thePoint.z}) +3 -- elevate to ground height
|
||||
trigger.action.smoke(thePoint, cfxArtilleryDemon.smokeColor2Index(args[1]))
|
||||
return true
|
||||
end
|
||||
|
||||
--
|
||||
-- BOOM command
|
||||
--
|
||||
function cfxArtilleryDemon.doBoom(args)
|
||||
--trigger.action.outText("sim shell str=" .. args.strength .. " x=" .. args.point.x .. " z = " .. args.point.z .. " Tdelta = " .. args.tDelta, 30)
|
||||
-- trigger.action.smoke(args.point, 2)
|
||||
trigger.action.explosion(args.point, args.strength)
|
||||
|
||||
end
|
||||
|
||||
function cfxArtilleryDemon.processBoomCommand(args, event)
|
||||
if not args[1] then args[1] = "750" end -- default to 750 strength
|
||||
local transitionTime = 20 -- seconds until shells hit
|
||||
local shellNum = 17
|
||||
local shellBaseStrength = 500
|
||||
local shellvariance = 0.2 -- 10%
|
||||
local center = event.pos -- center of where shells hit
|
||||
center.y = land.getHeight({x = center.x, y = center.z}) + 3
|
||||
-- we now can 'dirty' the position by something. not yet
|
||||
for i=1, shellNum do
|
||||
local thePoint = dcsCommon.randomPointInCircle(100, 0, center.x, center.z)
|
||||
local boomArgs = {}
|
||||
local strVar = shellBaseStrength * shellvariance
|
||||
strVar = strVar * (2 * dcsCommon.randomPercent() - 1.0) -- go from -1 to 1
|
||||
|
||||
boomArgs.strength = shellBaseStrength + strVar
|
||||
thePoint.y = land.getHeight({x = thePoint.x, y = thePoint.z}) + 1 -- elevate to ground height + 1
|
||||
boomArgs.point = thePoint
|
||||
local timeVar = 5 * (2 * dcsCommon.randomPercent() - 1.0) -- +/- 1.5 seconds
|
||||
boomArgs.tDelta = timeVar
|
||||
timer.scheduleFunction(cfxArtilleryDemon.doBoom, boomArgs, timer.getTime() + transitionTime + timeVar)
|
||||
end
|
||||
trigger.action.outText("Fire command confirmed. Artillery is firing at your designated co-ordinates.", 30)
|
||||
trigger.action.smoke(center, 2) -- mark location visually
|
||||
return true
|
||||
end
|
||||
|
||||
--
|
||||
-- cfxArtilleryZones interface
|
||||
--
|
||||
|
||||
function cfxArtilleryDemon.processTargetCommand(args, event)
|
||||
-- get position
|
||||
local center = event.pos -- center of where shells hit
|
||||
center.y = land.getHeight({x = center.x, y = center.z})
|
||||
|
||||
if not event.initiator then
|
||||
trigger.action.outText("Target entry aborted: no initiator.", 30)
|
||||
return true
|
||||
end
|
||||
local theUnit = event.initiator
|
||||
local theGroup = theUnit:getGroup()
|
||||
local coalition = theGroup:getCoalition()
|
||||
local spotRange = 3000
|
||||
local autoAdd = true
|
||||
local params = ""
|
||||
|
||||
for idx, param in pairs(args) do
|
||||
if params == "" then params = ": "
|
||||
else params = params .. " "
|
||||
end
|
||||
params = params .. param
|
||||
end
|
||||
|
||||
local name = "TgtData".. params .. " (" .. theUnit:getName() .. ")@T+" .. math.floor(timer.getTime())
|
||||
-- feed into arty zones
|
||||
cfxArtilleryZones.createArtilleryZone(name, center, coalition, spotRange, 500, autoAdd) -- 500 is base strength
|
||||
|
||||
trigger.action.outTextForCoalition(coalition, "New ARTY coordinates received from " .. theUnit:getName() .. ", standing by", 30)
|
||||
return true
|
||||
end
|
||||
|
||||
--
|
||||
-- cfxArtillery init and start
|
||||
--
|
||||
|
||||
function cfxArtilleryDemon.init()
|
||||
cfxArtilleryDemon.unitFilterMethod = cfxArtilleryDemon.authorizeAllUnits
|
||||
cfxArtilleryDemon.processCommandMethod = cfxArtilleryDemon.executeCommand
|
||||
|
||||
-- now add known commands to interpreter. Add your own commands the same way
|
||||
cfxArtilleryDemon.addCommndProcessor("smoke", cfxArtilleryDemon.processSmokeCommand)
|
||||
|
||||
cfxArtilleryDemon.addCommndProcessor("bumm", cfxArtilleryDemon.processBoomCommand)
|
||||
|
||||
cfxArtilleryDemon.addCommndProcessor("tgt",
|
||||
cfxArtilleryDemon.processTargetCommand)
|
||||
|
||||
-- you can add and remove command the same way
|
||||
trigger.action.outText("cf/x cfx Artillery Demon v" .. cfxArtilleryDemon.version .. " loaded", 30)
|
||||
end
|
||||
|
||||
function cfxArtilleryDemon.start()
|
||||
cfxArtilleryDemon.demonID = world.addEventHandler(cfxArtilleryDemon)
|
||||
trigger.action.outText("cf/x cfxArtilleryDemon v" .. cfxArtilleryDemon.version .. " started", 30)
|
||||
end
|
||||
|
||||
cfxArtilleryDemon.init()
|
||||
if cfxArtilleryDemon.autostart then
|
||||
cfxArtilleryDemon.start()
|
||||
end
|
||||
@@ -1,5 +1,5 @@
|
||||
cfxMX = {}
|
||||
cfxMX.version = "1.2.6"
|
||||
cfxMX.version = "2.0.0"
|
||||
cfxMX.verbose = false
|
||||
--[[--
|
||||
Mission data decoder. Access to ME-built mission structures
|
||||
@@ -7,26 +7,11 @@ cfxMX.verbose = false
|
||||
Copyright (c) 2022, 2023 by Christian Franz and cf/x AG
|
||||
|
||||
Version History
|
||||
1.0.0 - initial version
|
||||
1.0.1 - getStaticFromDCSbyName()
|
||||
1.1.0 - getStaticFromDCSbyName also copies groupID when not fetching orig
|
||||
- on start up collects a cross reference table of all
|
||||
original group id
|
||||
- add linkUnit for statics
|
||||
1.2.0 - added group name reference table
|
||||
- added group type reference
|
||||
- added references for allFixed, allHelo, allGround, allSea, allStatic
|
||||
1.2.1 - added countryByName
|
||||
- added linkByName
|
||||
1.2.2 - fixed ctry bug in countryByName
|
||||
- playerGroupByName
|
||||
- playerUnitByName
|
||||
1.2.3 - groupTypeByName
|
||||
- groupCoalitionByName
|
||||
1.2.4 - playerUnit2Group cross index
|
||||
1.2.5 - unitIDbyName index added
|
||||
1.2.6 - cfxMX.allTrainsByName
|
||||
- train carve-outs for vehicles
|
||||
2.0.0 - clean-up
|
||||
- harmonized with cfxGroups
|
||||
|
||||
--]]--
|
||||
cfxMX.groupNamesByID = {}
|
||||
cfxMX.groupIDbyName = {}
|
||||
@@ -47,13 +32,21 @@ cfxMX.playerGroupByName = {} -- returns data only if a player is in group
|
||||
cfxMX.playerUnitByName = {} -- returns data only if this is a player unit
|
||||
cfxMX.playerUnit2Group = {} -- returns a group data for player units.
|
||||
|
||||
cfxMX.groups = {} -- all groups indexed b yname, cfxGroups folded into cfxMX
|
||||
--[[-- group objects are
|
||||
{
|
||||
name= "",
|
||||
coalition = "" (red, blue, neutral),
|
||||
coanum = # (0, 1, 2 for neutral, red, blue)
|
||||
category = "" (helicopter, ship, plane, vehicle, static),
|
||||
hasPlayer = true/false,
|
||||
playerUnits = {} (for each player unit in group: name, point, action)
|
||||
|
||||
}
|
||||
|
||||
--]]--
|
||||
function cfxMX.getGroupFromDCSbyName(aName, fetchOriginal)
|
||||
if not fetchOriginal then fetchOriginal = false end
|
||||
-- fetch the group description for goup named aName (if exists)
|
||||
-- returned structure must be parsed for useful information
|
||||
-- returns data, category, countyID and coalitionID
|
||||
-- unless fetchOriginal is true, creates a deep clone of
|
||||
-- group data structure
|
||||
|
||||
for coa_name_miz, coa_data in pairs(env.mission.coalition) do -- iterate all coalitions
|
||||
local coa_name = coa_name_miz
|
||||
@@ -135,11 +128,6 @@ function cfxMX.getStaticFromDCSbyName(aName, fetchOriginal)
|
||||
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 == "static"
|
||||
-- obj_type_name == "helicopter" or
|
||||
-- obj_type_name == "ship" or
|
||||
-- obj_type_name == "plane" or
|
||||
-- obj_type_name == "vehicle" or
|
||||
-- obj_type_name == "static"
|
||||
then -- (only look at statics)
|
||||
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 static in group!
|
||||
@@ -149,7 +137,7 @@ function cfxMX.getStaticFromDCSbyName(aName, fetchOriginal)
|
||||
if group_data and group_data.route and group_data.route and group_data.route.points[1] then
|
||||
linkUnit = group_data.route.points[1].linkUnit
|
||||
if linkUnit then
|
||||
--trigger.action.outText("MX: found missing link to " .. linkUnit .. " in " .. group_data.name, 30)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -207,7 +195,7 @@ function cfxMX.createCrossReferences()
|
||||
obj_type_name == "plane" or
|
||||
obj_type_name == "vehicle" or
|
||||
obj_type_name == "static" -- what about "cargo"?
|
||||
-- not that trains appear as 'vehicle'
|
||||
-- note that trains appear as 'vehicle'
|
||||
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!
|
||||
@@ -252,6 +240,12 @@ function cfxMX.createCrossReferences()
|
||||
end
|
||||
-- now iterate all units in this group
|
||||
-- for unit xref like player info and ID
|
||||
local hasPlayer = false
|
||||
local playerUnits = {}
|
||||
local groupName = group_data.name
|
||||
if env.mission.version > 7 then -- translate raw to actual
|
||||
groupName = env.getValueDictByKey(groupName)
|
||||
end
|
||||
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
|
||||
@@ -259,10 +253,38 @@ function cfxMX.createCrossReferences()
|
||||
cfxMX.playerUnitByName[unit_data.name] = unit_data
|
||||
cfxMX.playerGroupByName[aName] = group_data -- inefficient, but works
|
||||
cfxMX.playerUnit2Group[unit_data.name] = group_data
|
||||
|
||||
hasPlayer = true
|
||||
local playerData = {}
|
||||
playerData.name = unit_data.name
|
||||
playerData.point = {}
|
||||
playerData.point.x = unit_data.x
|
||||
playerData.point.y = 0
|
||||
playerData.point.z = unit_data.y
|
||||
playerData.action = "none" -- default
|
||||
|
||||
-- access initial waypoint data by 'reaching up'
|
||||
-- into group data and extract route.points[1]
|
||||
if group_data.route and group_data.route.points and (#group_data.route.points > 0) then
|
||||
playerData.action = group_data.route.points[1].action
|
||||
end
|
||||
table.insert(playerUnits, playerData)
|
||||
|
||||
end -- if unit skill client
|
||||
end -- if has skill
|
||||
cfxMX.unitIDbyName[unit_data.name] = unit_data.unitId
|
||||
end -- for all units
|
||||
|
||||
local entry = {}
|
||||
entry.name = groupName
|
||||
entry.coalition = coa_name
|
||||
entry.coaNum = coaNum
|
||||
entry.category = category
|
||||
entry.hasPlayer = hasPlayer
|
||||
entry.playerUnits = playerUnits
|
||||
-- add to db
|
||||
cfxMX.groups[groupName] = entry
|
||||
|
||||
end -- for all groups
|
||||
end --if has category data
|
||||
end --if plane, helo etc... category
|
||||
@@ -274,6 +296,31 @@ function cfxMX.createCrossReferences()
|
||||
end --for all coalitions in mission
|
||||
end
|
||||
|
||||
|
||||
-- return all groups that can have players in them
|
||||
-- includes groups that currently are not or not anymore alive
|
||||
function cfxMX.getPlayerGroup()
|
||||
local playerGroups = {}
|
||||
for gName, gData in pairs (cfxMX.groups) do
|
||||
if gData.hasPlayer then
|
||||
table.insert(playerGroups, gData)
|
||||
end
|
||||
end
|
||||
return playerGroups
|
||||
end
|
||||
|
||||
-- return all group names that can have players in them
|
||||
-- includes groups that currently are not or not anymore alive
|
||||
function cfxMX.getPlayerGroupNames()
|
||||
local playerGroups = {}
|
||||
for gName, gData in pairs (cfxMX.groups) do
|
||||
if gData.hasPlayer then
|
||||
table.insert(playerGroups, gName)
|
||||
end
|
||||
end
|
||||
return playerGroups
|
||||
end
|
||||
|
||||
function cfxMX.catText2ID(inText)
|
||||
local outCat = 0 -- airplane
|
||||
local c = inText:lower()
|
||||
@@ -283,7 +330,7 @@ function cfxMX.catText2ID(inText)
|
||||
if c == "vehicle" then outCat = 2 end
|
||||
if c == "train" then outCat = 4 end
|
||||
if c == "static" then outCat = -1 end
|
||||
--trigger.action.outText("cat2text: in <" .. inText .. "> out <" .. outCat .. ">", 30)
|
||||
|
||||
return outCat
|
||||
end
|
||||
|
||||
@@ -292,6 +339,7 @@ function cfxMX.start()
|
||||
if cfxMX.verbose then
|
||||
trigger.action.outText("cfxMX: "..#cfxMX.groupNamesByID .. " groups processed successfully", 30)
|
||||
end
|
||||
trigger.action.outText("cfxMX v." .. cfxMX.version .. " started.", 30)
|
||||
end
|
||||
|
||||
-- start
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
cfxZones = {}
|
||||
cfxZones.version = "4.0.10"
|
||||
cfxZones.version = "4.1.1"
|
||||
|
||||
-- cf/x zone management module
|
||||
-- reads dcs zones and makes them accessible and mutable
|
||||
@@ -9,31 +9,6 @@ cfxZones.version = "4.0.10"
|
||||
--
|
||||
|
||||
--[[-- VERSION HISTORY
|
||||
- 3.0.0 - support for DCS 2.8 linkUnit attribute, integration with
|
||||
linedUnit and warning.
|
||||
- initZoneVerbosity()
|
||||
- 3.0.1 - updateMovingZones() better tracks linked units by name
|
||||
- 3.0.2 - maxRadius for all zones, only differs from radius in polyZones
|
||||
- re-factoring zone-base string processing from messenger module
|
||||
- new processStringWildcards() that does almost all that messenger can
|
||||
- 3.0.3 - new getLinkedUnit()
|
||||
- 3.0.4 - new createRandomPointOnZoneBoundary()
|
||||
- 3.0.5 - getPositiveRangeFromZoneProperty() now also supports upper bound (optional)
|
||||
- 3.0.6 - new createSimplePolyZone()
|
||||
- new createSimpleQuadZone()
|
||||
- 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.9 - new getFlareColorStringFromZoneProperty()
|
||||
- 3.1.0 - new getRGBVectorFromZoneProperty()
|
||||
new getRGBAVectorFromZoneProperty()
|
||||
- 3.1.1 - getRGBAVectorFromZoneProperty now supports #RRGGBBAA and #RRGGBB format
|
||||
- owner for all, default 0
|
||||
- 3.1.2 - getAllZoneProperties has numbersOnly option
|
||||
- 3.1.3 - new numberArrayFromString()
|
||||
- new declutterZone()
|
||||
- new getZoneVolume()
|
||||
- offsetZone also updates zone bounds when moving zones
|
||||
- corrected bug in calculateZoneBounds()
|
||||
- 4.0.0 - dmlZone OOP API started
|
||||
- code revision / refactoring
|
||||
- moved createPoint and copxPoint to dcsCommon, added bridging code
|
||||
@@ -66,6 +41,9 @@ cfxZones.version = "4.0.10"
|
||||
- createPolyZone now correctly inits dcsOrigin
|
||||
- createCircleZone noew correctly inits dcsOrigin
|
||||
- 4.0.10 - getBoolFromZoneProperty also supports "on" (=true) and "off" (=false)
|
||||
- 4.1.0 - getBoolFromZoneProperty 'on/off' support for dml variant as well
|
||||
- 4.1.1 - evalRemainder() updates
|
||||
|
||||
--]]--
|
||||
|
||||
--
|
||||
@@ -99,40 +77,6 @@ cfxZones.ups = 1 -- updates per second. updates moving zones
|
||||
cfxZones.zones = {} -- these are the zone as retrieved from the mission.
|
||||
-- ALWAYS USE THESE, NEVER DCS's ZONES!!!!
|
||||
|
||||
-- a zone has the following attributes
|
||||
-- x, z -- coordinate of center. note they have correct x, 0, z coordinates so no y-->z mapping
|
||||
-- radius (zero if quad zone)
|
||||
-- isCircle (true if quad zone)
|
||||
-- poly the quad coords are in the poly attribute and are a
|
||||
-- 1..n, wound counter-clockwise as (currently) in DCS:
|
||||
-- lower left, lower right upper left, upper right, all coords are x, 0, z
|
||||
-- bounds - contain the AABB coords for the zone: ul (upper left), ur, ll (lower left), lr
|
||||
-- for both circle and poly, all (x, 0, z)
|
||||
|
||||
-- zones can carry information in their names that can get processed into attributes
|
||||
-- use
|
||||
-- zones can also carry information in their 'properties' tag that ME allows to
|
||||
-- edit. cfxZones provides an easy method to access these properties
|
||||
-- - getZoneProperty (returns as string)
|
||||
-- - getMinMaxFromZoneProperty
|
||||
-- - getBoolFromZoneProperty
|
||||
-- - getNumberFromZoneProperty
|
||||
|
||||
|
||||
-- SUPPORTED PROPERTIES
|
||||
-- - "linkedUnit" - zone moves with unit of that name. must be exact match
|
||||
-- can be combined with other attributes that extend (e.g. scar manager and
|
||||
-- limited pilots/airframes
|
||||
--
|
||||
|
||||
--
|
||||
-- readZonesFromDCS is executed exactly once at the beginning
|
||||
-- from then on, use only the cfxZones.zones table
|
||||
-- WARNING: cfxZones is NOT case-sensitive. All zone names are
|
||||
-- indexed by upper case. If you have two zones with same name but
|
||||
-- different case, one will be replaced
|
||||
--
|
||||
|
||||
function cfxZones.readFromDCS(clearfirst)
|
||||
if (clearfirst) then
|
||||
cfxZones.zones = {}
|
||||
@@ -617,9 +561,7 @@ function cfxZones.createRandomZoneInZone(name, inZone, targetRadius, entirelyIns
|
||||
-- if entirelyInside is false, only the zone's center is guaranteed to be inside
|
||||
-- inZone.
|
||||
-- entirelyInside is not guaranteed for polyzones
|
||||
|
||||
-- trigger.action.outText("Zones: creating rZiZ with tr = " .. targetRadius .. " for " .. inZone.name .. " that as r = " .. inZone.radius, 10)
|
||||
|
||||
|
||||
if inZone.isCircle then
|
||||
local sourceRadius = inZone.radius
|
||||
if entirelyInside and targetRadius > sourceRadius then targetRadius = sourceRadius end
|
||||
@@ -1364,11 +1306,11 @@ end
|
||||
|
||||
|
||||
-- creating units in a zone
|
||||
function cfxZones.createGroundUnitsInZoneForCoalition (theCoalition, groupName, theZone, theUnits, formation, heading)
|
||||
function cfxZones.createGroundUnitsInZoneForCoalition (theCoalition, groupName, theZone, theUnits, formation, heading, liveries)
|
||||
-- theUnits can be string or table of string
|
||||
if not groupName then groupName = "G_"..theZone.name end
|
||||
-- group name will be taken from zone name and prependend with "G_"
|
||||
local theGroup = dcsCommon.createGroundGroupWithUnits(groupName, theUnits, theZone.radius, nil, formation)
|
||||
local theGroup = dcsCommon.createGroundGroupWithUnits(groupName, theUnits, theZone.radius, nil, formation, nil, liveries)
|
||||
|
||||
-- turn the entire formation to heading
|
||||
if (not heading) then heading = 0 end
|
||||
@@ -1380,7 +1322,6 @@ function cfxZones.createGroundUnitsInZoneForCoalition (theCoalition, groupName,
|
||||
theZone.point.x,
|
||||
theZone.point.z) -- watchit: Z!!!
|
||||
|
||||
|
||||
-- create the group in the world and return it
|
||||
-- first we need to translate the coalition to a legal
|
||||
-- country. we use UN for neutral, cjtf for red and blue
|
||||
@@ -1446,7 +1387,7 @@ function cfxZones.unPulseFlag(args)
|
||||
cfxZones.setFlagValue(theFlag, newVal, theZone)
|
||||
end
|
||||
|
||||
function cfxZones.evalRemainder(remainder)
|
||||
function cfxZones.evalRemainder(remainder, theZone)
|
||||
local rNum = tonumber(remainder)
|
||||
if not rNum then
|
||||
-- we use remainder as name for flag
|
||||
@@ -1475,6 +1416,10 @@ function cfxZones.evalRemainder(remainder)
|
||||
return rNum
|
||||
end
|
||||
|
||||
function dmlZone:evalRemainder(remainder)
|
||||
return cfxZones.evalRemainder(remainder, self)
|
||||
end
|
||||
|
||||
function cfxZones.doPollFlag(theFlag, method, theZone) -- no OOP equivalent
|
||||
-- WARNING:
|
||||
-- if method is a number string, it will be interpreted as follows:
|
||||
@@ -2381,7 +2326,7 @@ function cfxZones.getBoolFromZoneProperty(theZone, theProperty, defaultVal)
|
||||
if defaultVal == false then
|
||||
-- only go true if exact match to yes or true
|
||||
theBool = false
|
||||
theBool = (p == 'true') or (p == 'yes') or (p == "1") or (p == "on")
|
||||
theBool = (p == 'true') or (p == 'yes') or (p == "1") or (p == 'on')
|
||||
return theBool
|
||||
end
|
||||
|
||||
@@ -2407,13 +2352,13 @@ function dmlZone:getBoolFromZoneProperty(theProperty, defaultVal)
|
||||
if defaultVal == false then
|
||||
-- only go true if exact match to yes or true
|
||||
theBool = false
|
||||
theBool = (p == 'true') or (p == 'yes') or p == "1"
|
||||
theBool = (p == 'true') or (p == 'yes') or (p == "1") or (p=="on")
|
||||
return theBool
|
||||
end
|
||||
|
||||
local theBool = true
|
||||
-- only go false if exactly no or false or "0"
|
||||
theBool = (p ~= 'false') and (p ~= 'no') and (p ~= "0")
|
||||
theBool = (p ~= 'false') and (p ~= 'no') and (p ~= "0") and (p ~= "off")
|
||||
return theBool
|
||||
end
|
||||
|
||||
@@ -3576,8 +3521,6 @@ function cfxZones.init()
|
||||
|
||||
-- pre-read zone owner for all zones
|
||||
-- much like verbose, all zones have owner
|
||||
-- local pZones = cfxZones.zonesWithProperty("owner")
|
||||
-- for n, aZone in pairs(pZones) do
|
||||
for n, aZone in pairs(cfxZones.zones) do
|
||||
aZone.owner = cfxZones.getCoalitionFromZoneProperty(aZone, "owner", 0)
|
||||
end
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
cfxmon = {}
|
||||
cfxmon.version = "1.0.1"
|
||||
cfxmon.delay = 30 -- seconds for display
|
||||
--[[--
|
||||
Version History
|
||||
1.0.0 - initial version
|
||||
1.0.1 - better guard for even.initiator to check if unit exists
|
||||
- and handle static objcects and other non-grouped objects
|
||||
|
||||
cfxmon is a monitor for all cfx events and callbacks
|
||||
use monConfig to tell cfxmon which events and callbacks
|
||||
to monitor. a Property with "no" or "false" will turn
|
||||
that monitor OFF, else it will stay on
|
||||
|
||||
supported modules if loaded
|
||||
dcsCommon
|
||||
cfxPlayer
|
||||
cfxGroundTroops
|
||||
cfxObjectDestructDetector
|
||||
cfxSpawnZones
|
||||
|
||||
--]]--
|
||||
|
||||
--
|
||||
-- CALLBACKS
|
||||
--
|
||||
-- dcsCommon Callbacks
|
||||
function cfxmon.pre(event)
|
||||
trigger.action.outText("***mon - dcsPre: " .. event.id .. " (" .. dcsCommon.event2text(event.id) .. ")", cfxmon.delay)
|
||||
return true
|
||||
end
|
||||
|
||||
function cfxmon.post(event)
|
||||
trigger.action.outText("***mon - dcsPost: " .. event.id .. " (" .. dcsCommon.event2text(event.id) .. ")", cfxmon.delay)
|
||||
end
|
||||
|
||||
function cfxmon.rejected(event)
|
||||
trigger.action.outText("***mon - dcsReject: " .. event.id .. " (" .. dcsCommon.event2text(event.id) .. ")", cfxmon.delay)
|
||||
end
|
||||
|
||||
function cfxmon.dcsCB(event) -- callback
|
||||
local initiatorStat = ""
|
||||
if event.initiator and Unit.isExist(event.initiator) then
|
||||
local theUnit = event.initiator
|
||||
-- we assume it is unit, but it can be a static object,
|
||||
-- and may not have getGroup implemented!
|
||||
if theUnit.getGroup then
|
||||
local theGroup = theUnit:getGroup()
|
||||
|
||||
local theGroupName = "<none>"
|
||||
if theGroup then theGroupName = theGroup:getName() end
|
||||
initiatorStat = ", for " .. theUnit:getName()
|
||||
initiatorStat = initiatorStat .. " of " .. theGroupName
|
||||
else
|
||||
initiatorStat = ", non-unit (static?) " .. theUnit:getName()
|
||||
end
|
||||
else
|
||||
initiatorStat = ", NO Initiator"
|
||||
end
|
||||
trigger.action.outText("***mon - dcsMAIN: " .. event.id .. " (" .. dcsCommon.event2text(event.id) .. ")" .. initiatorStat, cfxmon.delay)
|
||||
end
|
||||
|
||||
-- cfxPlayer callback
|
||||
function cfxmon.playerEventCB(evType, description, info, data)
|
||||
trigger.action.outText("***mon - cfxPlayer: ".. evType ..": <" .. description .. ">", cfxmon.delay)
|
||||
end
|
||||
|
||||
-- cfxGroundTroops callback
|
||||
function cfxmon.groundTroopsCB(reason, theGroup, orders, data)
|
||||
trigger.action.outText("***mon - groundTroops: ".. reason ..": for group <" .. theGroup:getName() .. "> with orders " .. orders, cfxmon.delay)
|
||||
end
|
||||
|
||||
-- object destruct callbacks
|
||||
function cfxmon.oDestructCB(zone, ObjectID, name)
|
||||
trigger.action.outText("***mon - object destroyed: ".. ObjectID .." named <" .. name .. "> in zone " .. zone.name, cfxmon.delay)
|
||||
end
|
||||
|
||||
-- spawner callback
|
||||
function cfxmon.spawnZoneCB(reason, theGroup, theSpawner)
|
||||
local gName = "<nil>"
|
||||
if theGroup then gName = theGroup:getName() end
|
||||
trigger.action.outText("***mon - Spawner: ".. reason .." group <" .. gName .. "> in zone " .. theSpawner.name, cfxmon.delay)
|
||||
end
|
||||
|
||||
-- READ CONFIG AND SUBSCRIBE
|
||||
function cfxmon.start ()
|
||||
local theZone = cfxZones.getZoneByName("monConfig")
|
||||
if not theZone then
|
||||
trigger.action.outText("***mon: WARNING: NO config, defaulting", cfxmon.delay)
|
||||
theZone = cfxZones.createSimpleZone("MONCONFIG")
|
||||
end
|
||||
|
||||
-- own config
|
||||
cfxmon.delay = cfxZones.getNumberFromZoneProperty(theZone, "delay", 30)
|
||||
trigger.action.outText("!!!mon: Delay is set to: " .. cfxmon.delay .. "seconds", 50)
|
||||
|
||||
-- dcsCommon
|
||||
if cfxZones.getBoolFromZoneProperty(theZone, "dcsCommon", true) then
|
||||
-- subscribe to dcs event handlers
|
||||
-- note we have all, but only connect the main
|
||||
dcsCommon.addEventHandler(cfxmon.dcsCB) -- we only connect one
|
||||
trigger.action.outText("!!!mon: +dcsCommon", cfxmon.delay)
|
||||
else
|
||||
trigger.action.outText("***mon: -dcsCommon", cfxmon.delay)
|
||||
end
|
||||
|
||||
-- cfxPlayer
|
||||
if cfxPlayer and cfxZones.getBoolFromZoneProperty(theZone, "cfxPlayer", true) then
|
||||
cfxPlayer.addMonitor(cfxmon.playerEventCB)
|
||||
trigger.action.outText("!!!mon: +cfxPlayer", cfxmon.delay)
|
||||
else
|
||||
trigger.action.outText("***mon: -cfxPlayer", cfxmon.delay)
|
||||
end
|
||||
|
||||
-- cfxGroundTroops
|
||||
if cfxGroundTroops and cfxZones.getBoolFromZoneProperty(theZone, "cfxGroundTroops", true) then
|
||||
cfxGroundTroops.addTroopsCallback(cfxmon.groundTroopsCB)
|
||||
trigger.action.outText("!!!mon: +cfxGroundTroops", cfxmon.delay)
|
||||
else
|
||||
trigger.action.outText("***mon: -cfxGroundTroops", cfxmon.delay)
|
||||
end
|
||||
|
||||
-- objectDestructZones
|
||||
if cfxObjectDestructDetector and cfxZones.getBoolFromZoneProperty(theZone, "cfxObjectDestructDetector", true) then
|
||||
cfxObjectDestructDetector.addCallback(cfxmon.oDestructCB)
|
||||
trigger.action.outText("!!!mon: +cfxObjectDestructDetector", cfxmon.delay)
|
||||
else
|
||||
trigger.action.outText("***mon: -cfxObjectDestructDetector", cfxmon.delay)
|
||||
end
|
||||
|
||||
-- spawnZones
|
||||
if cfxSpawnZones and cfxZones.getBoolFromZoneProperty(theZone, "cfxSpawnZones", true) then
|
||||
cfxSpawnZones.addCallback(cfxmon.spawnZoneCB)
|
||||
trigger.action.outText("!!!mon: +cfxSpawnZones", cfxmon.delay)
|
||||
else
|
||||
trigger.action.outText("***mon: -cfxSpawnZones", cfxmon.delay)
|
||||
end
|
||||
end
|
||||
|
||||
cfxmon.start()
|
||||
@@ -1,5 +1,5 @@
|
||||
cloneZones = {}
|
||||
cloneZones.version = "1.9.1"
|
||||
cloneZones.version = "2.0.1"
|
||||
cloneZones.verbose = false
|
||||
cloneZones.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
@@ -27,79 +27,9 @@ cloneZones.respawnOnGroupID = true
|
||||
|
||||
--[[--
|
||||
Clones Groups from ME mission data
|
||||
Copyright (c) 2022 by Christian Franz and cf/x AG
|
||||
Copyright (c) 2022-2024 by Christian Franz and cf/x AG
|
||||
|
||||
Version History
|
||||
1.0.0 - initial version
|
||||
1.0.1 - preWipe attribute
|
||||
1.1.0 - support for static objects
|
||||
- despawn? attribute
|
||||
1.1.1 - despawnAll: isExist guard
|
||||
- map in? to f?
|
||||
1.2.0 - Lua API integration: callbacks
|
||||
- groupXlate struct
|
||||
- unitXlate struct
|
||||
- resolveReferences
|
||||
- getGroupsInZone rewritten for data
|
||||
- static resolve
|
||||
- linkUnit resolve
|
||||
- clone? synonym
|
||||
- empty! and method attributes
|
||||
1.3.0 - DML flag upgrade
|
||||
1.3.1 - groupTracker interface
|
||||
- trackWith: attribute
|
||||
1.4.0 - Watchflags
|
||||
1.4.1 - trackWith: accepts list of trackers
|
||||
1.4.2 - onstart delays for 0.1 s to prevent static stacking
|
||||
- turn bug for statics (bug in dcsCommon, resolved)
|
||||
1.4.3 - embark/disembark now works with cloners
|
||||
1.4.4 - removed some debugging verbosity
|
||||
1.4.5 - randomizeLoc, rndLoc keyword
|
||||
- cargo manager integration - pass cargo objects when present
|
||||
1.4.6 - removed some verbosity for spawned aircraft with airfields on their routes
|
||||
1.4.7 - DML watchflag and DML Flag polish, method-->cloneMethod
|
||||
1.4.8 - added 'wipe?' synonym
|
||||
1.4.9 - onRoad option
|
||||
- rndHeading option
|
||||
1.5.0 - persistence
|
||||
1.5.1 - fixed static data cloning bug (load & save)
|
||||
1.5.2 - fixed bug in trackWith: referencing wrong cloner
|
||||
1.5.3 - centerOnly/wholeGroups attribute for rndLoc, rndHeading and onRoad
|
||||
1.5.4 - parking for aircraft processing when cloning from template
|
||||
1.5.5 - removed some verbosity
|
||||
1.6.0 - fixed issues with cloning for zones with linked units
|
||||
- cloning with useHeading
|
||||
- major declutter
|
||||
1.6.1 - removed some verbosity when not rotating routes
|
||||
- updateTaskLocations ()
|
||||
- cloning groups now also adjusts tasks like search and engage in zone
|
||||
- cloning with rndLoc supports polygons
|
||||
- corrected rndLoc without centerOnly to not include individual offsets
|
||||
- ensure support of recovery tanker resolve cloned group
|
||||
1.6.2 - optimization to hasLiveUnits()
|
||||
1.6.3 - removed verbosity bug with rndLoc
|
||||
uniqueNameGroupData has provisions for naming scheme
|
||||
new uniqueNameStaticData() for naming scheme
|
||||
1.6.4 - uniqueCounter is now configurable via config zone
|
||||
new lclUniqueCounter config, also added to persistence
|
||||
new globalCount
|
||||
1.7.0 - wildcard "*" for masterOwner
|
||||
- identical attribute: makes identical ID and name for unit and group as template
|
||||
- new sameIDUnitData()
|
||||
- nameScheme attribute: allow parametric names
|
||||
- <o>, <z>, <s> wildcards
|
||||
- <uid>, <lcl>, <i>, <g> wildcards
|
||||
- identical=true overrides nameScheme
|
||||
- masterOwner "*" convenience shortcut
|
||||
1.7.1 - useDelicates handOff for delicates
|
||||
- forcedRespawn passes zone instead of verbose
|
||||
1.7.2 - onPerimeter attribute
|
||||
1.7.3 - declutter option
|
||||
1.8.0 - OOP cfxZones
|
||||
- removed "empty+1" as planned
|
||||
- upgraded config zone parsing
|
||||
1.8.1 - clone zone definition now supports quads
|
||||
1.8.2 - on pre-wipe, delay respawn by 0.5s to avoid 'dropping' statics
|
||||
1.9.0 - minor clean-up for synonyms
|
||||
- spawnWithSpawner alias for HeloTroops etc requestable SPAWN
|
||||
- requestable attribute
|
||||
@@ -107,6 +37,9 @@ cloneZones.respawnOnGroupID = true
|
||||
- cloner collects all types used
|
||||
- groupScheme attribute
|
||||
1.9.1 - useAI attribute
|
||||
2.0.0 - clean-up
|
||||
2.0.1 - improved empty! logic to account for deferred spawn
|
||||
when pre-wipe is active
|
||||
--]]--
|
||||
|
||||
--
|
||||
@@ -136,16 +69,6 @@ function cloneZones.addCallback(theCallback)
|
||||
table.insert(cloneZones.callbacks, theCallback)
|
||||
end
|
||||
|
||||
-- reasons for callback
|
||||
-- "will despawn group" - args is the group about to be despawned
|
||||
-- "did spawn group" -- args is group that was spawned
|
||||
-- "will despawn static"
|
||||
-- "did spawn static"
|
||||
-- "spawned" -- completed spawn cycle. args contains .groups and .statics spawned
|
||||
-- "empty" -- all spawns have been killed, args is empty
|
||||
-- "wiped" -- preWipe executed
|
||||
-- "<none" -- something went wrong
|
||||
|
||||
function cloneZones.invokeCallbacks(theZone, reason, args)
|
||||
if not theZone then return end
|
||||
if not reason then reason = "<none>" end
|
||||
@@ -158,8 +81,6 @@ function cloneZones.invokeCallbacks(theZone, reason, args)
|
||||
end
|
||||
end
|
||||
|
||||
-- group translation orig id
|
||||
|
||||
--
|
||||
-- reading zones
|
||||
--
|
||||
@@ -217,7 +138,6 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
|
||||
theZone.cloner = true -- this is a cloner zoner
|
||||
theZone.mySpawns = {}
|
||||
theZone.myStatics = {}
|
||||
-- update: getPoint is bad if it's a moving zone.
|
||||
-- use getDCSOrigin instead
|
||||
theZone.origin = theZone:getDCSOrigin()
|
||||
|
||||
@@ -247,7 +167,6 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
|
||||
-- iterate all units and save their individual types
|
||||
for idy, aUnit in pairs(rawData.units) do
|
||||
local theType = aUnit.type
|
||||
-- trigger.action.outText("proccing type <" .. theType .. ">", 30)
|
||||
if not theZone.allTypes[theType] then
|
||||
theZone.allTypes[theType] = 1 -- first one
|
||||
else
|
||||
@@ -318,11 +237,8 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
|
||||
|
||||
theZone.cooldown = theZone:getNumberFromZoneProperty("cooldown", -1) -- anything > 0 activates cd
|
||||
theZone.lastSpawnTimeStamp = -10000
|
||||
|
||||
theZone.onStart = theZone:getBoolFromZoneProperty("onStart", false)
|
||||
|
||||
theZone.moveRoute = theZone:getBoolFromZoneProperty("moveRoute", false)
|
||||
|
||||
theZone.preWipe = theZone:getBoolFromZoneProperty("preWipe", false)
|
||||
|
||||
if theZone:hasProperty("empty!") then
|
||||
@@ -383,7 +299,6 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
|
||||
theZone.rndHeading = theZone:getBoolFromZoneProperty("rndHeading", false)
|
||||
|
||||
theZone.onRoad = theZone:getBoolFromZoneProperty("onRoad", false)
|
||||
|
||||
theZone.onPerimeter = theZone:getBoolFromZoneProperty("onPerimeter", false)
|
||||
|
||||
-- check for name scheme and / or identical
|
||||
@@ -418,9 +333,7 @@ function cloneZones.despawnAll(theZone)
|
||||
if cloneZones.verbose or theZone.verbose then
|
||||
trigger.action.outText("+++clnZ: despawn all - wiping zone <" .. theZone.name .. ">", 30)
|
||||
end
|
||||
for idx, aGroup in pairs(theZone.mySpawns) do
|
||||
--trigger.action.outText("++clnZ: despawn all " .. aGroup.name, 30)
|
||||
|
||||
for idx, aGroup in pairs(theZone.mySpawns) do
|
||||
if aGroup:isExist() then
|
||||
if theZone.verbose then
|
||||
trigger.action.outText("+++clnZ: will destroy <" .. aGroup:getName() .. ">", 30)
|
||||
@@ -638,9 +551,6 @@ function cloneZones.nameFromSchema(schema, inName, theZone, sourceName, i)
|
||||
pos = string.find(outName, "<g>")
|
||||
end
|
||||
|
||||
--if theZone.verbose then
|
||||
-- trigger.action.outText("+++cln: schema [" .. schema .. "] for unit <" .. inName .. "> in zone <" .. theZone.name .. "> returns <" .. outName .. ">, iter = " .. iter, 30)
|
||||
--end
|
||||
return outName, iter
|
||||
end
|
||||
|
||||
@@ -866,7 +776,6 @@ function cloneZones.resolveWPReferences(rawData, theZone, dataTable)
|
||||
for grpIdx, gID in pairs(embarkers) do
|
||||
local resolvedID = cloneZones.resolveGroupID(gID, rawData, dataTable, "embark")
|
||||
table.insert(newEmbarkers, resolvedID)
|
||||
--trigger.action.outText("+++clnZ: resolved embark group id <" .. gID .. "> to <" .. resolvedID .. ">", 30)
|
||||
end
|
||||
-- replace old with new table
|
||||
taskData.params.groupsForEmbarking = newEmbarkers
|
||||
@@ -884,7 +793,6 @@ function cloneZones.resolveWPReferences(rawData, theZone, dataTable)
|
||||
-- translate old to new
|
||||
local resolvedID = cloneZones.resolveGroupID(gID, rawData, dataTable, "embark")
|
||||
table.insert(newEmbarkers, resolvedID)
|
||||
--trigger.action.outText("+++clnZ: resolved distribute unit/group id <" .. aUnit .. "/" .. gID .. "> to <".. newUnit .. "/" .. resolvedID .. ">", 30)
|
||||
end
|
||||
-- store this as new group for
|
||||
-- translated transportID
|
||||
@@ -892,7 +800,6 @@ function cloneZones.resolveWPReferences(rawData, theZone, dataTable)
|
||||
end
|
||||
-- replace old distribution with new
|
||||
taskData.params.distribution = newDist
|
||||
--trigger.action.outText("+++clnZ: rebuilt distribution", 30)
|
||||
end
|
||||
|
||||
-- resolve selectedTransport unit reference
|
||||
@@ -900,7 +807,6 @@ function cloneZones.resolveWPReferences(rawData, theZone, dataTable)
|
||||
local tID = taskData.params.selectedTransport
|
||||
local newTID = cloneZones.resolveUnitID(tID, rawData, dataTable, "transportID")
|
||||
taskData.params.selectedTransport = newTID
|
||||
--trigger.action.outText("+++clnZ: resolved selected transport <" .. tID .. "> to <" .. newTID .. ">", 30)
|
||||
end
|
||||
|
||||
-- note: we may need to process x and y as well
|
||||
@@ -932,7 +838,6 @@ function cloneZones.resolveReferences(theZone, dataTable)
|
||||
-- when an action refers to another group, we check if
|
||||
-- the group referred to is also a clone, and update
|
||||
-- the reference to the newest incardnation
|
||||
|
||||
for idx, rawData in pairs(dataTable) do
|
||||
-- resolve references in waypoints
|
||||
cloneZones.resolveWPReferences(rawData, theZone, dataTable)
|
||||
@@ -946,7 +851,6 @@ function cloneZones.handoffTracking(theGroup, theZone)
|
||||
return
|
||||
end
|
||||
local trackerName = theZone.trackWith
|
||||
--if trackerName == "*" then trackerName = theZone.name end
|
||||
-- now assemble a list of all trackers
|
||||
if cloneZones.verbose or theZone.verbose then
|
||||
trigger.action.outText("+++clnZ: clone pass-off: " .. trackerName, 30)
|
||||
@@ -1065,8 +969,6 @@ function cloneZones.forcedRespawn(args)
|
||||
if newGroupID == theData.CZTargetID then
|
||||
if verbose then
|
||||
trigger.action.outText("GOOD REPLACEMENT new ID <" .. newGroupID .. "> matches target <" .. theData.CZTargetID .. "> for <" .. theData.name .. ">", 30)
|
||||
-- we can now remove the former group
|
||||
-- and replace it with the new one
|
||||
trigger.action.outText("will replace table entry at <" .. pos .. "> with new group", 30)
|
||||
end
|
||||
spawnedGroups[pos] = theGroup
|
||||
@@ -1104,7 +1006,7 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
|
||||
local newCenter = spawnZone:getPoint() -- includes zone following updates
|
||||
local oCenter = theZone:getDCSOrigin() -- get original coords on map for cloning offsets
|
||||
-- calculate zoneDelta, is added to all vectors
|
||||
local zoneDelta = dcsCommon.vSub(newCenter, theZone.origin) -- oCenter) --theZone.origin)
|
||||
local zoneDelta = dcsCommon.vSub(newCenter, theZone.origin)
|
||||
|
||||
-- precalc turn value for linked rotation
|
||||
local dHeading = 0 -- for linked zones
|
||||
@@ -1280,7 +1182,6 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
|
||||
|
||||
-- make group, unit[1] and route point [1] all match up
|
||||
if rawData.route and rawData.units[1] then
|
||||
-- trigger.action.outText("matching route point 1 and group with unit 1", 30)
|
||||
rawData.route.points[1].x = rawData.units[1].x
|
||||
rawData.route.points[1].y = rawData.units[1].y
|
||||
rawData.x = rawData.units[1].x
|
||||
@@ -1439,7 +1340,6 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
|
||||
if not spawnZone.identical then
|
||||
-- make sure static name is unique and remember original
|
||||
cloneZones.uniqueNameStaticData(rawData, spawnZone, theZone.name)
|
||||
--rawData.name = dcsCommon.uuid(rawData.name)
|
||||
rawData.unitId = cloneZones.uniqueID()
|
||||
end
|
||||
rawData.CZTargetID = rawData.unitId
|
||||
@@ -1529,7 +1429,6 @@ function cloneZones.turnOffAI(args)
|
||||
local theGroup = args[1]
|
||||
local theController = theGroup:getController()
|
||||
theController:setOnOff(false)
|
||||
-- trigger.action.outText("turned off AI for group <" .. theGroup:getName() .. "> ", 30)
|
||||
end
|
||||
|
||||
-- retro-fit for helo troops and others to provide 'requestable' support
|
||||
@@ -1721,7 +1620,6 @@ function cloneZones.getRequestableClonersInRange(aPoint, aRange, aSide)
|
||||
if aSide ~= 0 then
|
||||
-- check if side is correct for owned zone
|
||||
local resolved = cloneZones.resolveOwningCoalition(aZone)
|
||||
--if resolved ~= 0 and resolved ~= aSide then
|
||||
if resolved == 0 or resolved ~= aSide then
|
||||
-- failed ownership test. must match and not be zero
|
||||
hasMatch = false
|
||||
@@ -1749,7 +1647,7 @@ function cloneZones.update()
|
||||
for idx, aZone in pairs(cloneZones.cloners) do
|
||||
-- see if deSpawn was pulled. Must run before spawn
|
||||
if aZone.deSpawnFlag then
|
||||
local currTriggerVal = aZone:getFlagValue(aZone.deSpawnFlag) -- trigger.misc.getUserFlag(aZone.deSpawnFlag)
|
||||
local currTriggerVal = aZone:getFlagValue(aZone.deSpawnFlag)
|
||||
if currTriggerVal ~= aZone.lastDeSpawnValue then
|
||||
if cloneZones.verbose or aZone.verbose then
|
||||
trigger.action.outText("+++clnZ: DEspawn triggered for <" .. aZone.name .. ">", 30)
|
||||
@@ -1760,20 +1658,23 @@ function cloneZones.update()
|
||||
end
|
||||
|
||||
-- see if we got spawn? command
|
||||
local willSpawn = false -- init to false.
|
||||
if aZone:testZoneFlag(aZone.spawnFlag, aZone.cloneTriggerMethod, "lastSpawnValue") then
|
||||
if cloneZones.verbose then
|
||||
if cloneZones.verbose or aZone.verbose then
|
||||
trigger.action.outText("+++clnZ: spawn triggered for <" .. aZone.name .. ">", 30)
|
||||
end
|
||||
cloneZones.spawnWithCloner(aZone)
|
||||
willSpawn = true -- in case prewipe, we delay
|
||||
-- can mess with empty, so we tell empty to skip
|
||||
end
|
||||
|
||||
-- empty handling
|
||||
local isEmpty = cloneZones.countLiveUnits(aZone) < 1 and aZone.hasClones
|
||||
if isEmpty then
|
||||
if isEmpty and (willSpawn == false) then
|
||||
-- see if we need to bang a flag
|
||||
if aZone.emptyBangFlag then
|
||||
aZone:pollFlag(aZone.emptyBangFlag, aZone.cloneMethod)
|
||||
if cloneZones.verbose then
|
||||
if cloneZones.verbose or aZone.verbose then
|
||||
trigger.action.outText("+++clnZ: bang! on " .. aZone.emptyBangFlag, 30)
|
||||
end
|
||||
end
|
||||
@@ -1983,7 +1884,6 @@ function cloneZones.loadData()
|
||||
trigger.action.outText("+++clnZ: linked static <" .. oName .. "> to unit <" .. newStatic.linkUnit .. ">", 30)
|
||||
end
|
||||
local cty = newStatic.cty
|
||||
-- local cat = staticData.cat
|
||||
-- spawn new one, replacing same.named old, dead if required
|
||||
gStatic = coalition.addStaticObject(cty, newStatic)
|
||||
|
||||
@@ -2004,7 +1904,7 @@ function cloneZones.loadData()
|
||||
for cName, cData in pairs(allCloners) do
|
||||
local theCloner = cloneZones.getCloneZoneByName(cName)
|
||||
if theCloner then
|
||||
theCloner.isStarted = true -- ALWAYS TRUE WHEN WE COME HERE! cData.isStarted
|
||||
theCloner.isStarted = true
|
||||
-- init myUniqueCounter if it exists
|
||||
if cData.myUniqueCounter then
|
||||
theCloner.myUniqueCounter = cData.myUniqueCounter
|
||||
@@ -2094,10 +1994,7 @@ function cloneZones.start()
|
||||
cloneZones.readConfigZone()
|
||||
|
||||
-- process cloner Zones
|
||||
local attrZones = cfxZones.getZonesWithAttributeNamed("cloner")
|
||||
|
||||
-- now create an rnd gen for each one and add them
|
||||
-- to our watchlist
|
||||
local attrZones = cfxZones.getZonesWithAttributeNamed("cloner")
|
||||
for k, aZone in pairs(attrZones) do
|
||||
cloneZones.createClonerWithZone(aZone) -- process attribute and add to zone
|
||||
cloneZones.addCloneZone(aZone)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
countDown = {}
|
||||
countDown.version = "1.3.2"
|
||||
countDown.version = "2.0.0"
|
||||
countDown.verbose = false
|
||||
countDown.ups = 1
|
||||
countDown.requiredLibs = {
|
||||
@@ -9,26 +9,14 @@ countDown.requiredLibs = {
|
||||
|
||||
--[[--
|
||||
count down on flags to generate new signal on out
|
||||
Copyright (c) 2022, 2023 by Christian Franz and cf/x AG
|
||||
Copyright (c) 2022 - 2024 by Christian Franz and cf/x AG
|
||||
|
||||
Version History
|
||||
1.0.0 - initial version
|
||||
1.1.0 - Lua interface: callbacks
|
||||
- corrected verbose (erroneously always suppressed)
|
||||
- triggerFlag --> triggerCountFlag
|
||||
1.1.1 - corrected bug in invokeCallback
|
||||
1.2.0 - DML Flags
|
||||
- counterOut!
|
||||
- ups config
|
||||
1.2.1 - disableCounter?
|
||||
1.3.0 - DML & Watchflags upgrade
|
||||
- method --> ctdwnMethod
|
||||
1.3.1 - clock? synonym
|
||||
- new reset? input
|
||||
- improved verbosity
|
||||
- zone-local verbosity
|
||||
1.3.2 - enableCounter? to balande disableCounter?
|
||||
|
||||
2.0.0 - dmlZones, OOP upgrade
|
||||
counterOut! --> counterOut#
|
||||
output method defaults to "inc"
|
||||
better config parsing
|
||||
cleanup
|
||||
--]]--
|
||||
|
||||
countDown.counters = {}
|
||||
@@ -77,7 +65,7 @@ end
|
||||
--
|
||||
function countDown.createCountDownWithZone(theZone)
|
||||
-- start val - a range
|
||||
theZone.startMinVal, theZone.startMaxVal = cfxZones.getPositiveRangeFromZoneProperty(theZone, "countDown", 1) -- we know this exists
|
||||
theZone.startMinVal, theZone.startMaxVal = theZone:getPositiveRangeFromZoneProperty("countDown", 1) -- we know this exists
|
||||
theZone.currVal = dcsCommon.randomBetween(theZone.startMinVal, theZone.startMaxVal)
|
||||
|
||||
if countDown.verbose then
|
||||
@@ -85,32 +73,32 @@ function countDown.createCountDownWithZone(theZone)
|
||||
end
|
||||
|
||||
-- loop
|
||||
theZone.loop = cfxZones.getBoolFromZoneProperty(theZone, "loop", false)
|
||||
theZone.loop = theZone:getBoolFromZoneProperty("loop", false)
|
||||
|
||||
-- extend after zero
|
||||
theZone.belowZero = cfxZones.getBoolFromZoneProperty(theZone, "belowZero", false)
|
||||
theZone.belowZero = theZone:getBoolFromZoneProperty("belowZero", false)
|
||||
|
||||
-- out method
|
||||
theZone.ctdwnMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "flip")
|
||||
if cfxZones.hasProperty(theZone, "ctdwnMethod") then
|
||||
theZone.ctdwnMethod = cfxZones.getStringFromZoneProperty(theZone, "ctdwnMethod", "flip")
|
||||
theZone.ctdwnMethod = theZone:getStringFromZoneProperty("method", "inc")
|
||||
if theZone:hasProperty("ctdwnMethod") then
|
||||
theZone.ctdwnMethod = theZone:getStringFromZoneProperty( "ctdwnMethod", "inc")
|
||||
end
|
||||
|
||||
-- triggerMethod for inputs
|
||||
theZone.ctdwnTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "triggerMethod", "change")
|
||||
theZone.ctdwnTriggerMethod = theZone:getStringFromZoneProperty( "triggerMethod", "change")
|
||||
|
||||
if cfxZones.hasProperty(theZone, "ctdwnTriggerMethod") then
|
||||
theZone.ctdwnTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "ctdwnTriggerMethod", "change")
|
||||
if theZone:hasProperty("ctdwnTriggerMethod") then
|
||||
theZone.ctdwnTriggerMethod = theZone:getStringFromZoneProperty("ctdwnTriggerMethod", "change")
|
||||
end
|
||||
|
||||
-- trigger flag "count" / "start?"
|
||||
if cfxZones.hasProperty(theZone, "count?") then
|
||||
theZone.triggerCountFlag = cfxZones.getStringFromZoneProperty(theZone, "count?", "<none>")
|
||||
elseif cfxZones.hasProperty(theZone, "clock?") then
|
||||
theZone.triggerCountFlag = cfxZones.getStringFromZoneProperty(theZone, "clock?", "<none>")
|
||||
if theZone:hasProperty("count?") then
|
||||
theZone.triggerCountFlag = theZone:getStringFromZoneProperty("count?", "<none>")
|
||||
elseif theZone:hasProperty("clock?") then
|
||||
theZone.triggerCountFlag = theZone:getStringFromZoneProperty("clock?", "<none>")
|
||||
-- can also use in? for counting. we always use triggerCountFlag
|
||||
elseif cfxZones.hasProperty(theZone, "in?") then
|
||||
theZone.triggerCountFlag = cfxZones.getStringFromZoneProperty(theZone, "in?", "<none>")
|
||||
elseif theZone:hasProperty("in?") then
|
||||
theZone.triggerCountFlag = theZone:getStringFromZoneProperty("in?", "<none>")
|
||||
end
|
||||
|
||||
if theZone.triggerCountFlag then
|
||||
@@ -118,40 +106,40 @@ function countDown.createCountDownWithZone(theZone)
|
||||
end
|
||||
|
||||
-- reset
|
||||
if cfxZones.hasProperty(theZone, "reset?") then
|
||||
theZone.resetFlag = cfxZones.getStringFromZoneProperty(theZone, "reset?", "<none>")
|
||||
if theZone:hasProperty("reset?") then
|
||||
theZone.resetFlag = theZone:getStringFromZoneProperty("reset?", "<none>")
|
||||
theZone.resetFlagValue = cfxZones.getFlagValue(theZone.resetFlag, theZone)
|
||||
end
|
||||
|
||||
-- zero! bang
|
||||
if cfxZones.hasProperty(theZone, "zero!") then
|
||||
theZone.zeroFlag = cfxZones.getStringFromZoneProperty(theZone, "zero!", "<none>")
|
||||
if theZone:hasProperty("zero!") then
|
||||
theZone.zeroFlag = theZone:getStringFromZoneProperty("zero!", "<none>")
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "out!") then
|
||||
theZone.zeroFlag = cfxZones.getStringFromZoneProperty(theZone, "out!", "<none>")
|
||||
if theZone:hasProperty("out!") then
|
||||
theZone.zeroFlag = theZone:getStringFromZoneProperty("out!", "<none>")
|
||||
end
|
||||
|
||||
-- TMinus! bang
|
||||
if cfxZones.hasProperty(theZone, "tMinus!") then
|
||||
theZone.tMinusFlag = cfxZones.getStringFromZoneProperty(theZone, "tMinus!", "<none>")
|
||||
if theZone:hasProperty("tMinus!") then
|
||||
theZone.tMinusFlag = theZone:getStringFromZoneProperty("tMinus!", "<none>")
|
||||
end
|
||||
|
||||
-- counterOut val
|
||||
if cfxZones.hasProperty(theZone, "counterOut!") then
|
||||
theZone.counterOut = cfxZones.getStringFromZoneProperty(theZone, "counterOut!", "<none>")
|
||||
if theZone:hasProperty("counterOut#") then
|
||||
theZone.counterOut = theZone:getStringFromZoneProperty( "counterOut#", "<none>")
|
||||
end
|
||||
|
||||
-- disableFlag/enableFlag
|
||||
theZone.counterDisabled = false
|
||||
if cfxZones.hasProperty(theZone, "disableCounter?") then
|
||||
theZone.disableCounterFlag = cfxZones.getStringFromZoneProperty(theZone, "disableCounter?", "<none>")
|
||||
theZone.disableCounterFlagVal = cfxZones.getFlagValue(theZone.disableCounterFlag, theZone)
|
||||
if theZone:hasProperty("disableCounter?") then
|
||||
theZone.disableCounterFlag = theZone:getStringFromZoneProperty("disableCounter?", "<none>")
|
||||
theZone.disableCounterFlagVal = theZone:getFlagValue(theZone.disableCounterFlag)
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "enableCounter?") then
|
||||
theZone.enableCounterFlag = cfxZones.getStringFromZoneProperty(theZone, "enableCounter?", "<none>")
|
||||
theZone.enableCounterFlagVal = cfxZones.getFlagValue(theZone.enableCounterFlag, theZone)
|
||||
if theZone:hasProperty("enableCounter?") then
|
||||
theZone.enableCounterFlag = theZone:getStringFromZoneProperty("enableCounter?", "<none>")
|
||||
theZone.enableCounterFlagVal = theZone:getFlagValue(theZone.enableCounterFlag)
|
||||
end
|
||||
|
||||
end
|
||||
@@ -170,7 +158,7 @@ function countDown.reset(theZone)
|
||||
cfxZones.setFlagValue(theZone.counterOut, val, theZone)
|
||||
end
|
||||
-- read and ignore any pulling of the clock flag
|
||||
local ignore = cfxZones.testZoneFlag(theZone, theZone.triggerCountFlag, theZone.ctdwnTriggerMethod, "lastCountTriggerValue")
|
||||
local ignore = theZone:testZoneFlag(theZone.triggerCountFlag, theZone.ctdwnTriggerMethod, "lastCountTriggerValue")
|
||||
-- simply updates lastTriggerValue to current clock value
|
||||
end
|
||||
|
||||
@@ -186,7 +174,7 @@ function countDown.isTriggered(theZone)
|
||||
local looping = false
|
||||
|
||||
if theZone.counterOut then
|
||||
cfxZones.setFlagValue(theZone.counterOut, val, theZone)
|
||||
theZone:setFlagValue(theZone.counterOut, val)
|
||||
end
|
||||
|
||||
if val > 0 then
|
||||
@@ -196,7 +184,7 @@ function countDown.isTriggered(theZone)
|
||||
if countDown.verbose or theZone.verbose then
|
||||
trigger.action.outText("+++cntD: <" .. theZone.name .. "> TMINUTS on flag <" .. theZone.tMinusFlag .. ">", 30)
|
||||
end
|
||||
cfxZones.pollFlag(theZone.tMinusFlag, theZone.ctdwnMethod, theZone)
|
||||
theZone:pollFlag(theZone.tMinusFlag, theZone.ctdwnMethod)
|
||||
end
|
||||
|
||||
elseif val == 0 then
|
||||
@@ -206,7 +194,7 @@ function countDown.isTriggered(theZone)
|
||||
if countDown.verbose or theZone.verbose then
|
||||
trigger.action.outText("+++cntD: ZERO <" .. theZone.name .. "> on flag <" .. theZone.zeroFlag .. ">", 30)
|
||||
end
|
||||
cfxZones.pollFlag(theZone.zeroFlag, theZone.ctdwnMethod, theZone)
|
||||
theZone:pollFlag(theZone.zeroFlag, theZone.ctdwnMethod)
|
||||
end
|
||||
|
||||
if theZone.loop then
|
||||
@@ -225,7 +213,7 @@ function countDown.isTriggered(theZone)
|
||||
if countDown.verbose or theZone.verbose then
|
||||
trigger.action.outText("+++cntD: Below Zero", 30)
|
||||
end
|
||||
cfxZones.pollFlag(theZone.zeroFlag, theZone.ctdwnMethod, theZone)
|
||||
theZone:pollFlag(theZone.zeroFlag, theZone.ctdwnMethod)
|
||||
end
|
||||
|
||||
end
|
||||
@@ -243,7 +231,7 @@ function countDown.update()
|
||||
|
||||
for idx, aZone in pairs(countDown.counters) do
|
||||
if aZone.resetFlag then
|
||||
if cfxZones.testZoneFlag(aZone, aZone.resetFlag, aZone.ctdwnTriggerMethod, "resetFlagValue") then
|
||||
if aZone:testZoneFlag(aZone.resetFlag, aZone.ctdwnTriggerMethod, "resetFlagValue") then
|
||||
-- reset pulled, reset the timer to start condition
|
||||
countDown.reset(aZone)
|
||||
end
|
||||
@@ -252,7 +240,7 @@ function countDown.update()
|
||||
-- make sure to re-start before reading time limit
|
||||
-- if reset, lastTriggerValue is updated and will not trigger
|
||||
if (not aZone.counterDisabled) and
|
||||
cfxZones.testZoneFlag(aZone, aZone.triggerCountFlag, aZone.ctdwnTriggerMethod, "lastCountTriggerValue")
|
||||
aZone:testZoneFlag(aZone.triggerCountFlag, aZone.ctdwnTriggerMethod, "lastCountTriggerValue")
|
||||
then
|
||||
if countDown.verbose then
|
||||
trigger.action.outText("+++cntD: triggered on in?", 30)
|
||||
@@ -260,14 +248,14 @@ function countDown.update()
|
||||
countDown.isTriggered(aZone)
|
||||
end
|
||||
|
||||
if cfxZones.testZoneFlag(aZone, aZone.disableCounterFlag, aZone.ctdwnTriggerMethod, "disableCounterFlagVal") then
|
||||
if aZone:testZoneFlag(aZone.disableCounterFlag, aZone.ctdwnTriggerMethod, "disableCounterFlagVal") then
|
||||
if countDown.verbose then
|
||||
trigger.action.outText("+++cntD: disabling counter " .. aZone.name, 30)
|
||||
end
|
||||
aZone.counterDisabled = true
|
||||
end
|
||||
|
||||
if cfxZones.testZoneFlag(aZone, aZone.enableCounterFlag, aZone.ctdwnTriggerMethod, "enableCounterFlagVal") then
|
||||
if aZone:testZoneFlag(aZone.enableCounterFlag, aZone.ctdwnTriggerMethod, "enableCounterFlagVal") then
|
||||
if countDown.verbose then
|
||||
trigger.action.outText("+++cntD: ENabling counter " .. aZone.name, 30)
|
||||
end
|
||||
@@ -282,16 +270,13 @@ end
|
||||
function countDown.readConfigZone()
|
||||
local theZone = cfxZones.getZoneByName("countDownConfig")
|
||||
if not theZone then
|
||||
if countDown.verbose then
|
||||
trigger.action.outText("+++cntD: NO config zone!", 30)
|
||||
end
|
||||
return
|
||||
theZone = cfxZones.createSimpleZone("countDownConfig")
|
||||
end
|
||||
|
||||
countDown.ups = cfxZones.getNumberFromZoneProperty(theZone, "ups", 1)
|
||||
countDown.ups = theZone:getNumberFromZoneProperty("ups", 1)
|
||||
if countDown.ups < 0.001 then countDown.ups = 0.001 end
|
||||
|
||||
countDown.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
|
||||
countDown.verbose = theZone.verbose
|
||||
|
||||
if countDown.verbose then
|
||||
trigger.action.outText("+++cntD: read config", 30)
|
||||
|
||||
@@ -1,183 +1,13 @@
|
||||
dcsCommon = {}
|
||||
dcsCommon.version = "2.9.8"
|
||||
--[[-- VERSION HISTORY
|
||||
2.2.6 - compassPositionOfARelativeToB
|
||||
- clockPositionOfARelativeToB
|
||||
2.2.7 - isTroopCarrier
|
||||
- distFlat
|
||||
2.2.8 - fixed event2text
|
||||
2.2.9 - getUnitAGL
|
||||
- getUnitAlt
|
||||
- getUnitSpeed
|
||||
- getUnitHeading
|
||||
- getUnitHeadingDegrees
|
||||
- mag
|
||||
- clockPositionOfARelativeToB with own heading
|
||||
2.3.0 - unitIsInfantry
|
||||
2.3.1 - bool2YesNo
|
||||
- bool2Text
|
||||
2.3.2 - getGroupAvgSpeed
|
||||
- getGroupMaxSpeed
|
||||
2.3.3 - getSizeOfTable
|
||||
2.3.4 - isSceneryObject
|
||||
coalition2county
|
||||
2.3.5 - smallRandom
|
||||
pickRandom uses smallRandom
|
||||
airfield handling, parking
|
||||
flight waypoint handling
|
||||
landing waypoint creation
|
||||
take-off waypoint creation
|
||||
2.3.6 - createOverheadAirdromeRoutPintData(aerodrome)
|
||||
2.3.7 - coalition2county - warning when creating UN
|
||||
2.3.8 - improved headingOfBInDegrees, new getClockDirection
|
||||
2.3.9 - getClosingVelocity
|
||||
- dot product
|
||||
- magSquare
|
||||
- vMag
|
||||
2.4.0 - libCheck
|
||||
2.4.1 - grid/square/rect formation
|
||||
- arrangeGroupInNColumns formation
|
||||
- 2Columns formation deep and wide formation
|
||||
2.4.2 - getAirbasesInRangeOfPoint
|
||||
2.4.3 - lerp
|
||||
2.4.4 - getClosestAirbaseTo
|
||||
- fixed bug in containsString when strings equal
|
||||
2.4.5 - added cargo and mass options to createStaticObjectData
|
||||
2.4.6 - fixed randompercent
|
||||
2.4.7 - smokeColor2Num(smokeColor)
|
||||
2.4.8 - linkStaticDataToUnit()
|
||||
2.4.9 - trim functions
|
||||
- createGroundUnitData uses trim function to remove leading/trailing blanks
|
||||
so now we can use blanks after comma to separate types
|
||||
- dcsCommon.trimArray(
|
||||
- createStaticObjectData uses trim for type
|
||||
- getEnemyCoalitionFor understands strings, still returns number
|
||||
- coalition2county also understands 'red' and 'blue'
|
||||
2.5.0 - "Line" formation with one unit places unit at center
|
||||
2.5.1 - vNorm(a)
|
||||
2.5.1 - added SA-18 Igla manpad to unitIsInfantry()
|
||||
2.5.2 - added copyArray method
|
||||
- corrected heading in createStaticObjectData
|
||||
2.5.3 - corrected rotateGroupData bug for cz
|
||||
- removed forced error in failed pickRandom
|
||||
2.5.4 - rotateUnitData()
|
||||
- randomBetween()
|
||||
2.5.5 - stringStartsWithDigit()
|
||||
- stringStartsWithLetter()
|
||||
- stringIsPositiveNumber()
|
||||
2.5.6 - corrected stringEndsWith() bug with str
|
||||
2.5.7 - point2text(p)
|
||||
2.5.8 - string2GroupCat()
|
||||
2.5.9 - string2ObjectCat()
|
||||
2.6.0 - unified uuid, removed uuIdent
|
||||
2.6.1 - removed bug in rotateUnitData: cy --> cz param passing
|
||||
2.6.2 - new combineTables()
|
||||
2.6.3 - new tacan2freq()
|
||||
2.6.4 - new processHMS()
|
||||
2.6.5 - new bearing2compass()
|
||||
- new bearingdegrees2compass()
|
||||
- new latLon2Text() - based on mist
|
||||
2.6.6 - new nowString()
|
||||
- new str2num()
|
||||
- new stringRemainsStartingWith()
|
||||
- new stripLF()
|
||||
- new removeBlanks()
|
||||
2.6.7 - new menu2text()
|
||||
2.6.8 - new getMissionName()
|
||||
- new flagArrayFromString()
|
||||
2.6.9 - new getSceneryObjectsInZone()
|
||||
- new getSceneryObjectInZoneByName()
|
||||
2.7.0 - new synchGroupData()
|
||||
clone, topClone and copyArray now all nil-trap
|
||||
2.7.1 - new isPlayerUnit() -- moved from cfxPlayer
|
||||
new getAllExistingPlayerUnitsRaw - from cfxPlayer
|
||||
new typeIsInfantry()
|
||||
2.7.2 - new rangeArrayFromString()
|
||||
fixed leading blank bug in flagArrayFromString
|
||||
new incFlag()
|
||||
new decFlag()
|
||||
nil trap in stringStartsWith()
|
||||
new getClosestFreeSlotForCatInAirbaseTo()
|
||||
2.7.3 - new string2Array()
|
||||
- additional guard for isPlayerUnit
|
||||
2.7.4 - new array2string()
|
||||
2.7.5 - new bitAND32()
|
||||
- new LSR()
|
||||
- new num2bin()
|
||||
2.7.6 - new getObjectsForCatAtPointWithRadius()
|
||||
2.7.7 - clone() has new stripMeta option. pass true to remove all meta tables
|
||||
- dumpVar2Str detects meta tables
|
||||
- rotateGroupData kills unit's psi value if it existed since it messes with heading
|
||||
- rotateGroupData - changes psi to -heading if it exists rather than nilling
|
||||
2.7.8 - new getGeneralDirection()
|
||||
- new getNauticalDirection()
|
||||
- more robust guards for getUnitSpeed
|
||||
2.7.9 - new bool2Num(theBool)
|
||||
- new aspectByDirection()
|
||||
- createGroundGroupWithUnits corrected spelling of minDist, crashed scattered formation
|
||||
- randomPointInCircle fixed erroneous local for x, z
|
||||
- "scattered" formation repaired
|
||||
2.7.10- semaphore groundwork
|
||||
2.8.0 - new collectMissionIDs at start-up
|
||||
- new getUnitNameByID
|
||||
- new getGroupNameByID
|
||||
- bool2YesNo alsco can return NIL
|
||||
- new getUnitStartPosByID
|
||||
2.8.1 - arrayContainsString: type checking for theArray and warning
|
||||
- processStringWildcards()
|
||||
- new wildArrayContainsString()
|
||||
- fix for stringStartsWith oddity with aircraft types
|
||||
2.8.2 - better fixes for string.find() in stringStartsWith and containsString
|
||||
- dcsCommon.isTroopCarrier(theUnit, carriers) new carriers optional param
|
||||
- better guards for getUnitAlt and getUnitAGL
|
||||
- new newPointAtDegreesRange()
|
||||
- new newPointAtAngleRange()
|
||||
- new isTroopCarrierType()
|
||||
- stringStartsWith now supports case insensitive match
|
||||
- isTroopCarrier() supports 'any' and 'all'
|
||||
- made getEnemyCoalitionFor() more resilient
|
||||
- fix to smallRandom for negative numbers
|
||||
- isTroopCarrierType uses wildArrayContainsString
|
||||
2.8.3 - small optimizations in bearingFromAtoB()
|
||||
- new whichSideOfMine()
|
||||
2.8.4 - new rotatePointAroundOriginRad()
|
||||
- new rotatePointAroundPointDeg()
|
||||
- new rotatePointAroundPointRad()
|
||||
- getClosestAirbaseTo() now supports passing list of air bases
|
||||
2.8.5 - better guard in getGroupUnit()
|
||||
2.8.6 - phonetic helpers
|
||||
new spellString()
|
||||
2.8.7 - new flareColor2Num()
|
||||
- new flareColor2Text()
|
||||
- new iteratePlayers()
|
||||
2.8.8 - new hexString2RGBA()
|
||||
- new playerName2Coalition()
|
||||
- new coalition2Text()
|
||||
2.8.9 - vAdd supports xy and xyz
|
||||
- vSub supports xy and xyz
|
||||
- vMultScalar supports xy and xyz
|
||||
2.8.10 - tacan2freq now integrated with module (blush)
|
||||
- array2string cosmetic default
|
||||
- vMultScalar corrected bug in accessing b.z
|
||||
- new randomLetter()
|
||||
- new getPlayerUnit()
|
||||
- new getMapName()
|
||||
- new getMagDeclForPoint()
|
||||
2.9.0 - createPoint() moved from cfxZones
|
||||
- copyPoint() moved from cfxZones
|
||||
- numberArrayFromString() moved from cfxZones
|
||||
2.9.1 - new createSimpleRoutePointData()
|
||||
- createOverheadAirdromeRoutPintData corrected and legacy support added
|
||||
- new bearingFromAtoBusingXY()
|
||||
- corrected verbosity for bearingFromAtoB
|
||||
- new getCountriesForCoalition()
|
||||
2.9.2 - updated event2text
|
||||
2.9.3 - getAirbasesWhoseNameContains now supports category tables for filtering
|
||||
2.9.4 - new bearing2degrees()
|
||||
2.9.5 - distanceOfPointPToLineXZ(p, p1, p2)
|
||||
2.9.6 - new addToTableIfNew()
|
||||
2.9.7 - createSimpleRoutePointData also accepts speed
|
||||
2.9.8 - isSceneryObject(theUnit) optimization, DCS 2.9 safe
|
||||
dcsCommon.version = "3.0.0"
|
||||
--[[-- VERSION HISTORY
|
||||
3.0.0 - removed bad bug in stringStartsWith, only relevant if caseSensitive is false
|
||||
- point2text new intsOnly option
|
||||
- arrangeGroupDataIntoFormation minDist harden
|
||||
- cleanup
|
||||
- new pointInDirectionOfPointXYY()
|
||||
- createGroundGroupWithUnits now supports liveries
|
||||
- new getAllExistingPlayersAndUnits()
|
||||
--]]--
|
||||
|
||||
-- dcsCommon is a library of common lua functions
|
||||
@@ -511,7 +341,6 @@ dcsCommon.version = "2.9.8"
|
||||
-- a table containing categories, e.g. {0, 2} = airfields and ships but not farps
|
||||
-- if no name given or aName = "*", then all bases are returned prior to filtering
|
||||
function dcsCommon.getAirbasesWhoseNameContains(aName, filterCat, filterCoalition)
|
||||
--trigger.action.outText("getAB(name): enter with " .. aName, 30)
|
||||
if not aName then aName = "*" end
|
||||
local allYourBase = world.getAirbases() -- get em all
|
||||
local areBelongToUs = {}
|
||||
@@ -520,9 +349,6 @@ dcsCommon.version = "2.9.8"
|
||||
local airBaseName = aBase:getName() -- get display name
|
||||
if aName == "*" or dcsCommon.containsString(airBaseName, aName) then
|
||||
-- containsString is case insesitive unless told otherwise
|
||||
--if aName ~= "*" then
|
||||
-- trigger.action.outText("getAB(name): matched " .. airBaseName, 30)
|
||||
--end
|
||||
local doAdd = true
|
||||
if filterCat then
|
||||
local aCat = dcsCommon.getAirbaseCat(aBase)
|
||||
@@ -628,13 +454,11 @@ dcsCommon.version = "2.9.8"
|
||||
for idx, aSlot in pairs(reallyFree) do
|
||||
local sp = {x = aSlot.vTerminalPos.x, y = 0, z = aSlot.vTerminalPos.z}
|
||||
local currDist = dcsCommon.distFlat(p, sp)
|
||||
--trigger.action.outText("slot <" .. aSlot.Term_Index .. "> has dist " .. math.floor(currDist) .. " and _0 of <" .. aSlot.Term_Index_0 .. ">", 30)
|
||||
if currDist < closestDist then
|
||||
closestSlot = aSlot
|
||||
closestDist = currDist
|
||||
end
|
||||
end
|
||||
--trigger.action.outText("slot <" .. closestSlot.Term_Index .. "> has closest dist <" .. math.floor(closestDist) .. ">", 30)
|
||||
return closestSlot
|
||||
end
|
||||
|
||||
@@ -888,12 +712,9 @@ dcsCommon.version = "2.9.8"
|
||||
if not A then return "***error:A***" end
|
||||
if not B then return "***error:B***" end
|
||||
if not headingOfBInDegrees then headingOfBInDegrees = 0 end
|
||||
|
||||
local bearing = dcsCommon.bearingInDegreesFromAtoB(B, A) -- returns 0..360
|
||||
-- trigger.action.outText("+++comm: oclock - bearing = " .. bearing .. " and inHeading = " .. headingOfBInDegrees, 30)
|
||||
local bearing = dcsCommon.bearingInDegreesFromAtoB(B, A) -- returns 0..360
|
||||
bearing = bearing - headingOfBInDegrees
|
||||
return dcsCommon.getClockDirection(bearing)
|
||||
|
||||
end
|
||||
|
||||
-- given a heading, return clock with 0 being 12, 180 being 6 etc.
|
||||
@@ -1079,7 +900,6 @@ dcsCommon.version = "2.9.8"
|
||||
end
|
||||
|
||||
-- if we get here, there was no live unit
|
||||
--trigger.action.outText("+++cmn: A group has no live units. returning nil", 10)
|
||||
return nil
|
||||
|
||||
end
|
||||
@@ -1107,7 +927,6 @@ dcsCommon.version = "2.9.8"
|
||||
end
|
||||
|
||||
-- if we get here, there was no live unit
|
||||
--trigger.action.outText("+++cmn A group has no live units. returning nil", 10)
|
||||
return nil
|
||||
|
||||
end
|
||||
@@ -1233,7 +1052,6 @@ dcsCommon.version = "2.9.8"
|
||||
-- when filtering occurs in pre, an alternative 'rejected' handler can be called
|
||||
function dcsCommon.addEventHandler(f, pre, post, rejected) -- returns ID
|
||||
local handler = {} -- build a wrapper and connect the onEvent
|
||||
--dcsCommon.cbID = dcsCommon.cbID + 1 -- increment unique count
|
||||
handler.id = dcsCommon.uuid("eventHandler")
|
||||
handler.f = f -- the callback itself
|
||||
if (rejected) then handler.rejected = rejected end
|
||||
@@ -1245,7 +1063,6 @@ dcsCommon.version = "2.9.8"
|
||||
function handler:onEvent(event)
|
||||
if not self.pre(event) then
|
||||
if dcsCommon.verbose then
|
||||
-- trigger.action.outText("event " .. event.id .. " discarded by pre-processor", 10)
|
||||
end
|
||||
if (self.rejected) then self.rejected(event) end
|
||||
return
|
||||
@@ -1428,8 +1245,8 @@ dcsCommon.version = "2.9.8"
|
||||
rp.action = "Turning Point"
|
||||
rp.type = "Turning Point"
|
||||
if action then rp.action = action; rp.type = action end -- warning: may not be correct, need to verify later
|
||||
rp.alt = altitudeInFeet * 0.3048
|
||||
rp.speed = knots * 0.514444 -- we use
|
||||
rp.alt = altitudeInFeet * 0.3048 -- in m
|
||||
rp.speed = knots * 0.514444 -- we use m/s
|
||||
rp.alt_type = "BARO"
|
||||
if (altType) then rp.alt_type = altType end
|
||||
return rp
|
||||
@@ -1485,7 +1302,7 @@ dcsCommon.version = "2.9.8"
|
||||
rp.action = "From Parking Area"
|
||||
rp.type = "TakeOffParking"
|
||||
|
||||
rp.speed = 100; -- in m/s? If so, that's 360 km/h
|
||||
rp.speed = 100 -- that's 360 km/h
|
||||
rp.alt_type = "BARO"
|
||||
return rp
|
||||
end
|
||||
@@ -1673,7 +1490,6 @@ dcsCommon.version = "2.9.8"
|
||||
|
||||
-- now do the formation stuff
|
||||
-- make sure that they keep minimum distance
|
||||
-- trigger.action.outText("dcsCommon - processing formation " .. formation .. " with radius = " .. radius, 30)
|
||||
if formation == "LINE_V" then
|
||||
-- top to bottom in zone (heding 0). -- will run through x-coordinate
|
||||
-- use entire radius top to bottom
|
||||
@@ -1682,7 +1498,6 @@ dcsCommon.version = "2.9.8"
|
||||
for i=1, num do
|
||||
|
||||
local u = theNewGroup.units[i]
|
||||
-- trigger.action.outText("formation unit " .. u.name .. " currX = " .. currX, 30)
|
||||
u.x = currX
|
||||
currX = currX + increment
|
||||
end
|
||||
@@ -1698,7 +1513,6 @@ dcsCommon.version = "2.9.8"
|
||||
local increment = radius * 2/(num - 1) -- MUST NOT TRY WITH 1 UNIT!
|
||||
for i=1, num do
|
||||
local u = theNewGroup.units[i]
|
||||
-- trigger.action.outText("formation unit " .. u.name .. " currX = " .. currY, 30)
|
||||
u.y = currY
|
||||
currY = currY + increment
|
||||
end
|
||||
@@ -1713,7 +1527,6 @@ dcsCommon.version = "2.9.8"
|
||||
local incrementX = radius * 2/(num - 1) -- MUST NOT TRY WITH 1 UNIT!
|
||||
for i=1, num do
|
||||
local u = theNewGroup.units[i]
|
||||
-- trigger.action.outText("formation unit " .. u.name .. " currX = " .. currX .. " currY = " .. currY, 30)
|
||||
u.x = currX
|
||||
u.y = currY
|
||||
-- calc coords for NEXT iteration
|
||||
@@ -1731,6 +1544,7 @@ dcsCommon.version = "2.9.8"
|
||||
elseif formation == "SCATTERED" or formation == "RANDOM" then
|
||||
-- use randomPointInCircle and tehn iterate over all vehicles for mindelta
|
||||
processedUnits = {}
|
||||
if not minDist then minDist = 10 end
|
||||
for i=1, num do
|
||||
local emergencyBreak = 1 -- prevent endless loop
|
||||
local lowDist = 10000
|
||||
@@ -1760,7 +1574,6 @@ dcsCommon.version = "2.9.8"
|
||||
|
||||
elseif dcsCommon.stringStartsWith(formation, "CIRCLE") then
|
||||
-- units are arranged on perimeter of circle defined by radius
|
||||
-- trigger.action.outText("formation circle detected", 30)
|
||||
local currAngle = 0
|
||||
local angleInc = 2 * 3.14157 / num -- increase per spoke
|
||||
for i=1, num do
|
||||
@@ -1786,40 +1599,7 @@ dcsCommon.version = "2.9.8"
|
||||
-- calculate w
|
||||
local w = math.floor(num^(0.5) + 0.5)
|
||||
dcsCommon.arrangeGroupInNColumns(theNewGroup, w, radius)
|
||||
--[[--
|
||||
local h = math.floor(num / w)
|
||||
--trigger.action.outText("AdcsC: num=" .. num .. " w=" .. w .. "h=" .. h .. " -- num%w=" .. num%w, 30)
|
||||
if (num % w) > 0 then
|
||||
h = h + 1
|
||||
end
|
||||
|
||||
--trigger.action.outText("BdcsC: num=" .. num .. " w=" .. w .. "h=" .. h, 30)
|
||||
|
||||
-- now w * h always >= num and num items fir in that grid
|
||||
-- w is width, h is height, of course :)
|
||||
-- now calculat xInc and yInc
|
||||
local i = 1
|
||||
local xInc = 0
|
||||
if w > 1 then xInc = 2 * radius / (w-1) end
|
||||
local yInc = 0
|
||||
if h > 1 then yInc = 2 * radius / (h-1) end
|
||||
local currY = radius
|
||||
if h < 2 then currY = 0 end -- special:_ place in Y middle if only one row)
|
||||
while h > 0 do
|
||||
local currX = radius
|
||||
local wCnt = w
|
||||
while wCnt > 0 and (i <= num) do
|
||||
local u = theNewGroup.units[i] -- get unit
|
||||
u.x = currX
|
||||
u.y = currY
|
||||
currX = currX - xInc
|
||||
wCnt = wCnt - 1
|
||||
i = i + 1
|
||||
end
|
||||
currY = currY - yInc
|
||||
h = h - 1
|
||||
end
|
||||
--]]--
|
||||
elseif formation == "2DEEP" or formation == "2COLS" then
|
||||
if num < 2 then return end
|
||||
-- arrange units in an 2 x h grid
|
||||
@@ -1896,11 +1676,14 @@ dcsCommon.version = "2.9.8"
|
||||
end
|
||||
|
||||
|
||||
function dcsCommon.createGroundGroupWithUnits(name, theUnitTypes, radius, minDist, formation, innerRadius)
|
||||
function dcsCommon.createGroundGroupWithUnits(name, theUnitTypes, radius, minDist, formation, innerRadius, liveries)
|
||||
-- liveries is indexed by typeName and provides alternate livery names
|
||||
-- from default.
|
||||
if not minDist then minDist = 4 end -- meters
|
||||
if not formation then formation = "line" end
|
||||
if not radius then radius = 30 end -- meters
|
||||
if not innerRadius then innerRadius = 0 end
|
||||
if not liveries then liveries = {} end
|
||||
formation = formation:upper()
|
||||
-- theUnitTypes can be either a single string or a table of strings
|
||||
-- see here for TypeName https://github.com/mrSkortch/DCS-miscScripts/tree/master/ObjectDB
|
||||
@@ -1919,14 +1702,9 @@ dcsCommon.version = "2.9.8"
|
||||
local theNewGroup = dcsCommon.createEmptyGroundGroupData(name)
|
||||
|
||||
-- now add a single unit or multiple units
|
||||
if type(theUnitTypes) ~= "table" then
|
||||
-- trigger.action.outText("dcsCommon - i am here", 30)
|
||||
-- trigger.action.outText("dcsCommon - name " .. name, 30)
|
||||
-- trigger.action.outText("dcsCommon - unit type " .. theUnitTypes, 30)
|
||||
|
||||
if type(theUnitTypes) ~= "table" then
|
||||
local aUnit = {}
|
||||
aUnit = dcsCommon.createGroundUnitData(name .. "-1", theUnitTypes, false)
|
||||
-- trigger.action.outText("dcsCommon - unit name retval " .. aUnit.name, 30)
|
||||
dcsCommon.addUnitToGroupData(aUnit, theNewGroup, 0, 0) -- create with data at location (0,0)
|
||||
return theNewGroup
|
||||
end
|
||||
@@ -1937,6 +1715,10 @@ dcsCommon.version = "2.9.8"
|
||||
for key, theType in pairs(theUnitTypes) do
|
||||
-- trigger.action.outText("+++dcsC: creating unit " .. name .. "-" .. num .. ": " .. theType, 30)
|
||||
local aUnit = dcsCommon.createGroundUnitData(name .. "-"..num, theType, false)
|
||||
local theLivery = liveries[theType]
|
||||
if theLivery then
|
||||
aUnit.livery_id = theLivery
|
||||
end
|
||||
dcsCommon.addUnitToGroupData(aUnit, theNewGroup, 0, 0)
|
||||
num = num + 1
|
||||
end
|
||||
@@ -1991,15 +1773,23 @@ dcsCommon.version = "2.9.8"
|
||||
elseif cat == Group.Category.GROUND then
|
||||
-- we got all we need
|
||||
else
|
||||
-- trigger.action.outText("dcsCommon - unknown category: " .. cat, 30)
|
||||
-- return nil
|
||||
-- we also got all we need
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end;
|
||||
|
||||
function dcsCommon.pointInDirectionOfPointXYY(dir, dist, p) -- dir in rad, p in XYZ returns XYY
|
||||
local fx = math.cos(dir)
|
||||
local fy = math.sin(dir)
|
||||
local p2 = {}
|
||||
p2.x = p.x + dist * fx
|
||||
p2.y = p.z + dist * fy
|
||||
p2.z = p2.y -- make p2 XYY vec2/3 upcast
|
||||
return p2
|
||||
end
|
||||
|
||||
function dcsCommon.rotatePointAroundOriginRad(inX, inY, angle) -- angle in degrees
|
||||
local c = math.cos(angle)
|
||||
local s = math.sin(angle)
|
||||
@@ -2046,7 +1836,6 @@ dcsCommon.version = "2.9.8"
|
||||
if not cx then cx = 0 end
|
||||
if not cz then cz = 0 end
|
||||
local cy = cz
|
||||
--trigger.action.outText("+++dcsC:rotGrp cy,cy = "..cx .. "," .. cy, 30)
|
||||
|
||||
local rads = degrees * 3.14152 / 180
|
||||
do
|
||||
@@ -2066,7 +1855,6 @@ dcsCommon.version = "2.9.8"
|
||||
if not cx then cx = 0 end
|
||||
if not cz then cz = 0 end
|
||||
local cy = cz
|
||||
--trigger.action.outText("+++dcsC:rotGrp cy,cy = "..cx .. "," .. cy, 30)
|
||||
|
||||
local rads = degrees * 3.14152 / 180
|
||||
-- turns all units in group around the group's center by degrees.
|
||||
@@ -2080,9 +1868,6 @@ dcsCommon.version = "2.9.8"
|
||||
|
||||
-- may also want to increase heading by degrees
|
||||
theUnit.heading = theUnit.heading + rads
|
||||
-- now kill psi if it existed before
|
||||
-- theUnit.psi = nil
|
||||
-- better code: psi is always -heading. Nobody knows what psi is, though
|
||||
if theUnit.psi then
|
||||
theUnit.psi = -theUnit.heading
|
||||
end
|
||||
@@ -2247,33 +2032,27 @@ end
|
||||
end
|
||||
if not caseSensitive then theString = string.upper(theString) end
|
||||
|
||||
--trigger.action.outText("wildACS: theString = <" .. theString .. ">, theArray contains <" .. #theArray .. "> elements", 30)
|
||||
local wildIn = dcsCommon.stringEndsWith(theString, "*")
|
||||
if wildIn then dcsCommon.removeEnding(theString, "*") end
|
||||
for idx, theElement in pairs(theArray) do -- i = 1, #theArray do
|
||||
--local theElement = theArray[i]
|
||||
--trigger.action.outText("test e <" .. theElement .. "> against s <" .. theString .. ">", 30)
|
||||
if not caseSensitive then theElement = string.upper(theElement) end
|
||||
local wildEle = dcsCommon.stringEndsWith(theElement, "*")
|
||||
if wildEle then theElement = dcsCommon.removeEnding(theElement, "*") end
|
||||
--trigger.action.outText("matching s=<" .. theString .. "> with e=<" .. theElement .. ">", 30)
|
||||
|
||||
if wildEle and wildIn then
|
||||
-- both end on wildcards, partial match for both
|
||||
if dcsCommon.stringStartsWith(theElement, theString) then return true end
|
||||
if dcsCommon.stringStartsWith(theString, theElement) then return true end
|
||||
--trigger.action.outText("match e* with s* failed.", 30)
|
||||
elseif wildEle then
|
||||
-- Element is a wildcard, partial match
|
||||
if dcsCommon.stringStartsWith(theString, theElement) then return true end
|
||||
--trigger.action.outText("startswith - match e* <" .. theElement .. "> with s <" .. theString .. "> failed.", 30)
|
||||
|
||||
elseif wildIn then
|
||||
-- theString is a wildcard. partial match
|
||||
if dcsCommon.stringStartsWith(theElement, theString) then return true end
|
||||
--trigger.action.outText("match e with s* failed.", 30)
|
||||
else
|
||||
-- standard: no wildcards, full match
|
||||
if theElement == theString then return true end
|
||||
--trigger.action.outText("match e with s (straight) failed.", 30)
|
||||
end
|
||||
|
||||
end
|
||||
@@ -2412,7 +2191,7 @@ end
|
||||
|
||||
if caseInsensitive then
|
||||
theString = string.upper(theString)
|
||||
thePrefix = string.upper(theString)
|
||||
thePrefix = string.upper(thePrefix)
|
||||
end
|
||||
-- superseded: string.find (s, pattern [, init [, plain]]) solves the problem
|
||||
local i, j = string.find(theString, thePrefix, 1, true)
|
||||
@@ -2467,12 +2246,19 @@ end
|
||||
return 0
|
||||
end
|
||||
|
||||
function dcsCommon.point2text(p)
|
||||
function dcsCommon.point2text(p, intsOnly)
|
||||
if not intsOnly then intsOnly = false end
|
||||
if not p then return "<!NIL!>" end
|
||||
local t = "[x="
|
||||
if p.x then t = t .. p.x .. ", " else t = t .. "<nil>, " end
|
||||
if p.y then t = t .. "y=" .. p.y .. ", " else t = t .. "y=<nil>, " end
|
||||
if p.z then t = t .. "z=" .. p.z .. "]" else t = t .. "z=<nil>]" end
|
||||
if intsOnly then
|
||||
if p.x then t = t .. math.floor(p.x) .. ", " else t = t .. "<nil>, " end
|
||||
if p.y then t = t .. "y=" .. math.floor(p.y) .. ", " else t = t .. "y=<nil>, " end
|
||||
if p.z then t = t .. "z=" .. math.floor(p.z) .. "]" else t = t .. "z=<nil>]" end
|
||||
else
|
||||
if p.x then t = t .. p.x .. ", " else t = t .. "<nil>, " end
|
||||
if p.y then t = t .. "y=" .. p.y .. ", " else t = t .. "y=<nil>, " end
|
||||
if p.z then t = t .. "z=" .. p.z .. "]" else t = t .. "z=<nil>]" end
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
@@ -2605,7 +2391,6 @@ end
|
||||
if not inrecursion then
|
||||
-- output a marker to find in the log / screen
|
||||
trigger.action.outText("=== dcsCommon vardump end", 30)
|
||||
--env.info("=== dcsCommon vardump end")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2636,7 +2421,9 @@ end
|
||||
"BDA", "AI Abort Mission", "DayNight", "Flight Time", -- 40
|
||||
"Pilot Suicide", "player cap airfield", "emergency landing", "unit create task", -- 44
|
||||
"unit delete task", "Simulation start", "weapon rearm", "weapon drop", -- 48
|
||||
"unit task timeout", "unit task stage",
|
||||
"unit task timeout", "unit task stage", -- 50
|
||||
"subtask score", "extra score", "mission restart", "winner",
|
||||
"postponed takeoff", "postponed land", -- 56
|
||||
"max"}
|
||||
if id > #events then return "Unknown (ID=" .. id .. ")" end
|
||||
return events[id]
|
||||
@@ -2926,6 +2713,21 @@ function dcsCommon.getAllExistingPlayerUnitsRaw()
|
||||
return apu
|
||||
end
|
||||
|
||||
function dcsCommon.getAllExistingPlayersAndUnits() -- units indexed by player name
|
||||
-- designed to replace cases for cfxPlayer.getAllPlayer invocations
|
||||
local apu = {}
|
||||
for idx, theSide in pairs(dcsCommon.coalitionSides) do
|
||||
local thePlayers = coalition.getPlayers(theSide)
|
||||
for idy, theUnit in pairs (thePlayers) do
|
||||
if theUnit and theUnit:isExist() then
|
||||
local pName = theUnit:getPlayerName()
|
||||
apu[pName] = theUnit
|
||||
end
|
||||
end
|
||||
end
|
||||
return apu
|
||||
end
|
||||
|
||||
function dcsCommon.getUnitAlt(theUnit)
|
||||
if not theUnit then return 0 end
|
||||
if not Unit.isExist(theUnit) then return 0 end -- safer
|
||||
@@ -3028,16 +2830,6 @@ function dcsCommon.unitIsInfantry(theUnit)
|
||||
if not theUnit then return false end
|
||||
if not theUnit:isExist() then return end
|
||||
local theType = theUnit:getTypeName()
|
||||
--[[--
|
||||
local isInfantry =
|
||||
dcsCommon.containsString(theType, "infantry", false) or
|
||||
dcsCommon.containsString(theType, "paratrooper", false) or
|
||||
dcsCommon.containsString(theType, "stinger", false) or
|
||||
dcsCommon.containsString(theType, "manpad", false) or
|
||||
dcsCommon.containsString(theType, "soldier", false) or
|
||||
dcsCommon.containsString(theType, "SA-18 Igla", false)
|
||||
return isInfantry
|
||||
--]]--
|
||||
return dcsCommon.typeIsInfantry(theType)
|
||||
end
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
delayFlag = {}
|
||||
delayFlag.version = "1.4.0"
|
||||
delayFlag.version = "2.0.0"
|
||||
delayFlag.verbose = false
|
||||
delayFlag.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
@@ -11,35 +11,12 @@ delayFlag.flags = {}
|
||||
delay flags - simple flag switch & delay, allows for randomize
|
||||
and dead man switching
|
||||
|
||||
Copyright (c) 2022 by Christian Franz and cf/x AG
|
||||
Copyright (c) 2022-2024 by Christian Franz and cf/x AG
|
||||
|
||||
Version History
|
||||
1.0.0 - Initial Version
|
||||
1.0.1 - message attribute
|
||||
1.0.2 - slight spelling correction
|
||||
- using cfxZones for polling
|
||||
- removed pollFlag
|
||||
1.0.3 - bug fix for config zone name
|
||||
- removed message attribute, moved to own module
|
||||
- triggerFlag --> triggerDelayFlag
|
||||
1.0.4 - startDelay
|
||||
1.1.0 - DML flag upgrade
|
||||
- removed onStart. use local raiseFlag instead
|
||||
- delayDone! synonym
|
||||
- pauseDelay?
|
||||
- unpauseDelay?
|
||||
1.2.0 - Watchflags
|
||||
1.2.1 - method goes to dlyMethod
|
||||
- delay done is correctly inited
|
||||
1.2.2 - delayMethod defaults to inc
|
||||
- zone-local verbosity
|
||||
- code clean-up
|
||||
1.2.3 - pauseDelay
|
||||
- continueDelay
|
||||
- delayLeft
|
||||
1.3.0 - persistence
|
||||
1.4.0 - dmlZones
|
||||
- delayLeft#
|
||||
2.0.0 - clean-up
|
||||
|
||||
--]]--
|
||||
|
||||
@@ -317,7 +294,7 @@ function delayFlag.readConfigZone()
|
||||
theZone = cfxZones.createSimpleZone("delayFlagsConfig")
|
||||
end
|
||||
|
||||
delayFlag.verbose = theZone.verbose -- cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
|
||||
delayFlag.verbose = theZone.verbose
|
||||
|
||||
if delayFlag.verbose then
|
||||
trigger.action.outText("+++dlyF: read config", 30)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
factoryZone = {}
|
||||
factoryZone.version = "2.0.0"
|
||||
factoryZone.version = "3.0.0"
|
||||
factoryZone.verbose = false
|
||||
factoryZone.name = "factoryZone"
|
||||
|
||||
@@ -9,6 +9,8 @@ factoryZone.name = "factoryZone"
|
||||
- "production" and "defenders" simplification
|
||||
- now optional specification for red/blue
|
||||
- use maxRadius from zone for spawning to support quad zones
|
||||
3.0.0 - support for liveries via "factoryLiveries" zone
|
||||
- OOP dmlZones
|
||||
|
||||
--]]--
|
||||
factoryZone.requiredLibs = {
|
||||
@@ -20,6 +22,7 @@ factoryZone.requiredLibs = {
|
||||
}
|
||||
|
||||
factoryZone.zones = {} -- my factory zones
|
||||
factoryZone.liveries = {} -- indexed by type name
|
||||
factoryZone.ups = 1
|
||||
factoryZone.initialized = false
|
||||
factoryZone.defendingTime = 100 -- seconds until new defenders are produced
|
||||
@@ -28,7 +31,7 @@ factoryZone.shockTime = 200 -- 'shocked' period of inactivity
|
||||
factoryZone.repairTime = 200 -- time until we raplace one lost unit, also repairs all other units to 100%
|
||||
|
||||
-- persistence: all attackers we ever sent out.
|
||||
-- is regularly verified and cut to size
|
||||
-- is regularly verified and cut to size via GC
|
||||
factoryZone.spawnedAttackers = {}
|
||||
|
||||
-- factoryZone is a module that manages production of units
|
||||
@@ -38,24 +41,6 @@ factoryZone.spawnedAttackers = {}
|
||||
--
|
||||
-- *** EXTENTDS ZONES ***
|
||||
--
|
||||
-- zone attributes when owned
|
||||
-- owner: coalition that owns the zone. Managed externally
|
||||
-- status: FSM for spawning
|
||||
-- defendersRED/BLUE - coma separated type string for the group to spawn on defense cycle completion
|
||||
-- attackersRED/BLUE - as above for attack cycle.
|
||||
-- timeStamp - time when zone switched into current state
|
||||
-- spawnRadius - overrides zone's radius when placing defenders. can be use to place defenders inside or outside zone itself
|
||||
-- formation - defender's formation
|
||||
-- attackFormation - attackers formation
|
||||
-- attackRadius - radius of circle in which attackers are spawned. informs formation
|
||||
-- attackDelta - polar coord: r from zone center where attackers are spawned
|
||||
-- attackPhi - polar degrees where attackers are to be spawned
|
||||
-- paused - will not spawn. default is false
|
||||
|
||||
--
|
||||
-- M I S C
|
||||
--
|
||||
|
||||
|
||||
function factoryZone.getFactoryZoneByName(zName)
|
||||
for zKey, theZone in pairs (factoryZone.zones) do
|
||||
@@ -65,59 +50,58 @@ function factoryZone.getFactoryZoneByName(zName)
|
||||
end
|
||||
|
||||
function factoryZone.addFactoryZone(aZone)
|
||||
--aZone.worksFor = cfxZones.getCoalitionFromZoneProperty(aZone, "factory", 0) -- currently unused, have RED/BLUE separate types
|
||||
aZone.state = "init"
|
||||
aZone.timeStamp = timer.getTime()
|
||||
|
||||
-- set up production default
|
||||
local factory = cfxZones.getStringFromZoneProperty(aZone, "factory", "none")
|
||||
local factory = aZone:getStringFromZoneProperty("factory", "none")
|
||||
|
||||
local production = cfxZones.getStringFromZoneProperty(aZone, "production", factory)
|
||||
local production = aZone:getStringFromZoneProperty("production", factory)
|
||||
|
||||
local defenders = cfxZones.getStringFromZoneProperty(aZone, "defenders", factory)
|
||||
local defenders = aZone:getStringFromZoneProperty("defenders", factory)
|
||||
|
||||
if cfxZones.hasProperty(aZone, "attackersRED") then
|
||||
if aZone:hasProperty("attackersRED") then
|
||||
-- legacy support
|
||||
aZone.attackersRED = cfxZones.getStringFromZoneProperty(aZone, "attackersRED", production)
|
||||
aZone.attackersRED = aZone:getStringFromZoneProperty( "attackersRED", production)
|
||||
else
|
||||
aZone.attackersRED = cfxZones.getStringFromZoneProperty(aZone, "productionRED", production)
|
||||
aZone.attackersRED = aZone:getStringFromZoneProperty( "productionRED", production)
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(aZone, "attackersBLUE") then
|
||||
if aZone:hasProperty("attackersBLUE") then
|
||||
-- legacy support
|
||||
aZone.attackersBLUE = cfxZones.getStringFromZoneProperty(aZone, "attackersBLUE", production)
|
||||
aZone.attackersBLUE = aZone:getStringFromZoneProperty( "attackersBLUE", production)
|
||||
else
|
||||
aZone.attackersBLUE = cfxZones.getStringFromZoneProperty(aZone, "productionBLUE", production)
|
||||
aZone.attackersBLUE = aZone:getStringFromZoneProperty( "productionBLUE", production)
|
||||
end
|
||||
|
||||
-- set up defenders default, or use production / factory
|
||||
aZone.defendersRED = cfxZones.getStringFromZoneProperty(aZone, "defendersRED", defenders)
|
||||
aZone.defendersBLUE = cfxZones.getStringFromZoneProperty(aZone, "defendersBLUE", defenders)
|
||||
aZone.defendersRED = aZone:getStringFromZoneProperty("defendersRED", defenders)
|
||||
aZone.defendersBLUE = aZone:getStringFromZoneProperty("defendersBLUE", defenders)
|
||||
|
||||
aZone.formation = cfxZones.getStringFromZoneProperty(aZone, "formation", "circle_out")
|
||||
aZone.attackFormation = cfxZones.getStringFromZoneProperty(aZone, "attackFormation", "circle_out") -- cfxZones.getZoneProperty(aZone, "attackFormation")
|
||||
aZone.spawnRadius = cfxZones.getNumberFromZoneProperty(aZone, "spawnRadius", aZone.maxRadius-5) -- "-5" so they remaininside radius
|
||||
aZone.attackRadius = cfxZones.getNumberFromZoneProperty(aZone, "attackRadius", aZone.maxRadius)
|
||||
aZone.attackDelta = cfxZones.getNumberFromZoneProperty(aZone, "attackDelta", 10) -- aZone.radius)
|
||||
aZone.attackPhi = cfxZones.getNumberFromZoneProperty(aZone, "attackPhi", 0)
|
||||
aZone.formation = aZone:getStringFromZoneProperty("formation", "circle_out")
|
||||
aZone.attackFormation = aZone:getStringFromZoneProperty( "attackFormation", "circle_out") -- cfxZones.getZoneProperty(aZone, "attackFormation")
|
||||
aZone.spawnRadius = aZone:getNumberFromZoneProperty("spawnRadius", aZone.maxRadius-5) -- "-5" so they remaininside radius
|
||||
aZone.attackRadius = aZone:getNumberFromZoneProperty("attackRadius", aZone.maxRadius)
|
||||
aZone.attackDelta = aZone:getNumberFromZoneProperty("attackDelta", 10) -- aZone.radius)
|
||||
aZone.attackPhi = aZone:getNumberFromZoneProperty("attackPhi", 0)
|
||||
|
||||
aZone.paused = cfxZones.getBoolFromZoneProperty(aZone, "paused", false)
|
||||
aZone.paused = aZone:getBoolFromZoneProperty("paused", false)
|
||||
aZone.factoryOwner = aZone.owner -- copy so we can compare next round
|
||||
|
||||
-- pause? and activate?
|
||||
if cfxZones.hasProperty(aZone, "pause?") then
|
||||
aZone.pauseFlag = cfxZones.getStringFromZoneProperty(aZone, "pause?", "none")
|
||||
if aZone:hasProperty("pause?") then
|
||||
aZone.pauseFlag = aZone:getStringFromZoneProperty("pause?", "none")
|
||||
aZone.lastPauseValue = trigger.misc.getUserFlag(aZone.pauseFlag)
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(aZone, "activate?") then
|
||||
aZone.activateFlag = cfxZones.getStringFromZoneProperty(aZone, "activate?", "none")
|
||||
if aZone:hasProperty("activate?") then
|
||||
aZone.activateFlag = aZone:getStringFromZoneProperty("activate?", "none")
|
||||
aZone.lastActivateValue = trigger.misc.getUserFlag(aZone.activateFlag)
|
||||
end
|
||||
|
||||
aZone.factoryTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "triggerMethod", "change")
|
||||
if cfxZones.hasProperty(aZone, "factoryTriggerMethod") then
|
||||
aZone.factoryTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "factoryTriggerMethod", "change")
|
||||
aZone.factoryTriggerMethod = aZone:getStringFromZoneProperty( "triggerMethod", "change")
|
||||
if aZone:hasProperty("factoryTriggerMethod") then
|
||||
aZone.factoryTriggerMethod = aZone:getStringFromZoneProperty( "factoryTriggerMethod", "change")
|
||||
end
|
||||
|
||||
factoryZone.zones[aZone.name] = aZone
|
||||
@@ -159,11 +143,12 @@ function factoryZone.spawnAttackTroops(theTypes, aZone, aCoalition, aFormation)
|
||||
|
||||
local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
|
||||
aCoalition, -- theCountry,
|
||||
aZone.name .. " (A) " .. dcsCommon.numberUUID(), -- must be unique
|
||||
spawnZone,
|
||||
unitTypes,
|
||||
aZone.name .. " (A) " .. dcsCommon.numberUUID(),
|
||||
spawnZone,
|
||||
unitTypes,
|
||||
aFormation, -- outward facing
|
||||
0)
|
||||
0,
|
||||
factoryZone.liveries)
|
||||
return theGroup, theData
|
||||
end
|
||||
|
||||
@@ -184,10 +169,12 @@ function factoryZone.spawnDefensiveTroops(theTypes, aZone, aCoalition, aFormatio
|
||||
local spawnZone = cfxZones.createSimpleZone("spawnZone", aZone.point, aZone.spawnRadius)
|
||||
local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
|
||||
aCoalition, --theCountry,
|
||||
aZone.name .. " (D) " .. dcsCommon.numberUUID(), -- must be unique
|
||||
spawnZone, unitTypes,
|
||||
aZone.name .. " (D) " .. dcsCommon.numberUUID(),
|
||||
spawnZone,
|
||||
unitTypes,
|
||||
aFormation, -- outward facing
|
||||
0)
|
||||
0,
|
||||
factoryZone.liveries)
|
||||
return theGroup, theData
|
||||
end
|
||||
|
||||
@@ -308,7 +295,8 @@ function factoryZone.repairDefenders(aZone)
|
||||
livingTypes,
|
||||
|
||||
aZone.formation, -- outward facing
|
||||
0)
|
||||
0,
|
||||
factoryZone.liveries)
|
||||
aZone.defenders = theGroup
|
||||
aZone.lastDefenders = theGroup:getSize()
|
||||
end
|
||||
@@ -686,15 +674,26 @@ end
|
||||
function factoryZone.readConfigZone(theZone)
|
||||
if not theZone then theZone = cfxZones.createSimpleZone("factoryZoneConfig") end
|
||||
factoryZone.name = "factoryZone" -- just in case, so we can access with cfxZones
|
||||
factoryZone.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
|
||||
factoryZone.defendingTime = cfxZones.getNumberFromZoneProperty(theZone, "defendingTime", 100)
|
||||
factoryZone.attackingTime = cfxZones.getNumberFromZoneProperty(theZone, "attackingTime", 300)
|
||||
factoryZone.shockTime = cfxZones.getNumberFromZoneProperty(theZone, "shockTime", 200)
|
||||
factoryZone.repairTime = cfxZones.getNumberFromZoneProperty(theZone, "repairTime", 200)
|
||||
factoryZone.verbose = theZone.verbose
|
||||
factoryZone.defendingTime = theZone:getNumberFromZoneProperty( "defendingTime", 100)
|
||||
factoryZone.attackingTime = theZone:getNumberFromZoneProperty( "attackingTime", 300)
|
||||
factoryZone.shockTime = theZone:getNumberFromZoneProperty("shockTime", 200)
|
||||
factoryZone.repairTime = theZone:getNumberFromZoneProperty( "repairTime", 200)
|
||||
factoryZone.targetZones = "OWNED"
|
||||
|
||||
end
|
||||
|
||||
function factoryZone.readLiveries()
|
||||
theZone = cfxZones.getZoneByName("factoryLiveries")
|
||||
if not theZone then return end
|
||||
factoryZone.liveries = theZone:getAllZoneProperties()
|
||||
trigger.action.outText("Custom liveries detected. All factories now use:", 30)
|
||||
for aType, aLivery in pairs (factoryZone.liveries) do
|
||||
trigger.action.outText(" type <" .. aType .. "> now uses livery <" .. aLivery .. ">", 30)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function factoryZone.init()
|
||||
-- check libs
|
||||
if not dcsCommon.libCheck("cfx Factory Zones",
|
||||
@@ -706,12 +705,12 @@ function factoryZone.init()
|
||||
local theZone = cfxZones.getZoneByName("factoryZoneConfig")
|
||||
factoryZone.readConfigZone(theZone)
|
||||
|
||||
-- read livery presets for factory production
|
||||
factoryZone.readLiveries()
|
||||
|
||||
-- collect all zones by their 'factory' property
|
||||
-- start the process
|
||||
local pZones = cfxZones.zonesWithProperty("factory")
|
||||
|
||||
-- now add all zones to my zones table, and convert the owner property into
|
||||
-- a proper attribute
|
||||
for k, aZone in pairs(pZones) do
|
||||
factoryZone.addFactoryZone(aZone)
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
fireFX = {}
|
||||
fireFX.version = "1.1.0"
|
||||
fireFX.version = "2.0.1"
|
||||
fireFX.verbose = false
|
||||
fireFX.ups = 1
|
||||
fireFX.requiredLibs = {
|
||||
@@ -15,7 +15,7 @@ fireFX.fx = {}
|
||||
1.1.1 - agl attribute
|
||||
2.0.0 - dmlZones OOP
|
||||
- rndLoc
|
||||
|
||||
2.0.1 - fixed rndLoc determination
|
||||
--]]--
|
||||
|
||||
function fireFX.addFX(theZone)
|
||||
@@ -92,9 +92,9 @@ function fireFX.createFXWithZone(theZone)
|
||||
end
|
||||
|
||||
theZone.rndLoc = theZone:getBoolFromZoneProperty("rndLoc", false)
|
||||
if theZone.max + 1 and (not theZone.rndLoc) then
|
||||
if theZone.max > 1 and (not theZone.rndLoc) then
|
||||
if theZone.verbose or fireFX.verbose then
|
||||
trigger.action.outText("+++ffx: more than 1 fires, will set to random loc")
|
||||
trigger.action.outText("+++ffx: more than 1 fires, will set to random loc", 30)
|
||||
end
|
||||
theZone.rndLoc = true
|
||||
end
|
||||
@@ -113,7 +113,7 @@ function fireFX.startTheFire(theZone)
|
||||
theZone.fireNames = {}
|
||||
local num = cfxZones.randomInRange(theZone.min, theZone.max)
|
||||
for i = 1, num do
|
||||
local p = cfxZones.getPoint(theZone)
|
||||
local p = theZone:getPoint()
|
||||
if theZone.rndLoc then
|
||||
p = theZone:randomPointInZone()
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
groupTracker = {}
|
||||
groupTracker.version = "1.2.1"
|
||||
groupTracker.version = "2.0.0"
|
||||
groupTracker.verbose = false
|
||||
groupTracker.ups = 1
|
||||
groupTracker.requiredLibs = {
|
||||
@@ -10,28 +10,7 @@ groupTracker.trackers = {}
|
||||
|
||||
--[[--
|
||||
Version History
|
||||
1.0.0 - Initial version
|
||||
1.1.0 - filtering added
|
||||
- array support for trackers
|
||||
- array support for trackers
|
||||
1.1.1 - corrected clone zone reference bug
|
||||
1.1.2 - corrected naming (removed bang from flags), deprecated old
|
||||
- more zone-local verbosity
|
||||
1.1.3 - spellings
|
||||
- addGroupToTrackerNamed bug removed accessing tracker
|
||||
- new removeGroupNamedFromTrackerNamed()
|
||||
1.1.4 - destroy? input
|
||||
- allGone! output
|
||||
- triggerMethod
|
||||
- method
|
||||
- isDead optimiz ation
|
||||
1.2.0 - double detection
|
||||
- numUnits output
|
||||
- persistence
|
||||
1.2.1 - allGone! bug removed
|
||||
1.2.2 - new groupTrackedBy() method
|
||||
- limbo for storing a unit in limbo so it is
|
||||
- not counted as missing when being transported
|
||||
2.0.0 - dmlZones, OOP, clean-up, legacy support
|
||||
|
||||
--]]--
|
||||
|
||||
@@ -259,62 +238,7 @@ function groupTracker.removeGroupNamedFromTrackerNamed(gName, trackerName)
|
||||
end
|
||||
|
||||
groupTracker.removeGroupNamedFromTracker(gName, theTracker)
|
||||
--[[--
|
||||
local filteredGroups = {}
|
||||
local foundOne = false
|
||||
local totalUnits = 0
|
||||
if not theTracker.trackedGroups then theTracker.trackedGroups = {} end
|
||||
for idx, aGroup in pairs(theTracker.trackedGroups) do
|
||||
if aGroup:getName() == gName then
|
||||
-- skip and remember
|
||||
foundOne = true
|
||||
else
|
||||
table.insert(filteredGroups, aGroup)
|
||||
if Group.isExist(aGroup) then
|
||||
totalUnits = totalUnits + aGroup:getSize()
|
||||
end
|
||||
end
|
||||
end
|
||||
-- also check limbo
|
||||
for limboName, limboNum in pairs (theTracker.limbo) do
|
||||
if gName == limboName then
|
||||
-- don't count, but remember that it existed
|
||||
foundOne = true
|
||||
else
|
||||
totalUnits = totalUnits + limboNum
|
||||
end
|
||||
end
|
||||
-- remove from limbo
|
||||
theTracker.limbo[gName] = nil
|
||||
|
||||
if (not foundOne) and (theTracker.verbose or groupTracker.verbose) then
|
||||
trigger.action.outText("+++gTrk: Removal Request Note: group <" .. gName .. "> wasn't tracked by <" .. trackerName .. ">", 30)
|
||||
end
|
||||
|
||||
-- remember the new, cleanded set
|
||||
theTracker.trackedGroups = filteredGroups
|
||||
|
||||
-- update number of tracked units. do it in any case
|
||||
if theTracker.tNumUnits then
|
||||
cfxZones.setFlagValue(theTracker.tNumUnits, totalUnits, theTracker)
|
||||
end
|
||||
|
||||
if foundOne then
|
||||
if theTracker.verbose or groupTracker.verbose then
|
||||
trigger.action.outText("+++gTrk: removed group <" .. gName .. "> from tracker <" .. trackerName .. ">", 30)
|
||||
end
|
||||
|
||||
-- now bang/invoke addGroup!
|
||||
if theTracker.tRemoveGroup then
|
||||
cfxZones.pollFlag(theTracker.tRemoveGroup, "inc", theTracker)
|
||||
end
|
||||
|
||||
-- now set numGroups
|
||||
if theTracker.tNumGroups then
|
||||
cfxZones.setFlagValue(theTracker.tNumGroups, dcsCommon.getSizeOfTable(theTracker.limbo) + #theTracker.trackedGroups, theTracker)
|
||||
end
|
||||
end
|
||||
--]]--
|
||||
|
||||
end
|
||||
|
||||
-- groupTrackedBy - return trackers that track group theGroup
|
||||
@@ -365,62 +289,56 @@ function groupTracker.createTrackerWithZone(theZone)
|
||||
theZone.limbo = {} -- name based, for groups that are tracked
|
||||
-- although technically off the map (helo etc)
|
||||
|
||||
theZone.trackerMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "inc")
|
||||
if cfxZones.hasProperty(theZone, "trackerMethod") then
|
||||
theZone.trackerMethod = cfxZones.getStringFromZoneProperty(theZone, "trackerMethod", "inc")
|
||||
theZone.trackerMethod = theZone:getStringFromZoneProperty("method", "inc")
|
||||
if theZone:hasProperty("trackerMethod") then
|
||||
theZone.trackerMethod = theZone:getStringFromZoneProperty( "trackerMethod", "inc")
|
||||
end
|
||||
|
||||
theZone.trackerTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "triggerMethod", "change")
|
||||
theZone.trackerTriggerMethod = theZone:getStringFromZoneProperty("triggerMethod", "change")
|
||||
|
||||
if cfxZones.hasProperty(theZone, "trackerTriggerMethod") then
|
||||
theZone.trackerTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "trackerTriggerMethod", "change")
|
||||
if theZone:hasProperty("trackerTriggerMethod") then
|
||||
theZone.trackerTriggerMethod = theZone:getStringFromZoneProperty("trackerTriggerMethod", "change")
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "numGroups") then
|
||||
theZone.tNumGroups = cfxZones.getStringFromZoneProperty(theZone, "numGroups", "*<none>")
|
||||
-- we may need to zero this flag
|
||||
elseif cfxZones.hasProperty(theZone, "numGroups!") then -- DEPRECATED!
|
||||
theZone.tNumGroups = cfxZones.getStringFromZoneProperty(theZone, "numGroups!", "*<none>")
|
||||
-- we may need to zero this flag
|
||||
if theZone:hasProperty("numGroups") then
|
||||
theZone.tNumGroups = theZone:getStringFromZoneProperty("numGroups", "*<none>") -- legacy support
|
||||
elseif theZone:hasProperty("numGroups#") then
|
||||
theZone.tNumGroups = theZone:getStringFromZoneProperty( "numGroups#", "*<none>")
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "numUnits") then
|
||||
theZone.tNumUnits = cfxZones.getStringFromZoneProperty(theZone, "numUnits", "*<none>")
|
||||
|
||||
if theZone:hasProperty("numUnits") then
|
||||
theZone.tNumUnits = theZOne:getStringFromZoneProperty("numUnits", "*<none>") -- legacy support
|
||||
elseif theZone:hasProperty("numUnits#") then
|
||||
theZone.tNumUnits = theZone:getStringFromZoneProperty("numUnits#", "*<none>")
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "addGroup") then
|
||||
theZone.tAddGroup = cfxZones.getStringFromZoneProperty(theZone, "addGroup", "*<none>")
|
||||
-- we may need to zero this flag
|
||||
elseif cfxZones.hasProperty(theZone, "addGroup!") then -- DEPRECATED
|
||||
theZone.tAddGroup = cfxZones.getStringFromZoneProperty(theZone, "addGroup!", "*<none>")
|
||||
-- we may need to zero this flag
|
||||
if theZone:hasProperty("addGroup") then
|
||||
theZone.tAddGroup = theZone:getStringFromZoneProperty("addGroup", "*<none>") -- legacy support
|
||||
elseif theZone:hasProperty("addGroup!") then
|
||||
theZone.tAddGroup = theZone:getStringFromZoneProperty("addGroup!", "*<none>")
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "removeGroup") then
|
||||
theZone.tRemoveGroup = cfxZones.getStringFromZoneProperty(theZone, "removeGroup", "*<none>")
|
||||
-- we may need to zero this flag
|
||||
elseif cfxZones.hasProperty(theZone, "removeGroup!") then -- DEPRECATED!
|
||||
theZone.tRemoveGroup = cfxZones.getStringFromZoneProperty(theZone, "removeGroup!", "*<none>")
|
||||
-- we may need to zero this flag
|
||||
if theZone:hasProperty("removeGroup") then
|
||||
theZone.tRemoveGroup = theZone:getStringFromZoneProperty( "removeGroup", "*<none>") -- legacy support
|
||||
elseif theZone:hasProperty("removeGroup!") then
|
||||
theZone.tRemoveGroup = theZone:getStringFromZoneProperty( "removeGroup!", "*<none>")
|
||||
end
|
||||
|
||||
|
||||
|
||||
if cfxZones.hasProperty(theZone, "groupFilter") then
|
||||
local filterString = cfxZones.getStringFromZoneProperty(theZone, "groupFilter", "2") -- ground
|
||||
|
||||
if theZone:hasProperty("groupFilter") then
|
||||
local filterString = theZone:getStringFromZoneProperty( "groupFilter", "2") -- ground
|
||||
theZone.groupFilter = dcsCommon.string2GroupCat(filterString)
|
||||
if groupTracker.verbose or theZone.verbose then
|
||||
trigger.action.outText("+++gTrck: filtering " .. theZone.groupFilter .. " in " .. theZone.name, 30)
|
||||
end
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "destroy?") then
|
||||
theZone.destroyFlag = cfxZones.getStringFromZoneProperty(theZone, "destroy?", "*<none>")
|
||||
if theZone:hasProperty("destroy?") then
|
||||
theZone.destroyFlag = theZone:getStringFromZoneProperty(theZone, "destroy?", "*<none>")
|
||||
theZone.lastDestroyValue = cfxZones.getFlagValue(theZone.destroyFlag, theZone)
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "allGone!") then
|
||||
theZone.allGoneFlag = cfxZones.getStringFromZoneProperty(theZone, "allGone!", "<None>") -- note string on number default
|
||||
if theZone:hasProperty("allGone!") then
|
||||
theZone.allGoneFlag = theZone:getStringFromZoneProperty("allGone!", "<None>") -- note string on number default
|
||||
end
|
||||
theZone.lastGroupCount = 0
|
||||
|
||||
@@ -652,15 +570,12 @@ end
|
||||
function groupTracker.readConfigZone()
|
||||
local theZone = cfxZones.getZoneByName("groupTrackerConfig")
|
||||
if not theZone then
|
||||
if groupTracker.verbose then
|
||||
trigger.action.outText("+++gTrk: NO config zone!", 30)
|
||||
end
|
||||
return
|
||||
theZone = cfxZones.createSimpleZone("groupTrackerConfig")
|
||||
end
|
||||
|
||||
groupTracker.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
|
||||
groupTracker.verbose = theZoneverbose
|
||||
|
||||
groupTracker.ups = cfxZones.getNumberFromZoneProperty(theZone, "ups", 1)
|
||||
groupTracker.ups = theZone:getNumberFromZoneProperty("ups", 1)
|
||||
|
||||
if groupTracker.verbose then
|
||||
trigger.action.outText("+++gTrk: read config", 30)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
messenger = {}
|
||||
messenger.version = "2.3.1"
|
||||
messenger.version = "3.0.0"
|
||||
messenger.verbose = false
|
||||
messenger.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
@@ -8,68 +8,9 @@ messenger.requiredLibs = {
|
||||
messenger.messengers = {}
|
||||
--[[--
|
||||
Version History
|
||||
1.0.0 - initial version
|
||||
1.0.1 - messageOut? synonym
|
||||
- spelling types in about
|
||||
1.1.0 - DML flag support
|
||||
- clearScreen option
|
||||
- inValue?
|
||||
- message preprocessor
|
||||
1.1.1 - firewalled coalition to msgCoalition
|
||||
- messageOn?
|
||||
- messageOff?
|
||||
1.2.0 - msgTriggerMethod (original Watchflag integration)
|
||||
1.2.1 - qoL: <n> = newline, <z> = zone name, <v> = value
|
||||
1.3.0 - messenger? saves messageOut? attribute
|
||||
1.3.1 - message now can interpret value as time with <h> <m> <s> <:h> <:m> <:s>
|
||||
1.3.2 - message interprets <t> as time in HH:MM:SS of current time
|
||||
- can interpret <lat>, <lon>, <mgrs>
|
||||
- zone-local verbosity
|
||||
1.3.3 - mute/messageMute option to start messenger in mute
|
||||
2.0.0 - re-engineered message wildcards
|
||||
- corrected dynamic content for time and latlon (classic)
|
||||
- new timeFormat attribute
|
||||
- <v: flagname>
|
||||
- <t: flagname>
|
||||
- added <ele>
|
||||
- added imperial
|
||||
- <lat: unit/zone>
|
||||
- <lon: unit/zone>
|
||||
- <ele: unit/zone>
|
||||
- <mgrs: unit/zone>
|
||||
- <latlon: unit/zone>
|
||||
- <lle: unit/zone>
|
||||
- messageError
|
||||
- unit
|
||||
- group
|
||||
2.0.1 - config optimization
|
||||
2.1.0 - unit only: dynamicUnitProcessing with other units/zones
|
||||
- <bae: u/z> bearing to unit/zone
|
||||
- <rbae u/z> response mapped by unit's heading
|
||||
- <clk: u/z> bearing in clock position to unit/zone
|
||||
- <rng: u/z> range to unit/zone
|
||||
- <hnd: u/z> bearing in left/right/ahead/behind
|
||||
- <sde: u/z> bearing in starboard/port/ahead/aft
|
||||
- added dynamicGroupProcessing to select unit 1
|
||||
- responses attribute
|
||||
- <rsp: flag>
|
||||
- <rrnd> response randomized
|
||||
- <rhdg: u/z> respons mapped by unit's heading
|
||||
- <cls unit> closing speed
|
||||
- <vel unit> velocity (speed)
|
||||
- <asp unit> aspect
|
||||
- fix to messageMute
|
||||
- <type: unit>
|
||||
2.1.1 - cosmetic: only output text if len>0 and not cls
|
||||
2.2.0 - <player: unit>
|
||||
- made dynamic string gen more portable in prep for move to cfxZones
|
||||
- refactoring wildcard processing: moved to cfxZones
|
||||
2.2.1 - when messenger is linked to a unit, it can use the linked
|
||||
unit as reference point for relative wildcards. Always broadcasts to coalition. Can be used to broadcase 'eye in the sky' type information
|
||||
- fixed verbosity bug
|
||||
2.3.0 - cfxZones OOP switch
|
||||
2.3.1 - triggering message AFTER the on/off switches are tested
|
||||
|
||||
3.0.0 - removed messenger, in?, f? attributes, harmonized on messenger?
|
||||
--]]--
|
||||
|
||||
function messenger.addMessenger(theZone)
|
||||
@@ -255,11 +196,11 @@ function messenger.createMessengerWithZone(theZone)
|
||||
if theZone:hasProperty("in?") then
|
||||
theZone.triggerMessagerFlag = theZone:getStringFromZoneProperty("in?", "none")
|
||||
end
|
||||
|
||||
--[[--
|
||||
if theZone:hasProperty("messageOut?") then
|
||||
theZone.triggerMessagerFlag = theZone:getStringFromZoneProperty("messageOut?", "none")
|
||||
end
|
||||
|
||||
--]]--
|
||||
-- try default only if no other is set
|
||||
if not theZone.triggerMessagerFlag then
|
||||
if not theZone:hasProperty("messenger?") then
|
||||
@@ -505,12 +446,13 @@ function messenger.start()
|
||||
|
||||
-- process messenger Zones
|
||||
-- old style
|
||||
--[[--
|
||||
local attrZones = cfxZones.getZonesWithAttributeNamed("messenger")
|
||||
for k, aZone in pairs(attrZones) do
|
||||
messenger.createMessengerWithZone(aZone) -- process attributes
|
||||
messenger.addMessenger(aZone) -- add to list
|
||||
end
|
||||
|
||||
--]]--
|
||||
-- new style that saves messageOut? flag by reading flags
|
||||
attrZones = cfxZones.getZonesWithAttributeNamed("messenger?")
|
||||
for k, aZone in pairs(attrZones) do
|
||||
|
||||
@@ -17,6 +17,7 @@ cfxObjectDestructDetector.requiredLibs = {
|
||||
re-wrote object determination to not be affected by
|
||||
ID changes (happens with map updates)
|
||||
fail addZone when name property is missing
|
||||
2.0.1 check that the object is within the zone onEvent
|
||||
--]]--
|
||||
|
||||
cfxObjectDestructDetector.objectZones = {}
|
||||
@@ -86,6 +87,8 @@ function cfxObjectDestructDetector:onEvent(event)
|
||||
if event.id == world.event.S_EVENT_DEAD then
|
||||
if not event.initiator then return end
|
||||
local theObject = event.initiator
|
||||
-- check location
|
||||
local pos = theObject:getPoint()
|
||||
local desc = theObject:getDesc()
|
||||
if not desc then return end
|
||||
local matchMe = desc.typeName -- we home in on object's typeName
|
||||
@@ -93,7 +96,10 @@ function cfxObjectDestructDetector:onEvent(event)
|
||||
matchMe = string.upper(matchMe)
|
||||
|
||||
for idx, aZone in pairs(cfxObjectDestructDetector.objectZones) do
|
||||
if (not aZone.isDestroyed) and aZone.objName == matchMe then
|
||||
if (not aZone.isDestroyed)
|
||||
and aZone.objName == matchMe
|
||||
and aZone:pointInZone(pos) -- make sure it's not a dupe
|
||||
then
|
||||
if aZone.outDestroyFlag then
|
||||
aZone:pollFlag(aZone.outDestroyFlag, aZone.oddMethod)
|
||||
end
|
||||
@@ -13,25 +13,6 @@ cfxObjectSpawnZones.verbose = false
|
||||
*** DOES NOT EXTEND ZONES ***
|
||||
|
||||
version history
|
||||
1.0.0 - based on 1.4.6 version from cfxSpawnZones
|
||||
1.1.0 - uses linkedUnit, so spawnming can occur on ships
|
||||
make sure you also enable useOffset to place the
|
||||
statics away from the center of the ship
|
||||
1.1.1 - also processes paused flag
|
||||
- despawnRemaining(spawner)
|
||||
1.1.2 - autoRemove option re-installed
|
||||
- added possibility to autoUnlink
|
||||
1.1.3 - ME-triggered flag via f? and triggerFlag
|
||||
1.1.4 - activate?, pause? attributes
|
||||
1.1.5 - spawn?, spawnObjects? synonyms
|
||||
1.2.0 - DML flag upgrade
|
||||
1.2.1 - config zone
|
||||
- autoLink bug (zone instead of spawner accessed)
|
||||
1.3.0 - better synonym handling
|
||||
- useDelicates link to delicate when spawned
|
||||
- spawned single and multi-objects can be made delicates
|
||||
1.3.1 - baseName can be set to zone's name by giving "*"
|
||||
1.3.2 - delicateName supports '*' to refer to own zone
|
||||
2.0.0 - dmlZones
|
||||
|
||||
--]]--
|
||||
@@ -1,5 +1,5 @@
|
||||
persistence = {}
|
||||
persistence.version = "1.0.7"
|
||||
persistence.version = "2.0.0"
|
||||
persistence.ups = 1 -- once every 1 seconds
|
||||
persistence.verbose = false
|
||||
persistence.active = false
|
||||
@@ -10,39 +10,25 @@ persistence.saveDir = nil -- set at start
|
||||
persistence.name = "persistence" -- for cfxZones
|
||||
persistence.missionData = {} -- loaded from file
|
||||
persistence.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
"cfxZones", -- Zones, of course
|
||||
"dcsCommon",
|
||||
"cfxZones",
|
||||
}
|
||||
--[[--
|
||||
Version History
|
||||
1.0.0 - initial version
|
||||
1.0.1 - when available, module sets flag "cfxPersistence" to 1
|
||||
- when data availabe, cfxPersistenceHasData is set to 1
|
||||
- spelling
|
||||
- cfxZones interface
|
||||
- always output save location
|
||||
1.0.2 - QoL when verbosity is on
|
||||
1.0.3 - no longer always tells " mission saved to"
|
||||
new 'saveNotification" can be off
|
||||
1.0.4 - new optional 'root' property
|
||||
1.0.5 - desanitize check on readConfig to early-abort
|
||||
1.0.6 - removed potential verbosity bug
|
||||
1.0.7 - correct abort for sanitized DCS, when non-verbose
|
||||
|
||||
2.0.0 - dml zones, OOP
|
||||
cleanup
|
||||
|
||||
PROVIDES LOAD/SAVE ABILITY TO MODULES
|
||||
PROVIDES STANDALONE/HOSTED SERVER COMPATIBILITY
|
||||
|
||||
--]]--
|
||||
|
||||
-- in order to work, Host must desanitize lfs and io
|
||||
-- only works when run as server
|
||||
-- in order to work, HOST MUST DESANITIZE lfs and io
|
||||
|
||||
--
|
||||
-- flags to save. can be added to by saveFlags attribute
|
||||
--
|
||||
persistence.flagsToSave = {} -- simple table
|
||||
persistence.callbacks = {} -- cbblocks, dictionary
|
||||
persistence.callbacks = {} -- cbblocks, dictionary by name
|
||||
|
||||
|
||||
--
|
||||
@@ -417,13 +403,13 @@ end
|
||||
--
|
||||
|
||||
function persistence.collectFlagsFromZone(theZone)
|
||||
local theFlags = cfxZones.getStringFromZoneProperty(theZone, "saveFlags", "*dummy")
|
||||
local theFlags = theZone:getStringFromZoneProperty("saveFlags", "*dummy")
|
||||
persistence.registerFlagsToSave(theFlags, theZone)
|
||||
end
|
||||
|
||||
function persistence.readConfigZone()
|
||||
if not _G["lfs"] then
|
||||
trigger.action.outText("+++persistence: DCS not correctly desanitized. Persistence disabled", 30)
|
||||
trigger.action.outText("+++persistence: DCS correctly not 'desanitized'. Persistence disabled", 30)
|
||||
return
|
||||
end
|
||||
|
||||
@@ -436,10 +422,10 @@ function persistence.readConfigZone()
|
||||
|
||||
-- serverDir is the path from the server save directory, usually "Missions/".
|
||||
-- will be added to lfs.writedir() unless given a root attribute
|
||||
if cfxZones.hasProperty(theZone, "root") then
|
||||
if theZone:hasProperty("root") then
|
||||
-- we split this to enable further processing down the
|
||||
-- line if neccessary
|
||||
persistence.root = cfxZones.getStringFromZoneProperty(theZone, "root", lfs.writedir()) -- safe default
|
||||
persistence.root = theZone:getStringFromZoneProperty("root", lfs.writedir()) -- safe default
|
||||
if not dcsCommon.stringEndsWith(persistence.root, "\\") then
|
||||
persistence.root = persistence.root .. "\\"
|
||||
end
|
||||
@@ -453,11 +439,11 @@ function persistence.readConfigZone()
|
||||
end
|
||||
end
|
||||
|
||||
persistence.serverDir = cfxZones.getStringFromZoneProperty(theZone, "serverDir", "Missions\\")
|
||||
persistence.serverDir = theZone:getStringFromZoneProperty("serverDir", "Missions\\")
|
||||
|
||||
if hasConfig then
|
||||
if cfxZones.hasProperty(theZone, "saveDir") then
|
||||
persistence.saveDir = cfxZones.getStringFromZoneProperty(theZone, "saveDir", "")
|
||||
if theZone:hasProperty("saveDir") then
|
||||
persistence.saveDir = theZone:getStringFromZoneProperty("saveDir", "")
|
||||
else
|
||||
-- local missname = net.dostring_in("gui", "return DCS.getMissionName()") .. " (data)"
|
||||
persistence.saveDir = dcsCommon.getMissionName() .. " (data)"
|
||||
@@ -472,32 +458,32 @@ function persistence.readConfigZone()
|
||||
trigger.action.outText("*** WARNING: persistence is set to write to main mission directory!", 30)
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "saveFileName") then
|
||||
persistence.saveFileName = cfxZones.getStringFromZoneProperty(theZone, "saveFileName", dcsCommon.getMissionName() .. " Data.txt")
|
||||
if theZone:hasProperty("saveFileName") then
|
||||
persistence.saveFileName = theZone:getStringFromZoneProperty("saveFileName", dcsCommon.getMissionName() .. " Data.txt")
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "versionID") then
|
||||
persistence.versionID = cfxZones.getStringFromZoneProperty(theZone, "versionID", "") -- to check for full restart
|
||||
if theZone:hasProperty("versionID") then
|
||||
persistence.versionID = theZone:getStringFromZoneProperty("versionID", "") -- to check for full restart
|
||||
end
|
||||
|
||||
persistence.saveInterval = cfxZones.getNumberFromZoneProperty(theZone, "saveInterval", -1) -- default to manual save
|
||||
persistence.saveInterval = theZone:getNumberFromZoneProperty("saveInterval", -1) -- default to manual save
|
||||
if persistence.saveInterval > 0 then
|
||||
persistence.saveTime = persistence.saveInterval * 60 + timer.getTime()
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "cleanRestart?") then
|
||||
persistence.cleanRestart = cfxZones.getStringFromZoneProperty(theZone, "cleanRestart?", "*<none>")
|
||||
persistence.lastCleanRestart = cfxZones.getFlagValue(persistence.cleanRestart, theZone)
|
||||
if theZone:hasProperty("cleanRestart?") then
|
||||
persistence.cleanRestart = theZone:getStringFromZoneProperty("cleanRestart?", "*<none>")
|
||||
persistence.lastCleanRestart = theZone:getFlagValue(persistence.cleanRestart)
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "saveMission?") then
|
||||
persistence.saveMission = cfxZones.getStringFromZoneProperty(theZone, "saveMission?", "*<none>")
|
||||
persistence.lastSaveMission = cfxZones.getFlagValue(persistence.saveMission, theZone)
|
||||
if theZone:hasProperty("saveMission?") then
|
||||
persistence.saveMission = theZone:getStringFromZoneProperty("saveMission?", "*<none>")
|
||||
persistence.lastSaveMission = theZone:getFlagValue(persistence.saveMission)
|
||||
end
|
||||
|
||||
persistence.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
|
||||
persistence.verbose = theZone.verbose
|
||||
|
||||
persistence.saveNotification = cfxZones.getBoolFromZoneProperty(theZone, "saveNotification", true)
|
||||
persistence.saveNotification = theZone:getBoolFromZoneProperty("saveNotification", true)
|
||||
|
||||
if persistence.verbose then
|
||||
trigger.action.outText("+++persistence: read config", 30)
|
||||
@@ -534,14 +520,13 @@ function persistence.start()
|
||||
return false
|
||||
end
|
||||
|
||||
-- local mainDir = lfs.writedir() .. persistence.serverDir
|
||||
local mainDir = persistence.root .. persistence.serverDir
|
||||
if not dcsCommon.stringEndsWith(mainDir, "\\") then
|
||||
mainDir = mainDir .. "\\"
|
||||
end
|
||||
|
||||
-- lets see if we can access the server's mission directory and
|
||||
-- save directory
|
||||
|
||||
-- save directory
|
||||
if persistence.isDir(mainDir) then
|
||||
if persistence.verbose then
|
||||
trigger.action.outText("persistence: main dir is <" .. mainDir .. ">", 30)
|
||||
@@ -613,7 +598,6 @@ function persistence.start()
|
||||
-- and start updating
|
||||
persistence.update()
|
||||
|
||||
|
||||
return persistence.active
|
||||
end
|
||||
|
||||
@@ -627,5 +611,3 @@ if not persistence.start() then
|
||||
end
|
||||
-- we do NOT remove the methods so we don't crash
|
||||
end
|
||||
|
||||
-- add zones for saveFlags so authors can easily save flag values
|
||||
|
||||
@@ -1,91 +1,17 @@
|
||||
cfxPlayerScore = {}
|
||||
cfxPlayerScore.version = "3.0.0"
|
||||
cfxPlayerScore.version = "3.0.1"
|
||||
cfxPlayerScore.name = "cfxPlayerScore" -- compatibility with flag bangers
|
||||
cfxPlayerScore.badSound = "Death BRASS.wav"
|
||||
cfxPlayerScore.scoreSound = "Quest Snare 3.wav"
|
||||
cfxPlayerScore.announcer = true
|
||||
cfxPlayerScore.firstSave = true -- to force overwrite
|
||||
--[[-- VERSION HISTORY
|
||||
1.0.1 - bug fixes to killDetected
|
||||
1.0.2 - messaging clean-up, less verbose
|
||||
1.1.0 - integrated score base system
|
||||
- accepts configZones
|
||||
- module validation
|
||||
- isNamedUnit(theUnit)
|
||||
- notify if named unit killed
|
||||
- kill weapon reported
|
||||
1.2.0 - score table
|
||||
- announcer attribute
|
||||
- badSound name
|
||||
- scoreSound name
|
||||
1.3.0 - object2score
|
||||
- static objects also can score
|
||||
- can now also score members of group by adding group name
|
||||
- scenery objects are now supported. use the
|
||||
number that is given under OBJECT ID when
|
||||
using assign as...
|
||||
1.3.1 - isStaticObject() to better detect buildings after Match 22 patch
|
||||
1.3.2 - corrected ground default score
|
||||
- removed dependency to cfxPlayer
|
||||
1.4.0 - persistence support
|
||||
- better unit-->static switch support for generic type kill
|
||||
1.5.0 - added feats to score
|
||||
- feats API
|
||||
- logFeatForPlayer(playerName, theFeat, coa)
|
||||
1.5.1 - init feats before reading
|
||||
2.0.0 - sound name for good and bad corrected
|
||||
- scoreOnly option
|
||||
- saveScore? input in config
|
||||
- incremental option in config for save
|
||||
- preProcessor() added land and birth for players
|
||||
- addSafeZone()
|
||||
- landing score possible as feat
|
||||
- also detect landing and birth events for players
|
||||
- birth zeroes deferred scores and feats
|
||||
- delayAfterLanding property
|
||||
- delayBetweenLandings to filter landings and
|
||||
space them properly from take offs
|
||||
- ranking option
|
||||
- scoreOnly option
|
||||
- scoreFileName option
|
||||
- update() loop
|
||||
- read "scoreSafe" zones
|
||||
- scoreaccu in playerScore
|
||||
- landing feat added, enabled with landing > 0 score attribute
|
||||
- delayed awards after landing
|
||||
- save score to file
|
||||
- show score to all
|
||||
- feat zones
|
||||
- awardLimit - feats can be limited in number total
|
||||
- wildcard support for feature
|
||||
- kill zones - limit kill score awards to inside zones
|
||||
- feats can be limited to once per player
|
||||
- persistence: featNum
|
||||
- persistence: awardedTo
|
||||
- always schedule
|
||||
- hasAward logic
|
||||
- unit2player
|
||||
- detect player plane death
|
||||
- ffMod attribute
|
||||
- pkMod attribute
|
||||
- pvp feat
|
||||
- immediate awarding of all negative scores, even if deferred
|
||||
2.0.1 - corrected access to nowString()
|
||||
- more robust config reading
|
||||
2.1.0 - coalition score
|
||||
- reportCoalition switch
|
||||
- persist coalition score
|
||||
- add score to coalition when scoring player
|
||||
2.1.1 - check ownership of scoreSafe zone upon touch-down
|
||||
- new scoreSummaryForPlayersOfCoalition()
|
||||
- new noGrief option in config
|
||||
- improved guards when checking ownership (nil zone owner)
|
||||
2.2.0 - score flags for red and blue
|
||||
3.0.0 - dmlFlags OOP
|
||||
- redScore#
|
||||
- blueScore#
|
||||
- sceneryObject detection improvements
|
||||
- DCS 2.9 safe
|
||||
3.0.1 - cleanup
|
||||
|
||||
--]]--
|
||||
|
||||
@@ -109,12 +35,6 @@ cfxPlayerScore.killZones = {} -- when set, kills only count here
|
||||
cfxPlayerScore.typeScore = {}
|
||||
cfxPlayerScore.lastPlayerLanding = {} -- timestamp, by player name
|
||||
cfxPlayerScore.delayBetweenLandings = 30 -- seconds to count as separate landings, also set during take-off to prevent janky t/o to count.
|
||||
--
|
||||
-- we subscribe to the kill event. each time a unit
|
||||
-- is killed, we check if it was killed by a player
|
||||
-- and if so, that player record is updated and the side
|
||||
-- whom the player belongs to is informed
|
||||
--
|
||||
cfxPlayerScore.aircraft = 50
|
||||
cfxPlayerScore.helo = 40
|
||||
cfxPlayerScore.ground = 10
|
||||
@@ -655,7 +575,6 @@ function cfxPlayerScore.linkUnitWithPlayer(theUnit)
|
||||
local uName = theUnit:getName()
|
||||
local pName = theUnit:getPlayerName()
|
||||
cfxPlayerScore.unit2player[uName] = pName
|
||||
--cfxPlayerScore.player2unit[pName] = uName -- is this needed?
|
||||
end
|
||||
|
||||
function cfxPlayerScore.unlinkUnit(theUnit)
|
||||
@@ -790,7 +709,6 @@ function cfxPlayerScore.checkKillFeat(name, killer, victim, fratricide)
|
||||
local killFeats = cfxPlayerScore.featsForLocation(name, theLoc, coa,"KILL", killer, victim)
|
||||
|
||||
if (not fratricide) and #killFeats > 0 then
|
||||
|
||||
-- use the feat description
|
||||
-- we may want to use closest, currently simply the first
|
||||
theFeatZone = killFeats[1]
|
||||
@@ -826,7 +744,6 @@ function cfxPlayerScore.killDetected(theEvent)
|
||||
if wasBuilding then
|
||||
-- these objects have no coalition; we simply award the score if
|
||||
-- it exists in look-up table.
|
||||
--trigger.action.outText("KILL SCENERY", 30)
|
||||
local staticScore = cfxPlayerScore.object2score(victim)
|
||||
if staticScore > 0 then
|
||||
trigger.action.outSoundForCoalition(killSide, cfxPlayerScore.scoreSound)
|
||||
@@ -847,10 +764,9 @@ function cfxPlayerScore.killDetected(theEvent)
|
||||
local isStO = cfxPlayerScore.isStaticObject(victim)
|
||||
--if not victim.getGroup then
|
||||
if isStO then
|
||||
-- static objects have no group
|
||||
|
||||
-- static objects have no group
|
||||
local staticName = victim:getName() -- on statics, this returns
|
||||
-- name as entered in TOP LINE
|
||||
-- name as entered in TOP LINE
|
||||
local staticScore = cfxPlayerScore.object2score(victim)
|
||||
|
||||
if staticScore > 0 then
|
||||
@@ -870,9 +786,7 @@ function cfxPlayerScore.killDetected(theEvent)
|
||||
|
||||
if not fraternicide then
|
||||
cfxPlayerScore.checkKillFeat(killerName, killer, victim, false)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
@@ -881,7 +795,7 @@ function cfxPlayerScore.killDetected(theEvent)
|
||||
trigger.action.outText("+++scr: strange stuff:group, outta here", 30)
|
||||
return
|
||||
end
|
||||
local vicCat = vicGroup:getCategory()
|
||||
local vicCat = vicGroup:getCategory() -- group cat is DCS 2.9 safe
|
||||
if not vicCat then
|
||||
trigger.action.outText("+++scr: strange stuff:cat, outta here", 30)
|
||||
return
|
||||
@@ -889,25 +803,24 @@ function cfxPlayerScore.killDetected(theEvent)
|
||||
local unitScore = cfxPlayerScore.unit2score(victim)
|
||||
|
||||
-- see which weapon was used. gun kills score 2x
|
||||
local killMeth = ""
|
||||
local killWeap = theEvent.weapon
|
||||
-- local killMeth = "" -- meth is currently not defined
|
||||
-- local killWeap = theEvent.weapon -- not supported either
|
||||
|
||||
if pk then
|
||||
if pk then -- player kill - add player's name
|
||||
vicDesc = victim:getPlayerName() .. " in " .. vicDesc
|
||||
scoreMod = scoreMod * cfxPlayerScore.pkMod
|
||||
end
|
||||
|
||||
|
||||
-- if fratricide, times ffMod (friedlyFire)
|
||||
if fraternicide then
|
||||
scoreMod = scoreMod * cfxPlayerScore.ffMod ---2
|
||||
if cfxPlayerScore.announcer then
|
||||
trigger.action.outTextForCoalition(killSide, killerName .. " in " .. killVehicle .. " killed FRIENDLY " .. vicDesc .. killMeth .. "!", 30)
|
||||
trigger.action.outTextForCoalition(killSide, killerName .. " in " .. killVehicle .. " killed FRIENDLY " .. vicDesc .. "!", 30)
|
||||
trigger.action.outSoundForCoalition(killSide, cfxPlayerScore.badSound)
|
||||
end
|
||||
else
|
||||
if cfxPlayerScore.announcer then
|
||||
trigger.action.outText(killerName .. " in " .. killVehicle .." killed " .. vicDesc .. killMeth .."!", 30)
|
||||
trigger.action.outText(killerName .. " in " .. killVehicle .." killed " .. vicDesc .. "!", 30)
|
||||
trigger.action.outSoundForCoalition(vicSide, cfxPlayerScore.badSound)
|
||||
trigger.action.outSoundForCoalition(killSide, cfxPlayerScore.scoreSound)
|
||||
end
|
||||
@@ -960,7 +873,6 @@ function cfxPlayerScore.handlePlayerLanding(theEvent)
|
||||
-- we may want to use closest, currently simply the first
|
||||
theFeatZone = landingFeats[1]
|
||||
desc = cfxPlayerScore.evalFeatDescription(playerName, theFeatZone, thePlayerUnit) -- nil victim, defaults to player
|
||||
|
||||
else
|
||||
if theEvent.place then
|
||||
desc = desc .. " successfully (" .. theEvent.place:getName() .. ")"
|
||||
@@ -1067,8 +979,7 @@ function cfxPlayerScore.scheduledAward(args)
|
||||
trigger.action.outTextForUnit(uid, "Can't award score for <" .. playerName .. ">, not in safe zone.", 30)
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
|
||||
local theScore = cfxPlayerScore.getPlayerScore(playerName)
|
||||
local playerSide = dcsCommon.playerName2Coalition(playerName)
|
||||
if playerSide < 1 then
|
||||
@@ -1214,59 +1125,58 @@ function cfxPlayerScore.handlePlayerEvent(theEvent)
|
||||
end
|
||||
|
||||
function cfxPlayerScore.readConfigZone(theZone)
|
||||
cfxPlayerScore.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
|
||||
cfxPlayerScore.verbose = theZone.verbose
|
||||
-- default scores
|
||||
cfxPlayerScore.aircraft = cfxZones.getNumberFromZoneProperty(theZone, "aircraft", 50)
|
||||
cfxPlayerScore.helo = cfxZones.getNumberFromZoneProperty(theZone, "helo", 40)
|
||||
cfxPlayerScore.ground = cfxZones.getNumberFromZoneProperty(theZone, "ground", 10)
|
||||
cfxPlayerScore.ship = cfxZones.getNumberFromZoneProperty(theZone, "ship", 80)
|
||||
cfxPlayerScore.train = cfxZones.getNumberFromZoneProperty(theZone, "train", 5)
|
||||
cfxPlayerScore.landing = cfxZones.getNumberFromZoneProperty(theZone, "landing", 0) -- if > 0 then feat
|
||||
cfxPlayerScore.aircraft = theZone:getNumberFromZoneProperty("aircraft", 50)
|
||||
cfxPlayerScore.helo = theZone:getNumberFromZoneProperty("helo", 40)
|
||||
cfxPlayerScore.ground = theZone:getNumberFromZoneProperty("ground", 10)
|
||||
cfxPlayerScore.ship = theZone:getNumberFromZoneProperty("ship", 80)
|
||||
cfxPlayerScore.train = theZone:getNumberFromZoneProperty( "train", 5)
|
||||
cfxPlayerScore.landing = theZone:getNumberFromZoneProperty("landing", 0) -- if > 0 then feat
|
||||
|
||||
cfxPlayerScore.pkMod = cfxZones.getNumberFromZoneProperty(theZone, "pkMod", 1) -- factor for killing a player
|
||||
cfxPlayerScore.ffMod = cfxZones.getNumberFromZoneProperty(theZone, "ffMod", -2) -- factor for friendly fire
|
||||
cfxPlayerScore.planeLoss = cfxZones.getNumberFromZoneProperty(theZone, "planeLoss", -10) -- points added when player's plane crashes
|
||||
cfxPlayerScore.pkMod = theZone:getNumberFromZoneProperty( "pkMod", 1) -- factor for killing a player
|
||||
cfxPlayerScore.ffMod = theZone:getNumberFromZoneProperty( "ffMod", -2) -- factor for friendly fire
|
||||
cfxPlayerScore.planeLoss = theZone:getNumberFromZoneProperty("planeLoss", -10) -- points added when player's plane crashes
|
||||
|
||||
cfxPlayerScore.announcer = cfxZones.getBoolFromZoneProperty(theZone, "announcer", true)
|
||||
cfxPlayerScore.announcer = theZone:getBoolFromZoneProperty("announcer", true)
|
||||
|
||||
if cfxZones.hasProperty(theZone, "badSound") then
|
||||
cfxPlayerScore.badSound = cfxZones.getStringFromZoneProperty(theZone, "badSound", "<nosound>")
|
||||
if theZone:hasProperty("badSound") then
|
||||
cfxPlayerScore.badSound = theZone:getStringFromZoneProperty("badSound", "<nosound>")
|
||||
end
|
||||
if cfxZones.hasProperty(theZone, "scoreSound") then
|
||||
cfxPlayerScore.scoreSound = cfxZones.getStringFromZoneProperty(theZone, "scoreSound", "<nosound>")
|
||||
if theZone:hasProperty("scoreSound") then
|
||||
cfxPlayerScore.scoreSound = theZone:getStringFromZoneProperty("scoreSound", "<nosound>")
|
||||
end
|
||||
|
||||
-- triggering saving scores
|
||||
if cfxZones.hasProperty(theZone, "saveScore?") then
|
||||
cfxPlayerScore.saveScore = cfxZones.getStringFromZoneProperty(theZone, "saveScore?", "none")
|
||||
if theZone:hasProperty("saveScore?") then
|
||||
cfxPlayerScore.saveScore = theZone:getStringFromZoneProperty("saveScore?", "none")
|
||||
cfxPlayerScore.lastSaveScore = trigger.misc.getUserFlag(cfxPlayerScore.saveScore)
|
||||
cfxPlayerScore.incremental = cfxZones.getBoolFromZoneProperty(theZone, "incremental", false) -- incremental saves
|
||||
cfxPlayerScore.incremental = theZone:getBoolFromZoneProperty("incremental", false) -- incremental saves
|
||||
end
|
||||
|
||||
-- triggering show all scores
|
||||
if cfxZones.hasProperty(theZone, "showScore?") then
|
||||
cfxPlayerScore.showScore = cfxZones.getStringFromZoneProperty(theZone, "showScore?", "none")
|
||||
if theZone:hasProperty("showScore?") then
|
||||
cfxPlayerScore.showScore = theZone:getStringFromZoneProperty("showScore?", "none")
|
||||
cfxPlayerScore.lastShowScore = trigger.misc.getUserFlag(cfxPlayerScore.showScore)
|
||||
end
|
||||
|
||||
cfxPlayerScore.rankPlayers = cfxZones.getBoolFromZoneProperty(theZone, "rankPlayers", false)
|
||||
cfxPlayerScore.rankPlayers = theZone:getBoolFromZoneProperty("rankPlayers", false)
|
||||
|
||||
cfxPlayerScore.scoreOnly = cfxZones.getBoolFromZoneProperty(theZone, "scoreOnly", true)
|
||||
cfxPlayerScore.scoreOnly = theZone:getBoolFromZoneProperty("scoreOnly", true)
|
||||
|
||||
cfxPlayerScore.deferred = cfxZones.getBoolFromZoneProperty(theZone, "deferred", false)
|
||||
cfxPlayerScore.deferred = theZone:getBoolFromZoneProperty("deferred", false)
|
||||
|
||||
cfxPlayerScore.delayAfterLanding = cfxZones.getNumberFromZoneProperty(theZone, "delayAfterLanding", 10)
|
||||
cfxPlayerScore.delayAfterLanding = theZone:getNumberFromZoneProperty("delayAfterLanding", 10)
|
||||
|
||||
cfxPlayerScore.scoreFileName = cfxZones.getStringFromZoneProperty(theZone, "scoreFileName", "Player Scores")
|
||||
cfxPlayerScore.scoreFileName = theZone:getStringFromZoneProperty("scoreFileName", "Player Scores")
|
||||
|
||||
cfxPlayerScore.reportScore = cfxZones.getBoolFromZoneProperty(theZone, "reportScore", true)
|
||||
cfxPlayerScore.reportScore = theZone:getBoolFromZoneProperty("reportScore", true)
|
||||
|
||||
cfxPlayerScore.reportFeats = cfxZones.getBoolFromZoneProperty(theZone, "reportFeats", true)
|
||||
cfxPlayerScore.reportFeats = theZone:getBoolFromZoneProperty("reportFeats", true)
|
||||
|
||||
cfxPlayerScore.reportCoalition = cfxZones.getBoolFromZoneProperty(
|
||||
theZone, "reportCoalition", false) -- also show coalition score
|
||||
cfxPlayerScore.reportCoalition = theZone:getBoolFromZoneProperty("reportCoalition", false) -- also show coalition score
|
||||
|
||||
cfxPlayerScore.noGrief = cfxZones.getBoolFromZoneProperty(theZone, "noGrief", true) -- noGrief = only add positive score
|
||||
cfxPlayerScore.noGrief = theZone:getBoolFromZoneProperty( "noGrief", true) -- noGrief = only add positive score
|
||||
|
||||
if theZone:hasProperty("redScore#") then
|
||||
cfxPlayerScore.redScoreOut = theZone:getStringFromZoneProperty("redScore#")
|
||||
@@ -10,6 +10,7 @@ cfxPlayerScoreUI.verbose = false
|
||||
- 2.1.0 - soundfile cleanup
|
||||
- score summary for side
|
||||
- allowAll
|
||||
- 2.1.1 - minor cleanup
|
||||
|
||||
--]]--
|
||||
cfxPlayerScoreUI.requiredLibs = {
|
||||
@@ -116,7 +117,7 @@ function cfxPlayerScoreUI.start()
|
||||
world.addEventHandler(cfxPlayerScoreUI)
|
||||
-- process all existing players (late start)
|
||||
dcsCommon.iteratePlayers(cfxPlayerScore.processPlayerUnit)
|
||||
trigger.action.outText("cf/x cfxPlayerScoreUI v" .. cfxPlayerScoreUI.version .. " started", 30)
|
||||
trigger.action.outText("cf/x PlayerScoreUI v" .. cfxPlayerScoreUI.version .. " started", 30)
|
||||
return true
|
||||
end
|
||||
|
||||
@@ -1,40 +1,39 @@
|
||||
playerZone = {}
|
||||
playerZone.version = "1.0.0"
|
||||
playerZone.version = "2.0.0"
|
||||
playerZone.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
"cfxZones", -- Zones, of course
|
||||
"dcsCommon",
|
||||
"cfxZones",
|
||||
}
|
||||
playerZone.playerZones = {}
|
||||
--[[--
|
||||
Version History
|
||||
1.0.0 - Initial version
|
||||
1.0.1 - pNum --> pNum#
|
||||
2.0.0 - dmlZones
|
||||
red#, blue#, total#
|
||||
|
||||
--]]--
|
||||
|
||||
function playerZone.createPlayerZone(theZone)
|
||||
-- start val - a range
|
||||
theZone.pzCoalition = cfxZones.getCoalitionFromZoneProperty(theZone, "playerZone", 0)
|
||||
|
||||
|
||||
theZone.pzCoalition = theZone:getCoalitionFromZoneProperty( "playerZone", 0)
|
||||
-- Method for outputs
|
||||
theZone.pzMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "inc")
|
||||
if cfxZones.hasProperty(theZone, "pzMethod") then
|
||||
theZone.pzMethod = cfxZones.getStringFromZoneProperty(theZone, "pwMethod", "inc")
|
||||
|
||||
theZone.pzMethod = theZone:getStringFromZoneProperty("method", "inc")
|
||||
if theZone:hasProperty("pzMethod") then
|
||||
theZone.pzMethod = theZone:getStringFromZoneProperty("pwMethod", "inc")
|
||||
end
|
||||
|
||||
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")
|
||||
if theZone:hasProperty("pNum#") then
|
||||
theZone.pNum = theZone:getStringFromZoneProperty("pNum#", "none")
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "added!") then
|
||||
theZone.pAdd = cfxZones.getStringFromZoneProperty(theZone, "added!", "none")
|
||||
if theZone:hasProperty("added!") then
|
||||
theZone.pAdd = theZone:getStringFromZoneProperty("added!", "none")
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "gone!") then
|
||||
theZone.pRemove = cfxZones.getStringFromZoneProperty(theZone, "gone!", "none")
|
||||
if theZone:hasProperty("gone!") then
|
||||
theZone.pRemove = theZone:getStringFromZoneProperty("gone!", "none")
|
||||
end
|
||||
|
||||
theZone.playersInZone = {} -- indexed by unit name
|
||||
@@ -49,7 +48,7 @@ function playerZone.collectPlayersForZone(theZone)
|
||||
local allPlayers = coalition.getPlayers(f)
|
||||
for idy, theUnit in pairs (allPlayers) do
|
||||
local loc = theUnit:getPoint()
|
||||
if cfxZones.pointInZone(loc, theZone) then
|
||||
if theZone:pointInZone(loc) then
|
||||
zonePlayers[theUnit:getName()] = theUnit
|
||||
end
|
||||
end
|
||||
@@ -86,21 +85,21 @@ function playerZone.processZone(theZone)
|
||||
|
||||
-- flag handling and banging
|
||||
if theZone.pNum then
|
||||
cfxZones.setFlagValueMult(theZone.pNum, newCount, theZone)
|
||||
theZone:setFlagValue(theZone.pNum, newCount)
|
||||
end
|
||||
|
||||
if theZone.pAdd and hasNew then
|
||||
if theZone.verbose or playerZone.verbose then
|
||||
trigger.action.outText("+++pZone: banging <" .. theZone.name .. ">'s 'added!' flags <" .. theZone.pAdd .. ">", 30)
|
||||
end
|
||||
cfxZones.pollFlag(theZone.pAdd, theZone.pzMethod, theZone)
|
||||
theZone:pollFlag(theZone.pAdd, theZone.pzMethod)
|
||||
end
|
||||
|
||||
if theZone.pRemove and hasGone then
|
||||
if theZone.verbose or playerZone.verbose then
|
||||
trigger.action.outText("+++pZone: banging <" .. theZone.name .. ">'s 'gone' flags <" .. theZone.pRemove .. ">", 30)
|
||||
end
|
||||
cfxZones.pollFlag(theZone.pAdd, theZone.pzMethod, theZone)
|
||||
theZone:pollFlag(theZone.pAdd, theZone.pzMethod)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -113,6 +112,25 @@ function playerZone.update()
|
||||
|
||||
-- iterate all zones and check them
|
||||
for idx, theZone in pairs(playerZone.playerZones) do
|
||||
local neutrals = coalition.getPlayers(0)
|
||||
local neutralNum = #neutrals
|
||||
local reds = coalition.getPlayers(1)
|
||||
local redNum = #reds
|
||||
local blues = coalition.getPlayers(2)
|
||||
local blueNum = #blues
|
||||
local totalNum = neutralNum + redNum + blueNum
|
||||
if playerZone.neutralNum then
|
||||
cfxZones.setFlagValue(playerZone.neutralNum, neutralNum, playerZone)
|
||||
end
|
||||
if playerZone.redNum then
|
||||
cfxZones.setFlagValue(playerZone.redNum, redNum, playerZone)
|
||||
end
|
||||
if playerZone.blueNum then
|
||||
cfxZones.setFlagValue(playerZone.blueNum, blueNum, playerZone)
|
||||
end
|
||||
if playerZone.totalNum then
|
||||
cfxZones.setFlagValue(playerZone.totalNum, totalNum, playerZone)
|
||||
end
|
||||
playerZone.processZone(theZone)
|
||||
end
|
||||
end
|
||||
@@ -121,7 +139,20 @@ end
|
||||
-- Read Config Zone
|
||||
--
|
||||
function playerZone.readConfigZone(theZone)
|
||||
playerZone.name = "playerZoneConfig" -- cfxZones compat
|
||||
-- currently nothing to do
|
||||
if theZone:hasProperty("red#") then
|
||||
playerZone.redNum = theZone:getStringFromZoneProperty("red#", "none")
|
||||
end
|
||||
if theZone:hasProperty("blue#") then
|
||||
playerZone.blueNum = theZone:getStringFromZoneProperty("blue#", "none")
|
||||
end
|
||||
if theZone:hasProperty("neutral#") then
|
||||
playerZone.neutralNum = theZone:getStringFromZoneProperty("neutral#", "none")
|
||||
end
|
||||
if theZone:hasProperty("total#") then
|
||||
playerZone.totalNum = theZone:getStringFromZoneProperty("total#", "none")
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
|
||||
@@ -11,34 +11,6 @@ pulseFlags.requiredLibs = {
|
||||
Copyright 2022 by Christian Franz and cf/x
|
||||
|
||||
Version History
|
||||
- 1.0.0 Initial version
|
||||
- 1.0.1 pause behavior debugged
|
||||
- 1.0.2 zero pulse optional initial pulse suppress
|
||||
- 1.0.3 pollFlag switched to cfxZones
|
||||
uses randomDelayFromPositiveRange
|
||||
flag! now is string
|
||||
WARNING: still needs full alphaNum flag upgrade
|
||||
- 1.1.0 Full DML flag integration
|
||||
removed zone!
|
||||
made pulse and pulse! the out flag carrier
|
||||
done!
|
||||
pulsesDone! synonym
|
||||
pausePulse? synonym
|
||||
pulseMethod synonym
|
||||
startPulse? synonym
|
||||
pulseStopped synonym
|
||||
- 1.2.0 DML Watchflag integration
|
||||
corrected bug in loading last pulse value for paused
|
||||
- 1.2.1 pulseInterval synonym for time
|
||||
pulses now supports range
|
||||
zone-local verbosity
|
||||
- 1.2.2 outputMethod synonym
|
||||
- 1.2.3 deprecated paused/pulsePaused
|
||||
returned onStart, defaulting to true
|
||||
- 1.3.0 persistence
|
||||
- 1.3.1 typos corrected
|
||||
- 1.3.2 removed last pulse's timeID upon entry in doPulse
|
||||
- 1.3.3 removed 'pulsing' when pausing, so we can restart
|
||||
- 2.0.0 dmlZones / OOP
|
||||
using method on all outputs
|
||||
- 2.0.1 activateZoneFlag now works correctly
|
||||
@@ -165,7 +137,7 @@ function pulseFlags.doPulse(args)
|
||||
-- first, we only do an initial pulse if zeroPulse is set
|
||||
if theZone.hasPulsed or theZone.zeroPulse then
|
||||
if pulseFlags.verbose or theZone.verbose then
|
||||
trigger.action.outText("+++pulF: will bang " .. theZone.pulseFlag, 30);
|
||||
trigger.action.outText("+++pulF: will bang " .. theZone.pulseFlag .. " for <" .. theZone.name .. ">", 30);
|
||||
end
|
||||
|
||||
theZone:pollFlag(theZone.pulseFlag, theZone.pulseMethod)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
radioMenu = {}
|
||||
radioMenu.version = "2.1.1"
|
||||
radioMenu.version = "2.2.0"
|
||||
radioMenu.verbose = false
|
||||
radioMenu.ups = 1
|
||||
radioMenu.requiredLibs = {
|
||||
@@ -10,21 +10,6 @@ radioMenu.menus = {}
|
||||
|
||||
--[[--
|
||||
Version History
|
||||
1.0.0 - Initial version
|
||||
1.0.1 - spelling corrections
|
||||
1.1.0 - removeMenu
|
||||
addMenu
|
||||
menuVisible
|
||||
2.0.0 - redesign: handles multiple receivers
|
||||
optional MX module
|
||||
group option
|
||||
type option
|
||||
multiple group names
|
||||
multiple types
|
||||
gereric helo type
|
||||
generic plane type
|
||||
type works with coalition
|
||||
2.0.1 - corrections to installMenu(), as suggested by GumidekCZ
|
||||
2.1.0 - valA/valB/valC/valD attributes
|
||||
OOP cfxZones
|
||||
corrected CD setting for "D"
|
||||
@@ -32,6 +17,7 @@ radioMenu.menus = {}
|
||||
valA-D now define full method, not just values
|
||||
full wildcard support for ack and cooldown
|
||||
2.1.1 - outMessage now works correctly
|
||||
2.2.0 - clean-up
|
||||
--]]--
|
||||
|
||||
function radioMenu.addRadioMenu(theZone)
|
||||
@@ -63,8 +49,7 @@ function radioMenu.filterPlayerIDForType(theZone)
|
||||
end
|
||||
|
||||
-- now iterate all types, and include any player that matches
|
||||
-- note that a player may match twice, so we use a dict instead of an
|
||||
-- array. Since we later iterate ID by idx, that's not an issue
|
||||
-- note that players may match twice, so we use a dict
|
||||
|
||||
for idx, aType in pairs(allTypes) do
|
||||
local theType = dcsCommon.trim(aType)
|
||||
@@ -145,7 +130,6 @@ function radioMenu.filterPlayerIDForGroup(theZone)
|
||||
end
|
||||
|
||||
function radioMenu.installMenu(theZone)
|
||||
-- local theGroup = 0 -- was: nil
|
||||
local gID = nil
|
||||
if theZone.menuGroup then
|
||||
if not cfxMX then
|
||||
@@ -570,6 +554,5 @@ if not radioMenu.start() then
|
||||
end
|
||||
|
||||
--[[--
|
||||
callbacks for the menus
|
||||
check CD/standby code for multiple groups
|
||||
--]]--
|
||||
@@ -1,25 +1,12 @@
|
||||
cfxSmokeZone = {}
|
||||
cfxSmokeZone.version = "1.2.0"
|
||||
cfxSmokeZone.version = "2.0.0"
|
||||
cfxSmokeZone.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
"cfxZones", -- Zones, of course
|
||||
}
|
||||
--[[--
|
||||
Version History
|
||||
1.0.0 - initial version
|
||||
1.0.1 - added removeSmokeZone
|
||||
1.0.2 - added altitude
|
||||
1.0.3 - added paused attribute
|
||||
- added f? attribute --> onFlag
|
||||
- broke out startSmoke
|
||||
1.0.4 - startSmoke? synonym
|
||||
- alphanum DML flag upgrade
|
||||
- random color support
|
||||
1.1.0 - Watchflag upgrade
|
||||
1.1.1 - stopSmoke? input
|
||||
1.1.2 - 'agl', 'alt' synonymous for altitude to keep in line with fireFX
|
||||
1.1.3 - corrected smokeTriggerMethod in zone definition
|
||||
1.2.0 - first OOP guinea pig.
|
||||
2.0.0 - clean up
|
||||
|
||||
--]]--
|
||||
cfxSmokeZone.smokeZones = {}
|
||||
@@ -48,7 +35,6 @@ function cfxSmokeZone.processSmokeZone(aZone)
|
||||
-- paused
|
||||
aZone.paused = aZone:getBoolFromZoneProperty("paused", false)
|
||||
|
||||
-- f? query flags
|
||||
if aZone:hasProperty("f?") then
|
||||
aZone.onFlag = aZone:getStringFromZoneProperty("f?", "*<none>")
|
||||
elseif aZone:hasProperty("startSmoke?") then
|
||||
@@ -64,8 +50,6 @@ function cfxSmokeZone.processSmokeZone(aZone)
|
||||
aZone.smkLastStopFlag = aZone:getFlagValue(aZone.smkStopFlag)
|
||||
end
|
||||
|
||||
-- watchflags:
|
||||
-- triggerMethod
|
||||
aZone.smokeTriggerMethod = aZone:getStringFromZoneProperty( "triggerMethod", "change")
|
||||
|
||||
if aZone:hasProperty("smokeTriggerMethod") then
|
||||
@@ -165,14 +149,10 @@ function cfxSmokeZone.start()
|
||||
end
|
||||
|
||||
-- collect all zones with 'smoke' attribute
|
||||
-- collect all spawn zones
|
||||
local attrZones = cfxZones.getZonesWithAttributeNamed("smoke")
|
||||
|
||||
-- now create a smoker for all, add them to updater,
|
||||
-- smoke all that aren't paused
|
||||
for k, aZone in pairs(attrZones) do
|
||||
cfxSmokeZone.processSmokeZone(aZone) -- process attribute and add to zone
|
||||
cfxSmokeZone.addSmokeZone(aZone) -- remember it so we can smoke it
|
||||
for k, aZone in pairs(attrZones) do
|
||||
cfxSmokeZone.processSmokeZone(aZone)
|
||||
cfxSmokeZone.addSmokeZone(aZone)
|
||||
end
|
||||
|
||||
-- start update loop
|
||||
@@ -21,53 +21,7 @@ cfxSpawnZones.spawnedGroups = {}
|
||||
-- *** DOES NOT EXTEND ZONES *** LINKED OWNER via masterOwner ***
|
||||
--
|
||||
--[[--
|
||||
-- version history
|
||||
1.3.0
|
||||
- maxSpawn
|
||||
- orders
|
||||
- range
|
||||
1.3.1 - spawnWithSpawner correct translation of country to coalition
|
||||
- createSpawner - corrected reading from properties
|
||||
1.3.2 - createSpawner - correct reading 'owner' from properties, now
|
||||
directly reads coalition
|
||||
1.4.0 - checks modules
|
||||
- orders 'train' or 'training' - will make the
|
||||
ground troops be issued HOLD WEAPS and
|
||||
not added to any queue. 'Training' troops
|
||||
are target dummies.
|
||||
- optional heading attribute
|
||||
- typeMult: repeate type this many time (can produce army in one call)
|
||||
1.4.1 - 'requestable' attribute. will automatically set zone to
|
||||
- paused, so troops can be produced on call
|
||||
- getRequestableSpawnersInRange
|
||||
1.4.2 - target attribute. used for
|
||||
- orders: attackZone
|
||||
- spawner internally copies name from cfxZone used for spawning (convenience only)
|
||||
1.4.3 - can subscribe to callbacks. currently called when spawnForSpawner is invoked, reason is "spawned"
|
||||
- masterOwner to link ownership to other zone
|
||||
1.4.4 - autoRemove flag to instantly start CD and respawn
|
||||
1.4.5 - verify that maxSpawns ~= 0 on initial spawn on start-up
|
||||
1.4.6 - getSpawnerForZoneNamed(aName)
|
||||
- nil-trapping orders before testing for 'training'
|
||||
1.4.7 - defaulting orders to 'guard'
|
||||
- also accept 'dummy' and 'dummies' as substitute for training
|
||||
1.4.8 - spawnWithSpawner uses getPoint to support linked spawn zones
|
||||
- update spawn count on initial spawn
|
||||
1.5.0 - f? support to trigger spawn
|
||||
- spawnWithSpawner made string compatible
|
||||
1.5.1 - relaxed baseName and default to dcsCommon.uuid()
|
||||
- verbose
|
||||
1.5.2 - activate?, pause? flag
|
||||
1.5.3 - spawn?, spawnUnits? flags
|
||||
1.6.0 - trackwith interface for group tracker
|
||||
1.7.0 - persistence support
|
||||
1.7.1 - improved verbosity
|
||||
- spelling check
|
||||
1.7.2 - baseName now can can be set to zone name by issuing "*"
|
||||
1.7.3 - ability to hand off to delicates, useDelicates attribute
|
||||
1.7.4 - wait-attackZone fixes
|
||||
1.7.5 - improved verbosity on spawning
|
||||
- getRequestableSpawnersInRange() ignores height for distance
|
||||
-- version history
|
||||
2.0.0 - dmlZones
|
||||
- moved "types" to spawner
|
||||
- baseName defaults to zone name, as it is safe for naming
|
||||
@@ -324,6 +324,9 @@ function stopGap.update()
|
||||
busy = true
|
||||
-- count up for auto-release after n seconds
|
||||
trigger.action.setUserFlag(theGroup.sgName, sgState + 1)
|
||||
if stopGap.verbose then
|
||||
trigger.action.outText("+++StopG: [cooldown] cooldown for group <" .. name .. ">, val now is <" .. sgState .. ">.", 30)
|
||||
end
|
||||
end
|
||||
|
||||
if busy then
|
||||
@@ -331,6 +334,9 @@ function stopGap.update()
|
||||
else
|
||||
local theStaticGroup = stopGap.createStandInsForMXGroup(theGroup)
|
||||
stopGap.standInGroups[name] = theStaticGroup
|
||||
if stopGap.verbose then
|
||||
trigger.action.outText("+++StopG: [server command] placing static stand-in for group <" .. name .. ">.", 30)
|
||||
end
|
||||
end
|
||||
else
|
||||
-- plane is currently static and visible
|
||||
|
||||
@@ -52,7 +52,7 @@ stopGap.requiredLibs = {
|
||||
|
||||
--]]--
|
||||
|
||||
stopGap.standInGroups = {}
|
||||
stopGap.standInGroups = {} -- idx by name, if set has a static
|
||||
stopGap.myGroups = {} -- for fast look-up of mx orig data
|
||||
stopGap.stopGapZones = {} -- DML only
|
||||
|
||||
|
||||
@@ -12,24 +12,27 @@ debugger.log = ""
|
||||
|
||||
--[[--
|
||||
Version History
|
||||
1.0.0 - Initial version
|
||||
1.0.1 - made ups available to config zone
|
||||
- changed 'on' to 'active' in config zone
|
||||
- merged debugger and debugDemon
|
||||
- QoL check for 'debug' attribute (no '?')
|
||||
1.1.0 - logging
|
||||
- trigger.action --> debugger for outText
|
||||
- persistence of logs
|
||||
- save <name>
|
||||
1.1.1 - warning when trying to set a flag to a non-int
|
||||
1.1.2 - remove command
|
||||
2.0.0 - dmlZones OOP
|
||||
- eventmon command
|
||||
- all, off, event #
|
||||
- eventmon all, off, event #
|
||||
- standard events
|
||||
- adding events
|
||||
- adding events via #
|
||||
- events? attribute from any zone
|
||||
|
||||
- eventmon last command
|
||||
- q - query MSE Lua variables
|
||||
- w - write/overwrite MSE Lua variables
|
||||
- a - analyse Lua tables / variables
|
||||
- smoke
|
||||
- spawn system with predefines
|
||||
- spawn coalition
|
||||
- spawn number
|
||||
- spawn heading
|
||||
- spawn types
|
||||
- spawn aircraft: add waypoints
|
||||
- spawn "?"
|
||||
- debuggerSpawnTypes zone
|
||||
- reading debuggerSpawnTypes
|
||||
- removed some silly bugs / inconsistencies
|
||||
--]]--
|
||||
|
||||
debugger.requiredLibs = {
|
||||
@@ -45,6 +48,7 @@ debugger.debugUnits = {}
|
||||
debugger.debugGroups = {}
|
||||
debugger.debugObjects = {}
|
||||
debugger.showEvents = {}
|
||||
debugger.lastEvent = nil
|
||||
|
||||
debugDemon.eventList = {
|
||||
["0"] = "S_EVENT_INVALID = 0",
|
||||
@@ -106,6 +110,26 @@ debugDemon.eventList = {
|
||||
["56"] = "S_EVENT_POSTPONED_LAND = 56",
|
||||
["57"] = "S_EVENT_MAX = 57",
|
||||
}
|
||||
|
||||
debugger.spawnTypes = {
|
||||
["inf"] = "Soldier M4",
|
||||
["ifv"] = "BTR-80",
|
||||
["tank"] = "T-90",
|
||||
["ship"] = "PERRY",
|
||||
["helo"] = "AH-1W",
|
||||
["jet"] = "MiG-21Bis",
|
||||
["awacs"] = "A-50",
|
||||
["ww2"] = "SpitfireLFMkIX",
|
||||
["bomber"] = "B-52H",
|
||||
["cargo"] = "ammo_cargo",
|
||||
["sam"] = "Roland ADS",
|
||||
["aaa"] = "ZSU-23-4 Shilka",
|
||||
["arty"] = "M-109",
|
||||
["truck"] = "KAMAZ Truck",
|
||||
["drone"] = "MQ-9 Reaper",
|
||||
["manpad"] = "Soldier stinger",
|
||||
["obj"] = "house2arm"
|
||||
}
|
||||
--
|
||||
-- Logging & saving
|
||||
--
|
||||
@@ -235,7 +259,6 @@ function debugger.createEventMonWithZone(theZone)
|
||||
debugger.outText("*** monitoring events defined in <" .. theZone.name .. ">:", 30)
|
||||
end
|
||||
for idx, aFlag in pairs(flagArray) do
|
||||
|
||||
local evt = tonumber(aFlag)
|
||||
if evt and (debugger.verbose or theZone.verbose) then
|
||||
if evt < 0 then evt = 0 end
|
||||
@@ -288,7 +311,6 @@ function debugger.isObserving(flagName)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return observers
|
||||
end
|
||||
|
||||
@@ -476,43 +498,62 @@ end
|
||||
function debugger.readConfigZone()
|
||||
local theZone = cfxZones.getZoneByName("debuggerConfig")
|
||||
if not theZone then
|
||||
if debugger.verbose then
|
||||
debugger.outText("+++debug: NO config zone!", 30)
|
||||
end
|
||||
theZone = cfxZones.createSimpleZone("debuggerConfig")
|
||||
end
|
||||
debugger.configZone = theZone
|
||||
|
||||
debugger.active = cfxZones.getBoolFromZoneProperty(theZone, "active", true)
|
||||
debugger.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
|
||||
debugger.active = theZone:getBoolFromZoneProperty("active", true)
|
||||
debugger.verbose = theZone.verbose
|
||||
|
||||
if cfxZones.hasProperty(theZone, "on?") then
|
||||
debugger.onFlag = cfxZones.getStringFromZoneProperty(theZone, "on?", "<none>")
|
||||
if theZone:hasProperty("on?") then
|
||||
debugger.onFlag = theZone:getStringFromZoneProperty("on?", "<none>")
|
||||
debugger.lastOn = cfxZones.getFlagValue(debugger.onFlag, theZone)
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "off?") then
|
||||
debugger.offFlag = cfxZones.getStringFromZoneProperty(theZone, "off?", "<none>")
|
||||
if theZone:hasProperty("off?") then
|
||||
debugger.offFlag = theZone:getStringFromZoneProperty("off?", "<none>")
|
||||
debugger.lastOff = cfxZones.getFlagValue(debugger.offFlag, theZone)
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "reset?") then
|
||||
debugger.resetFlag = cfxZones.getStringFromZoneProperty(theZone, "reset?", "<none>")
|
||||
if theZone:hasProperty("reset?") then
|
||||
debugger.resetFlag = theZone:getStringFromZoneProperty("reset?", "<none>")
|
||||
debugger.lastReset = cfxZones.getFlagValue(debugger.resetFlag, theZone)
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "state?") then
|
||||
debugger.stateFlag = cfxZones.getStringFromZoneProperty(theZone, "state?", "<none>")
|
||||
if theZone:hasProperty("state?") then
|
||||
debugger.stateFlag = theZone:getStringFromZoneProperty("state?", "<none>")
|
||||
debugger.lastState = cfxZones.getFlagValue(debugger.stateFlag, theZone)
|
||||
end
|
||||
|
||||
debugger.ups = cfxZones.getNumberFromZoneProperty(theZone, "ups", 4)
|
||||
|
||||
if debugger.verbose then
|
||||
debugger.outText("+++debug: read config", 30)
|
||||
end
|
||||
debugger.ups = theZone:getNumberFromZoneProperty("ups", 4)
|
||||
end
|
||||
|
||||
function debugger.readSpawnTypeZone()
|
||||
local theZone = cfxZones.getZoneByName("debuggerSpawnTypes")
|
||||
if not theZone then
|
||||
theZone = cfxZones.createSimpleZone("debuggerSpawnTypes")
|
||||
end
|
||||
local allAttribuites = theZone:getAllZoneProperties()
|
||||
for attrName, aValue in pairs(allAttribuites) do
|
||||
local theLow = string.lower(attrName)
|
||||
local before = debugger.spawnTypes[theLow]
|
||||
if before then
|
||||
debugger.spawnTypes[theLow] = aValue
|
||||
if theZone.verbose or debugger.verbose then
|
||||
trigger.action.outText("+++debug: changed generic '" .. theLow .. "' from <" .. before .. "> to <" .. aValue .. ">", 30)
|
||||
end
|
||||
else
|
||||
if theZone.verbose or debugger.verbose then
|
||||
if theLow == "verbose" then -- filtered
|
||||
else
|
||||
trigger.action.outText("+++debug: generic '" .. theLow .. "' unknown, not replaced.", 30)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function debugger.start()
|
||||
-- lib check
|
||||
if not dcsCommon.libCheck then
|
||||
@@ -526,6 +567,9 @@ function debugger.start()
|
||||
-- read config
|
||||
debugger.readConfigZone()
|
||||
|
||||
-- read spawn types
|
||||
debugger.readSpawnTypeZone()
|
||||
|
||||
-- process debugger Zones
|
||||
-- old style
|
||||
local attrZones = cfxZones.getZonesWithAttributeNamed("debug?")
|
||||
@@ -536,7 +580,7 @@ function debugger.start()
|
||||
|
||||
local attrZones = cfxZones.getZonesWithAttributeNamed("debug")
|
||||
for k, aZone in pairs(attrZones) do
|
||||
debugger.outText("***Warning: Zone <" .. aZone.name .. "> has a 'debug' flag. Are you perhaps missing a '?'", 30)
|
||||
debugger.outText("***Warning: Zone <" .. aZone.name .. "> has a 'debug' attribute. Are you perhaps missing a '?'", 30)
|
||||
end
|
||||
|
||||
local attrZones = cfxZones.getZonesWithAttributeNamed("events?")
|
||||
@@ -546,7 +590,7 @@ function debugger.start()
|
||||
|
||||
local attrZones = cfxZones.getZonesWithAttributeNamed("events")
|
||||
for k, aZone in pairs(attrZones) do
|
||||
debugger.outText("***Warning: Zone <" .. aZone.name .. "> has a 'debug' flag. Are you perhaps missing a '?'", 30)
|
||||
debugger.outText("***Warning: Zone <" .. aZone.name .. "> has an 'events' attribute. Are you perhaps missing a '?'", 30)
|
||||
end
|
||||
-- events
|
||||
|
||||
@@ -683,7 +727,7 @@ function debugDemon.getArgs(theCommands)
|
||||
end
|
||||
|
||||
--
|
||||
-- stage demon's main command interpreter.
|
||||
-- debug demon's main command interpreter.
|
||||
-- magic lies in using the keywords as keys into a
|
||||
-- function table that holds all processing functions
|
||||
-- I wish we had that back in the Oberon days.
|
||||
@@ -743,7 +787,7 @@ debugger.outText("*** debugger: commands are:" ..
|
||||
"\n " .. debugDemon.markOfDemon .. "o <flagname> [with <observername>] -- observe a flag for change" ..
|
||||
"\n " .. debugDemon.markOfDemon .. "forget <flagname> [with <observername>] -- stop observing a flag" ..
|
||||
"\n " .. debugDemon.markOfDemon .. "new <observername> [[for] <condition>] -- create observer for flags" ..
|
||||
"\n " .. debugDemon.markOfDemon .. "update <observername> [[to] <condition>] -- change observer's condition" ..
|
||||
"\n " .. debugDemon.markOfDemon .. "update <observername> [to] <condition> -- change observer's condition" ..
|
||||
"\n " .. debugDemon.markOfDemon .. "drop <observername> -- remove observer from debugger" ..
|
||||
"\n " .. debugDemon.markOfDemon .. "list [<match>] -- list observers [name contains <match>]" ..
|
||||
"\n " .. debugDemon.markOfDemon .. "who <flagname> -- all who observe <flagname>" ..
|
||||
@@ -752,10 +796,16 @@ debugger.outText("*** debugger: commands are:" ..
|
||||
"\n\n " .. debugDemon.markOfDemon .. "snap [<observername>] -- create new snapshot of flags" ..
|
||||
"\n " .. debugDemon.markOfDemon .. "compare -- compare snapshot flag values with current" ..
|
||||
"\n " .. debugDemon.markOfDemon .. "note <your note> -- add <your note> to the text log" ..
|
||||
"\n\n " .. debugDemon.markOfDemon .. "remove <group/unit/object name> -- remove named item from mission" ..
|
||||
"\n\n " .. debugDemon.markOfDemon .. "spawn [<number>] [<coalition>] <type> [heading=<number>] | [?] -- spawn" ..
|
||||
"\n units/aircraft/objects (? for help)" ..
|
||||
"\n " .. debugDemon.markOfDemon .. "remove <group/unit/object name> -- remove named item from mission" ..
|
||||
"\n " .. debugDemon.markOfDemon .. "smoke <color> -- place colored smoke on the ground" ..
|
||||
"\n " .. debugDemon.markOfDemon .. "boom <number> -- place explosion of strenght <number> on the ground" ..
|
||||
|
||||
"\n\n " .. debugDemon.markOfDemon .. "eventmon [all | off | <number> | ?] -- show events for all | none | event <number> | list" ..
|
||||
"\n " .. debugDemon.markOfDemon .. "eventmon last -- analyse last reported event" ..
|
||||
"\n\n " .. debugDemon.markOfDemon .. "q <Lua Var> -- Query value of Lua variable <Lua Var>" ..
|
||||
"\n " .. debugDemon.markOfDemon .. "a <Lua Var> -- Analyse structure of Lua variable <Lua Var>" ..
|
||||
"\n " .. debugDemon.markOfDemon .. "w <Lua Var> [=] <Lua Value> -- Write <Lua Value> to variable <Lua Var>" ..
|
||||
"\n\n " .. debugDemon.markOfDemon .. "start -- starts debugger" ..
|
||||
"\n " .. debugDemon.markOfDemon .. "stop -- stop debugger" ..
|
||||
@@ -807,7 +857,7 @@ function debugDemon.processNewCommand(args, event)
|
||||
return false
|
||||
end
|
||||
theZone.debugInputMethod = condition
|
||||
end
|
||||
end
|
||||
|
||||
debugger.addDebugger(theZone)
|
||||
debugger.outText("*** [" .. dcsCommon.nowString() .. "] debugger: new observer <" .. observerName .. "> for <" .. theZone.debugInputMethod .. ">", 30)
|
||||
@@ -815,7 +865,7 @@ function debugDemon.processNewCommand(args, event)
|
||||
end
|
||||
|
||||
function debugDemon.processUpdateCommand(args, event)
|
||||
-- syntax update <observername> [[to] <condition>]
|
||||
-- syntax update <observername> [to] <condition>
|
||||
local observerName = args[1]
|
||||
if not observerName then
|
||||
debugger.outText("*** update: missing observer name.", 30)
|
||||
@@ -1271,6 +1321,7 @@ end
|
||||
|
||||
function debugDemon.doEventMon(theEvent)
|
||||
if not theEvent then return end
|
||||
if not debugger.active then return end
|
||||
local ID = theEvent.id
|
||||
if debugger.showEvents[ID] then
|
||||
-- we show this event
|
||||
@@ -1287,11 +1338,47 @@ function debugDemon.doEventMon(theEvent)
|
||||
end
|
||||
end
|
||||
debugger.outText(m, 30)
|
||||
-- save it to lastevent so we can analyse
|
||||
debugger.lastEvent = theEvent
|
||||
end
|
||||
end
|
||||
|
||||
debugDemon.m = ""
|
||||
-- dumpVar2m, invoke externally dumpVar2m(varname, var)
|
||||
function debugDemon.dumpVar2m(key, value, prefix, inrecursion)
|
||||
-- based on common's dumpVar, appends to var "m"
|
||||
if not inrecursion then
|
||||
-- start, init m
|
||||
debugDemon.m = "analysis of <" .. key .. ">\n==="
|
||||
end
|
||||
if not value then value = "nil" end
|
||||
if not prefix then prefix = "" end
|
||||
prefix = " " .. prefix
|
||||
if type(value) == "table" then
|
||||
debugDemon.m = debugDemon.m .. "\n" .. prefix .. key .. ": [ "
|
||||
-- iterate through all kvp
|
||||
for k,v in pairs (value) do
|
||||
debugDemon.dumpVar2m(k, v, prefix, true)
|
||||
end
|
||||
debugDemon.m = debugDemon.m .. "\n" .. prefix .. " ] - end " .. key
|
||||
|
||||
elseif type(value) == "boolean" then
|
||||
local b = "false"
|
||||
if value then b = "true" end
|
||||
debugDemon.m = debugDemon.m .. "\n" .. prefix .. key .. ": " .. b
|
||||
|
||||
else -- simple var, show contents, ends recursion
|
||||
debugDemon.m = debugDemon.m .. "\n" .. prefix .. key .. ": " .. value
|
||||
end
|
||||
|
||||
if not inrecursion then
|
||||
-- output a marker to find in the log / screen
|
||||
debugDemon.m = debugDemon.m .. "\n" .. "=== analysis end\n"
|
||||
end
|
||||
end
|
||||
|
||||
function debugDemon.processEventMonCommand(args, event)
|
||||
-- turn event monito on/off
|
||||
-- turn event monitor all/off/?/last
|
||||
-- syntax: -eventmon on|off
|
||||
local aParam = dcsCommon.trim(event.remainder)
|
||||
if not aParam or aParam:len() < 1 then
|
||||
@@ -1300,7 +1387,6 @@ function debugDemon.processEventMonCommand(args, event)
|
||||
aParam = string.upper(aParam)
|
||||
evtNum = tonumber(aParam)
|
||||
if aParam == "ON" or aParam == "ALL" then
|
||||
-- debugger.eventmon = true
|
||||
debugger.outText("*** eventmon: turned ON, showing ALL events", 30)
|
||||
local events = {}
|
||||
for idx,evt in pairs(debugDemon.eventList) do
|
||||
@@ -1322,6 +1408,13 @@ function debugDemon.processEventMonCommand(args, event)
|
||||
m = m .. "\n" .. evt
|
||||
end
|
||||
debugger.outText(m .. "\n*** end of list", 30)
|
||||
elseif aParam == "LAST" then
|
||||
if debugger.lastEvent then
|
||||
debugDemon.dumpVar2m("event", debugger.lastEvent)
|
||||
debugger.outText(debugDemon.m, 39)
|
||||
else
|
||||
debugger.outText("*** eventmon: no event on record", 39)
|
||||
end
|
||||
else
|
||||
debugger.outText("*** eventmon: unknown parameter <" .. event.remainder .. ">", 30)
|
||||
end
|
||||
@@ -1335,9 +1428,7 @@ end
|
||||
function debugDemon.processQueryCommand(args, event)
|
||||
-- syntax -q <name> with name a (qualified) Lua table reference
|
||||
local theName = args[1]
|
||||
-- local p = args [2]
|
||||
-- trigger.action.outText("args1 = " .. theName, 30)
|
||||
-- if args[2] then trigger.action.outText("param = " .. args[2], 30) end
|
||||
|
||||
if not theName then
|
||||
debugger.outText("*** q: missing Lua table/element name.", 30)
|
||||
return false -- allows correction
|
||||
@@ -1369,6 +1460,33 @@ function debugDemon.processQueryCommand(args, event)
|
||||
return true
|
||||
end
|
||||
|
||||
function debugDemon.processAnalyzeCommand(args, event)
|
||||
-- syntax -a <name> with name a (qualified) Lua table reference
|
||||
local theName = args[1]
|
||||
|
||||
if not theName then
|
||||
debugger.outText("*** a: missing Lua table/element name.", 30)
|
||||
return false -- allows correction
|
||||
end
|
||||
theName = dcsCommon.stringRemainsStartingWith(event.remainder, theName)
|
||||
|
||||
-- put this into a string, and execute it
|
||||
local exec = "return " .. theName
|
||||
local f = loadstring(exec)
|
||||
local res
|
||||
if pcall(f) then
|
||||
res = f()
|
||||
debugDemon.dumpVar2m(theName, res)
|
||||
res = debugDemon.m
|
||||
else
|
||||
res = "[Lua error]"
|
||||
end
|
||||
|
||||
debugger.outText("[" .. dcsCommon.nowString() .. "] <" .. theName .. "> = ".. res, 30)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function debugDemon.processWriteCommand(args, event)
|
||||
-- syntax -w <name> <value> with name a (qualified) Lua table reference and value a Lua value (including strings, with quotes of course). {} means an empty set etc. you CAN call into DCS MSE with this, and create a lot of havoc.
|
||||
-- also, allow "=" semantic, -w p = {x=1, y=2}
|
||||
@@ -1402,6 +1520,324 @@ function debugDemon.processWriteCommand(args, event)
|
||||
return true
|
||||
end
|
||||
|
||||
--
|
||||
-- smoke & boom
|
||||
--
|
||||
|
||||
function debugDemon.processSmokeCommand(args, event)
|
||||
-- syntax -color
|
||||
local color = 0 -- green default
|
||||
local colorCom = args[1]
|
||||
if colorCom then
|
||||
colorCom = colorCom:lower()
|
||||
if colorCom == "red" or colorCom == "1" then color = 1
|
||||
elseif colorCom == "white" or colorCom == "2" then color = 2
|
||||
elseif colorCom == "orange" or colorCom == "3" then color = 3
|
||||
elseif colorCom == "blue" or colorCom == "4" then color = 4
|
||||
elseif colorCom == "green" or colorCom == "0" then color = 0
|
||||
else
|
||||
debugger.outText("*** smoke: unknown color <" .. colorCom .. ">, using green.", 30)
|
||||
end
|
||||
local pos = event.pos
|
||||
local h = land.getHeight({x = pos.x, y = pos.z}) + 1
|
||||
local p = { x = event.pos.x, y = h, z = event.pos.z}
|
||||
trigger.action.smoke(p, color)
|
||||
debugger.outText("*** smoke: placed smoke at <" .. dcsCommon.point2text(p, true) .. ">.", 30)
|
||||
end
|
||||
end
|
||||
|
||||
function debugDemon.processBoomCommand(args, event)
|
||||
-- syntax -color
|
||||
local power = 1 -- boom default
|
||||
local powerCom = args[1]
|
||||
if powerCom then
|
||||
powerCom = tonumber(powerCom)
|
||||
if powerCom then
|
||||
power = powerCom
|
||||
end
|
||||
end
|
||||
local pos = event.pos
|
||||
local h = land.getHeight({x = pos.x, y = pos.z}) + 1
|
||||
local p = { x = event.pos.x, y = h, z = event.pos.z}
|
||||
trigger.action.explosion(p, power)
|
||||
debugger.outText("*** boom: placed <" .. power .. "> explosion at <" .. dcsCommon.point2text(p, true) .. ">.", 30)
|
||||
end
|
||||
|
||||
--
|
||||
-- spawning units at the location of the mark
|
||||
--
|
||||
|
||||
function debugDemon.getCoaFromCommand(args)
|
||||
for i=1, #args do
|
||||
local aParam = args[i]
|
||||
if dcsCommon.stringStartsWith(aParam, "red", true) then return 1, i end
|
||||
if dcsCommon.stringStartsWith(aParam, "blu", true) then return 2, i end
|
||||
if dcsCommon.stringStartsWith(aParam, "neu", true) then return 0, i end
|
||||
end
|
||||
return 0, nil
|
||||
end
|
||||
|
||||
function debugDemon.getAirFromCommand(args)
|
||||
for i=1, #args do
|
||||
local aParam = args[i]
|
||||
if aParam:lower() == "inair" then return true, i end
|
||||
if aParam:lower() == "air" then return true, i end
|
||||
end
|
||||
return false, nil
|
||||
end
|
||||
|
||||
function debugDemon.getHeadingFromCommand(args)
|
||||
for i=1, #args do
|
||||
local aParam = args[i]
|
||||
if dcsCommon.stringStartsWith(aParam, "heading=", true) then
|
||||
local parts = dcsCommon.splitString(aParam, "=")
|
||||
local num = parts[2]
|
||||
if num and tonumber(num) then
|
||||
return tonumber(num), i
|
||||
end
|
||||
end
|
||||
end
|
||||
return 0, nil
|
||||
end
|
||||
|
||||
function debugDemon.getNumFromCommand(args)
|
||||
for i=1, #args do
|
||||
local aParam = args[i]
|
||||
local num = tonumber(aParam)
|
||||
if num then return num, i end
|
||||
end
|
||||
return 1, nil
|
||||
end
|
||||
|
||||
function debugDemon.processSpawnCommand(args, event)
|
||||
-- complex syntax:
|
||||
-- spawn [red|blue|neutral] [number] <type> [heading=<number>] | "?"
|
||||
local params = dcsCommon.clone(args)
|
||||
-- for i=1, #params do
|
||||
-- trigger.action.outText("arg[" .. i .."] = <" .. params[i] .. ">", 30)
|
||||
-- end
|
||||
|
||||
-- get coalition from input
|
||||
|
||||
local coa, idx = debugDemon.getCoaFromCommand(params)
|
||||
if idx then table.remove(params, idx) end
|
||||
local inAir, idy = debugDemon.getAirFromCommand(params)
|
||||
if idy then table.remove(params, idy) end
|
||||
local num, idz = debugDemon.getNumFromCommand(params)
|
||||
if idz then table.remove(params, idz) end
|
||||
local heading, idk = debugDemon.getHeadingFromCommand(params)
|
||||
if idk then table.remove(params, idk) end
|
||||
|
||||
local class = params[1]
|
||||
if not class then
|
||||
debugger.outText("*** spawn: missing keyword (what to spawn).", 30)
|
||||
return
|
||||
end
|
||||
|
||||
class = class:lower()
|
||||
|
||||
-- when we are here, we have reduced all params, so class is [1]
|
||||
-- trigger.action.outText("spawn with class <" .. class .. ">, num <" .. num .. ">, inAir <" .. dcsCommon.bool2Text(inAir) .. ">, coa <" .. coa .. ">, hdg <" .. heading .. ">", 30)
|
||||
heading = heading * 0.0174533 -- in rad
|
||||
|
||||
local pos = event.pos
|
||||
local h = land.getHeight({x = pos.x, y = pos.z}) + 1
|
||||
local p = { x = event.pos.x, y = h, z = event.pos.z}
|
||||
|
||||
if class == "tank" or class == "tanks" then
|
||||
-- spawn the 'tank' class
|
||||
local theType = debugger.spawnTypes["tank"]
|
||||
return debugDemon.spawnTypeWithCat(theType, coa, num, p, nil,heading)
|
||||
elseif class == "man" or class == "soldier" or class == "men" then
|
||||
local theType = debugger.spawnTypes["inf"]
|
||||
return debugDemon.spawnTypeWithCat(theType, coa, num, p, nil,heading)
|
||||
|
||||
elseif class == "inf" or class == "ifv" or class == "sam" or
|
||||
class == "arty" or class == "aaa" then
|
||||
local theType = debugger.spawnTypes[class]
|
||||
return debugDemon.spawnTypeWithCat(theType, coa, num, p, nil,heading)
|
||||
elseif class == "truck" or class == "trucks" then
|
||||
local theType = debugger.spawnTypes["truck"]
|
||||
return debugDemon.spawnTypeWithCat(theType, coa, num, p, nil,heading)
|
||||
elseif class == "manpad" or class == "manpads" or class == "pad" or class == "pads" then
|
||||
local theType = debugger.spawnTypes["manpad"]
|
||||
return debugDemon.spawnTypeWithCat(theType, coa, num, p, nil,heading)
|
||||
|
||||
elseif class == "ship" or class == "ships" then
|
||||
local theType = debugger.spawnTypes["ship"]
|
||||
return debugDemon.spawnTypeWithCat(theType, coa, num, p, Group.Category.SHIP, heading)
|
||||
|
||||
elseif class == "jet" or class == "jets" then
|
||||
local theType = debugger.spawnTypes["jet"]
|
||||
return debugDemon.spawnAirWIthCat(theType, coa, num, p, nil, 1000, 160, heading)
|
||||
|
||||
elseif class == "ww2" then
|
||||
local theType = debugger.spawnTypes[class]
|
||||
return debugDemon.spawnAirWIthCat(theType, coa, num, p, nil, 1000, 100, heading)
|
||||
|
||||
elseif class == "bomber" or class == "awacs" then
|
||||
local theType = debugger.spawnTypes[class]
|
||||
return debugDemon.spawnAirWIthCat(theType, coa, num, p, nil, 8000, 200, heading)
|
||||
|
||||
elseif class == "drone" then
|
||||
local theType = debugger.spawnTypes[class]
|
||||
return debugDemon.spawnAirWIthCat(theType, coa, num, p, nil, 3000, 77, heading)
|
||||
|
||||
elseif class == "helo" or class == "helos" then
|
||||
local theType = debugger.spawnTypes["helo"]
|
||||
return debugDemon.spawnAirWIthCat(theType, coa, num, p, Group.Category.HELICOPTER, 200, 40, heading)
|
||||
|
||||
elseif class == "cargo" or class == "obj" then
|
||||
local isCargo = (class == "cargo")
|
||||
local theType = debugger.spawnTypes[class]
|
||||
return debugDemon.spawnObjects(theType, coa, num, p, isCargo, heading)
|
||||
|
||||
elseif class == "?" then
|
||||
local m = " spawn: invoke '-spawn [number] [coalition] <type> [heading]' with \n" ..
|
||||
" number = any number, default is 1\n" ..
|
||||
" coalition = 'red' | 'blue' | 'neutral', default is neutral\n" ..
|
||||
" heading = 'heading=<number>' - direction to face, in degrees, no blanks\n" ..
|
||||
" <type> = what to spawn, any of the following pre-defined (no quotes)\n" ..
|
||||
" 'tank' - a tank " .. debugDemon.tellType("tank") .. "\n" ..
|
||||
" 'ifv' - an IFV " .. debugDemon.tellType("ifv") .. "\n" ..
|
||||
" 'inf' - an infantry soldier " .. debugDemon.tellType("inf") .. "\n" ..
|
||||
" 'sam' - a SAM vehicle " .. debugDemon.tellType("sam") .. "\n" ..
|
||||
" 'aaa' - a AAA vehicle " .. debugDemon.tellType("aaa") .. "\n" ..
|
||||
" 'arty' - artillery vehicle " .. debugDemon.tellType("arty") .. "\n" ..
|
||||
" 'manpad' - a soldier with SAM " .. debugDemon.tellType("manpad") .. "\n" ..
|
||||
" 'truck' - a truck " .. debugDemon.tellType("truck") .. "\n\n" ..
|
||||
" 'jet' - a fast aircraft " .. debugDemon.tellType("jet") .. "\n" ..
|
||||
" 'ww2' - a warbird " .. debugDemon.tellType("ww2") .. "\n" ..
|
||||
" 'bomber' - a heavy bomber " .. debugDemon.tellType("bomber") .. "\n" ..
|
||||
" 'awacs' - an AWACS plane " .. debugDemon.tellType("awacs") .. "\n" ..
|
||||
" 'drone' - a drone " .. debugDemon.tellType("drone") .. "\n" ..
|
||||
" 'helo' - a helicopter " .. debugDemon.tellType("helo") .. "\n\n" ..
|
||||
" 'ship' - a naval unit" .. debugDemon.tellType("ship") .. "\n\n" ..
|
||||
" 'cargo' - some helicopter cargo " .. debugDemon.tellType("cargo") .. "\n" ..
|
||||
" 'obj' - a static object " .. debugDemon.tellType("obj") .. "\n"
|
||||
|
||||
debugger.outText(m, 30)
|
||||
return true
|
||||
else
|
||||
debugger.outText("*** spawn: unknown kind <" .. class .. ">.", 30)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function debugDemon.tellType(theType)
|
||||
return " [" .. debugger.spawnTypes[theType] .. "]"
|
||||
end
|
||||
|
||||
function debugDemon.spawnTypeWithCat(theType, coa, num, p, cat, heading)
|
||||
trigger.action.outText("heading is <" .. heading .. ">", 30)
|
||||
if not cat then cat = Group.Category.GROUND end
|
||||
if not heading then heading = 0 end
|
||||
|
||||
local xOff = 0
|
||||
local yOff = 0
|
||||
-- build group
|
||||
local groupName = dcsCommon.uuid(theType)
|
||||
local gData = dcsCommon.createEmptyGroundGroupData(groupName)
|
||||
for i=1, num do
|
||||
local aUnit = {}
|
||||
aUnit = dcsCommon.createGroundUnitData(groupName .. "-" .. i, theType)
|
||||
--aUnit.heading = heading
|
||||
dcsCommon.addUnitToGroupData(aUnit, gData, xOff, yOff, heading)
|
||||
xOff = xOff + 10
|
||||
yOff = yOff + 10
|
||||
end
|
||||
|
||||
-- arrange in a grid formation
|
||||
local radius = math.floor(math.sqrt(num) * 10)
|
||||
if cat == Group.Category.SHIP then
|
||||
radius = math.floor(math.sqrt(num) * 100)
|
||||
end
|
||||
|
||||
dcsCommon.arrangeGroupDataIntoFormation(gData, radius, 10, "GRID")
|
||||
|
||||
-- move to destination
|
||||
dcsCommon.moveGroupDataTo(gData, p.x, p.z)
|
||||
|
||||
-- spawn
|
||||
local cty = dcsCommon.getACountryForCoalition(coa)
|
||||
local theGroup = coalition.addGroup(cty, cat, gData)
|
||||
if theGroup then
|
||||
debugger.outText("[" .. dcsCommon.nowString() .. "] created units at " .. dcsCommon.point2text(p, true), 30)
|
||||
return true
|
||||
else
|
||||
debugger.outText("[" .. dcsCommon.nowString() .. "] failed to created units", 30)
|
||||
return false
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function debugDemon.spawnAirWIthCat(theType, coa, num, p, cat, alt, speed, heading)
|
||||
if not cat then cat = Group.Category.AIRPLANE end
|
||||
local xOff = 0
|
||||
local yOff = 0
|
||||
-- build group
|
||||
local groupName = dcsCommon.uuid(theType)
|
||||
local gData = dcsCommon.createEmptyAircraftGroupData(groupName)
|
||||
for i=1, num do
|
||||
local aUnit = {}
|
||||
aUnit = dcsCommon.createAircraftUnitData(groupName .. "-" .. i, theType, false, alt, speed)
|
||||
--aUnit.heading = heading
|
||||
dcsCommon.addUnitToGroupData(aUnit, gData, xOff, yOff, heading)
|
||||
xOff = xOff + 30
|
||||
yOff = yOff + 30
|
||||
end
|
||||
-- move to destination
|
||||
dcsCommon.moveGroupDataTo(gData, p.x, p.z)
|
||||
|
||||
-- make waypoints: initial point and 200 km away in direction heading
|
||||
local p2 = dcsCommon.pointInDirectionOfPointXYY(heading, 200000, p)
|
||||
local wp1 = dcsCommon.createSimpleRoutePointData(p, alt, speed)
|
||||
local wp2 = dcsCommon.createSimpleRoutePointData(p2, alt, speed)
|
||||
-- add waypoints
|
||||
dcsCommon.addRoutePointForGroupData(gData, wp1)
|
||||
dcsCommon.addRoutePointForGroupData(gData, wp2)
|
||||
|
||||
-- spawn
|
||||
local cty = dcsCommon.getACountryForCoalition(coa)
|
||||
local theGroup = coalition.addGroup(cty, cat, gData)
|
||||
if theGroup then
|
||||
debugger.outText("[" .. dcsCommon.nowString() .. "] created air units at " .. dcsCommon.point2text(p, true), 30)
|
||||
return true
|
||||
else
|
||||
debugger.outText("[" .. dcsCommon.nowString() .. "] failed to created air units", 30)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function debugDemon.spawnObjects(theType, coa, num, p, cargo, heading)
|
||||
if not cargo then cargo = false end
|
||||
local cty = dcsCommon.getACountryForCoalition(coa)
|
||||
local xOff = 0
|
||||
local yOff = 0
|
||||
local success = false
|
||||
-- build static objects and spawn individually
|
||||
for i=1, num do
|
||||
local groupName = dcsCommon.uuid(theType)
|
||||
local gData = dcsCommon.createStaticObjectData(groupName, theType, 0, false, cargo, 1000)
|
||||
gData.x = xOff + p.x
|
||||
gData.y = yOff + p.z
|
||||
gData.heading = heading
|
||||
local theGroup = coalition.addStaticObject(cty, gData)
|
||||
success = theGroup
|
||||
xOff = xOff + 10 -- stagger by 10m, 10m
|
||||
yOff = yOff + 10
|
||||
end
|
||||
|
||||
-- was it worth it?
|
||||
if success then
|
||||
debugger.outText("[" .. dcsCommon.nowString() .. "] created objects at " .. dcsCommon.point2text(p, true), 30)
|
||||
return true
|
||||
else
|
||||
debugger.outText("[" .. dcsCommon.nowString() .. "] failed to create objects", 30)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- init and start
|
||||
@@ -1430,6 +1866,7 @@ function debugDemon.readConfigZone()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function debugDemon.init()
|
||||
if not dcsCommon.libCheck then
|
||||
trigger.action.outText("cfx interactive debugger requires dcsCommon", 30)
|
||||
@@ -1473,10 +1910,15 @@ function debugDemon.init()
|
||||
debugDemon.addCommndProcessor("help", debugDemon.processHelpCommand)
|
||||
|
||||
debugDemon.addCommndProcessor("remove", debugDemon.processRemoveCommand)
|
||||
debugDemon.addCommndProcessor("spawn", debugDemon.processSpawnCommand)
|
||||
debugDemon.addCommndProcessor("add", debugDemon.processSpawnCommand)
|
||||
|
||||
debugDemon.addCommndProcessor("eventmon", debugDemon.processEventMonCommand)
|
||||
debugDemon.addCommndProcessor("q", debugDemon.processQueryCommand)
|
||||
debugDemon.addCommndProcessor("w", debugDemon.processWriteCommand)
|
||||
debugDemon.addCommndProcessor("a", debugDemon.processAnalyzeCommand)
|
||||
debugDemon.addCommndProcessor("smoke", debugDemon.processSmokeCommand)
|
||||
debugDemon.addCommndProcessor("boom", debugDemon.processBoomCommand)
|
||||
return true
|
||||
end
|
||||
|
||||
@@ -1512,14 +1954,9 @@ end
|
||||
- inspect objects, dumping category, life, if it's tasking, latLon, alt, speed, direction
|
||||
|
||||
- exec files. save all commands and then run them from script
|
||||
|
||||
- query objects: -q persistence.active returns boolean, true
|
||||
-q x.y returns table, 12 elements
|
||||
-q a.b.x returns number 12
|
||||
-q d.e.f returns string "asdasda..."
|
||||
-q sada returs <nil>
|
||||
|
||||
- xref: which zones/attributes reference a flag, g.g. '-xref go'
|
||||
|
||||
- track lua vars for change in value
|
||||
|
||||
- dml version can config to start with events list, e.g. 1, 4, 7
|
||||
--]]--
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
unitPersistence = {}
|
||||
unitPersistence.version = '1.1.1'
|
||||
unitPersistence.version = '2.0.0'
|
||||
unitPersistence.verbose = false
|
||||
unitPersistence.updateTime = 60 -- seconds. Once every minute check statics
|
||||
unitPersistence.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
"cfxZones", -- Zones, of course
|
||||
"dcsCommon",
|
||||
"cfxZones",
|
||||
"persistence",
|
||||
"cfxMX",
|
||||
}
|
||||
@@ -19,7 +19,8 @@ unitPersistence.requiredLibs = {
|
||||
1.1.0 - added air and sea units - for filtering destroyed units
|
||||
1.1.1 - fixed static link (again)
|
||||
- fixed air spawn (fixed wing)
|
||||
|
||||
2.0.0 - dmlZones, OOP
|
||||
cleanup
|
||||
|
||||
REQUIRES PERSISTENCE AND MX
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
williePete = {}
|
||||
williePete.version = "1.0.2"
|
||||
williePete.version = "2.0.0"
|
||||
williePete.ups = 10 -- we update at 10 fps, so accuracy of a
|
||||
-- missile moving at Mach 2 is within 33 meters,
|
||||
-- with interpolation even at 3 meters
|
||||
@@ -14,12 +14,16 @@ williePete.requiredLibs = {
|
||||
1.0.0 - Initial version
|
||||
1.0.1 - update to suppress verbosity
|
||||
1.0.2 - added Gazelle WP
|
||||
|
||||
2.0.0 - dmlZones, OOP
|
||||
- Guards for multi-unit player groups
|
||||
- getFirstLivingPlayerInGroupNamed()
|
||||
--]]--
|
||||
|
||||
williePete.willies = {}
|
||||
williePete.wpZones = {}
|
||||
williePete.playerGUIs = {} -- used for unit guis
|
||||
williePete.groupGUIs = {} -- because some people may want to install
|
||||
-- multip-unit player groups
|
||||
williePete.blastedObjects = {} -- used when we detonate something
|
||||
|
||||
-- recognizes WP munitions. May require regular update when new
|
||||
@@ -90,30 +94,29 @@ end
|
||||
|
||||
|
||||
function williePete.createWPZone(aZone)
|
||||
aZone.coalition = cfxZones.getCoalitionFromZoneProperty(aZone, "wpTarget", 0) -- side that marks it on map, and who fires arty
|
||||
aZone.shellStrength = cfxZones.getNumberFromZoneProperty(aZone, "shellStrength", 500) -- power of shells (strength)
|
||||
aZone.shellNum = cfxZones.getNumberFromZoneProperty(aZone, "shellNum", 17) -- number of shells in bombardment
|
||||
aZone.transitionTime = cfxZones.getNumberFromZoneProperty(aZone, "transitionTime", 20) -- average time of travel for projectiles
|
||||
aZone.coolDown = cfxZones.getNumberFromZoneProperty(aZone, "coolDown", 180) -- cooldown after arty fire, used to set readyTime
|
||||
aZone.baseAccuracy = cfxZones.getNumberFromZoneProperty(aZone, "baseAccuracy", 50)
|
||||
aZone.coalition = aZone:getCoalitionFromZoneProperty("wpTarget", 0) -- side that marks it on map, and who fires arty
|
||||
aZone.shellStrength = aZone:getNumberFromZoneProperty( "shellStrength", 500) -- power of shells (strength)
|
||||
aZone.shellNum = aZone:getNumberFromZoneProperty("shellNum", 17) -- number of shells in bombardment
|
||||
aZone.transitionTime = aZone:getNumberFromZoneProperty( "transitionTime", 20) -- average time of travel for projectiles
|
||||
aZone.coolDown = aZone:getNumberFromZoneProperty("coolDown", 180) -- cooldown after arty fire, used to set readyTime
|
||||
aZone.baseAccuracy = aZone:getNumberFromZoneProperty( "baseAccuracy", 50)
|
||||
|
||||
aZone.readyTime = 0 -- if readyTime > now we are not ready
|
||||
aZone.trackingPlayer = nil -- name player's unit who is being tracked for wp. may not be neccessary
|
||||
aZone.checkedIn = {} -- dict of all planes currently checked in
|
||||
|
||||
aZone.wpMethod = cfxZones.getStringFromZoneProperty(aZone, "wpMethod", "change")
|
||||
|
||||
aZone.checkInRange = cfxZones.getNumberFromZoneProperty(aZone, "checkInRange", williePete.checkInRange) -- default to my default
|
||||
|
||||
aZone.ackSound = cfxZones.getStringFromZoneProperty(aZone, "ackSound", williePete.ackSound)
|
||||
aZone.guiSound = cfxZones.getStringFromZoneProperty(aZone, "guiSound", williePete.guiSound)
|
||||
|
||||
if cfxZones.hasProperty(aZone, "method") then
|
||||
aZone.wpMethod = cfxZones.getStringFromZoneProperty(aZone, "method", "change")
|
||||
aZone.wpMethod = aZone:getStringFromZoneProperty("wpMethod", "change")
|
||||
if aZone:hasProperty("method") then
|
||||
aZone.wpMethod = aZone:getStringFromZoneProperty("method", "change")
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(aZone, "wpFire!") then
|
||||
aZone.wpFire = cfxZones.getStringFromZoneProperty(aZone, "wpFire!", "<none)")
|
||||
aZone.checkInRange = aZone:getNumberFromZoneProperty( "checkInRange", williePete.checkInRange) -- default to my default
|
||||
|
||||
aZone.ackSound = aZone:getStringFromZoneProperty("ackSound", williePete.ackSound)
|
||||
aZone.guiSound = aZone:getStringFromZoneProperty("guiSound", williePete.guiSound)
|
||||
|
||||
if aZone:hasProperty("wpFire!") then
|
||||
aZone.wpFire = aZone:getStringFromZoneProperty("wpFire!", "<none)")
|
||||
end
|
||||
|
||||
if aZone.verbose then
|
||||
@@ -142,6 +145,7 @@ function williePete.startPlayerGUI()
|
||||
end
|
||||
|
||||
unitInfo.name = uName -- needed for reverse-lookup
|
||||
unitInfo.gName = gName -- also needed for reverse lookup
|
||||
unitInfo.coa = coa
|
||||
unitInfo.gID = gData.groupId
|
||||
unitInfo.uID = uData.unitId
|
||||
@@ -159,12 +163,20 @@ function williePete.startPlayerGUI()
|
||||
|
||||
if pass then -- we install a menu for this group
|
||||
-- we may not want check in stuff, but it could be cool
|
||||
unitInfo.root = missionCommands.addSubMenuForGroup(unitInfo.gID, "FAC")
|
||||
unitInfo.checkIn = missionCommands.addCommandForGroup(unitInfo.gID, "Check In", unitInfo.root, williePete.redirectCheckIn, unitInfo)
|
||||
if williePete.playerGUIs[gName] then
|
||||
trigger.action.outText("+++WP: Warning: we already have WP menu for unit <" .. uName .. "> in group <" .. gName .. ">. Skipped.", 30)
|
||||
elseif williePete.groupGUIs[gName] then
|
||||
trigger.action.outText("+++WP: Warning: POSSIBLE MULTI-PLAYER UNIT GROUP DETECTED. We already have WP menu for Player Group <" .. gName .. ">. Skipped, only first unit supported. ", 30)
|
||||
else
|
||||
unitInfo.root = missionCommands.addSubMenuForGroup(unitInfo.gID, "FAC")
|
||||
unitInfo.checkIn = missionCommands.addCommandForGroup(unitInfo.gID, "Check In", unitInfo.root, williePete.redirectCheckIn, unitInfo)
|
||||
williePete.groupGUIs[gName] = unitInfo
|
||||
williePete.playerGUIs[gName] = unitInfo
|
||||
end
|
||||
end
|
||||
|
||||
-- store it
|
||||
williePete.playerGUIs[uName] = unitInfo
|
||||
-- store it - WARNING: ASSUMES SINGLE-UNIT Player Groups
|
||||
--williePete.playerGUIs[uName] = unitInfo
|
||||
end
|
||||
end
|
||||
|
||||
@@ -177,7 +189,7 @@ function williePete.doBoom(args)
|
||||
-- note that unit who commânded fire may no longer be alive
|
||||
-- so check it every time. unit must be alive
|
||||
-- to receive credits later
|
||||
local uName = unitInfo.name
|
||||
local uName = unitInfo.gName
|
||||
local blastRad = math.floor(math.sqrt(args.strength)) * 2
|
||||
if blastRad < 10 then blastRad = 10 end
|
||||
|
||||
@@ -187,7 +199,7 @@ function williePete.doBoom(args)
|
||||
if williePete.verbose then
|
||||
trigger.action.outText("<" .. aName .. "> is in blast Radius (" .. blastRad .. "m) of shells for <" .. uName .. ">'s target coords", 30)
|
||||
end
|
||||
williePete.blastedObjects[aName] = uName -- last one gets the kill
|
||||
williePete.blastedObjects[aName] = unitInfo.name -- last one gets the kill
|
||||
end
|
||||
end
|
||||
trigger.action.explosion(args.point, args.strength)
|
||||
@@ -237,16 +249,36 @@ function williePete.redirectCheckIn(unitInfo)
|
||||
timer.scheduleFunction(williePete.doCheckIn, unitInfo, timer.getTime() + 0.1)
|
||||
end
|
||||
|
||||
-- fix for multi-unit player groups where only one of them is
|
||||
-- alive: get first living player in group. Will be added to
|
||||
-- dcsCommon soon
|
||||
function williePete.getFirstLivingPlayerInGroupNamed(gName)
|
||||
local theGroup = Group.getByName(gName)
|
||||
if not theGroup then return nil end
|
||||
local theUnits = theGroup:getUnits()
|
||||
for idx, aUnit in pairs(theUnits) do
|
||||
if Unit.isExist(aUnit) and aUnit.getPlayerName and
|
||||
aUnit:getPlayerName() then
|
||||
return aUnit -- return first living player unit
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function williePete.doCheckIn(unitInfo)
|
||||
--trigger.action.outText("check-in received", 30)
|
||||
local theUnit = Unit.getByName(unitInfo.name)
|
||||
-- WARNING: unitInfo points to first processed player in
|
||||
-- group. May not work fully with multi-unit player groups
|
||||
local gName = unitInfo.gName
|
||||
local theUnit = williePete.getFirstLivingPlayerInGroupNamed(gName) --Unit.getByName(unitInfo.name)
|
||||
if not theUnit then
|
||||
-- dead man calling. Pilot dead but unit still alive
|
||||
-- OR second unit in multiplayer group, but unit 1
|
||||
-- does not / no longer exists
|
||||
trigger.action.outText("Calling station, say again, can't read you.", 30)
|
||||
return
|
||||
end
|
||||
|
||||
local p = theUnit:getPoint()
|
||||
local p = theUnit:getPoint() -- only react to first player unit
|
||||
local theZone, dist = williePete.closestCheckInTgtZoneForCoa(p, unitInfo.coa)
|
||||
|
||||
if not theZone then
|
||||
@@ -256,21 +288,23 @@ function williePete.doCheckIn(unitInfo)
|
||||
trigger.action.outSoundForGroup(unitInfo.gID, williePete.guiSound)
|
||||
return
|
||||
end
|
||||
|
||||
trigger.action.outTextForGroup(unitInfo.gID, "Too far from target zone, closest target zone is " .. theZone.name, 30)
|
||||
dist = math.floor(dist /100) / 10
|
||||
bearing = dcsCommon.bearingInDegreesFromAtoB(p, theZone:getPoint())
|
||||
trigger.action.outTextForGroup(unitInfo.gID, unitInfo.gName .. ", you are too far from target zone, closest target zone is " .. theZone.name .. ", " .. dist .. "km at bearing " .. bearing .. "°", 30)
|
||||
trigger.action.outSoundForGroup(unitInfo.gID, theZone.guiSound)
|
||||
return
|
||||
end
|
||||
|
||||
-- we are now checked in to zone -- unless we are already checked in
|
||||
if theZone.checkedIn[unitInfo.name] then
|
||||
trigger.action.outTextForGroup(unitInfo.gID, unitInfo.name .. ", " .. theZone.name .. ", we heard you the first time, proceed.", 30)
|
||||
-- NOTE: we use group name, not unit name!
|
||||
if theZone.checkedIn[unitInfo.gName] then
|
||||
trigger.action.outTextForGroup(unitInfo.gID, unitInfo.gName .. ", " .. theZone.name .. ", we heard you the first time, proceed.", 30)
|
||||
trigger.action.outSoundForGroup(unitInfo.gID, theZone.guiSound)
|
||||
return
|
||||
end
|
||||
|
||||
-- we now check in
|
||||
theZone.checkedIn[unitInfo.name] = unitInfo
|
||||
theZone.checkedIn[unitInfo.gName] = unitInfo
|
||||
|
||||
-- add the 'Target marked' menu
|
||||
unitInfo.targetMarked = missionCommands.addCommandForGroup(unitInfo.gID, "Target Marked, commence firing", unitInfo.root, williePete.redirectTargetMarked, unitInfo)
|
||||
@@ -280,7 +314,7 @@ function williePete.doCheckIn(unitInfo)
|
||||
-- add 'check out'
|
||||
unitInfo.checkOut = missionCommands.addCommandForGroup(unitInfo.gID, "Check Out of " .. theZone.name, unitInfo.root, williePete.redirectCheckOut, unitInfo)
|
||||
|
||||
trigger.action.outTextForGroup(unitInfo.gID, "Roger " .. unitInfo.name .. ", " .. theZone.name .. " tracks you, standing by for target data.", 30)
|
||||
trigger.action.outTextForGroup(unitInfo.gID, "Roger " .. unitInfo.gName .. ", " .. theZone.name .. " tracks you, standing by for target data.", 30)
|
||||
trigger.action.outSoundForGroup(unitInfo.gID, theZone.guiSound)
|
||||
end
|
||||
|
||||
@@ -293,17 +327,17 @@ function williePete.doCheckOut(unitInfo)
|
||||
local wasCheckedIn = false
|
||||
local fromZone = ""
|
||||
for idx, theZone in pairs(williePete.wpZones) do
|
||||
if theZone.checkedIn[unitInfo.name] then
|
||||
if theZone.checkedIn[unitInfo.gName] then
|
||||
wasCheckedIn = true
|
||||
fromZone = theZone.name
|
||||
end
|
||||
theZone.checkedIn[unitInfo.name] = nil
|
||||
theZone.checkedIn[unitInfo.gName] = nil
|
||||
end
|
||||
if not wasCheckedIn then
|
||||
trigger.action.outTextForGroup(unitInfo.gID, unitInfo.name .. ", roger cecked-out. Good hunting!", 30)
|
||||
trigger.action.outTextForGroup(unitInfo.gID, unitInfo.gName .. ", roger cecked-out. Good hunting!", 30)
|
||||
trigger.action.outSoundForGroup(unitInfo.gID, williePete.guiSound)
|
||||
else
|
||||
trigger.action.outTextForGroup(unitInfo.gID, unitInfo.name .. "has checked out of " .. fromZone ..".", 30)
|
||||
trigger.action.outTextForGroup(unitInfo.gID, unitInfo.gName .. " has checked out of " .. fromZone ..".", 30)
|
||||
trigger.action.outSoundForGroup(unitInfo.gID, williePete.guiSound)
|
||||
end
|
||||
|
||||
@@ -326,7 +360,7 @@ function williePete.rogerDodger(args)
|
||||
local unitInfo = args[1]
|
||||
local theZone = args[2]
|
||||
|
||||
trigger.action.outTextForCoalition(unitInfo.coa, "Roger " .. unitInfo.name .. ", good copy, firing.", 30)
|
||||
trigger.action.outTextForCoalition(unitInfo.coa, "Roger " .. unitInfo.gName .. ", good copy, firing.", 30)
|
||||
trigger.action.outSoundForCoalition(unitInfo.coa, theZone.ackSound)
|
||||
end
|
||||
|
||||
@@ -357,9 +391,9 @@ function williePete.doTargetMarked(unitInfo)
|
||||
|
||||
local tgtZone = unitInfo.wpInZone
|
||||
-- see if we are checked into that zone
|
||||
if not tgtZone.checkedIn[unitInfo.name] then
|
||||
if not tgtZone.checkedIn[unitInfo.gName] then
|
||||
-- zones don't match
|
||||
trigger.action.outTextForGroup(unitInfo.gID, "Say again " .. unitInfo.name .. ", we have crosstalk. Try and reset coms", 30)
|
||||
trigger.action.outTextForGroup(unitInfo.gID, "Say again " .. unitInfo.gName .. ", we have crosstalk. Try and reset coms", 30)
|
||||
trigger.action.outSoundForGroup(unitInfo.gID, williePete.guiSound)
|
||||
return
|
||||
end
|
||||
@@ -368,7 +402,7 @@ function williePete.doTargetMarked(unitInfo)
|
||||
local timeRemaining = math.floor(tgtZone.readyTime - now)
|
||||
if timeRemaining > 0 then
|
||||
-- zone not ready
|
||||
trigger.action.outTextForGroup(unitInfo.gID, "Stand by " .. unitInfo.name .. ", artillery not ready. Expect " .. timeRemaining + math.random(1, 5) .. " seconds.", 30)
|
||||
trigger.action.outTextForGroup(unitInfo.gID, "Stand by " .. unitInfo.gName .. ", artillery not ready. Expect " .. timeRemaining + math.random(1, 5) .. " seconds.", 30)
|
||||
trigger.action.outSoundForGroup(unitInfo.gID, tgtZone.guiSound)
|
||||
return
|
||||
end
|
||||
@@ -378,7 +412,7 @@ function williePete.doTargetMarked(unitInfo)
|
||||
local grid = coord.LLtoMGRS(coord.LOtoLL(unitInfo.pos))
|
||||
local mgrs = grid.UTMZone .. ' ' .. grid.MGRSDigraph .. ' ' .. grid.Easting .. ' ' .. grid.Northing
|
||||
local theLoc = mgrs
|
||||
trigger.action.outTextForCoalition(unitInfo.coa, tgtZone.name ..", " .. unitInfo.name .." is transmitting target location. Fire at " .. theLoc .. ", elevation " .. alt .. " meters, target marked.", 30)
|
||||
trigger.action.outTextForCoalition(unitInfo.coa, tgtZone.name ..", " .. unitInfo.gName .." is transmitting target location. Fire at " .. theLoc .. ", elevation " .. alt .. " meters, target marked.", 30)
|
||||
trigger.action.outSoundForCoalition(unitInfo.coa, tgtZone.guiSound)
|
||||
timer.scheduleFunction(williePete.rogerDodger, {unitInfo, tgtZone},timer.getTime() + math.random(2, 5))
|
||||
|
||||
@@ -401,10 +435,13 @@ end
|
||||
-- return true if a zone is actively tracking theUnit to place
|
||||
-- a wp
|
||||
|
||||
function williePete.zoneIsTracking(theUnit)
|
||||
function williePete.zoneIsTracking(theUnit) -- group level!
|
||||
local uName = theUnit:getName()
|
||||
local uGroup = theUnit:getGroup()
|
||||
local gName = uGroup:getName()
|
||||
|
||||
for idx, theZone in pairs(williePete.wpZones) do
|
||||
if theZone.checkedIn[uName] then return true end
|
||||
if theZone.checkedIn[gName] then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
@@ -426,6 +463,8 @@ function williePete.zedsDead(theObject)
|
||||
|
||||
local theName = theObject:getName()
|
||||
-- now check if it's a registered blasted object:getSampleRate()
|
||||
-- in multi-unit player groups, this can can lead to
|
||||
-- mis-attribution, beware!
|
||||
local blaster = williePete.blastedObjects[theName]
|
||||
if blaster then
|
||||
local theUnit = Unit.getByName(blaster)
|
||||
@@ -459,7 +498,7 @@ function williePete:onEvent(event)
|
||||
|
||||
local theUnit = event.initiator
|
||||
local pType = "(AI)"
|
||||
if theUnit.getPlayerName then pType = "(" .. theUnit:getName() .. ")" end
|
||||
if theUnit.getPlayerName and theUnit:getPlayerName() then pType = "(" .. theUnit:getName() .. ")" end
|
||||
|
||||
if event.id == 1 then -- S_EVENT_SHOT
|
||||
-- initiator is who fired. maybe want to test if player
|
||||
@@ -470,7 +509,7 @@ function williePete:onEvent(event)
|
||||
end
|
||||
|
||||
-- make sure that whoever fired it is being tracked by
|
||||
-- a zone
|
||||
-- a zone. zoneIsTracking checks on GROUP level!
|
||||
if not williePete.zoneIsTracking(theUnit) then
|
||||
return
|
||||
end
|
||||
@@ -479,6 +518,8 @@ function williePete:onEvent(event)
|
||||
local theWillie = {}
|
||||
theWillie.firedBy = theUnit:getName()
|
||||
theWillie.theUnit = theUnit
|
||||
theWillie.theGroup = theUnit:getGroup()
|
||||
theWillie.gName = theWillie.theGroup:getName()
|
||||
theWillie.weapon = event.weapon
|
||||
theWillie.wt = theWillie.weapon:getTypeName()
|
||||
theWillie.pos = theWillie.weapon:getPoint()
|
||||
@@ -486,15 +527,6 @@ function williePete:onEvent(event)
|
||||
|
||||
williePete.addWillie(theWillie)
|
||||
end
|
||||
|
||||
--[[--
|
||||
if event.id == 2 then -- hit
|
||||
local what = "something"
|
||||
if event.target then what = event.target:getName() end
|
||||
--trigger.action.outText("Weapon " .. event.weapon:getTypeName() .. " fired by unit ".. theUnit:getName() .. " " .. pType .. " hit " .. what, 30)
|
||||
-- may need to remove willie from willies
|
||||
end
|
||||
--]]--
|
||||
|
||||
end
|
||||
|
||||
@@ -505,17 +537,15 @@ function williePete.isInside(theWillie)
|
||||
local theUnit = Unit.getByName(theUnitName)
|
||||
if not theUnit then return false end -- unit dead
|
||||
if not Unit.isExist(theUnit) then return false end -- dito
|
||||
|
||||
local thePlayer = williePete.playerGUIs[theUnitName]
|
||||
if not thePlayer then return nil end
|
||||
local theGroup = theUnit:getGroup()
|
||||
local gName = theGroup:getName()
|
||||
local unitInfo = williePete.groupGUIs[gName] -- returns unitInfo struct, contains group info
|
||||
if not unitInfo then return nil end
|
||||
for idx, theZone in pairs(williePete.wpZones) do
|
||||
if cfxZones.pointInZone(thePoint, theZone) then
|
||||
-- we are inside. but is this the right coalition?
|
||||
if thePlayer.coa == theZone.coalition then
|
||||
--trigger.action.outText("Willie in " .. theZone.name, 30)
|
||||
if unitInfo.coa == theZone.coalition then
|
||||
return theZone
|
||||
else
|
||||
--trigger.action.outText("Willie wrong coa", 30)
|
||||
end
|
||||
-- if we want to allow neutral zones (doens't make sense)
|
||||
-- add another guard below
|
||||
@@ -532,8 +562,9 @@ function williePete.projectileHit(theWillie)
|
||||
local vmod = dcsCommon.vMultScalar(theWillie.v, 0.5 / williePete.ups)
|
||||
theWillie.pos = dcsCommon.vAdd(theWillie.pos, vmod)
|
||||
|
||||
-- reset last mark for player
|
||||
local thePlayer = williePete.playerGUIs[theWillie.firedBy]
|
||||
-- reset last mark for player's group
|
||||
-- access unitInfo
|
||||
local thePlayer = williePete.playerGUIs[theWillie.gName]
|
||||
thePlayer.pos = nil
|
||||
thePlayer.wpInZone = nil
|
||||
|
||||
@@ -583,20 +614,25 @@ function williePete.playerUpdate()
|
||||
-- the zone that they checked in, or they are checked out
|
||||
--local zp = cfxZones.getPoint(theZone)
|
||||
for idy, unitInfo in pairs(theZone.checkedIn) do
|
||||
-- make sure unit still exists
|
||||
-- make sure at least one unit still exists
|
||||
local dropUnit = true
|
||||
local theUnit = Unit.getByName(unitInfo.name)
|
||||
if theUnit and Unit.isExist(theUnit) then
|
||||
local up = theUnit:getPoint()
|
||||
up.y = 0
|
||||
local isInside, dist = cfxZones.isPointInsideZone(up, theZone, theZone.checkInRange)
|
||||
|
||||
if isInside then
|
||||
dropUnit = false
|
||||
local theGroup = Group.getByName(unitInfo.gName)
|
||||
local allUnits = theGroup:getUnits()
|
||||
for idx, theUnit in pairs(allUnits) do
|
||||
--local theUnit = Unit.getByName(unitInfo.name)
|
||||
if theUnit and Unit.isExist(theUnit) and
|
||||
theUnit.getPlayerName and theUnit:getPlayerName() then
|
||||
local up = theUnit:getPoint()
|
||||
up.y = 0
|
||||
local isInside, dist = cfxZones.isPointInsideZone(up, theZone, theZone.checkInRange)
|
||||
|
||||
if isInside then
|
||||
dropUnit = false
|
||||
end
|
||||
end
|
||||
end
|
||||
if dropUnit then
|
||||
-- remove from zone check-in
|
||||
-- all outside, remove from zone check-in
|
||||
-- williePete.doCheckOut(unitInfo)
|
||||
timer.scheduleFunction(williePete.doCheckOut, unitInfo, timer.getTime() + 0.1) -- to not muck up iteration
|
||||
end
|
||||
@@ -612,13 +648,10 @@ end
|
||||
function williePete.readConfigZone()
|
||||
local theZone = cfxZones.getZoneByName("wpConfig")
|
||||
if not theZone then
|
||||
if williePete.verbose then
|
||||
trigger.action.outText("+++wp: NO config zone!", 30)
|
||||
end
|
||||
theZone = cfxZones.createSimpleZone("wpConfig")
|
||||
end
|
||||
|
||||
local facTypes = cfxZones.getStringFromZoneProperty(theZone, "facTypes", "all")
|
||||
local facTypes = theZone:getStringFromZoneProperty("facTypes", "all")
|
||||
facTypes = string.upper(facTypes)
|
||||
|
||||
-- make this an array
|
||||
@@ -631,19 +664,19 @@ function williePete.readConfigZone()
|
||||
williePete.facTypes = dcsCommon.trimArray(allTypes)
|
||||
|
||||
-- how long a wp is active. must not be more than 5 minutes
|
||||
williePete.wpMaxTime = cfxZones.getNumberFromZoneProperty(theZone, "wpMaxTime", 3 * 60)
|
||||
williePete.wpMaxTime = theZone:getNumberFromZoneProperty( "wpMaxTime", 3 * 60)
|
||||
|
||||
-- default check-in range, added to target zone's range and used
|
||||
-- for auto-check-out
|
||||
williePete.checkInRange = cfxZones.getNumberFromZoneProperty(theZone, "checkInRange", 10000) -- 10 km outside
|
||||
williePete.checkInRange = theZone:getNumberFromZoneProperty("checkInRange", 10000) -- 10 km outside
|
||||
|
||||
williePete.ackSound = cfxZones.getStringFromZoneProperty(theZone, "ackSound", "some")
|
||||
williePete.guiSound = cfxZones.getStringFromZoneProperty(theZone, "guiSound", "some")
|
||||
williePete.ackSound = theZone:getStringFromZoneProperty( "ackSound", "some")
|
||||
williePete.guiSound = theZone:getStringFromZoneProperty( "guiSound", "some")
|
||||
|
||||
williePete.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
|
||||
williePete.verbose = theZone.verbose
|
||||
|
||||
if williePete.verbose then
|
||||
trigger.action.outText("+++msgr: read config", 30)
|
||||
trigger.action.outText("+++wp: read config", 30)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
xFlags = {}
|
||||
xFlags.version = "1.3.1"
|
||||
xFlags.version = "2.0.0"
|
||||
xFlags.verbose = false
|
||||
xFlags.hiVerbose = false
|
||||
xFlags.ups = 1 -- overwritten in get config when configZone is present
|
||||
@@ -11,30 +11,11 @@ xFlags.requiredLibs = {
|
||||
xFlags - flag array transmogrifier
|
||||
|
||||
Version History
|
||||
1.0.0 - Initial version
|
||||
1.0.1 - allow flags names for ops as well
|
||||
1.1.0 - Watchflags harmonization
|
||||
1.2.0 - xDirect flag,
|
||||
- direct array support
|
||||
1.2.1 - verbosity changes
|
||||
- "most" operator
|
||||
- "half or more" operator
|
||||
- fixed reset
|
||||
- xSuccess optimizations
|
||||
- inc, dec, quoted flags
|
||||
- matchNum can carry flag
|
||||
1.2.2 - on/off/suspend commands
|
||||
- hiVerbose option
|
||||
- corrected bug in reset checksum
|
||||
1.3.0 - xCount! flag
|
||||
- "never" operator
|
||||
1.3.1 - guards for xtriggerOffFlag and xtriggerOnFlag
|
||||
to prevent QoL warnings
|
||||
- guards for xDirect
|
||||
- guards for xCount
|
||||
|
||||
|
||||
|
||||
2.0.0 - dmlZones
|
||||
- OOP
|
||||
- xDirect#
|
||||
- xCount#
|
||||
- cleanup
|
||||
--]]--
|
||||
xFlags.xFlagZones = {}
|
||||
|
||||
@@ -62,11 +43,7 @@ end
|
||||
|
||||
function xFlags.createXFlagsWithZone(theZone)
|
||||
local theArray = ""
|
||||
if cfxZones.hasProperty(theZone, "xFlags") then
|
||||
theArray = cfxZones.getStringFromZoneProperty(theZone, "xFlags", "<none>")
|
||||
else
|
||||
theArray = cfxZones.getStringFromZoneProperty(theZone, "xFlags?", "<none>")
|
||||
end
|
||||
theArray = theZone:getStringFromZoneProperty("xFlags?", "<none>")
|
||||
|
||||
-- now process the array and create the value arrays
|
||||
theZone.flagNames = cfxZones.flagArrayFromString(theArray)
|
||||
@@ -86,95 +63,72 @@ function xFlags.createXFlagsWithZone(theZone)
|
||||
end
|
||||
end
|
||||
theZone.xHasFired = false
|
||||
if cfxZones.hasProperty(theZone, "xSuccess!") then
|
||||
theZone.xSuccess = cfxZones.getStringFromZoneProperty(theZone, "xSuccess!", "<none>")
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "out!") then
|
||||
theZone.xSuccess = cfxZones.getStringFromZoneProperty(theZone, "out!", "*<none>")
|
||||
if theZone:hasProperty("xSuccess!") then
|
||||
theZone.xSuccess = theZone:getStringFromZoneProperty("xSuccess!", "<none>")
|
||||
elseif theZone:hasProperty("out!") then
|
||||
theZone.xSuccess = theZone:getStringFromZoneProperty("out!", "*<none>")
|
||||
end
|
||||
|
||||
if not theZone.xSuccess then
|
||||
theZone.xSuccess = "*<none>"
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "xChange!") then
|
||||
theZone.xChange = cfxZones.getStringFromZoneProperty(theZone, "xChange!", "*<none>")
|
||||
if theZone:hasProperty("xChange!") then
|
||||
theZone.xChange = theZone:getStringFromZoneProperty("xChange!", "*<none>")
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "xDirect") then
|
||||
theZone.xDirect = cfxZones.getStringFromZoneProperty(theZone, "xDirect", "*<none>")
|
||||
if theZone:hasProperty("xDirect#") then
|
||||
theZone.xDirect = theZone:getStringFromZoneProperty("xDirect#", "*<none>")
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "xCount") then
|
||||
theZone.xCount = cfxZones.getStringFromZoneProperty(theZone, "xCount", "*<none>")
|
||||
if theZone:hasProperty("xCount#") then
|
||||
theZone.xCount = theZone:getStringFromZoneProperty("xCount#", "*<none>")
|
||||
end
|
||||
|
||||
theZone.inspect = cfxZones.getStringFromZoneProperty(theZone, "require", "or") -- same as any
|
||||
theZone.inspect = theZone:getStringFromZoneProperty("require", "or") -- same as any
|
||||
-- supported any/or, all/and, moreThan, atLeast, exactly
|
||||
theZone.inspect = string.lower(theZone.inspect)
|
||||
theZone.inspect = dcsCommon.trim(theZone.inspect)
|
||||
|
||||
theZone.matchNum = cfxZones.getStringFromZoneProperty(theZone, "#hits", "1")
|
||||
|
||||
theZone.xTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "xFlagMethod", "change") -- (<>=[number or reference flag], off, on, yes, no, true, false, change
|
||||
|
||||
theZone.matchNum = theZone:getStringFromZoneProperty("#hits", "1") -- string because can also be a flag ref
|
||||
|
||||
theZone.xTriggerMethod = theZone:getStringFromZoneProperty( "xFlagMethod", "change")
|
||||
|
||||
theZone.xTriggerMethod = string.lower(theZone.xTriggerMethod)
|
||||
theZone.xTriggerMethod = dcsCommon.trim(theZone.xTriggerMethod)
|
||||
|
||||
if cfxZones.hasProperty(theZone, "xReset?") then
|
||||
theZone.xReset = cfxZones.getStringFromZoneProperty(theZone, "xReset?", "<none>")
|
||||
theZone.xLastReset = cfxZones.getFlagValue(theZone.xReset, theZone)
|
||||
if theZone:hasProperty("xReset?") then
|
||||
theZone.xReset = theZone:getStringFromZoneProperty("xReset?", "<none>")
|
||||
theZone.xLastReset = theZone:getFlagValue(theZone.xReset)
|
||||
end
|
||||
|
||||
theZone.xMethod = cfxZones.getStringFromZoneProperty(theZone, "xMethod", "inc")
|
||||
if cfxZones.hasProperty(theZone, "method") then
|
||||
theZone.xMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "inc")
|
||||
theZone.xMethod = theZone:getStringFromZoneProperty("xMethod", "inc")
|
||||
if theZone:hasProperty("method") then
|
||||
theZone.xMethod = theZone:getStringFromZoneProperty("method", "inc")
|
||||
end
|
||||
|
||||
theZone.xOneShot = cfxZones.getBoolFromZoneProperty(theZone, "oneShot", true)
|
||||
theZone.xOneShot = theZone:getBoolFromZoneProperty("oneShot", true)
|
||||
|
||||
-- on / off commands
|
||||
-- on/off flags
|
||||
theZone.xSuspended = cfxZones.getBoolFromZoneProperty(theZone, "xSuspended", false) -- we are turned on
|
||||
theZone.xSuspended = theZone:getBoolFromZoneProperty("xSuspended", false) -- we are turned on
|
||||
if theZone.xSuspended and (xFlags.verbose or theZone.verbose) then
|
||||
trigger.action.outText("+++xFlg: <" .. theZone.name .. "> starts suspended", 30)
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "xOn?") then
|
||||
theZone.xtriggerOnFlag = cfxZones.getStringFromZoneProperty(theZone, "xOn?", "*<none1>")
|
||||
theZone.xlastTriggerOnValue = cfxZones.getFlagValue(theZone.xtriggerOnFlag, theZone)
|
||||
if theZone:hasProperty("xOn?") then
|
||||
theZone.xtriggerOnFlag = theZone:getStringFromZoneProperty("xOn?", "*<none1>")
|
||||
theZone.xlastTriggerOnValue = theZone:getFlagValue(theZone.xtriggerOnFlag)
|
||||
end
|
||||
if cfxZones.hasProperty(theZone, "xOff?") then
|
||||
theZone.xtriggerOffFlag = cfxZones.getStringFromZoneProperty(theZone, "xOff?", "*<none2>")
|
||||
theZone.xlastTriggerOffValue = cfxZones.getFlagValue(theZone.xtriggerOffFlag, theZone)
|
||||
if theZone:hasProperty("xOff?") then
|
||||
theZone.xtriggerOffFlag = theZone:getStringFromZoneProperty( "xOff?", "*<none2>")
|
||||
theZone.xlastTriggerOffValue = theZone:getFlagValue(theZone.xtriggerOffFlag)
|
||||
end
|
||||
end
|
||||
|
||||
function xFlags.evaluateNumOrFlag(theAttribute, theZone)
|
||||
-- on entry, theAttribute contains a string
|
||||
-- if it's a number, we return that, if it's a
|
||||
-- string, we see if it's a quoted flag or
|
||||
-- direct flag. in any way, we fetch and return
|
||||
-- that flag's value
|
||||
local aNum = tonumber(theAttribute)
|
||||
if aNum then return aNum end
|
||||
local remainder = dcsCommon.trim(theAttribute)
|
||||
local esc = string.sub(remainder, 1, 1)
|
||||
local last = string.sub(remainder, -1)
|
||||
|
||||
if esc == "(" and last == ")" and string.len(remainder) > 2 then
|
||||
remainder = string.sub(remainder, 2, -2)
|
||||
remainder = dcsCommon.trim(remainder)
|
||||
end
|
||||
|
||||
if esc == "\"" and last == "\"" and string.len(remainder) > 2 then
|
||||
remainder = string.sub(remainder, 2, -2)
|
||||
remainder = dcsCommon.trim(remainder)
|
||||
end
|
||||
|
||||
rNum = cfxZones.getFlagValue(remainder, theZone)
|
||||
return cfxZones.evalRemainder(theAttribute, theZone)
|
||||
end
|
||||
|
||||
function xFlags.evaluateFlags(theZone)
|
||||
@@ -184,7 +138,7 @@ function xFlags.evaluateFlags(theZone)
|
||||
-- since the checksum is order dependent,
|
||||
-- we must preserve the order of the array
|
||||
local flagName = theZone.flagNames[i]
|
||||
currVals[i] = cfxZones.getFlagValue(flagName, theZone)
|
||||
currVals[i] = theZone:getFlagValue(flagName)
|
||||
end
|
||||
|
||||
-- now perform comparison flag by flag
|
||||
@@ -194,37 +148,10 @@ function xFlags.evaluateFlags(theZone)
|
||||
local firstChar = string.sub(op, 1, 1)
|
||||
local remainder = string.sub(op, 2)
|
||||
remainder = dcsCommon.trim(remainder) -- remove all leading and trailing spaces
|
||||
local rNum = tonumber(remainder)
|
||||
if not rNum then
|
||||
-- interpret remainder as flag name
|
||||
-- so we can say >*killMax or "22" with 22 a flag name
|
||||
|
||||
-- we use remainder as name for flag
|
||||
-- PROCESS ESCAPE SEQUENCES
|
||||
local esc = string.sub(remainder, 1, 1)
|
||||
local last = string.sub(remainder, -1)
|
||||
if esc == "@" then
|
||||
remainder = string.sub(remainder, 2)
|
||||
remainder = dcsCommon.trim(remainder)
|
||||
end
|
||||
|
||||
if esc == "(" and last == ")" and string.len(remainder) > 2 then
|
||||
-- note: iisues with startswith("(") ???
|
||||
remainder = string.sub(remainder, 2, -2)
|
||||
remainder = dcsCommon.trim(remainder)
|
||||
end
|
||||
if esc == "\"" and last == "\"" and string.len(remainder) > 2 then
|
||||
remainder = string.sub(remainder, 2, -2)
|
||||
remainder = dcsCommon.trim(remainder)
|
||||
end
|
||||
if cfxZones.verbose then
|
||||
trigger.action.outText("+++zne: accessing flag <" .. remainder .. ">", 30)
|
||||
end
|
||||
|
||||
rNum = cfxZones.getFlagValue(remainder, theZone)
|
||||
end
|
||||
|
||||
local rNum = theZone:evalRemainder(remainder)
|
||||
|
||||
-- this mimics cfxZones.testFlagByMethodForZone method (and is
|
||||
-- the following mimics cfxZones.testFlagByMethodForZone method (and is
|
||||
-- that method's genesis), but is different enough not to invoke that
|
||||
-- method
|
||||
for i = 1, #theZone.flagNames do
|
||||
@@ -298,7 +225,6 @@ function xFlags.evaluateFlags(theZone)
|
||||
return 0, ""
|
||||
end
|
||||
if xFlags.verbose and lastHits ~= hits then
|
||||
--trigger.action.outText("+++xF: hit detected for " .. theZone.flagNames[i] .. " in " .. theZone.name .. "(" .. op .. ")", 30)
|
||||
end
|
||||
end
|
||||
return hits, checkSum
|
||||
@@ -361,7 +287,7 @@ function xFlags.evaluateZone(theZone)
|
||||
end
|
||||
|
||||
if theZone.xChange then
|
||||
cfxZones.pollFlag(theZone.xChange, theZone.xMethod, theZone)
|
||||
theZone:pollFlag(theZone.xChange, theZone.xMethod)
|
||||
if xFlags.verbose then
|
||||
trigger.action.outText("+++xFlag: change bang! on " .. theZone.xChange .. " for " .. theZone.name, 30)
|
||||
end
|
||||
@@ -378,15 +304,15 @@ function xFlags.evaluateZone(theZone)
|
||||
-- true (1)/false(0), no matter if changed or not
|
||||
if theZone.xDirect then
|
||||
if evalResult then
|
||||
cfxZones.setFlagValueMult(theZone.xDirect, 1, theZone)
|
||||
theZone:setFlagValue(theZone.xDirect, 1)
|
||||
else
|
||||
cfxZones.setFlagValueMult(theZone.xDirect, 0, theZone)
|
||||
theZone:setFlagValue(theZone.xDirect, 0)
|
||||
end
|
||||
end
|
||||
|
||||
-- directly set the xCount flag
|
||||
if theZone.xCount then
|
||||
cfxZones.setFlagValueMult(theZone.xCount, hits, theZone)
|
||||
theZone:setFlagValueMult(theZone.xCount, hits)
|
||||
end
|
||||
|
||||
-- now see if we bang the output according to method
|
||||
@@ -394,7 +320,7 @@ function xFlags.evaluateZone(theZone)
|
||||
if xFlags.verbose or theZone.verbose then
|
||||
trigger.action.outText("+++xFlag: success bang! on <" .. theZone.xSuccess .. "> for <" .. theZone.name .. "> with method <" .. theZone.xMethod .. ">", 30)
|
||||
end
|
||||
cfxZones.pollFlag(theZone.xSuccess, theZone.xMethod, theZone)
|
||||
theZone:pollFlag(theZone.xSuccess, theZone.xMethod)
|
||||
theZone.xHasFired = true
|
||||
end
|
||||
end
|
||||
@@ -407,14 +333,14 @@ function xFlags.update()
|
||||
|
||||
for idx, theZone in pairs (xFlags.xFlagZones) do
|
||||
-- see if we should suspend
|
||||
if theZone.xtriggerOnFlag and cfxZones.testZoneFlag(theZone, theZone.xtriggerOnFlag, "change", "xlastTriggerOnValue") then
|
||||
if theZone.xtriggerOnFlag and theZone:testZoneFlag( theZone.xtriggerOnFlag, "change", "xlastTriggerOnValue") then
|
||||
if xFlags.verbose or theZone.verbose then
|
||||
trigger.action.outText("+++xFlg: enabling " .. theZone.name, 30)
|
||||
end
|
||||
theZone.xSuspended = false
|
||||
end
|
||||
|
||||
if theZone.xtriggerOffFlag and cfxZones.testZoneFlag(theZone, theZone.xtriggerOffFlag, "change", "xlastTriggerOffValue") then
|
||||
if theZone.xtriggerOffFlag and theZone:testZoneFlag( theZone.xtriggerOffFlag, "change", "xlastTriggerOffValue") then
|
||||
if xFlags.verbose or theZone.verbose then
|
||||
trigger.action.outText("+++xFlg: DISabling " .. theZone.name, 30)
|
||||
end
|
||||
@@ -428,7 +354,7 @@ function xFlags.update()
|
||||
|
||||
-- see if they should reset
|
||||
if theZone.xReset then
|
||||
local currVal = cfxZones.getFlagValue(theZone.xReset, theZone)
|
||||
local currVal = theZone:getFlagValue(theZone.xReset)
|
||||
if currVal ~= theZone.xLastReset then
|
||||
theZone.xLastReset = currVal
|
||||
if xFlags.verbose or theZone.verbose then
|
||||
@@ -446,14 +372,11 @@ function xFlags.readConfigZone()
|
||||
-- note: must match exactly!!!!
|
||||
local theZone = cfxZones.getZoneByName("xFlagsConfig")
|
||||
if not theZone then
|
||||
if xFlags.verbose then
|
||||
trigger.action.outText("***xFlag: NO config zone!", 30)
|
||||
end
|
||||
return
|
||||
theZone = cfxZones.createSimpleZone("xFlagsConfig")
|
||||
end
|
||||
|
||||
xFlags.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
|
||||
xFlags.ups = cfxZones.getNumberFromZoneProperty(theZone, "ups", 1)
|
||||
xFlags.verbose = theZone.verbose
|
||||
xFlags.ups = theZone:getNumberFromZoneProperty("ups", 1)
|
||||
|
||||
if xFlags.verbose then
|
||||
trigger.action.outText("***xFlg: read config", 30)
|
||||
@@ -476,9 +399,6 @@ function xFlags.start()
|
||||
|
||||
-- process RND Zones
|
||||
local attrZones = cfxZones.getZonesWithAttributeNamed("xFlags")
|
||||
|
||||
-- now create an rnd gen for each one and add them
|
||||
-- to our watchlist
|
||||
for k, aZone in pairs(attrZones) do
|
||||
xFlags.createXFlagsWithZone(aZone) -- process attribute and add to zone
|
||||
xFlags.addxFlags(aZone) -- remember it
|
||||
@@ -509,4 +429,5 @@ end
|
||||
--[[--
|
||||
Additional features:
|
||||
- make #hits compatible to flags and numbers
|
||||
- autoReset -- can be done by short-circuiting xsuccess! into xReset?
|
||||
--]]--
|
||||
Reference in New Issue
Block a user