Version 2.2.3

Many changes, most relating to "Expansion", some community requests. Little Documentation of those changes available yet, though.
This commit is contained in:
Christian Franz 2024-05-09 09:39:22 +02:00
parent 2422d89a32
commit 07a32bd051
24 changed files with 2202 additions and 241 deletions

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,5 @@
FARPZones = {}
FARPZones.version = "2.0.0"
FARPZones.version = "2.1.0"
FARPZones.verbose = false
--[[--
Version History
@ -16,6 +16,15 @@ FARPZones.verbose = false
1.2.1 - now gracefully handles a FARP Zone that does not
contain a FARP, but is placed beside it
2.0.0 - dmlZones
2.0.1 - locking up FARPS for the first five seconds
loadMission handles lockup
FARPs now can show their name
showTitle attribute
refresh attribute (config)
2.0.2 - clean-up
verbosity enhancements
2.1.0 - integration with camp: needs repairs, produceResourceVehicles()
--]]--
@ -23,6 +32,7 @@ FARPZones.requiredLibs = {
"dcsCommon",
"cfxZones", -- Zones, of course
}
FARPZones.lockup = {} -- for the first 5 seconds of the game, released later
-- *** DOES NOT EXTEND ZONES, USES OWN STRUCT ***
-- *** SETS ZONE.OWNER IF PRESENT, POSSIBLE CONFLICT
@ -77,11 +87,11 @@ FARPZones.resourceTypes = {
FARPZones.spinUpDelay = 30 -- seconds until FARP becomes operational after capture
FARPZones.allFARPZones = {}
FARPZones.allFARPZones = {} -- indexed by zone, returns dmlFARP struct
FARPZones.startingUp = false -- not needed / read anywhere
-- FARP ZONE ACCESS
function FARPZones.addFARPZone(aFARP)
function FARPZones.addFARPZone(aFARP) -- entry with dmlFARP struct
FARPZones.allFARPZones[aFARP.zone] = aFARP
end
@ -89,8 +99,8 @@ function FARPZones.removeFARPZone(aFARP)
FARPZones.allFARPZones[aFARP.zone] = nil
end
function FARPZones.getFARPForZone(aZone)
return FARPZones.allFARPZones[aZone]
function FARPZones.getFARPForZone(aZone) -- enter with zone
return FARPZones.allFARPZones[aZone] -- returns dmlFARP
end
function FARPZones.getFARPZoneByName(aName)
@ -153,7 +163,12 @@ function FARPZones.createFARPFromZone(aZone)
theFarp.point = theFarp.mainFarp:getPoint() -- this is FARP, not zone!!!
theFarp.owner = theFarp.mainFarp:getCoalition()
aZone.owner = theFarp.owner
-- end
-- lock up the faction until release in a few seconds after mission start
theFarp.mainFarp:autoCapture(false)
table.insert(FARPZones.lockup, theFarp.mainFarp) -- will be released in 5 seconds
if aZone.verbose then
trigger.action.outText("FARPzone <" .. aZone.name .. "> currently has owner <" .. aZone.owner .. ">", 30)
end
-- get r and phi for defenders
local rPhi = aZone:getVectorFromZoneProperty("rPhiHDef",3)
@ -191,7 +206,7 @@ function FARPZones.createFARPFromZone(aZone)
theFarp.hideBlue = aZone:getBoolFromZoneProperty("hideBlue", false)
theFarp.hideGrey = aZone:getBoolFromZoneProperty("hideGrey", false)
theFarp.hidden = aZone:getBoolFromZoneProperty("hidden", false)
theFarp.showTitle = aZone:getBoolFromZoneProperty("showTitle", true)
theFarp.neutralProduction = aZone:getBoolFromZoneProperty("neutralProduction", false)
return theFarp
end
@ -206,6 +221,12 @@ function FARPZones.drawFARPCircleInMap(theFarp)
theFarp.zone.markID = nil
end
if theFarp.zone and theFarp.zone.titleID then
-- remove previous mark
trigger.action.removeMark(theFarp.zone.titleID)
theFarp.zone.titleID = nil
end
if theFarp.hideRed and
theFarp.owner == 1 then
-- hide only when red
@ -251,39 +272,12 @@ function FARPZones.drawFARPCircleInMap(theFarp)
trigger.action.circleToAll(-1, markID, thePoint, 2000, lineColor, fillColor, 1, true, "")
aZone.markID = markID
end
--[[--
function FARPZones.drawZoneInMap(aZone, owner)
-- owner is 0 = neutral, 1 = red, 2 = blue
-- will save markID in zone's markID
-- should be moved to cfxZones
-- should be able to only show owned
if aZone.markID then
trigger.action.removeMark(aZone.markID)
end
local lineColor = {1.0, 0, 0, 1.0} -- red
local fillColor = {1.0, 0, 0, 0.2} -- red
if owner == 2 then
lineColor = {0.0, 0, 1.0, 1.0}
fillColor = {0.0, 0, 1.0, 0.2}
elseif owner == 0 then
lineColor = {0.8, 0.8, 0.8, 1.0}
fillColor = {0.8, 0.8, 0.8, 0.2}
if theFarp.showTitle then
aZone.titleID = aZone:drawText(aZone.name, 16, lineColor, {0.8, 0.8, 0.8, 0.0})
end
local theShape = 2 -- circle
local markID = dcsCommon.numberUUID()
trigger.action.circleToAll(-1, markID, aZone.point, aZone.radius, lineColor, fillColor, 1, true, "")
aZone.markID = markID
end
--]]--
function FARPZones.scheduedProduction(args)
-- args contain [aFarp, owner]
@ -306,6 +300,35 @@ function FARPZones.scheduedProduction(args)
end
end
function FARPZones.serviceNeedsRepair(theFarp)
if theFarp.owner == 0 then return false end
if not theFarp.resources then return false end -- no group yet
if not Group.isExist(theFarp.resources) then return true end
if theFarp.resources:getSize() < #FARPZones.resourceTypes then
return true
end
return false
end
function FARPZones.produceResourceVehicles(theFarp, coa)
if theFarp.resources and Group.isExist(theFarp.resources) then --theFarp.resources:isExist() then
Group.destroy(theFarp.resources) --theFarp.resources:destroy()
theFarp.resources = nil
end
local unitTypes = FARPZones.resourceTypes -- an array
local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
coa,
theFarp.name .. "-R" .. theFarp.count, -- must be unique
theFarp.resZone,
unitTypes,
"line_v",
theFarp.resHeading)
theFarp.resources = theGroup
theFarp.resourceData = theData
-- update unique counter
theFarp.count = theFarp.count + 1
end
function FARPZones.produceVehicles(theFarp)
local theZone = theFarp.zone
-- trigger.action.outText("entering veh prod run for farp zone <" .. theZone.name .. ">, owner is <" .. theFarp.owner .. ">", 30)
@ -357,6 +380,9 @@ function FARPZones.produceVehicles(theFarp)
theFarp.defenderData = theData
end
-- spawn resource vehicles
FARPZones.produceResourceVehicles(theFarp, theCoalition)
--[[--
unitTypes = FARPZones.resourceTypes
local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
theCoalition,
@ -366,7 +392,8 @@ function FARPZones.produceVehicles(theFarp)
"line_v",
theFarp.resHeading)
theFarp.resources = theGroup
theFarp.resourceData = theData
theFarp.resourceData = theData
--]]--
-- update unique counter
theFarp.count = theFarp.count + 1
end
@ -453,6 +480,20 @@ function FARPZones.somethingHappened(event)
end
--
-- Update / Refresh
--
function FARPZones.refreshMap()
timer.scheduleFunction(FARPZones.refreshMap, {}, timer.getTime() + FARPZones.refresh)
if FARPZones.verbose then
trigger.action.outText("+++Farp map refresh started", 30)
end
for idx, theFARP in pairs(FARPZones.allFARPZones) do
FARPZones.drawFARPCircleInMap(theFARP)
end
end
--
-- LOAD / SAVE
@ -503,6 +544,8 @@ function FARPZones.loadMission()
if theFARP then
theFARP.owner = fData.owner
theFARP.zone.owner = fData.owner
local theAB = theFARP.mainFarp
theAB:setCoalition(theFARP.owner) -- FARP is in lockup.
theFARP.defenderData = dcsCommon.clone(fData.defenderData)
local groupData = fData.defenderData
if groupData and #groupData.units > 0 then
@ -532,6 +575,14 @@ end
--
-- Start
--
function FARPZones.releaseFARPS()
-- trigger.action.outText("Releasing hold on FARPS", 30)
for idx, aFarp in pairs(FARPZones.lockup) do
aFarp:autoCapture(true)
-- trigger.action.outText("releasing farp <" .. aFarp:getName() .. ">", 30)
end
end
function FARPZones.readConfig()
local theZone = cfxZones.getZoneByName("farpZonesConfig")
if not theZone then
@ -542,6 +593,8 @@ function FARPZones.readConfig()
FARPZones.spinUpDelay = theZone:getNumberFromZoneProperty( "spinUpDelay", 30)
FARPZones.refresh = theZone:getNumberFromZoneProperty("refresh", -1)
end
@ -598,6 +651,12 @@ function FARPZones.start()
FARPZones.startingUp = false -- not needed / read anywhere
timer.scheduleFunction(FARPZones.releaseFARPS, {}, timer.getTime() + 5)
if FARPZones.refresh > 0 then
timer.scheduleFunction(FARPZones.refreshMap, {}, timer.getTime() + FARPZones.refresh)
end
trigger.action.outText("cf/x FARP Zones v" .. FARPZones.version .. " started", 30)
return true
end

View File

@ -1,5 +1,5 @@
cfxSSBClient = {}
cfxSSBClient.version = "3.0.1"
cfxSSBClient.version = "4.0.0"
cfxSSBClient.verbose = false
cfxSSBClient.singleUse = false -- set to true to block crashed planes
-- NOTE: singleUse (true) requires SSB to disable immediate respawn after kick
@ -8,76 +8,16 @@ cfxSSBClient.reUseAfter = -1 -- seconds for re-use delay
cfxSSBClient.requiredLibs = {
"dcsCommon", -- always
"cfxGroups", -- for slot access
"cfxMX", --"cfxGroups", -- for slot access
"cfxZones", -- Zones, of course
}
--[[--
Version History
1.0.0 - initial version
1.1.0 - detect airfield by action and location, not group name
1.1.1 - performance tuning. only read player groups once
- and remove in-air-start groups from scan. this requires
- ssb (server) be not modified
1.2.0 - API to close airfields: invoke openAirFieldNamed()
and closeAirfieldNamed() with name as string (exact match required)
to block an airfield for any player aircraft.
Works for FARPS as well
API to associate a player group with any airfied's status (nil for unbind):
cfxSSBClient.bindGroupToAirfield(group, airfieldName)
API shortcut to unbind groups: cfxSSBClient.unbindGroup(group)
verbose messages now identify better: "+++SSB:"
keepInAirGroups option
2.0.0 - include single-use ability: crashed airplanes are blocked from further use
- single-use can be turned off
- getPlayerGroupForGroupNamed()
- split setSlotAccess to single accessor
and interator
- reUseAfter option for single-use
- dcsCommon, cfxZones import
2.0.1 - stricter verbosity: moved more comments to verbose only
2.0.2 - health check code (initial)
- added verbosity
2.0.3 - getPlayerName nil-trap on cloned player planes guard
in onEvent
2.1.0 - slotState
- persistence
3.0.0 - closing an airfield will not kick players who are active
- much better verbosity
- open?
- close?
- also persists closed airfield list
3.0.1 - ability to detect if an airfield doesn't exist (late activate)
WHAT IT IS
SSB Client is a small script that forms the client-side counterpart to
Ciribob's simple slot block. It will block slots for all client airframes
that are on an airfield that does not belong to the faction that currently
owns the airfield.
REQUIRES CIRIBOB's SIMPLE SLOT BLOCK (SSB) TO RUN ON THE SERVER
If run without SSB, your planes will not be blocked.
In order to work, a plane that should be blocked when the airfield or
FARP doesn't belong to the player's faction, the group's first unit
must be within 3000 meters of the airfield and on the ground.
Previous versions of this script relied on group names. No longer.
WARNING:
If you modified ssb's flag values, this script will not work
YOU DO NOT NEED TO ACTIVATE SBB, THIS SCRIPT DOES SO AUTOMAGICALLY
4.0.0 - dmlZones
- cfxMX instead of cfxGroups
--]]--
-- below value for enabled MUST BE THE SAME AS THE VALUE OF THE SAME NAME
-- IN SSB. DEFAULT IS ZERO, AND THIS WILL WORK
cfxSSBClient.enabledFlagValue = 0 -- DO NOT CHANGE, MUST MATCH SSB
cfxSSBClient.disabledFlagValue = cfxSSBClient.enabledFlagValue + 100 -- DO NOT CHANGE
cfxSSBClient.allowNeutralFields = false -- set to FALSE if players can't spawn on neutral airfields
@ -124,26 +64,26 @@ end
-- read client zones
--
function cfxSSBClient.createClientZone(theZone)
local thePoint = cfxZones.getPoint(theZone)
local thePoint = theZone:getPoint()
local theAF = cfxSSBClient.getClosestAirbaseTo(thePoint)
local afName = theAF:getName()
if cfxSSBClient.verbose or theZone.verbose then
trigger.action.outText("+++ssbc: zone <" .. theZone.name .. "> linked to AF/FARP <" .. afName .. ">", 30)
end
theZone.afName = afName
theZone.ssbTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "ssbTriggerMethod", "change")
theZone.ssbTriggerMethod = theZone:getStringFromZoneProperty( "ssbTriggerMethod", "change")
if cfxZones.hasProperty(theZone, "open?") then
theZone.ssbOpen = cfxZones.getStringFromZoneProperty(theZone, "open?", "none")
if theZone:hasProperty("open?") then
theZone.ssbOpen = theZone:getStringFromZoneProperty("open?", "none")
theZone.lastSsbOpen = cfxZones.getFlagValue(theZone.ssbOpen, theZone)
end
if cfxZones.hasProperty(theZone, "close?") then
theZone.ssbClose = cfxZones.getStringFromZoneProperty(theZone, "close?", "none")
if theZone:hasProperty("close?") then
theZone.ssbClose = theZone:getStringFromZoneProperty("close?", "none")
theZone.lastSsbClose = cfxZones.getFlagValue(theZone.ssbClose, theZone)
end
theZone.ssbOpenOnStart = cfxZones.getBoolFromZoneProperty(theZone, "openOnStart", true)
theZone.ssbOpenOnStart = theZone:getBoolFromZoneProperty( "openOnStart", true)
if not theZone.ssbOpenOnStart then
cfxSSBClient.closeAirfieldNamed(theZone.afName)
end
@ -494,7 +434,7 @@ end
-- pre-process static player data to minimize
-- processor load on checks
function cfxSSBClient.processPlayerData()
cfxSSBClient.playerGroups = cfxGroups.getPlayerGroup()
cfxSSBClient.playerGroups = cfxMX.getPlayerGroup()
local pGroups = cfxSSBClient.playerGroups
local filteredPlayers = {}
for idx, theGroup in pairs(pGroups) do
@ -511,7 +451,7 @@ end
-- add airfield information to each player group
function cfxSSBClient.processGroupData()
local pGroups = cfxGroups.getPlayerGroup() -- we want the group.name attribute
local pGroups = cfxMX.getPlayerGroup() -- we want the group.name attribute
for idx, theGroup in pairs(pGroups) do
-- we always use the first player's plane as referenced
local playerData = theGroup.playerUnits[1]

View File

@ -16,6 +16,7 @@ asw.fixes = {} -- all subs that we have a fix on. indexed by sub name
Version History
1.0.0 - initial version
1.0.1 - integration with playerScore
1.0.2 - new useSmoke attribute
--]]--
@ -151,7 +152,9 @@ function asw.dropBuoyFrom(theUnit)
local theBuoy = asw.createBuoyForUnit(theUnit)
-- mark point
dcsCommon.markPointWithSmoke(theBuoy.point, theBuoy.smokeColor)
if asw.useSmoke then
dcsCommon.markPointWithSmoke(theBuoy.point, theBuoy.smokeColor)
end
theBuoy.smokeTimer = now + 5 * 60
-- add buoy to my inventory
@ -202,7 +205,9 @@ function asw.dropBuoyFromZone(theZone)
local theBuoy = asw.createBuoyForZone(theZone)
-- mark point
dcsCommon.markPointWithSmoke(theBuoy.point, theBuoy.smokeColor)
if asw.useSmoke then
dcsCommon.markPointWithSmoke(theBuoy.point, theBuoy.smokeColor)
end
theBuoy.smokeTimer = now + 5 * 60
-- add buoy to my inventory
@ -369,7 +374,9 @@ function asw.updateBuoy(theBuoy, allSubs)
-- see if we need to resmoke
if now > theBuoy.smokeTimer then
--env.info(" resmoking buoy, continue")
dcsCommon.markPointWithSmoke(theBuoy.point, theBuoy.smokeColor)
if asw.useSmoke then
dcsCommon.markPointWithSmoke(theBuoy.point, theBuoy.smokeColor)
end
theBuoy.smokeTimer = now + 5 * 60
--env.info(" resmoke done, continue")
end
@ -1042,7 +1049,8 @@ function asw.readConfigZone()
asw.smokeColor = cfxZones.getSmokeColorStringFromZoneProperty(theZone, "smokeColor", "red")
asw.smokeColor = dcsCommon.smokeColor2Num(asw.smokeColor)
asw.useSmoke = theZone:getBoolFromZoneProperty("useSmoke", true)
asw.killScore = cfxZones.getNumberFromZoneProperty(theZone, "killScore", 0)
if cfxZones.hasProperty(theZone, "killFeat") then

View File

@ -1,5 +1,5 @@
autoCSAR = {}
autoCSAR.version = "2.0.1"
autoCSAR.version = "2.1.0"
autoCSAR.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
@ -16,6 +16,7 @@ autoCSAR.trackedEjects = {} -- we start tracking on eject
2.0.0 - OOP, code clean-up
2.0.1 - fix for coalition change when ejected player changes coas or is forced to neutral
- GC
2.1.0 - persistence support
--]]--
function autoCSAR.removeGuy(args)
@ -195,6 +196,33 @@ function autoCSAR.GC()
autoCSAR.pilotInfo = filtered
end
--
-- load/save
--
function autoCSAR.saveData()
local theData = {}
theData.counter = autoCSAR.counter
return theData, autoCSAR.sharedData
end
function autoCSAR.loadData()
if not persistence then return end
local theData = persistence.getSavedDataForModule("autoCSAR", autoCSAR.sharedData)
if not theData then
if autoCSAR.verbose then
trigger.action.outText("+++autoCSAR: no save data received, skipping.", 30)
end
return
end
if theData.counter then
autoCSAR.counter = theData.counter
end
end
--
-- GO!
--
function autoCSAR.start()
-- lib check
if not dcsCommon.libCheck then
@ -211,6 +239,16 @@ function autoCSAR.start()
-- connect event handler
world.addEventHandler(autoCSAR)
-- do persistence
if persistence then
-- sign up for persistence
callbacks = {}
callbacks.persistData = autoCSAR.saveData
persistence.registerModule("autoCSAR", callbacks)
-- now load my data
autoCSAR.loadData()
end
-- start GC
timer.scheduleFunction(autoCSAR.GC, {}, timer.getTime() + 1)

View File

@ -1,10 +1,10 @@
bank = {}
bank.version = "0.0.0"
bank.version = "1.0.0"
bank.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
}
bank.acts = {}
bank.acts = {} -- 'accounts'
function bank.addFunds(act, amt)
if not act then act = "!!NIL!!" end
@ -57,9 +57,9 @@ function bank.getBalance(act)
return true, curVal
end
function bank.openAccount(act, amount)
function bank.openAccount(act, amount, oride)
if not amount then amount = 0 end
if bank.acts[act] then return false end -- account exists
if bank.acts[act] and not oride then return false end -- account exists
bank.acts[act] = amount
return true
end
@ -79,9 +79,43 @@ function bank.readConfigZone()
bank.acts["blue"] = bank.blue
bank.acts["neutral"] = bank.neutral
if theZone:hasProperty("sharedData") then -- future-proof
bank.sharedData = theZone:getStringFromZoneProperty("sharedData", "cfxNameMissing")
end
bank.verbose = theZone.verbose
end
--
-- load / save (persistence)
--
function bank.saveData()
local theData = {}
-- save current score list. simple clone
local acts = dcsCommon.clone(bank.acts)
theData.acts = acts
return theData, bank.sharedData
end
function bank.loadData()
if not persistence then return end
local theData = persistence.getSavedDataForModule("bank", bank.sharedData)
if not theData then
if bank.verbose then
trigger.action.outText("+++bank: no save data received, skipping.", 30)
end
return
end
local acts = theData.acts
bank.acts = acts
end
--
-- start
--
function bank.start()
-- lib check
if not dcsCommon.libCheck then
@ -94,6 +128,17 @@ function bank.start()
-- read config
bank.readConfigZone()
-- load data if persisted
if persistence then
-- sign up for persistence
callbacks = {}
callbacks.persistData = bank.saveData
persistence.registerModule("bank", callbacks)
-- now load my data
bank.loadData()
end
trigger.action.outText("bank v" .. bank.version .. " started.", 30)
return true

View File

@ -1,5 +1,5 @@
bombRange = {}
bombRange.version = "1.1.1"
bombRange.version = "1.1.2"
bombRange.dh = 1 -- meters above ground level burst
bombRange.requiredLibs = {
@ -20,6 +20,8 @@ VERSION HISTORY
also sampling kill events
1.1.1 - fixed reading smoke color for zone
minor clean-up
1.1.2 - corrected bug when no bomb range is detected
--]]--
bombRange.bombs = {} -- live tracking
bombRange.collector = {} -- post-impact collections for 0.5 secs
@ -492,7 +494,7 @@ function bombRange.impacted(weapon, target, finalPass)
-- see if inside a range
if #bombRange.ranges < 1 then
trigger.action.outText("+++bRng: No Bomb Ranges detected!")
trigger.action.outText("+++bRng: No Bomb Ranges detected!", 30)
return -- no need to update anything
end
local minDist = math.huge

View File

@ -1,15 +1,27 @@
camp = {}
camp.ups = 1
camp.version = "0.0.0"
camp.version = "1.0.2"
camp.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
"cfxMX",
"bank"
}
-- AUTOMATICALLY INTEGRATES WITH income MODULE IF PRESENT
-- REQUIRES CLONEZONES TO RUN (BUT NOT TO START)
--[[--
VERSION HISTORY
1.0.0 - initial version
1.0.1 - changed "Ground Repairs / Upgrades" to "Funds / Repairs / Upgrades"
- provided income info for camp if it exists
- provide income total if exists
- actionSound
- output sound with communications
1.0.2 - integration with FARPZones
--]]--
--
-- CURRENTLY REQUIRES SINGLE-UNIT PLAYER GROUPS
-- REQUIRES CLONEZONES MODULE TO BE RUNNING, BUT NOT TO BE LOADED ON START
--
camp.camps = {} -- all camps on the map
camp.roots = {} -- all player group comms roots
@ -18,16 +30,27 @@ function camp.addCamp(theZone)
camp.camps[theZone.name] = theZone
end
function camp.getMyCurrentCamp(theUnit) -- returns first hit plaayer is in
function camp.getMyCurrentCamp(theUnit) -- returns first hit player is in
local coa = theUnit:getCoalition()
local p = theUnit:getPoint()
for idx, theCamp in pairs(camp.camps) do
if theCamp:pointInZone(p) then
if theCamp.owner == coa and theCamp:pointInZone(p) then
return theCamp
end
end
return nil
end
function camp.getCampsForCoa(coa)
local myCamps = {}
for idx, theCamp in pairs(camp.camps) do
if theCamp.owner == coa then
table.insert(myCamps, theCamp)
end
end
return myCamps
end
function camp.createCampWithZone(theZone)
-- look for all cloners inside my zone
if theZone.verbose or camp.verbose then
@ -47,7 +70,7 @@ function camp.createCampWithZone(theZone)
table.insert(cloners, aZone)
if not aZone:hasProperty("blueOnly") then
table.insert(redCloners, aZone)
end
end
if not aZone:hasProperty("redOnly") then
table.insert(blueCloners, aZone)
end
@ -70,6 +93,12 @@ function camp.createCampWithZone(theZone)
theZone.upgradable = theZone:getBoolFromZoneProperty("upgrade", true)
theZone.repairCost = theZone:getNumberFromZoneProperty("repairCost", 100)
theZone.upgradeCost = theZone:getNumberFromZoneProperty("upgradeCost", 3 * theZone.repairCost)
if theZone:hasProperty("FARP") then
theZone.isAlsoFARP = true
if theZone.verbose or camp.verbose then
trigger.action.outText("+++camp: <" .. theZone.name .. "> has FARP attached", 30)
end
end
end
--
@ -93,7 +122,7 @@ function camp.processPlayers()
for idx, gData in pairs(cfxMX.playerGroupByName) do
gID = gData.groupId
gName = gData.name
local theRoot = missionCommands.addSubMenuForGroup(gID, "Ground Repairs / Upgrades")
local theRoot = missionCommands.addSubMenuForGroup(gID, "Funds / Repairs / Upgrades")
camp.roots[gName] = theRoot
local c00 = missionCommands.addCommandForGroup(gID, "Theatre Overview", theRoot, camp.redirectTFunds, {gName, gID, "tfunds"})
local c0 = missionCommands.addCommandForGroup(gID, "Local Funds & Status Overview", theRoot, camp.redirectFunds, {gName, gID, "funds"})
@ -126,12 +155,16 @@ function camp.doTFunds(args)
local hasBalance, amount = bank.getBalance(coa)
if not hasBalance then return end
local msg = "\nYour faction currently has §" .. amount .. " available for repairs/upgrades.\n"
local income = 0
-- now iterate all camps that are on my side
for idx, theZone in pairs(camp.camps) do
if theZone.owner == coa then
msg = msg .. "\n - <" .. theZone.name .. ">"
if theZone.income then
msg = msg .. " Income: §" .. theZone.income
income = income + theZone.income
end
if theZone.repairable and theZone.upgradable then
msg = msg .. "" .. theZone.repairCost .. "" .. theZone.upgradeCost .. ")"
if camp.zoneNeedsRepairs(theZone, coa) then
@ -165,8 +198,12 @@ function camp.doTFunds(args)
end
end
end
if income > 0 then
msg = msg .. "\n\nTotal Income: §" .. income
end
msg = msg .. "\n"
trigger.action.outTextForGroup(gID, msg, 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
end
function camp.doFunds(args)
@ -183,11 +220,13 @@ function camp.doFunds(args)
if not Unit.isExist(theUnit) or theUnit:getLife() < 1 or
theUnit:inAir() or dcsCommon.getUnitSpeed(theUnit) > 1 then
trigger.action.outTextForGroup(gID, msg, 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
return
end
local theZone = camp.getMyCurrentCamp(theUnit)
if not theZone or (not theZone.repairable) or theZone.owner ~= theUnit:getCoalition() then
trigger.action.outTextForGroup(gID, msg, 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
return
end
@ -204,17 +243,30 @@ function camp.doFunds(args)
msg = msg .. "\nZone <" .. theZone.name .. "> is fully upgraded.\n"
end
trigger.action.outTextForGroup(gID, msg, 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
end
--
-- REPAIRS
--
function camp.zoneNeedsRepairs(theZone, coa)
-- return true if this zone needs repairs, i.e. it has cloners that have a damaged clone set
-- return true if this zone needs repairs, i.e. it has cloners that have a damaged clone set or FARP resource vehicles are incomplete
if theZone.isAlsoFARP and FARPZones then
local theFarp = FARPZones.getFARPForZone(theZone)
if FARPZones.serviceNeedsRepair(theFarp) then
if theZone.verbose or camp.verbose then
trigger.action.outText("camp: <" .. theZone.name .. "> has FARP service is dinged up...", 30)
end
return true
-- WARNING: RETURNS BOOLEAN, not a dmlZone!
end
end
local myCloners = theZone.cloners
if not coa then
trigger.action.outText("+++camp: warning: no coa on zoneNeedsRepair for zone <" .. theZone.name .. ">", 30)
trigger.action.outText("+++camp: warning: no coa on zoneNeedsRepairs for zone <" .. theZone.name .. ">", 30)
elseif coa == 1 then
myCloners = theZone.redCloners
elseif coa == 2 then
@ -251,19 +303,23 @@ function camp.doRepairs(args)
if theUnit:getLife() < 1 then return end
if theUnit:inAir() then
trigger.action.outTextForGroup(gID, "\nPlease land inside a fortified zone to order repairs\n", 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
return
end
if dcsCommon.getUnitSpeed(theUnit) > 1 then
trigger.action.outTextForGroup(gID, "\nYou must come to a complete stop before being able to order repairs\n", 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
return
end
local theZone = camp.getMyCurrentCamp(theUnit)
if not theZone or not theZone.repairable then
trigger.action.outTextForGroup(gID, "\nYou are not inside a zone that can be repaired.\n", 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
return
end
if theZone.owner ~= theUnit:getCoalition() then
trigger.action.outTextForGroup(gID, "\nYou currently do not own zone <" .. theZone.name .. ">. Capture it first.\n", 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
return
end
@ -274,6 +330,7 @@ function camp.doRepairs(args)
msg = msg .. "\nZone <" .. theZone.name .. "> can be upgraded.\n"
end
trigger.action.outTextForGroup(gID, msg, 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
return
end
@ -285,7 +342,9 @@ function camp.doRepairs(args)
end
if amount < theZone.repairCost then
trigger.action.outTextForGroup(gID, "\nYou curently cannot afford repairs here\n", 30)
-- trigger.action.outTextForGroup(gID, "\nYou curently cannot afford repairs here\n", 30)
trigger.action.outTextForGroup(gID, "\nYou curently cannot afford repairs here (§" .. theZone.repairCost .. " required, you have §" .. amount .. ")\n", 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
return
end
@ -296,13 +355,23 @@ function camp.doRepairs(args)
-- cloneZones.spawnWithCloner(theCloner)
bank.withdawFunds(coa, theZone.repairCost)
local ignore, remain = bank.getBalance(coa)
trigger.action.outTextForCoalition(coa, "\nZone <" .. theZone.name .. "> was repaired by <" .. pName ..
trigger.action.outTextForCoalition(coa, "\nZone <" .. theZone.name .. "> was ordered repaired by <" .. pName ..
"> for §" .. theZone.repairCost .. ".\nFaction has §" .. remain .. " remaining funds.\n", 30)
trigger.action.outSoundForCoalition(coa, camp.actionSound)
end
function camp.repairZone(theZone, coa)
theCloner = camp.zoneNeedsRepairs(theZone, coa)
if not theCloner then return end
if type(theCloner) == "boolean" then -- at least farp was dinged up
local theFarp = FARPZones.getFARPForZone(theZone)
FARPZones.produceResourceVehicles(theFarp, coa)
if theZone.verbose or camp.verbose then
trigger.action.outText("+++camp: repaired FARP in camp <" .. theZone.name .. ">", 30)
end
end
theCloner = camp.zoneNeedsRepairs(theZone, coa) -- do again to see if other repairs are needed. FARP repairs come free with first fix
if not theCloner then return end
cloneZones.despawnAll(theCloner)
cloneZones.spawnWithCloner(theCloner)
end
@ -311,7 +380,7 @@ end
--
function camp.zoneNeedsUpgrades(theZone, coa)
-- return true if this zone can be upgraded, i.e. it has cloners that have an empty clone set
-- returns first cloner in this zone that can be upgraded, i.e. it has cloners that have an empty clone set
if not theZone.upgradable then return nil end
local myCloners = theZone.cloners
@ -352,30 +421,36 @@ function camp.doUpgrades(args)
if not pName then pName = "<Big Err>" end
if theUnit:inAir() then
trigger.action.outTextForGroup(gID, "\nPlease land inside a fortified zone to order upgrades.\n", 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
return
end
if dcsCommon.getUnitSpeed(theUnit) > 1 then
trigger.action.outTextForGroup(gID, "\nYou must come to a complete stop before being able to order upgrades\n", 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
return
end
local theZone = camp.getMyCurrentCamp(theUnit)
if not theZone or not theZone.upgradable then
trigger.action.outTextForGroup(gID, "\nYou are not inside a zone that can be upgraded.\n", 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
return
end
if theZone.owner ~= theUnit:getCoalition() then
trigger.action.outTextForGroup(gID, "\nYou currently do not own zone <" .. theZone.name .. ">. Capture it first.\n", 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
return
end
if camp.zoneNeedsRepairs(theZone, coa) then
trigger.action.outTextForGroup(gID, "\nZone <" .. theZone.name .. "> requires repairs before it can be upgraded.\n", 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
return
end
-- if we get here, we are inside a zone that can be upgraded. see if it needs upgrades and then get upgrade cost and see if we have enough fund to do it
if not camp.zoneNeedsUpgrades(theZone, coa) then
trigger.action.outTextForGroup(gID, "\nZone <" .. theZone.name .. "> has been fully upgraded.\n", 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
return
end
@ -387,7 +462,8 @@ function camp.doUpgrades(args)
end
if amount < theZone.upgradeCost then
trigger.action.outTextForGroup(gID, "\nYou curently cannot afford an upgrade here\n", 30)
trigger.action.outTextForGroup(gID, "\nYou curently cannot afford an upgrade here (§" .. theZone.upgradeCost .. " required, you have §" .. amount .. ")\n", 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
return
end
@ -398,8 +474,9 @@ function camp.doUpgrades(args)
-- bill it to side
bank.withdawFunds(coa, theZone.upgradeCost)
local ignore, remain = bank.getBalance(coa)
trigger.action.outTextForCoalition(coa, "\nZone <" .. theZone.name .. "> was upgraded by <" .. pName ..
trigger.action.outTextForCoalition(coa, "\nZone <" .. theZone.name .. "> was ordered upgraded by <" .. pName ..
"> for §" .. theZone.upgradeCost .. ".\nFaction has §" .. remain .. " remaining funds.\n", 30)
trigger.action.outSoundForCoalition(coa, camp.actionSound)
end
-- can be called externally
@ -408,6 +485,32 @@ function camp.upgradeZone(theZone, coa)
if not theCloner then return end
cloneZones.spawnWithCloner(theCloner)
end
--
-- API
--
function camp.campsThatNeedRepairs(coa) -- returns the zones that need repairs
local repairs = {}
for idx, theZone in pairs(camp.camps) do
if theZone.repairable and theZone.owner == coa and camp.zoneNeedsRepairs(theZone, coa) then
table.insert(repairs, theZone)
end
end
return repairs
end
function camp.campsThatNeedUpgrades(coa) -- returns the zones that can be upgraded
local repairs = {}
for idx, theZone in pairs(camp.camps) do
if theZone.upgradable and theZone.owner == coa and camp.zoneNeedsUpgrades(theZone, coa) then
table.insert(repairs, theZone)
end
end
return repairs
end
--
-- Config & Go
--
@ -417,7 +520,7 @@ function camp.readConfigZone()
if not theZone then
theZone = cfxZones.createSimpleZone("campConfig")
end
camp.actionSound = theZone:getStringFromZoneProperty("actionSound", "Quest Snare 3.wav")
camp.verbose = theZone.verbose
end

View File

@ -1,5 +1,5 @@
cfxZones = {}
cfxZones.version = "4.3.1"
cfxZones.version = "4.3.2"
-- cf/x zone management module
-- reads dcs zones and makes them accessible and mutable
@ -50,6 +50,8 @@ cfxZones.version = "4.3.1"
- randomDelayFromPositiveRange also allows 0
- 4.3.1 - new drawText() for zones
- dmlZones:getClosestZone() bridge
- 4.3.2 - new getListFromZoneProperty()
--]]--
--
@ -2097,7 +2099,7 @@ end
function cfxZones.drawZone(theZone, lineColor, fillColor, markID)
if not theZone then return 0 end
if not lineColor then lineColor = {0.8, 0.8, 0.8, 1.0} end
if not fillColor then fillColor = {0.8, 0.8, 0.8, 0.2} end
if not fillColor then fillColor = {0.8, 0.8, 0.8, 0.0} end
if not markID then markID = dcsCommon.numberUUID() end
if theZone.isCircle then
@ -2118,7 +2120,7 @@ function cfxZones.drawText(theZone, theText, fSize, lineColor, fillColor)
if not theZone then return end
if not fSize then fSize = 12 end
if not lineColor then lineColor = {0.8, 0.8, 0.8, 1.0} end
if not fillColor then fillColor = lineColor end
if not fillColor then fillColor = {0, 0, 0, 0} end
local markID = dcsCommon.numberUUID()
local p = theZone:getPoint()
local offset = {x = p.x, y = 0, z = p.z}
@ -2322,6 +2324,24 @@ function dmlZone:getPositiveRangeFromZoneProperty(theProperty, default, defaultm
return lo, up
end
function cfxZones.getListFromZoneProperty(theZone, theProperty, defaultItem) -- comma delimited
if not defaultItem then defaultItem = "default" end
local theString = theZone:getStringFromZoneProperty(theProperty, defaultItem)
if dcsCommon.containsString(theString, ",") then
local theArray = dcsCommon.splitString(theString, ',')
theArray = dcsCommon.trimArray(theArray)
return theArray
else
return {theString}
end
return nil
end
function dmlZone:getListFromZoneProperty(theProperty, defaultItem)
return cfxZones.getListFromZoneProperty(self, theProperty, defaultItem)
end
function cfxZones.hasProperty(theZone, theProperty)
if not theProperty then

View File

@ -1,5 +1,5 @@
cloneZones = {}
cloneZones.version = "2.2.0"
cloneZones.version = "2.2.1"
cloneZones.verbose = false
cloneZones.requiredLibs = {
"dcsCommon", -- always
@ -50,6 +50,8 @@ cloneZones.respawnOnGroupID = true
- damaged! output
- health# output
- persistence: persist oSize and set lastSize
2.2.1 - verbosity updates for post-check
- if cloned group is late activation, turn it off
--]]--
--
@ -1054,7 +1056,12 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
local dataToSpawn = {} -- temp save so we can connect in-group references
for idx, aGroupName in pairs(theZone.cloneNames) do
local rawData, cat, ctry = cfxMX.getGroupFromDCSbyName(aGroupName)
local rawData, cat, ctry = cfxMX.getGroupFromDCSbyName(aGroupName) -- fetches a clone!
-- sanity checks: lateActivation etc
if rawData.lateActivation then
trigger.action.outText("+++clnZ: WARNING - clone group <" .. rawData.name .. "> in cloner <" .. theZone.name .. "> is set to 'late activation'. Ignored.", 30)
rawData.lateActivation = false
end
rawData.CZorigName = rawData.name -- save original group name
local origID = rawData.groupId -- save original group ID
rawData.CZorigID = origID
@ -1287,7 +1294,9 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
end
cloneZones.unitXlate[aUnit.CZorigID] = uID
else
trigger.action.outText("clnZ: post-clone verifiaction failed for unit <" .. uName .. ">: not found", 30)
if spawnZone.verbose then
trigger.action.outText("clnZ: post-clone verifiaction failed for unit <" .. uName .. ">: not found", 30)
end
end
end

View File

@ -1,5 +1,5 @@
csarManager = {}
csarManager.version = "3.2.7"
csarManager.version = "3.4.0"
csarManager.ups = 1
--[[-- VERSION HISTORY
@ -42,12 +42,15 @@ csarManager.ups = 1
- useRanks option
3.2.6 - inBuiltup analogon to cloner
3.2.7 - createCSARForParachutist now supports optional coa (autoCSAR)
3.3.0 - persistence support
3.4.0 - global timeLimit option in config zone
- fixes expiration bug when persisting data
INTEGRATES AUTOMATICALLY WITH playerScore
INTEGRATES WITH LIMITED AIRFRAMES
INTEGRATES AUTOMATICALLY WITH SCRIBE
SUPPORTS PERSISTENCE
--]]--
-- modules that need to be loaded BEFORE I run
@ -201,10 +204,22 @@ function csarManager.createCSARMissionData(point, theSide, freq, name, numCrew,
newMission.timeStamp = timer.getTime() -- now
-- if no time limit given but csarManager's own global dictates it,
-- set it now
if not timeLimit and csarManager.timeLimit then
if csarManager.verbose then
trigger.action.outText("+++csar: setting GLOBAL csar time limit (" .. csarManager.timeLimit[1] .. "," .. csarManager.timeLimit[2] .. ") for new mission " .. name, 30)
end
timeLimit = csarManager.timeLimit
end
-- set timeLimit if enabled
if timeLimit then
local theLimit = cfxZones.randomDelayFromPositiveRange(timeLimit[1], timeLimit[2]) * 60
newMission.expires = timer.getTime() + theLimit
if csarManager.verbose then
trigger.action.outText("+++csar: setting time limit to expire in (" .. math.floor(theLimit/60) .. ") minutes for mission " .. name, 30)
end
end
-- update counter and return
@ -794,10 +809,10 @@ function csarManager.doListCSARRequests(args)
local status = "alive"
if mission.expires then
delta = math.floor ((mission.expires - now) / 60)
if delta < 10 then status = "+deteriorating+" end
if delta < 5 then status = "*critical*" end
if delta < 30 then status = "+deteriorating+" end
if delta < 15 then status = "*critical*" end
if csarManager.verbose then
status = status .. "[" .. delta .. "]" -- remove me
status = status .. " [" .. delta .. "]" -- remove me
end
end
if csarManager.vectoring then
@ -841,9 +856,9 @@ function csarManager.doStatusCarrying(args)
report = report .. "\n".. i .. ") " .. evacMission.name
if evacMission.expires then
delta = math.floor ((evacMission.expires - now) / 60)
if delta > 20 then
report = report .. " is hurt but stable"
elseif delta > 10 then
if delta > 30 then
report = report .. " is hurt and stable"
elseif delta > 15 then
report = report .. " is badly hurt"
else
report = report .. " is in critical condition" -- or 'beat up, but will live'
@ -1438,7 +1453,11 @@ function csarManager.readCSARZone(theZone)
-- add to list of startable csar
if theZone.startCSAR then
csarManager.addCSARZone(theZone)
if persistence and persistence.hasDate then
-- we load data instead of spawning on start
else
csarManager.addCSARZone(theZone)
end
end
if (not deferred) then
@ -1592,12 +1611,88 @@ function csarManager.readConfigZone()
local typeArray = dcsCommon.splitString(hTypes, ",")
typeArray = dcsCommon.trimArray(typeArray)
csarManager.rescueTypes = typeArray
if theZone:hasProperty("timeLimit") then
local tmin, tmax = theZone:getPositiveRangeFromZoneProperty("timeLimit", 1)
csarManager.timeLimit = {tmin, tmax}
else
csarManager.timeLimit = nil
end
if csarManager.verbose then
trigger.action.outText("+++csar: read config", 30)
end
end
--
-- Save and Load Data
--
function csarManager.saveData()
local now = timer.getTime()
local theData = {}
local missions = {}
-- gather dater from all currently open missions and
-- place them in a new array
for idx, aMission in pairs(csarManager.openMissions) do
m = {}
m.point = aMission.zone:getPoint()
m.side = aMission.side
m.freq = aMission.freq
m.name = aMission.name
m.score = aMission.score
if aMission.expires then
remains = (aMission.expires - now) / 60 -- limit in minutes!
m.timeLimit = {remains, remains}
end
table.insert(missions, m)
end
theData.missions = missions
theData.missionID = csarManager.missionID
return theData, csarManager.sharedData
end
function csarManager.loadData()
if not persistence then return end
local theData = persistence.getSavedDataForModule("csarManager", csarManager.sharedData)
if not theData then
if csarManager.verbose then
trigger.action.outText("+++csarManager: no save data received, skipping.", 30)
end
return
end
if theData.missionID then
csarManager.missionID = theData.missionID
end
if theData.missions then
for idx, m in pairs(theData.missions) do
-- csarManager.createCSARMissionData(point, theSide, freq, name, numCrew, timeLimit, mapMarker, inRadius, parashootUnit)
if m.timeLimit and csarManager.verbose then
trigger.action.outText("+++csar: loadData - timelimit of [" .. m.timeLimit[1] .. "," .. m.timeLimit[2] .. "] read for csar <" .. m.name .. ">", 30)
end
local theMission = csarManager.createCSARMissionData(
m.point, -- point,
m.side, -- theSide,
m.freq, -- freq,
m.name, -- name,
nil, -- numCrew,
m.timeLimit, -- timeLimit, can be nil or {lower, upper}
nil, --mapMarker,
0.1, -- inRadius,
nil -- parashootUnit)
)
theMission.score = m.score
csarManager.addMission(theMission)
if csarManager.verbose then
trigger.action.outText("+++csarM (persitence): restored csar mission <" .. m.name .. ">", 30)
end
end
end
end
--
-- Start
--
function csarManager.start()
-- make sure we have loaded all relevant libraries
@ -1624,6 +1719,16 @@ function csarManager.start()
csarManager.setCommsMenu(aUnit)
end
-- connect to persistence if it exists
if persistence then
-- sign up for persistence
callbacks = {}
callbacks.persistData = csarManager.saveData
persistence.registerModule("csarManager", callbacks)
-- now load my data
csarManager.loadData()
end
-- start updating and track all helicopters in the air against missions
csarManager.update()

View File

@ -1,5 +1,5 @@
dcsCommon = {}
dcsCommon.version = "3.0.5"
dcsCommon.version = "3.0.6"
--[[-- VERSION HISTORY
3.0.0 - removed bad bug in stringStartsWith, only relevant if caseSensitive is false
- point2text new intsOnly option
@ -18,6 +18,9 @@ dcsCommon.version = "3.0.5"
- new getFirstItem()
- arrayContainsString() can handle dicts
- new pointXpercentYdegOffAB()
3.0.6 - new arrayContainsStringCaseInsensitive()
3.0.7 - fixed small bug in wildArrayContainsString
--]]--
-- dcsCommon is a library of common lua functions
@ -2093,7 +2096,7 @@ end
if not caseSensitive then theString = string.upper(theString) end
local wildIn = dcsCommon.stringEndsWith(theString, "*")
if wildIn then dcsCommon.removeEnding(theString, "*") end
if wildIn then theString = dcsCommon.removeEnding(theString, "*") end
for idx, theElement in pairs(theArray) do -- i = 1, #theArray do
if not caseSensitive then theElement = string.upper(theElement) end
local wildEle = dcsCommon.stringEndsWith(theElement, "*")
@ -2132,6 +2135,19 @@ end
end
return false
end
function dcsCommon.arrayContainsStringCaseInsensitive(theArray, theString) -- case insensitive
if not theArray then return false end
if not theString then return false end
if type(theArray) ~= "table" then
trigger.action.outText("***arrayContainsStringCI: theArray is not type <table> but <" .. type(theArray) .. ">", 30)
end
theString = string.upper(theString)
for idx, item in pairs(theArray) do
if string.upper(item) == theString then return true end
end
return false
end
function dcsCommon.splitString(inputstr, sep)
if sep == nil then

View File

@ -35,25 +35,40 @@ function income.update()
-- schedule next round
timer.scheduleFunction(income.update, {}, timer.getTime() + income.interval)
local neuI, redI, blueI = income.neutral, income.red, income.blue
-- base income
bank.addFunds(0, income.neutral)
bank.addFunds(1, income.red)
bank.addFunds(2, income.blue)
-- bank.addFunds(0, income.neutral)
-- bank.addFunds(1, income.red)
-- bank.addFunds(2, income.blue)
for idx, theZone in pairs(income.sources) do
bank.addFunds(0, income.getIncomeForZoneAndCoa(theZone, 0))
bank.addFunds(1, income.getIncomeForZoneAndCoa(theZone, 1))
bank.addFunds(2, income.getIncomeForZoneAndCoa(theZone, 2))
local ni = income.getIncomeForZoneAndCoa(theZone, 0)
local ri = income.getIncomeForZoneAndCoa(theZone, 1)
local bi = income.getIncomeForZoneAndCoa(theZone, 2)
redI = redI + ri
blueI = blueI + bi
neuI = neuI + ni
end
bank.addFunds(0, neuI)
bank.addFunds(1, redI)
bank.addFunds(2, blueI)
if income.announceTicks then
-- trigger.action.outText(income.tickMessage, 30)
local has, balance = bank.getBalance(0)
trigger.action.outTextForCoalition(0, "\n" .. income.tickMessage .. "\nNew balance: §" .. balance .. "\n", 30)
local tick = string.gsub(income.tickMessage, "<i>", neuI)
trigger.action.outTextForCoalition(0, "\n" .. tick .. "\nNew balance: §" .. balance .. "\n", 30)
has, balance = bank.getBalance(1)
trigger.action.outTextForCoalition(1, "\n" .. income.tickMessage .. "\nNew balance: §" .. balance .. "\n", 30)
tick = string.gsub(income.tickMessage, "<i>", redI)
trigger.action.outTextForCoalition(1, "\n" .. tick .. "\nNew balance: §" .. balance .. "\n", 30)
has, balance = bank.getBalance(2)
trigger.action.outTextForCoalition(2, "\n" .. income.tickMessage .. "\nNew balance: §" .. balance .. "\n", 30)
tick = string.gsub(income.tickMessage, "<i>", blueI)
trigger.action.outTextForCoalition(2, "\n" .. tick .. "\nNew balance: §" .. balance .. "\n", 30)
end
end
@ -71,7 +86,7 @@ function income.readConfigZone()
income.neutral = theZone:getNumberFromZoneProperty ("neutral", income.base)
income.interval = theZone:getNumberFromZoneProperty("interval", 10 * 60) -- every 10 minutes
income.tickMessage = theZone:getStringFromZoneProperty("tickMessage", "New funds from income available.")
income.tickMessage = theZone:getStringFromZoneProperty("tickMessage", "New funds from income available: §<i>")
income.announceTicks = theZone:getBoolFromZoneProperty("announceTicks", true)
income.verbose = theZone.verbose
end

View File

@ -6,8 +6,6 @@ jtacGrpUI.requiredLibs = {
"cfxGroundTroops",
}
--[[-- VERSION HISTORY
- 1.0.2 - also include idling JTACS
- add positional info when using owned zones
- 2.0.0 - dmlZones
- sanity checks upon load
- eliminated cfxPlayer dependence
@ -21,14 +19,6 @@ jtacGrpUI.requiredLibs = {
jtacGrpUI.groupConfig = {} -- all inited group private config data, indexed by group name.
jtacGrpUI.simpleCommands = true -- if true, f10 other invokes directly
--
-- C O N F I G H A N D L I N G
-- =============================
--
-- Each group has their own config block that can be used to
-- store group-private data and configuration items.
--
function jtacGrpUI.resetConfig(conf)
end

126
modules/milGround.lua Normal file
View File

@ -0,0 +1,126 @@
milGround = {}
milGround.version = "0.0.0"
milGround.requiredLibs = {
"dcsCommon",
"cfxZones",
"cfxMX",
"cloneZones",
}
milGround.ups = 0.5 -- every 2 seconds is enough
milGround.zones = {}
function milGround.addMilGroundZone(theZone)
milGround.zones[theZone.name] = theZone
end
--
-- Reading zones
--
function milGround.readMilGroundZone(theZone)
-- first, check if this zone is also a cloner
if not theZone:hasProperty("cloner") then
trigger.action.outText("mGnd: WARNING: milGround zone <" .. theZone.name .. "> has no 'cloner' interface, will fail!", 30)
end
-- now get the target zone. it's inside the milGround property
local tzn = theZone:getStringFromZoneProperty("milGround", "cfxNone")
local tz = cfxZones.getZoneByName(tzn)
if not tz then
trigger.action.outText("mGnd: target zone <" .. tzn .. "> not found for milGroundZone <" .. theZone.name .. ">, will fail!", 30)
end
theZone.targetZone = tz
if theZone:hasProperty("coalition") then
theZone.owner = theZone:getCoalitionFromZoneProperty("coalition")
end
end
--
-- Update
--
function milGround.update()
timer.scheduleFunction(milGround.update, {}, timer.getTime() + 1/milGround.ups)
for zName, theZone in pairs(milGround.zones) do
-- synch owner and coa
local mo = theZone.masterowner
if mo then
theZone.owner = mo.owner
if theZone.isDynamic then
theZone.coa = theZone.owner
end
end
end
end
--
-- config
--
function milGround.readConfigZone()
local theZone = cfxZones.getZoneByName("milGroundConfig")
if not theZone then
theZone = cfxZones.createSimpleZone("milGroundConfig")
end
milGround.verbose = theZone.verbose
end
--
-- API
--
function milGround.getAttackersForEnemiesOfCoa(coa, addNeutral)
-- return all milGround zones that attack zones that belong to
-- the enemy of coa
local theOtherSide = dcsCommon.getEnemyCoalitionFor(coa)
local attackers = {}
for zName, theZone in pairs(milGround.zones) do
local tz = theZone.targetZone
if tz.owner ~= coa then
if addNeutral then
table.insert(attackers, theZone)
else
if tz.owner == theOtherSide then
table.insert(attackers, theZone)
end
end
end
end
return attackers
end
function milGround.startAttackFrom(theZone)
cloneZones.spawnWithCloner(theZone) -- that's all, folx
end
--
-- start up
--
function milGround.start()
-- lib check
if not dcsCommon.libCheck then
trigger.action.outText("cfx mil ground requires dcsCommon", 30)
return false
end
if not dcsCommon.libCheck("cfx mil ground", milGround.requiredLibs) then
return false
end
-- read config
milGround.readConfigZone()
-- process milGround Zones
local attrZones = cfxZones.getZonesWithAttributeNamed("milGround")
for k, aZone in pairs(attrZones) do
milGround.readMilGroundZone(aZone) -- process attributes
milGround.addMilGroundZone(aZone) -- add to list
end
-- start update in 5 seconds
timer.scheduleFunction(milGround.update, {}, timer.getTime() + 1/milGround.ups)
-- say hi
trigger.action.outText("milGround v" .. milGround.version .. " started.", 30)
return true
end
if not milGround.start() then
trigger.action.outText("milGround failed to start.", 30)
milGround = nil
end

View File

@ -1,5 +1,5 @@
milHelo = {}
milHelo.version = "0.0.0"
milHelo.version = "1.0.0"
milHelo.requiredLibs = {
"dcsCommon",
"cfxZones",
@ -7,7 +7,7 @@ milHelo.requiredLibs = {
}
milHelo.zones = {}
milHelo.targetKeywords = {
"milTarget", -- my own
"milTarget", -- my own zone
"camp", -- camps
"airfield", -- airfields
"FARP", -- FARPzones
@ -74,6 +74,19 @@ function milHelo.readMilHeloZone(theZone) -- process attributes
theZone.msnType = "cas"
end
-- see if our ownership is tied to a master
-- adds dynamic coalition capability
if theZone:hasProperty("masterOwner") then
local mo = theZone:getStringFromZoneProperty("masterOwner")
local mz = cfxZones.getZoneByName(mo)
if not mz then
trigger.action.outText("+++milH: WARNING: Master Owner <" .. mo .. "> for zone <" .. theZone.name .. "> does not exist!", 30)
else
theZone.masterOwner = mz
end
theZone.isDynamic = theZone:getBoolFromZoneProperty("dynamic", true)
end
-- get all groups inside me
local myGroups, count = milHelo.allGroupsInZoneByData(theZone)
theZone.myGroups = myGroups
@ -179,6 +192,9 @@ function milHelo.createROETask(num, roe)
end
function milHelo.createEngageIZTask(num, theZone)
-- trigger.action.outText("Creating engage in zone task for zone <" .. theZone.name .. ">, marking on map", 30)
-- theZone:drawZone()
-- theZone:drawText("casz - " .. theZone.name, 20)
local p = theZone:getPoint()
if not num then num = 1 end
local task = {}
@ -266,7 +282,8 @@ function milHelo.createCommandTask(theCommand, num)
return t
end
function milHelo.createTakeOffWP(theZone, engageInZone, engageZone)
function milHelo.createTakeOffWP(theZone, engageInZone, engageZone, ROE)
if not ROE then ROE = 0 end -- wepons free
local WP = {}
WP.alt = 500 -- theZone.alt
WP.alt_type = "BARO"
@ -281,7 +298,7 @@ function milHelo.createTakeOffWP(theZone, engageInZone, engageZone)
local tasks = {}
local casTask = milHelo.createCASTask(1)
tasks[1] = casTask
local roeTask = milHelo.createROETask(2,0) -- 0 = weapons free
local roeTask = milHelo.createROETask(2,ROE) -- 0 = weapons free, 4 = weapon hold
tasks[2] = roeTask
if engageInZone then
if not engageZone then
@ -353,7 +370,7 @@ function milHelo.createLandWP(gName, theZone, targetZone)
return toWP
end
function milHelo.createOMWCallbackWP(gName, number, pt, alt, speed, action) -- name is group name
function milHelo.createOMWCallbackWP(gName, number, pt, alt, speed, action, ROE) -- name is group name
if not action then action = "none" end
local omwWP = dcsCommon.createSimpleRoutePointData(pt, alt, speed)
omwWP.alt_type = "RADIO"
@ -364,29 +381,23 @@ function milHelo.createOMWCallbackWP(gName, number, pt, alt, speed, action) -- n
local ttsk = {}
local command = "milHelo.reachedWP('" .. gName .. "', '" .. number .. "', '" .. action .."')"
ttsk[1] = milHelo.createCommandTask(command,1)
if ROE then
ttsk[2] = milHelo.createROETask(2, ROE)
end
task.params.tasks = ttsk
omwWP.task = task
return omwWP
end
-- a point yDegrees off the path from AB, xPercent of the total distance
-- between A and B away from A
--[[--
function milHelo.pointXpercentYdegOffAB(A, B, xPer, yDeg) -- rets xzz point
local bearingRad = dcsCommon.bearingFromAtoB(A, B)
local dist = dcsCommon.dist(A, B)
local deviation = bearingRad + yDeg * 0.0174533
local newDist = dist * xPer/100
local newPoint = dcsCommon.pointInDirectionOfPointXYY(deviation, newDist, A)
return newPoint
end
--]]--
function milHelo.spawnForZone(theZone, targetZone)
-- note that each zone only has a single msnType, so zone
-- defines msn type
local n = dcsCommon.randomBetween(1, theZone.hCount)
local theRawData = dcsCommon.getNthItem(theZone.hGroups, n)
local gData = dcsCommon.clone(theRawData)
local oName = gData.name
gData.lateActivation = false
-- pre-process gData: names, id etc
gData.name = dcsCommon.uuid(gData.name)
@ -414,41 +425,59 @@ function milHelo.spawnForZone(theZone, targetZone)
trigger.action.outText("Setting up casZ for <" .. theZone.name .. "> to <" .. targetZone.name .. ">", 30)
end
local wpTOff = milHelo.createTakeOffWP(theZone, casInZone, targetZone)
local wpTOff = milHelo.createTakeOffWP(theZone, casInZone, targetZone) -- no ROE = weapons free
-- depending on mission, create an orbit or land WP
local dest = targetZone:getPoint()
local B = dest
local A = theZone:getPoint()
if theZone.msnType == "cas" or theZone.msnType == "patrol" then
dcsCommon.addRoutePointForGroupData(gData, wpTOff)
-- patrol and cas go straight to the target, they do not
-- have an ingress. Meaning: they have the same route
-- profile as 'insert'
wpTOff = milHelo.createTakeOffWP(theZone, casInZone, targetZone, 4) -- 4 = weapons HOLD
dcsCommon.addRoutePointForGroupData(gData, wpTOff) -- wp 1
-- on approach, 70% at target, go weapons hot
local apr = dcsCommon.vLerp(A, B, 0.75)
local omw2 = milHelo.createOMWCallbackWP(gName, 2, apr, theZone.alt, theZone.speed, "weapons free", 0) -- wp 2
dcsCommon.addRoutePointForGroupData(gData, omw2)
-- possible expandion: if cas, we have an ingress point?
local wpDest = milHelo.createOrbitWP(theZone, dest)
dcsCommon.addRoutePointForGroupData(gData, wpDest)
local retPt = milHelo.createLandWP(gName, theZone, theZone)
dcsCommon.addRoutePointForGroupData(gData, retPt)
--dcsCommon.dumpVar2Str("caser group", gData)
dcsCommon.addRoutePointForGroupData(gData, wpDest) -- wp 3
--local retPt = milHelo.createLandWP(gName, theZone, theZone)
--dcsCommon.addRoutePointForGroupData(gData, retPt)
local retpt = theZone:getPoint()
local omw4 = milHelo.createOMWCallbackWP(gName, 4, retpt, theZone.alt, theZone.speed, "remove")
dcsCommon.addRoutePointForGroupData(gData, omw4) -- wp 4
elseif theZone.msnType == "casz" then
dcsCommon.addRoutePointForGroupData(gData, wpTOff)
wpTOff = milHelo.createTakeOffWP(theZone, casInZone, targetZone, 4) -- 4 = ROE weapons hold
dcsCommon.addRoutePointForGroupData(gData, wpTOff) -- wp 1
-- go to CAS destination with Engage in Zone active
-- we may want to make ingress and egress wp before heading to
-- the 'real' CASZ point
-- make ingress point, in direction of target, 30 degrees to the right, half distance.
local ingress = dcsCommon.pointXpercentYdegOffAB(A, B, math.random(50,80), math.random(20,50))
--local pt = targetZone:getPoint()
local omw1 = milHelo.createOMWCallbackWP(gName, 2, ingress, theZone.alt, theZone.speed, "none")
local omw1 = milHelo.createOMWCallbackWP(gName, 2, ingress, theZone.alt, theZone.speed, "weapons free", 0) -- wp 2
dcsCommon.addRoutePointForGroupData(gData, omw1)
local omw2 = milHelo.createOMWCallbackWP(gName, 3, B, theZone.alt, theZone.speed, "none")
dcsCommon.addRoutePointForGroupData(gData, omw2)
dcsCommon.addRoutePointForGroupData(gData, omw2) -- wp 3
-- egress point
local egress = dcsCommon.pointXpercentYdegOffAB(B, A, math.random(20, 50), math.random(20,50))
local omw3 = milHelo.createOMWCallbackWP(gName, 4, egress, theZone.alt, theZone.speed, "none")
dcsCommon.addRoutePointForGroupData(gData, omw3)
local retPt = milHelo.createLandWP(gName, theZone, theZone)
dcsCommon.addRoutePointForGroupData(gData, retPt)
dcsCommon.addRoutePointForGroupData(gData, omw3) -- wp 4
-- return to aerodrome, deallocate
local retpt = theZone:getPoint()
local omw4 = milHelo.createOMWCallbackWP(gName, 5, retpt, theZone.alt, theZone.speed, "remove")
dcsCommon.addRoutePointForGroupData(gData, omw4) -- wp 5
elseif theZone.msnType == "insert" then
local wpDest = milHelo.createLandWP(gName, theZone, targetZone)
dcsCommon.addRoutePointForGroupData(gData, wpTOff)
dcsCommon.addRoutePointForGroupData(gData, wpDest)
dcsCommon.addRoutePointForGroupData(gData, wpTOff) -- wp 1
dcsCommon.addRoutePointForGroupData(gData, wpDest) -- wp 2
-- will land and dealloc there after spawning troops
end
-- make coa a cty
@ -481,6 +510,7 @@ function milHelo.insertTroops(theUnit, targetZone, srcZone)
end
local gData = dcsCommon.clone(theRawData)
gData.lateActivation = false -- force false
-- deploy in ring formation
-- remove all routes
-- mayhaps prepare for orders and formation
@ -511,7 +541,7 @@ function milHelo.insertTroops(theUnit, targetZone, srcZone)
local groupCat = Group.Category.GROUND
local theSpawnedGroup = coalition.addGroup(cty, groupCat, gData)
trigger.action.outText("Inserted troops <" .. gName .. ">", 30)
--trigger.action.outText("Inserted troops <" .. gName .. ">", 30)
return theSpawnedGroup, gData
end
@ -580,12 +610,24 @@ function milHelo.spawnImpostorsFromData(rawData, cat, ctry)
end
function milHelo.reachedWP(gName, wpNum, action)
trigger.action.outText("MilH group <" .. gName .. " reached wp #" .. wpNum .. ".", 30)
if not action then action = "NIL" end
if milHelo.verbose then
trigger.action.outText("MilH group <" .. gName .. " reached wp #" .. wpNum .. " with action <" .. action .. ">.", 30)
end
if action == "remove" then
theGroup = Group.getByName(gName)
if theGroup and Group.isExist(theGroup) then
if milHelo.verbose then
trigger.action.outText("%%%%%%%%%% removing mil hel <" .. gName .. ">", 30)
end
Group.destroy(theGroup)
end
end
end
function milHelo.landedCB(who, where, from) -- who group name, where a zone
trigger.action.outText("milhelo landed CB for group <" .. who .. ">", 30)
-- trigger.action.outText("milhelo landed CB for group <" .. who .. ">", 30)
-- step 1: remove the flight
local theGroup = Group.getByName(who)
if theGroup then
@ -601,7 +643,8 @@ function milHelo.landedCB(who, where, from) -- who group name, where a zone
local theFlight = milHelo.flights[who]
local oName = theFlight.oName
local theZone = theFlight.origin
if theZone.msn == "insertion" then
-- note: "insertion" is probably wrong, remove in line below
if theZone.msnType == "insertion" or theZone.msnType == "insert" then
-- create a static stand-in for scenery
local rawData, cat, ctry = milHelo.getRawDataFromGroupNamed(who, oName)
Group.destroy(aGroup)
@ -620,6 +663,17 @@ end
--
function milHelo.update()
timer.scheduleFunction(milHelo.update, {}, timer.getTime() + 1)
-- update all master owners
for idx, theZone in pairs (milHelo.zones) do
local mo = theZone.masterOwner
if mo then
theZone.owner = mo.owner
if theZone.isDynamic then
theZone.coa = mo.owner
end
end
end
end
function milHelo.GCcollected(gName)
@ -672,10 +726,16 @@ function milHelo:onEvent(theEvent)
else
-- maybe its a return flight
if srcZone:pointInZone(p) then
trigger.action.outText("Flight <" .. gName .. "> originating from <" .. srcZone.name .. "> landed back home", 30)
-- trigger.action.outText("Flight <" .. gName .. "> originating from <" .. srcZone.name .. "> landed back home", 30)
else
trigger.action.outText("Flight <" .. gName .. "> originating from <" .. srcZone.name .. "> landed OUTSIDE of src or target zone <" .. tgtZone.name .. ">", 30)
end
-- trigger.action.outText("Flight <" .. gName .. "> originating from <" .. srcZone.name .. "> landed OUTSIDE of src or target zone <" .. tgtZone.name .. ">, clearing.", 30)
end
-- remove it now
local theGroup = Group.getByName(gName)
if theGroup and Group.isExist(theGroup) then
Group.destroy(theGroup)
end
end
end
@ -690,7 +750,7 @@ function milHelo.getMilSources(side, msnType) -- msnType is optional
if side == "blue" then side = 2 end
local sources = {}
for idx, theZone in pairs(milHelo.zones) do
if theZone.owner == side then -- must be owned by same side
if theZone.coa == side then -- coa must be same side, use masterOwner for dynamism
if msnType then
if theZone.msnType == msnType then
table.insert(sources, theZone)
@ -703,13 +763,18 @@ function milHelo.getMilSources(side, msnType) -- msnType is optional
return sources -- an array, NOT dict so we can pickrandom
end
function milHelo.getMilTargets(side) -- gets mil targets that DO NOT belong to side
function milHelo.getMilTargets(side, ignoreNeutral) -- gets mil targets that DO NOT belong to side
if side == "red" then side = 1 end -- better safe...
if side == "blue" then side = 2 end
local tgt = {}
for idx, theZone in pairs(milHelo.targets) do
-- we use OWNER, not COA here!
if theZone.owner ~= side then -- must NOT be owned by same side
table.insert(tgt, theZone)
if ignoreNeutral and theZone.owner == 0 then
else
table.insert(tgt, theZone)
--trigger.action.outText("zone <" .. theZone.name .. "> owned by <" .. theZone.owner .. "> is possible target for coa <" .. side .. ">", 30)
end
end
end
return tgt
@ -730,7 +795,7 @@ end
function milHelo.start()
-- lib check
if not dcsCommon.libCheck then
trigger.action.outText("cfx civ helo requires dcsCommon", 30)
trigger.action.outText("cfx mil helo requires dcsCommon", 30)
return false
end
if not dcsCommon.libCheck("cfx mil helo", milHelo.requiredLibs) then
@ -758,6 +823,9 @@ function milHelo.start()
-- start update in 5 seconds
timer.scheduleFunction(milHelo.update, {}, timer.getTime() + 1/milHelo.ups)
-- start GC
milHelo.GC()
-- install event handler
world.addEventHandler(milHelo)

572
modules/milWings.lua Normal file
View File

@ -0,0 +1,572 @@
milWings = {}
milWings.version = "0.9.5"
milWings.requiredLibs = {
"dcsCommon",
"cfxZones",
"cfxMX",
"wingTaskHelper",
}
milWings.zones = {} -- mil wings zones for flights. can have master owner
milWings.targetKeywords = { -- same as mil helo plus x
"wingTarget", -- my own zone
"milTarget", -- milH zones
"camp", -- camps
"airfield", -- airfields
"FARP", -- FARPzones
}
milWings.targets = {} -- targets for mil wings. can have master owner
-- includes wingTarget and other keywors
milWings.pureTargets = {} -- targets with wingTarget keyword
milWings.seadTargets = {}
milWings.flights = {} -- all currently active mil helo flights
milWings.ups = 1
milWings.missionTypes = {
"cas", -- standard cas
"cap", -- orbit over zone for duration
"sead", -- engage in zone for target zone's radius
"bomb",
}
function milWings.addMilWingsZone(theZone)
milWings.zones[theZone.name] = theZone
end
function milWings.addMilWingsTargetZone(theZone)
milWings.targets[theZone.name] = theZone -- overwrite if duplicate
if theZone:hasProperty("SEAD") then
milWings.seadTargets[theZone.name] = theZone
end
if theZone:hasProperty("wingTarget") then
milWings.pureTargets[theZone.name] = theZone
theZone.wingTargetName = theZone:getStringFromZoneProperty("wingTarget", "<*" .. theZone.name .. ">")
end
end
--
-- Reading / Processing milWings Zones
--
function milWings.partOfGroupDataInZone(theZone, theUnits) -- move to mx?
local zP = theZone:getDCSOrigin() -- don't use getPoint now.
zP.y = 0
for idx, aUnit in pairs(theUnits) do
local uP = {}
uP.x = aUnit.x
uP.y = 0
uP.z = aUnit.y -- !! y-z
if theZone:pointInZone(uP) then return true end
end
return false
end
function milWings.allGroupsInZoneByData(theZone) -- move to MX?
local theGroupsInZone = {}
local count = 0
for groupName, groupData in pairs(cfxMX.groupDataByName) do
if groupData.units then
if milWings.partOfGroupDataInZone(theZone, groupData.units) then
theGroupsInZone[groupName] = groupData -- DATA! work on clones!
count = count + 1
if theZone.verbose then
trigger.action.outText("+++milH: added group <" .. groupName .. "> for zone <" .. theZone.name .. ">", 30)
end
end
end
end
return theGroupsInZone, count
end
function milWings.readMilWingsZone(theZone) -- process attributes
theZone.msnType = string.lower(theZone:getStringFromZoneProperty("milWings", "cas"))
if dcsCommon.arrayContainsString(milWings.missionTypes, theZone.msnType) then
-- great, mission type is known
else
trigger.action.outText("+++milW: zone <" .. theZone.name .. ">: unknown wing mission type <" .. theZone.msnType .. ">, defaulting to 'CAS'", 30)
theZone.msnType = "cas"
end
-- see if our ownership is tied to a master
-- this adds dynamic coalition capability
if theZone:hasProperty("masterOwner") then
local mo = theZone:getStringFromZoneProperty("masterOwner")
local mz = cfxZones.getZoneByName(mo)
if not mz then
trigger.action.outText("+++milW: WARNING: Master Owner <" .. mo .. "> for zone <" .. theZone.name .. "> does not exist!", 30)
else
theZone.masterOwner = mz
end
theZone.isDynamic = theZone:getBoolFromZoneProperty("dynamic", true)
if theZone.verbose then
trigger.action.outText("milwing target <" .. theZone.name .. "> has masterOwner <" .. theZone.mz.name .. ">", 30)
if theZone.isDynamic then
trigger.action.outText("and coa is dynamically linked to owner.", 30)
end
end
end
-- get all groups inside me
local myGroups, count = milWings.allGroupsInZoneByData(theZone)
theZone.fGroups = {}
theZone.fCount = 0
-- sort into ground, helo and fixed
for groupName, data in pairs(myGroups) do
local catRaw = cfxMX.groupTypeByName[groupName]
if theZone.verbose then
trigger.action.outText("Proccing zone <" .. theZone.name .. ">: group <" .. groupName .. "> - type <" .. catRaw .. ">", 30)
end
if catRaw == "plane" then
theZone.fGroups[groupName] = data
theZone.fCount = theZone.fCount + 1
else
trigger.action.outText("+++milH: ignored group <" .. groupName .. ">: wrong type <" .. catRaw .. ">", 30)
end
end
theZone.coa = theZone:getCoalitionFromZoneProperty("coalition", 0)
theZone.hot = theZone:getBoolFromZoneProperty("hot", false)
theZone.inAir = theZone:getBoolFromZoneProperty("inAir", false)
theZone.speed = theZone:getNumberFromZoneProperty("speed", 220) -- 800 kmh
theZone.alt = theZone:getNumberFromZoneProperty("alt", 6000) -- we are always radar alt
theZone.loiter = theZone:getNumberFromZoneProperty("loiter", 3600) -- 1 hour loiter default
-- wipe all existing
for groupName, data in pairs(theZone.fGroups) do
local g = Group.getByName(groupName)
if g then
Group.destroy(g)
end
end
if theZone.verbose or milWings.verbose then
trigger.action.outText("+++milW: processed milWings zone <" .. theZone.name .. ">", 30)
end
end
function milWings.readMilWingsTargetZone(theZone)
-- can also be "camp", "farp", "airfield"
theZone.wingRadius = theZone:getNumberFromZoneProperty("wingRadius", theZone.radius)
if (not theZone.isCircle) and (not theZone:hasProperty("wingRadius")) then
-- often when we have a camp there is no cas radius, use 60km
-- and zone is ploygonal
if theZone.verbose then
trigger.action.outText("+++milH: Warning - milH target zone <" .. theZone.name .. "> is polygonal and has no CAS radius attribute. Defaulting to 80km", 30)
end
-- theZone.casRadius = 80000
theZone.wingRadius = 80000
end
if theZone:hasProperty("wingTypes") then -- if present, else all good
theZone.wingTypes = theZone:getListFromZoneProperty("wingTypes", "empty")
end
if theZone.verbose or milWings.verbose then
trigger.action.outText("+++milH: processed milWings TARGET zone <" .. theZone.name .. ">", 30)
end
if theZone:hasProperty("masterOwner") then
local mo = theZone:getStringFromZoneProperty("masterOwner")
local mz = cfxZones.getZoneByName(mo)
if not mz then
trigger.action.outText("+++milW: WARNING: Master Owner <" .. mo .. "> for zone <" .. theZone.name .. "> does not exist!", 30)
else
theZone.masterOwner = mz
end
end
end
--
-- creating flights
--
function milWings.createTakeOffWayPoint(theZone, targetZone)
local wp = {}
wp.alt = theZone.alt
wp.action = "Turning Point" -- default. overrides hot
wp.type = "Turning Point"
if not theZone.inAir then
if theZone.hot then
wp.action = "From Parking Area Hot"
wp.type = "TakeOffParkingHot"
else
wp.action = "From Parking Area"
wp.type = "TakeOffParking"
end
wp.alt = 0
wp.speed = 0
local af = dcsCommon.getClosestAirbaseTo(theZone:getPoint(), 0)
-- trigger.action.outText("closest airfield for this flight is <" .. af:getName() .. ">", 30)
wp.airdromeId = af:getID()
end
-- trigger.action.outText("flight has action <" .. wp.action .. "> and type <" .. wp.type .. ">", 30)
wp.speed = theZone.speed
local p = theZone:getPoint()
wp.x = p.x
wp.y = p.z
wp.formation_template= ""
if theZone.msnType == "cas" then
wp.task = dcsCommon.clone(wingTaskHelper.casTOTask)
-- now simply change some bits from the template
p = targetZone:getPoint()
wp.task.params.tasks[8].params.x = p.x
wp.task.params.tasks[8].params.y = p.z
if targetZone.wingRadius then -- should ALWAYS be true
wp.task.params.tasks[8].params.zoneRadius = targetZone.wingRadius
else
wp.task.params.tasks[8].params.zoneRadius = 80000
trigger.action.outText("WARNING: creating CAS flight <" .. theZone.name .. "> with no radius for target zone <" .. targetZone.name .. ">", 30)
end
elseif theZone.msnType == "cap" then
wp.task = dcsCommon.clone(wingTaskHelper.capTOTask)
p = targetZone:getPoint()
wp.task.params.tasks[6].params.x = p.x
wp.task.params.tasks[6].params.y = p.z
-- wp.task.params.tasks[6].params.zoneRadius = theZone.capRadius
if targetZone.wingRadius then -- should ALWAYS be true
wp.task.params.tasks[6].params.zoneRadius = targetZone.wingRadius
else
wp.task.params.tasks[6].params.zoneRadius = 80000
-- trigger.action.outText("WARNING: creating CAP flight <" .. theZone.name .. "> with no radius for target zone <" .. targetZone.name .. ">", 30)
end
elseif theZone.msnType == "sead" then
wp.task = dcsCommon.clone(wingTaskHelper.seadTOTask)
elseif theZone.msnType == "bomb" then
wp.task = dcsCommon.clone(wingTaskHelper.bombTOTask)
else
trigger.action.outText("milW: unknown msnType <" .. theZone.msnType .. "> in zone <" .. theZone.name .. ">", 30)
end
return wp
end
function milWings.createActionWaypoint(theZone, targetZone)
local p = targetZone:getPoint()
local wp = dcsCommon.createSimpleRoutePointData(p, theZone.alt, theZone.speed)
if theZone.msnType == "bomb" then
local task = dcsCommon.clone(wingTaskHelper.bombActionTask)
task.params.tasks[1].params.x = p.x
task.params.tasks[1].params.y = p.z
task.params.tasks[1].params.altitude = theZone.alt
task.params.tasks[1].params.speed = theZone.speed
wp.task = task
end
return wp
end
function milWings.createCommandTask(theCommand, num)
if not num then num = 1 end
local t = {}
t.enabled = true
t.auto = false
t.id = "WrappedAction"
t.number = num
local params = {}
t.params = params
local action = {}
params.action = action
action.id = "Script"
local p2 = {}
action.params = p2
p2.command = theCommand
return t
end
function milWings.createCallbackWP(gName, number, pt, alt, speed, action) -- name is group name
if not action then action = "none" end
local omwWP = dcsCommon.createSimpleRoutePointData(pt, alt, speed)
omwWP.alt_type = "BARO"
-- create a command waypoint
local task = {}
task.id = "ComboTask"
task.params = {}
local ttsk = {}
local command = "milWings.inFlightCB('" .. gName .. "', '" .. number .. "', '" .. action .."')"
ttsk[1] = milWings.createCommandTask(command,1)
task.params.tasks = ttsk
omwWP.task = task
return omwWP
end
function milWings.spawnForZone(theZone, targetZone)
-- pick one of the flight groups
if not theZone.fCount or theZone.fCount < 1 then
trigger.action.outText("+++milW: WARNING - no f-groups in zone <" .. theZone.name .. "> at spawnForZone", 30)
return nil, nil
end
local n = dcsCommon.randomBetween(1, theZone.fCount)
local theRawData = dcsCommon.getNthItem(theZone.fGroups, n)
local gData = dcsCommon.clone(theRawData)
if not gData then
trigger.action.outText("+++milW: WARNING: NIL gData in spawnForZone for <" .. theZone.name .. ">", 30)
return nil, nil
end
gData.lateActivation = false
local oName = gData.name
-- pre-process gData: names, id etc
gData.name = dcsCommon.uuid(gData.name)
local gName = gData.name
for idx, uData in pairs(gData.units) do
uData.name = dcsCommon.uuid(uData.name)
uData.alt = theZone.alt
uData.alt_type = "BARO"
uData.speed = theZone.speed
uData.unitId = nil
end
gData.groupId = nil
-- set task for group
gData.task = "CAS" -- default
if theZone.msnType == "cap" then
gData.task = "CAP"
elseif theZone.msnType == "sead" then
gData.task = "SEAD"
elseif theZone.msnType == "bomb" then
gData.task = "Ground Attack"
end
-- trigger.action.outText("main task is " .. gData.task, 30)
-- create route
local route = {}
route.routeRelativeTOT = true
route.points = {}
gData.route = route
-- create take-off waypoint for this flight
local wpTOff = milWings.createTakeOffWayPoint(theZone, targetZone)
dcsCommon.addRoutePointForGroupData(gData, wpTOff)
-- ingress point
local dest = targetZone:getPoint()
local B = dest
local A = theZone:getPoint()
local ingress = dcsCommon.pointXpercentYdegOffAB(A, B, math.random(50,80), math.random(20,50))
local omwWP = dcsCommon.createSimpleRoutePointData(ingress, theZone.alt, theZone.speed)
dcsCommon.addRoutePointForGroupData(gData, omwWP)
-- action waypoint
local awp = milWings.createActionWaypoint(theZone, targetZone)
dcsCommon.addRoutePointForGroupData(gData, awp)
-- egress
local egress = dcsCommon.pointXpercentYdegOffAB(B, A, math.random(20, 50), math.random(20,50))
local egWP = dcsCommon.createSimpleRoutePointData(egress, theZone.alt, theZone.speed)
dcsCommon.addRoutePointForGroupData(gData, egWP)
-- maybe add another to safety and then dealloc?
local final = milWings.createCallbackWP(gData.name, 4, theZone:getPoint(), theZone.alt, theZone.speed, "delete")
dcsCommon.addRoutePointForGroupData(gData, final)
-- spawn and return
local cty = dcsCommon.getACountryForCoalition(theZone.coa)
-- spawn
local groupCat = Group.Category.AIRPLANE
local theSpawnedGroup = coalition.addGroup(cty, groupCat, gData)
local theFlight = {}
theFlight.oName = oName
theFlight.spawn = theSpawnedGroup
theFlight.origin = theZone
theFlight.destination = targetZone
milWings.flights[gName] = theFlight --theSpawnedGroup
return theSpawnedGroup, gData
end
--
-- Update
--
function milWings.update()
timer.scheduleFunction(milWings.update, {}, timer.getTime() + 1)
-- update all master owners
for idx, theZone in pairs (milWings.zones) do
local mo = theZone.masterOwner
if mo then
theZone.owner = mo.owner
if theZone.isDynamic then
theZone.coa = mo.owner
end
if theZone.verbose then
trigger.action.outText("Copied master onwer <" .. mo.owner .. "> from <" .. mo.name .. "> to <" .. theZone.name .. ">", 30)
end
end
end
for idx, theZone in pairs (milWings.targets) do
local mo = theZone.masterOwner
if mo then
theZone.owner = mo.owner
if theZone.verbose then
trigger.action.outText("Copied master onwer <" .. mo.owner .. "> from <" .. mo.name .. "> to <" .. theZone.name .. ">", 30)
end
end
end
end
--
-- Event Handler
--
function milWings:onEvent(theEvent)
if not theEvent then return end
if not theEvent.initiator then return end
local theUnit = theEvent.initiator
if not theUnit.getGroup then return end
local theGroup = theUnit:getGroup()
if not theGroup then return end
local gName = theGroup:getName()
if not gName then return end
local theFlight = milWings.flights[gName]
if not theFlight then return end -- none of ours
local id = theEvent.id
if id == 4 then
-- flight landed -- milFlights currently do not land
-- except later transport flights -- we'll deal with those
-- later
-- trigger.action.outText("+++milW: flight <> landed (and removed)", 30)
if Group.isExist(theGroup) then
-- maybe schedule in a few seconds?
Group.destroy(theGroup)
end
end -- if landed
end
--
-- callback from the flight
--
function milWings.inFlightCB(gName)
-- trigger.action.outText("*****===***** callback in-flight for group <" .. gName .. ">", 30)
local theGroup = Group.getByName(gName)
if theGroup and Group.isExist(theGroup) then Group.destroy(theGroup) end
end
--
-- GC
--
function milWings.GCcollected(gName)
-- do some housekeeping?
if milWings.verbose then
trigger.action.outText("removed MIL flight <" .. gName .. ">", 30)
end
end
function milWings.GC()
timer.scheduleFunction(milWings.GC, {}, timer.getTime() + 5)
local filtered = {}
for gName, theFlight in pairs(milWings.flights) do
local theGroup = Group.getByName(gName)
if theGroup and Group.isExist(theGroup) then
-- all fine, keep it
filtered[gName] = theFlight
else
milWings.GCcollected(gName)
end
end
milWings.flights = filtered
end
--
-- API
--
function milWings.getMilWingSources(side, msnType) -- msnType is optional
if side == "red" then side = 1 end -- better safe...
if side == "blue" then side = 2 end
local sources = {}
for idx, theZone in pairs(milWings.zones) do
if theZone.coa == side then -- coa must be same side, use masterOwner for dynamism
if msnType then
if theZone.msnType == msnType then
table.insert(sources, theZone)
end
else
table.insert(sources, theZone)
end
end
end
return sources -- an array, NOT dict so we can pickrandom
end
function milWings.getMilWingTargets(side, msnType, ignoreNeutral, pure) -- gets mil targets that DO NOT belong to side
-- enter with side = -1 to get all
local source = milWings.targets
if pure then source = milWings.pureTargets end
if side == "red" then side = 1 end -- better safe...
if side == "blue" then side = 2 end
local tgt = {}
for idx, theZone in pairs(source) do
if theZone.owner ~= side then -- must NOT be owned by same side
if ignoreNeutral and theZone.owner == 0 then
-- neutral ignored
else
-- now see if we need to filter by zone's msnType
if msnType then
if theZone.wingTypes and dcsCommon.arrayContainsStringCaseInsensitive(theZone.wingTypes, msnType) then
table.insert(tgt, theZone)
end
else
table.insert(tgt, theZone)
end
end
end
end
return tgt
end
--
-- config
--
function milWings.readConfigZone()
local theZone = cfxZones.getZoneByName("milWingsConfig")
if not theZone then
theZone = cfxZones.createSimpleZone("milWingsConfig")
end
milWings.verbose = theZone.verbose
end
--
-- Start
--
function milWings.start()
-- lib check
if not dcsCommon.libCheck then
trigger.action.outText("cfx mil wings requires dcsCommon", 30)
return false
end
if not dcsCommon.libCheck("cfx mil wings", milWings.requiredLibs) then
return false
end
-- read config
milWings.readConfigZone()
-- process milWings Zones
local attrZones = cfxZones.getZonesWithAttributeNamed("milWings")
for k, aZone in pairs(attrZones) do
milWings.readMilWingsZone(aZone) -- process attributes
milWings.addMilWingsZone(aZone) -- add to list
end
for idx, keyWord in pairs(milWings.targetKeywords) do
attrZones = cfxZones.getZonesWithAttributeNamed(keyWord)
for k, aZone in pairs(attrZones) do
milWings.readMilWingsTargetZone(aZone) -- process attributes
milWings.addMilWingsTargetZone(aZone) -- add to list
end
end
-- start update in 5 seconds
timer.scheduleFunction(milWings.update, {}, timer.getTime() + 1/milWings.ups)
timer.scheduleFunction(milWings.GC, {}, timer.getTime() + 1/milWings.ups * 5)
-- install event handler
world.addEventHandler(milWings)
-- say hi
trigger.action.outText("milWings v" .. milWings.version .. " started.", 30)
return true
end
if not milWings.start() then
trigger.action.outText("milWings failed to start.", 30)
milWings = nil
end

View File

@ -1,5 +1,5 @@
cfxOwnedZones = {}
cfxOwnedZones.version = "2.3.0"
cfxOwnedZones.version = "2.3.1"
cfxOwnedZones.verbose = false
cfxOwnedZones.announcer = true
cfxOwnedZones.name = "cfxOwnedZones"
@ -42,6 +42,7 @@ cfxOwnedZones.name = "cfxOwnedZones"
- per-zone local numkeep
- title attribute
- code clean-up
2.3.1 - restored getNearestOwnedZoneToPoint
--]]--
cfxOwnedZones.requiredLibs = {
"dcsCommon",
@ -250,7 +251,7 @@ function cfxOwnedZones.zoneConquered(aZone, theSide, formerOwner) -- 0 = neutral
elseif theSide == 0 then who = "NEUTRAL" end
aZone.owner = theSide -- just to be sure
if aZone.announcer then
if cfxOwnedZones.announcer or aZone.announcer then
if theSide == 0 then
trigger.action.outText(aZone.name .. " has become NEUTRAL", 30)
else
@ -632,6 +633,7 @@ end
-- collect zones can filter owned zones.
-- by default it filters all zones that are in water
-- includes all managed-owner zones
-- called from external sources
function cfxOwnedZones.collectZones(mode)
if not mode then mode = "land" end
if mode == "land" then
@ -651,6 +653,12 @@ function cfxOwnedZones.collectZones(mode)
end
end
-- getNearestOwnedZoneToPoint invoked by heloTroops
function cfxOwnedZones.getNearestOwnedZoneToPoint(p)
local allZones = cfxOwnedZones.collectZones()
return cfxZones.getClosestZone(p, allZones)
end
-- getNearestEnemyOwnedZone invoked by cfxGroundTroops
function cfxOwnedZones.getNearestEnemyOwnedZone(theZone, targetNeutral)
if not targetNeutral then targetNeutral = false else targetNeutral = true end

View File

@ -14,7 +14,7 @@ cfxPlayerScore.firstSave = true -- to force overwrite
3.0.1 - cleanup
3.0.2 - interface with ObjectDestructDetector for scoring scenery objects
3.1.0 - shared data for persistence
3.2.0 - integration with bank
3.2.0 - integration with bank
--]]--
cfxPlayerScore.requiredLibs = {
@ -1023,6 +1023,7 @@ function cfxPlayerScore.scheduledAward(args)
cfxPlayerScore.coalitionScore[playerSide] = cfxPlayerScore.coalitionScore[playerSide] + theScore.scoreaccu
if bank and bank.addFunds then
bank.addFunds(playerSide, cfxPlayerScore.score2finance * theScore.scoreaccu)
desc = desc .. "(transferred §" .. cfxPlayerScore.score2finance * theScore.scoreaccu .. " to funding)\n"
end
theScore.scoreaccu = 0
hasAward = true
@ -1360,14 +1361,21 @@ function cfxPlayerScore.update()
-- score!
cfxPlayerScore.coalitionScore[coa] = cfxPlayerScore.coalitionScore[coa] + cfxPlayerScore.blueTriggerScore[tName]
cfxPlayerScore.blueTriggerFlags[tName] = newVal
-- bank it if exists
if bank and bank.addFunds then
bank.addFunds(coa, cfxPlayerScore.score2finance * cfxPlayerScore.blueTriggerScore[tName])
end
if cfxPlayerScore.announcer then
trigger.action.outTextForCoalition(coa, "BLUE goal [" .. tName .. "] achieved, new BLUE coalition score is " .. cfxPlayerScore.coalitionScore[coa], 30)
trigger.action.outSoundForCoalition(coa, cfxPlayerScore.scoreSound)
end
-- bank it if exists
local amount
if bank and bank.addFunds then
amount = cfxPlayerScore.score2finance * cfxPlayerScore.blueTriggerScore[tName]
bank.addFunds(coa, amount)
if cfxPlayerScore.announcer then
trigger.action.outTextForCoalition(coa, "Transferred §" .. amount .. " to funds.", 30)
end
end
end
end
end
@ -1380,13 +1388,23 @@ function cfxPlayerScore.update()
cfxPlayerScore.coalitionScore[coa] = cfxPlayerScore.coalitionScore[coa] + cfxPlayerScore.redTriggerScore[tName]
cfxPlayerScore.redTriggerFlags[tName] = newVal
if bank and bank.addFunds then
bank.addFunds(coa, cfxPlayerScore.score2finance * cfxPlayerScore.blueTriggerScore[tName])
end
--if bank and bank.addFunds then
-- bank.addFunds(coa, cfxPlayerScore.score2finance * cfxPlayerScore.blueTriggerScore[tName])
--end
if cfxPlayerScore.announcer then
trigger.action.outTextForCoalition(coa, "RED goal [" .. tName .. "] achieved, new RED coalition score is " .. cfxPlayerScore.coalitionScore[coa], 30)
trigger.action.outSoundForCoalition(coa, cfxPlayerScore.scoreSound)
end
-- bank it if exists
local amount
if bank and bank.addFunds then
amount = cfxPlayerScore.score2finance * cfxPlayerScore.redTriggerScore[tName]
bank.addFunds(coa, amount)
if cfxPlayerScore.announcer then
trigger.action.outTextForCoalition(coa, "Transferred §" .. amount .. " to funds.", 30)
end
end
end
end
end

View File

@ -1,5 +1,5 @@
radioMenu = {}
radioMenu.version = "2.2.1"
radioMenu.version = "2.3.0"
radioMenu.verbose = false
radioMenu.ups = 1
radioMenu.requiredLibs = {
@ -19,6 +19,8 @@ radioMenu.menus = {}
2.1.1 - outMessage now works correctly
2.2.0 - clean-up
2.2.1 - corrected ackD
2.3.0 - added wildcard "*" ability for group name match
- added ackASnd .. ackDSnd sounds as options
--]]--
function radioMenu.addRadioMenu(theZone)
@ -114,17 +116,35 @@ function radioMenu.filterPlayerIDForGroup(theZone)
end
for idx, gName in pairs(allGroups) do
-- if gName ends in wildcard "*" we process differently
gName = dcsCommon.trim(gName)
local theGroup = cfxMX.playerGroupByName[gName]
if theGroup then
local gID = theGroup.groupId
table.insert(theIDs, gID)
if theZone.verbose or radioMenu.verbose then
trigger.action.outText("+++menu: Player Group <" .. gName .. "> found: <" .. gID .. ">", 30)
if dcsCommon.stringEndsWith(gName, "*") then
-- we must check all group names if they start with the
-- the same root. WARNING: CASE-SENSITIVE!!!!
gName = dcsCommon.removeEnding(gName, "*")
for mxName, theGroupData in pairs(cfxMX.playerGroupByName) do
if dcsCommon.stringStartsWith(mxName, gName) then
-- group match, install menu
local gID = theGroupData.groupId
table.insert(theIDs, gID)
if theZone.verbose or radioMenu.verbose then
trigger.action.outText("+++menu: WILDCARD Player Group <" .. gName .. "*> matched with <" .. mxName .. ">: gID = <" .. gID .. ">", 30)
end
else
end
end
else
trigger.action.outText("+++menu: Player Group <" .. gName .. "> does not exist", 30)
end
local theGroup = cfxMX.playerGroupByName[gName]
if theGroup then
local gID = theGroup.groupId
table.insert(theIDs, gID)
if theZone.verbose or radioMenu.verbose then
trigger.action.outText("+++menu: Player Group <" .. gName .. "> found: <" .. gID .. ">", 30)
end
else
trigger.action.outText("+++menu: Player Group <" .. gName .. "> does not exist", 30)
end
end
end
return theIDs
@ -272,6 +292,9 @@ function radioMenu.createRadioMenuWithZone(theZone)
if theZone:hasProperty("ackA") then
theZone.ackA = theZone:getStringFromZoneProperty("ackA", "Acknowledged: A")
end
if theZone:hasProperty("ackASnd") then
theZone.ackASnd = theZone:getStringFromZoneProperty("ackASnd", "<none>")
end
theZone.itemBChosen = theZone:getStringFromZoneProperty("B!", "*<none>")
theZone.cooldownB = theZone:getNumberFromZoneProperty("cooldownB", 0)
@ -282,6 +305,9 @@ function radioMenu.createRadioMenuWithZone(theZone)
if theZone:hasProperty("ackB") then
theZone.ackB = theZone:getStringFromZoneProperty("ackB", "Acknowledged: B")
end
if theZone:hasProperty("ackBSnd") then
theZone.ackBSnd = theZone:getStringFromZoneProperty("ackBSnd", "<none>")
end
theZone.itemCChosen = theZone:getStringFromZoneProperty("C!", "*<none>")
theZone.cooldownC = theZone:getNumberFromZoneProperty("cooldownC", 0)
@ -292,6 +318,9 @@ function radioMenu.createRadioMenuWithZone(theZone)
if theZone:hasProperty("ackC") then
theZone.ackC = theZone:getStringFromZoneProperty("ackC", "Acknowledged: C")
end
if theZone:hasProperty("ackCSnd") then
theZone.ackCSnd = theZone:getStringFromZoneProperty("ackCSnd", "<none>")
end
theZone.itemDChosen = theZone:getStringFromZoneProperty("D!", "*<none>")
theZone.cooldownD = theZone:getNumberFromZoneProperty("cooldownD", 0)
@ -302,6 +331,9 @@ function radioMenu.createRadioMenuWithZone(theZone)
if theZone:hasProperty("ackD") then
theZone.ackD = theZone:getStringFromZoneProperty("ackD", "Acknowledged: D")
end
if theZone:hasProperty("ackDSnd") then
theZone.ackDSnd = theZone:getStringFromZoneProperty("ackDSnd", "<none>")
end
if theZone:hasProperty("removeMenu?") then
theZone.removeMenu = theZone:getStringFromZoneProperty( "removeMenu?", "*<none>")
@ -395,6 +427,7 @@ function radioMenu.doMenuX(args)
local theFlag = theZone.itemAChosen
local outVal = theZone.outValA
local ack = theZone.ackA
local ackSnd = theZone.ackASnd
-- decode A..X
if theItemIndex == "B"then
@ -403,18 +436,21 @@ function radioMenu.doMenuX(args)
theFlag = theZone.itemBChosen
outVal = theZone.outValB
ack = theZone.ackB
ackSnd = theZone.ackBSnd
elseif theItemIndex == "C" then
cd = radioMenu.cdByGID(theZone.mcdC, theZone, theGroup) -- theZone.mcdC
busy = theZone.busyC
theFlag = theZone.itemCChosen
outVal = theZone.outValC
ack = theZone.ackC
ackSnd = theZone.ackCSnd
elseif theItemIndex == "D" then
cd = radioMenu.cdByGID(theZone.mcdD, theZone, theGroup) -- theZone.mcdD
busy = theZone.busyD
theFlag = theZone.itemDChosen
outVal = theZone.outValD
ack = theZone.ackD
ackSnd = theZone.ackDSnd
end
-- see if we are on cooldown
@ -427,10 +463,13 @@ function radioMenu.doMenuX(args)
return
else
-- see if we have an acknowledge
local gid = theGroup
if ack then
local gid = theGroup
radioMenu.radioOutMsg(ack, gid, theZone)
end
if ackSnd then
trigger.action.outSoundForGroup(gid, ackSnd)
end
end
-- set new cooldown -- needs own decoder A..X

133
modules/sweeper.lua Normal file
View File

@ -0,0 +1,133 @@
sweeper = {}
sweeper.version = "1.0.0"
sweeper.requiredLibs = {
"dcsCommon",
"cfxZones",
}
-- remove all units that are detected twice in a row in the same
-- zone after a time interval. Used to remove deadlocked units.
sweeper.zones = {}
sweeper.interval = 5 * 60 -- 5 mins (max 10 mins) in zone will kill you
sweeper.verbose = false
sweeper.flights = {}
function sweeper.addSweeperZone(theZone)
sweeper.zones[theZone.name] = theZone
end
function sweeper.readSweeperZone(theZone)
theZone.aircraft = theZone:getBoolFromZoneProperty("aircraft", true)
theZone.helos = theZone:getBoolFromZoneProperty("helos", false)
end
function sweeper.update()
timer.scheduleFunction(sweeper.update, {}, timer.getTime() + sweeper.interval)
local toKill = {}
local newFlights = {}
for idx, theZone in pairs(sweeper.zones) do
for i= 0, 2 do
local allGroups = coalition.getGroups(i, 2) -- get all ground
for idy, theGroup in pairs(allGroups) do
local allUnits = theGroup:getUnits()
for idz, theUnit in pairs(allUnits) do
if theZone:unitInZone(theUnit) then
table.insert(toKill, theUnit)
end
end
end
if theZone.aircraft then
local allGroups = coalition.getGroups(i, 0) -- get all planes
for idy, theGroup in pairs(allGroups) do
local allUnits = theGroup:getUnits()
for idz, theUnit in pairs(allUnits) do
if theZone:unitInZone(theUnit) then
-- see if this was was already noted
uName = theUnit:getName()
if sweeper.flights[uName] then
table.insert(toKill, theUnit)
if sweeper.verbose then
trigger.action.outText("Sweeping aircraft <" .. uName .. "> off zone for obstruction", 30)
end
else
newFlights[uName] = true
if sweeper.verbose then
trigger.action.outText("sweep: aircraft <" .. uName .. "> on notice", 30)
end
end
end
end
end
end
if theZone.helos then
local allGroups = coalition.getGroups(i, 1) -- get all helos
for idy, theGroup in pairs(allGroups) do
local allUnits = theGroup:getUnits()
for idz, theUnit in pairs(allUnits) do
if theZone:unitInZone(theUnit) then
-- see if this was was already noted
uName = theUnit:getName()
if sweeper.flights[uName] then
table.insert(toKill, theUnit)
if sweeper.verbose then
trigger.action.outText("Sweeping helicopter <" .. uName .. "> off zone for obstruction", 30)
end
else
newFlights[uName] = true
if sweeper.verbose then
trigger.action.outText("sweep: helicopter <" .. uName .. "> on notice", 30)
end
end
end
end
end
end
end
end
-- remove all units in my kill list
for idx, theUnit in pairs(toKill) do
if theUnit.getPlayerName and theUnit:getPlayerName() then
-- we do not sweep players
else
if sweeper.verbose then
trigger.action.outText("*** sweeper: sweeping <" .. theUnit:getName() .. ">", 30)
end
if Unit.isExist(theUnit) then Unit.destroy(theUnit) end
end
end
-- remember new list, forget old
sweeper.flights = newFlights
end
function sweeper.start()
-- lib check
if not dcsCommon.libCheck then
trigger.action.outText("cfx sweeper requires dcsCommon", 30)
return false
end
if not dcsCommon.libCheck("cfx sweeper", sweeper.requiredLibs) then
return false
end
-- process sweeper Zones
local attrZones = cfxZones.getZonesWithAttributeNamed("sweeper")
for k, aZone in pairs(attrZones) do
sweeper.readSweeperZone(aZone) -- process attributes
sweeper.addSweeperZone(aZone) -- add to list
end
-- start update in 5 seconds
timer.scheduleFunction(sweeper.update, {}, timer.getTime() + sweeper.interval)
-- say hi
trigger.action.outText("sweeper v" .. sweeper.version .. " started.", 30)
return true
end
if not sweeper.start() then
trigger.action.outText("sweeper failed to start.", 30)
sweeper = nil
end

View File

@ -1,10 +1,13 @@
tacan = {}
tacan.version = "1.1.0"
tacan.version = "1.2.2"
--[[--
Version History
1.0.0 - initial version
1.1.0 - OOP cfxZones
1.2.0 - desc attribute
1.2.1 - actionSound config attribute
- sound with output
1.2.2 - corrected typo when reading sound file for actionSound
--]]--
tacan.verbose = false
tacan.requiredLibs = {
@ -61,6 +64,10 @@ function tacan.createTacanZone(theZone)
theZone.announcer = theZone:getBoolFromZoneProperty("announcer", false)
if theZone:hasProperty("desc") then
theZone.desc = theZone:getStringFromZoneProperty("desc", "<none>")
end
-- interface to groupTracker
if theZone:hasProperty("trackWith:") then
theZone.trackWith = theZone:getStringFromZoneProperty( "trackWith:", "<None>")
@ -138,6 +145,7 @@ function tacan.createTacanInZone(theZone, channel, mode, callsign)
t.activeChan = channel
t.theGroup = theGroup
t.theData = theCopy
t.desc = theZone.desc
table.insert(theZone.spawnedTACANS, t)
-- run a GC cycle
@ -175,8 +183,10 @@ function tacan.TacanFromZone(theZone, silent)
local str = "NOTAM: Deployed new TACAN " .. theZone.name .. " <" .. callsign .. ">, channel " .. channel .. mode .. ", active now"
if theZone.coa == 0 then
trigger.action.outText(str, 30)
trigger.action.outSound(tacan.actionSound)
else
trigger.action.outTextForCoalition(theZone.coa, str, 30)
trigger.action.outSoundForCoalition(theZone.coa, tacan.actionSound)
end
end
end
@ -193,6 +203,7 @@ function tacan.destroyTacan(theZone, announce)
trigger.action.outText(str, 30)
else
trigger.action.outTextForCoalition(coa, str, 30)
trigger.action.outSoundForCoalition(coa, tacan.actionSound)
end
end
@ -331,7 +342,6 @@ end
function tacan.installComms()
tacan.redC = missionCommands.addCommandForCoalition(1, "Available TACAN stations", nil, tacan.listTacan, 1)
tacan.blueC = missionCommands.addCommandForCoalition(2, "Available TACAN stations", nil, tacan.listTacan, 2)
end
function tacan.listTacan(side)
@ -353,6 +363,7 @@ function tacan.doListTacan(args)
if #theTs < 1 then
trigger.action.outTextForCoalition(args, "No active TACAN.", 30)
trigger.action.outSoundForCoalition(args, tacan.actionSound)
return
end
@ -360,9 +371,13 @@ function tacan.doListTacan(args)
for idx, aTacan in pairs(theTs) do
msg = msg .. "\n - " .. aTacan.activeCallsign .. ": " .. aTacan.activeChan .. aTacan.activeMode
if aTacan.desc then
msg = msg .. " - " .. aTacan.desc
end
end
msg = msg .. "\n"
trigger.action.outTextForCoalition(args, msg, 30)
trigger.action.outSoundForCoalition(args, tacan.actionSound)
end
--
@ -379,7 +394,7 @@ function tacan.readConfigZone()
if theZone:hasProperty("GUI") then
tacan.list = theZone:getBoolFromZoneProperty("GUI", false)
end
tacan.actionSound = theZone:getStringFromZoneProperty("actionSound", "Quest Snare 3.wav")
if tacan.verbose then
trigger.action.outText("+++tcn: read config", 30)
end

632
modules/wingTaskHelper.lua Normal file
View File

@ -0,0 +1,632 @@
wingTaskHelper = {}
-- WP 1 Task for a CAS flight
wingTaskHelper.casTOTask = {
["id"] = "ComboTask",
["params"] =
{
["tasks"] =
{
[1] =
{
["enabled"] = true,
["auto"] = true,
["id"] = "EngageTargets",
["number"] = 1,
["key"] = "CAS",
["params"] =
{
["targetTypes"] =
{
[1] = "Helicopters",
[2] = "Ground Units",
[3] = "Light armed ships",
}, -- end of ["targetTypes"]
["priority"] = 0,
}, -- end of ["params"]
}, -- end of [1]
[2] =
{
["number"] = 2,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["value"] = 2,
["name"] = 1,
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [2]
[3] =
{
["number"] = 3,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["value"] = 1,
["name"] = 3,
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [3]
[4] =
{
["number"] = 4,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["variantIndex"] = 2,
["name"] = 5,
["formationIndex"] = 2,
["value"] = 131074, -- trail formation
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [4]
[5] =
{
["number"] = 5,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["value"] = true,
["name"] = 15,
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [5]
[6] =
{
["number"] = 6,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["targetTypes"] =
{
}, -- end of ["targetTypes"]
["name"] = 21,
["value"] = "none;",
["noTargetTypes"] =
{
[1] = "Fighters",
[2] = "Multirole fighters",
[3] = "Bombers",
[4] = "Helicopters",
[5] = "Infantry",
[6] = "Fortifications",
[7] = "Tanks",
[8] = "IFV",
[9] = "APC",
[10] = "Artillery",
[11] = "Unarmed vehicles",
[12] = "AAA",
[13] = "SR SAM",
[14] = "MR SAM",
[15] = "LR SAM",
[16] = "Aircraft Carriers",
[17] = "Cruisers",
[18] = "Destroyers",
[19] = "Frigates",
[20] = "Corvettes",
[21] = "Light armed ships",
[22] = "Unarmed ships",
[23] = "Submarines",
[24] = "Cruise missiles",
[25] = "Antiship Missiles",
[26] = "AA Missiles",
[27] = "AG Missiles",
[28] = "SA Missiles",
[29] = "UAVs",
}, -- end of ["noTargetTypes"]
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [6]
[7] =
{
["number"] = 7,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["value"] = true,
["name"] = 19,
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [7]
[8] =
{
["number"] = 8,
["auto"] = false,
["id"] = "EngageTargetsInZone",
["enabled"] = true,
["params"] =
{
["targetTypes"] =
{
[1] = "All",
}, -- end of ["targetTypes"]
["x"] = 19632.860434462,
["y"] = 323453.87978006,
["value"] = "All;",
["noTargetTypes"] =
{
}, -- end of ["noTargetTypes"]
["priority"] = 0,
["zoneRadius"] = 76200,
}, -- end of ["params"]
}, -- end of [8]
}, -- end of ["tasks"]
}, -- end of ["params"]
} -- end of ["task"]
wingTaskHelper.capTOTask = {
["id"] = "ComboTask",
["params"] =
{
["tasks"] =
{
[1] =
{
["number"] = 1,
["key"] = "CAP",
["id"] = "EngageTargets",
["enabled"] = true,
["auto"] = true,
["params"] =
{
["targetTypes"] =
{
[1] = "Air",
}, -- end of ["targetTypes"]
["priority"] = 0,
}, -- end of ["params"]
}, -- end of [1]
[2] =
{
["number"] = 2,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["value"] = true,
["name"] = 17, -- restrict ground attack
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [2]
[3] =
{
["number"] = 3,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["value"] = 0,
["name"] = 18, -- max range launch
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [3]
[4] =
{
["number"] = 4,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["value"] = true,
["name"] = 19, -- no reporting
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [4]
[5] =
{
["number"] = 5,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["targetTypes"] =
{
}, -- end of ["targetTypes"]
["name"] = 21,
["value"] = "none;",
["noTargetTypes"] =
{
[1] = "Fighters",
[2] = "Multirole fighters",
[3] = "Bombers",
[4] = "Helicopters",
[5] = "Infantry",
[6] = "Fortifications",
[7] = "Tanks",
[8] = "IFV",
[9] = "APC",
[10] = "Artillery",
[11] = "Unarmed vehicles",
[12] = "AAA",
[13] = "SR SAM",
[14] = "MR SAM",
[15] = "LR SAM",
[16] = "Aircraft Carriers",
[17] = "Cruisers",
[18] = "Destroyers",
[19] = "Frigates",
[20] = "Corvettes",
[21] = "Light armed ships",
[22] = "Unarmed ships",
[23] = "Submarines",
[24] = "Cruise missiles",
[25] = "Antiship Missiles",
[26] = "AA Missiles",
[27] = "AG Missiles",
[28] = "SA Missiles",
[29] = "UAVs",
}, -- end of ["noTargetTypes"]
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [5]
[6] =
{
["number"] = 6,
["auto"] = false,
["id"] = "EngageTargetsInZone",
["enabled"] = true,
["params"] =
{
["targetTypes"] =
{
[1] = "Planes",
}, -- end of ["targetTypes"]
["x"] = -1421.6419952991,
["y"] = 311601.25461373,
["value"] = "Planes;",
["noTargetTypes"] =
{
}, -- end of ["noTargetTypes"]
["priority"] = 0,
["zoneRadius"] = 76200,
}, -- end of ["params"]
}, -- end of [6]
}, -- end of ["tasks"]
}, -- end of ["params"]
} -- end of ["task"]
wingTaskHelper.seadTOTask = {
["id"] = "ComboTask",
["params"] =
{
["tasks"] =
{
[1] =
{
["number"] = 1,
["key"] = "SEAD",
["id"] = "EngageTargets",
["enabled"] = true,
["auto"] = true,
["params"] =
{
["targetTypes"] =
{
[1] = "Air Defence",
}, -- end of ["targetTypes"]
["priority"] = 0,
}, -- end of ["params"]
}, -- end of [1]
[2] =
{
["number"] = 2,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["value"] = 2,
["name"] = 1,
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [2]
[3] =
{
["number"] = 3,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["value"] = 2,
["name"] = 13,
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [3]
[4] =
{
["number"] = 4,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["value"] = true,
["name"] = 19,
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [4]
[5] =
{
["number"] = 5,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["targetTypes"] =
{
[1] = "Air Defence",
}, -- end of ["targetTypes"]
["name"] = 21,
["value"] = "Air Defence;",
["noTargetTypes"] =
{
[1] = "Fighters",
[2] = "Multirole fighters",
[3] = "Bombers",
[4] = "Helicopters",
[5] = "Infantry",
[6] = "Fortifications",
[7] = "Tanks",
[8] = "IFV",
[9] = "APC",
[10] = "Artillery",
[11] = "Unarmed vehicles",
[12] = "Aircraft Carriers",
[13] = "Cruisers",
[14] = "Destroyers",
[15] = "Frigates",
[16] = "Corvettes",
[17] = "Light armed ships",
[18] = "Unarmed ships",
[19] = "Submarines",
[20] = "Cruise missiles",
[21] = "Antiship Missiles",
[22] = "AA Missiles",
[23] = "AG Missiles",
[24] = "SA Missiles",
[25] = "UAVs",
}, -- end of ["noTargetTypes"]
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [5]
[6] =
{
["number"] = 6,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "EPLRS",
["params"] =
{
["value"] = true,
["groupId"] = 2,
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [6]
[7] =
{
["number"] = 7,
["auto"] = false,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["value"] = true,
["name"] = 15,
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [7]
}, -- end of ["tasks"]
}, -- end of ["params"]
} -- end of ["task"]
wingTaskHelper.bombTOTask = {
["id"] = "ComboTask",
["params"] =
{
["tasks"] =
{
[1] =
{
["number"] = 1,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["value"] = 2,
["name"] = 1,
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [1]
[2] =
{
["number"] = 2,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "Option",
["params"] =
{
["value"] = true,
["name"] = 15,
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [2]
[3] =
{
["number"] = 3,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "EPLRS",
["params"] =
{
["value"] = true,
["groupId"] = 3,
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [3]
}, -- end of ["tasks"]
}, -- end of ["params"]
} -- end of ["task"]
wingTaskHelper.bombActionTask = {
["id"] = "ComboTask",
["params"] =
{
["tasks"] =
{
[1] =
{
["number"] = 1,
["auto"] = false,
["id"] = "CarpetBombing",
["enabled"] = true,
["params"] =
{
["attackType"] = "Carpet",
["attackQtyLimit"] = false,
["attackQty"] = 1,
["expend"] = "All", -- yay!
["altitude"] = 7620,
["x"] = -7222.3894291577,
["carpetLength"] = 500,
["y"] = 294267.9527197,
["altitudeEnabled"] = false,
["weaponType"] = 9663676414,
["groupAttack"] = false,
}, -- end of ["params"]
}, -- end of [1]
}, -- end of ["tasks"]
}, -- end of ["params"]
} -- end of ["task"]