mirror of
https://github.com/weyne85/DML.git
synced 2025-10-29 16:57:49 +00:00
Version 2.2.2
Maintenance update
This commit is contained in:
parent
8f7371825d
commit
7e4c147071
Binary file not shown.
Binary file not shown.
@ -1,5 +1,5 @@
|
||||
FARPZones = {}
|
||||
FARPZones.version = "1.2.1"
|
||||
FARPZones.version = "2.0.0"
|
||||
FARPZones.verbose = false
|
||||
--[[--
|
||||
Version History
|
||||
@ -15,7 +15,7 @@ FARPZones.verbose = false
|
||||
- handles contested state
|
||||
1.2.1 - now gracefully handles a FARP Zone that does not
|
||||
contain a FARP, but is placed beside it
|
||||
|
||||
2.0.0 - dmlZones
|
||||
|
||||
--]]--
|
||||
|
||||
@ -120,9 +120,9 @@ function FARPZones.createFARPFromZone(aZone)
|
||||
local theFarp = {}
|
||||
theFarp.zone = aZone
|
||||
theFarp.name = aZone.name
|
||||
theFarp.point = cfxZones.getPoint(aZone) -- failsafe
|
||||
theFarp.point = aZone:getPoint() -- failsafe
|
||||
-- find the FARPS that belong to this zone
|
||||
local thePoint = cfxZones.getPoint(aZone)
|
||||
local thePoint = aZone:getPoint()
|
||||
local mapFarps = dcsCommon.getAirbasesInRangeOfPoint(
|
||||
thePoint,
|
||||
aZone.radius,
|
||||
@ -156,10 +156,7 @@ function FARPZones.createFARPFromZone(aZone)
|
||||
-- end
|
||||
|
||||
-- get r and phi for defenders
|
||||
local rPhi = cfxZones.getVectorFromZoneProperty(
|
||||
aZone,
|
||||
"rPhiHDef",
|
||||
3)
|
||||
local rPhi = aZone:getVectorFromZoneProperty("rPhiHDef",3)
|
||||
|
||||
-- get r and phi for facilities
|
||||
-- create a new defenderzone for this
|
||||
@ -167,17 +164,14 @@ function FARPZones.createFARPFromZone(aZone)
|
||||
local phi = rPhi[2] * 0.0174533 -- 1 degree = 0.0174533 rad
|
||||
local dx = aZone.point.x + r * math.cos(phi)
|
||||
local dz = aZone.point.z + r * math.sin(phi)
|
||||
local formRad = cfxZones.getNumberFromZoneProperty(aZone, "rFormation", 100)
|
||||
local formRad = aZone:getNumberFromZoneProperty("rFormation", 100)
|
||||
|
||||
theFarp.defZone = cfxZones.createSimpleZone(aZone.name .. "-Def", {x=dx, y = 0, z=dz}, formRad)
|
||||
theFarp.defHeading = rPhi[3]
|
||||
|
||||
rPhi = {}
|
||||
rPhi = cfxZones.getVectorFromZoneProperty(
|
||||
aZone,
|
||||
"rPhiHRes",
|
||||
3)
|
||||
--trigger.action.outText("*** RES rPhi are " .. rPhi[1] .. " and " .. rPhi[2] .. " heading " .. rPhi[3], 30)
|
||||
rPhi = aZone:getVectorFromZoneProperty("rPhiHRes", 3)
|
||||
|
||||
r = rPhi[1]
|
||||
phi = rPhi[2] * 0.0174533 -- 1 degree = 0.0174533 rad
|
||||
dx = aZone.point.x + r * math.cos(phi)
|
||||
@ -187,17 +181,18 @@ function FARPZones.createFARPFromZone(aZone)
|
||||
theFarp.resHeading = rPhi[3]
|
||||
|
||||
-- get redDefenders - defenders produced when red owned
|
||||
theFarp.redDefenders = cfxZones.getStringFromZoneProperty(aZone, "redDefenders", "none")
|
||||
theFarp.redDefenders = aZone:getStringFromZoneProperty( "redDefenders", "none")
|
||||
-- get blueDefenders - defenders produced when blue owned
|
||||
theFarp.blueDefenders = cfxZones.getStringFromZoneProperty(aZone, "blueDefenders", "none")
|
||||
theFarp.blueDefenders = aZone:getStringFromZoneProperty( "blueDefenders", "none")
|
||||
-- get formation for defenders
|
||||
theFarp.formation = cfxZones.getStringFromZoneProperty(aZone, "formation", "circle_out")
|
||||
theFarp.formation = aZone:getStringFromZoneProperty("formation", "circle_out")
|
||||
theFarp.count = 0 -- for uniqueness
|
||||
theFarp.hideRed = cfxZones.getBoolFromZoneProperty(aZone, "hideRed")
|
||||
theFarp.hideBlue = cfxZones.getBoolFromZoneProperty(aZone, "hideBlue")
|
||||
theFarp.hideGrey = cfxZones.getBoolFromZoneProperty(aZone, "hideGrey")
|
||||
theFarp.hidden = cfxZones.getBoolFromZoneProperty(aZone, "hidden")
|
||||
theFarp.hideRed = aZone:getBoolFromZoneProperty("hideRed", false)
|
||||
theFarp.hideBlue = aZone:getBoolFromZoneProperty("hideBlue", false)
|
||||
theFarp.hideGrey = aZone:getBoolFromZoneProperty("hideGrey", false)
|
||||
theFarp.hidden = aZone:getBoolFromZoneProperty("hidden", false)
|
||||
|
||||
theFarp.neutralProduction = aZone:getBoolFromZoneProperty("neutralProduction", false)
|
||||
return theFarp
|
||||
end
|
||||
|
||||
@ -225,7 +220,7 @@ function FARPZones.drawFARPCircleInMap(theFarp)
|
||||
|
||||
if theFarp.hideGrey and
|
||||
theFarp.owner == 0 then
|
||||
-- hide only when blue
|
||||
-- hide only when grey
|
||||
return
|
||||
end
|
||||
|
||||
@ -258,7 +253,7 @@ function FARPZones.drawFARPCircleInMap(theFarp)
|
||||
aZone.markID = markID
|
||||
|
||||
end
|
||||
|
||||
--[[--
|
||||
function FARPZones.drawZoneInMap(aZone, owner)
|
||||
-- owner is 0 = neutral, 1 = red, 2 = blue
|
||||
-- will save markID in zone's markID
|
||||
@ -288,6 +283,7 @@ function FARPZones.drawZoneInMap(aZone, owner)
|
||||
aZone.markID = markID
|
||||
|
||||
end
|
||||
--]]--
|
||||
|
||||
function FARPZones.scheduedProduction(args)
|
||||
-- args contain [aFarp, owner]
|
||||
@ -311,6 +307,16 @@ function FARPZones.scheduedProduction(args)
|
||||
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)
|
||||
--end
|
||||
|
||||
-- abort production if farp is owned by neutral and
|
||||
-- neutralproduction is false
|
||||
if theFarp.owner == 0 and not theFarp.neutralProduction then
|
||||
return
|
||||
end
|
||||
|
||||
-- first, remove anything that may still be there
|
||||
if theFarp.defenders and theFarp.defenders:isExist() then
|
||||
theFarp.defenders:destroy()
|
||||
@ -529,20 +535,13 @@ end
|
||||
function FARPZones.readConfig()
|
||||
local theZone = cfxZones.getZoneByName("farpZonesConfig")
|
||||
if not theZone then
|
||||
if FARPZones.verbose then
|
||||
trigger.action.outText("***frpZ: NO config zone!", 30)
|
||||
end
|
||||
return
|
||||
theZone = cfxZones.createSimpleZone("farpZonesConfig")
|
||||
end
|
||||
|
||||
FARPZones.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
|
||||
FARPZones.verbose = theZone.verbose
|
||||
|
||||
FARPZones.spinUpDelay = cfxZones.getNumberFromZoneProperty(theZone, "spinUpDelay", 30)
|
||||
FARPZones.spinUpDelay = theZone:getNumberFromZoneProperty( "spinUpDelay", 30)
|
||||
|
||||
|
||||
if FARPZones.verbose then
|
||||
trigger.action.outText("***frpZ: read config", 30)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -618,4 +617,5 @@ Improvements:
|
||||
|
||||
make farps repair their service vehicles after a time, or simply refresh them every x minutes, to make the algo simpler
|
||||
|
||||
allow for ownership control via the airfield module?
|
||||
--]]--
|
||||
@ -1,5 +1,5 @@
|
||||
autoCSAR = {}
|
||||
autoCSAR.version = "2.0.0"
|
||||
autoCSAR.version = "2.0.1"
|
||||
autoCSAR.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
"cfxZones", -- Zones, of course
|
||||
@ -14,6 +14,8 @@ autoCSAR.trackedEjects = {} -- we start tracking on eject
|
||||
1.1.0 - allow open water CSAR, fake pilot with GRG Soldier
|
||||
- can be disabled by seaCSAR = false
|
||||
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
|
||||
--]]--
|
||||
|
||||
function autoCSAR.removeGuy(args)
|
||||
@ -30,13 +32,16 @@ function autoCSAR.isOverWater(theUnit)
|
||||
return surf == 2 or surf == 3
|
||||
end
|
||||
|
||||
function autoCSAR.createNewCSAR(theUnit)
|
||||
function autoCSAR.createNewCSAR(theUnit, coa)
|
||||
if not csarManager then
|
||||
trigger.action.outText("+++aCSAR: CSAR Manager not loaded, aborting", 30)
|
||||
end
|
||||
-- enter with unit from landing_after_eject event
|
||||
-- unit has no group
|
||||
local coa = theUnit:getCoalition()
|
||||
if not coa then
|
||||
trigger.action.outText("+++autoCSAR: unresolved coalition, assumed neutral", 30)
|
||||
coa = 0
|
||||
end
|
||||
if coa == 0 then -- neutral
|
||||
trigger.action.outText("Neutral Pilot made it safely to ground.", 30)
|
||||
return
|
||||
@ -75,7 +80,7 @@ function autoCSAR.createNewCSAR(theUnit)
|
||||
theUnit = allUnits[1] -- get first (and only) unit
|
||||
end
|
||||
-- create a CSAR mission now
|
||||
csarManager.createCSARForParachutist(theUnit, "Xray-" .. autoCSAR.counter)
|
||||
csarManager.createCSARForParachutist(theUnit, "Xray-" .. autoCSAR.counter, coa)
|
||||
autoCSAR.counter = autoCSAR.counter + 1
|
||||
|
||||
-- schedule removal of pilot
|
||||
@ -88,37 +93,67 @@ function autoCSAR.createNewCSAR(theUnit)
|
||||
end
|
||||
end
|
||||
|
||||
-- we backtrack the pilot to their seat to their plane if they have ejector seat
|
||||
autoCSAR.pilotInfo = {}
|
||||
function autoCSAR:onEvent(event)
|
||||
if not event.initiator then return end
|
||||
local initiator = event.initiator
|
||||
if event.id == 31 then -- landing_after_eject, does not happen at sea
|
||||
-- to prevent double invocations for same process
|
||||
-- check that we are still tracking this ejection
|
||||
if event.initiator then
|
||||
local uid = tonumber(event.initiator:getID())
|
||||
if autoCSAR.trackedEjects[uid] then
|
||||
trigger.action.outText("aCSAR: filtered double sea csar (player) event for uid = <" .. uid .. ">", 30)
|
||||
autoCSAR.trackedEjects[uid] = nil -- reset
|
||||
return
|
||||
end
|
||||
autoCSAR.createNewCSAR(event.initiator)
|
||||
local uid = tonumber(initiator:getID())
|
||||
if autoCSAR.trackedEjects[uid] then
|
||||
trigger.action.outText("aCSAR: filtered double sea csar (player) event for uid = <" .. uid .. ">", 30)
|
||||
autoCSAR.trackedEjects[uid] = nil -- reset
|
||||
return
|
||||
end
|
||||
-- now get the coalition of the pilot.
|
||||
-- if pilot had an ejection seat, we need to get the seat's coa
|
||||
local coa = initiator:getCoalition()
|
||||
for idx, info in pairs(autoCSAR.pilotInfo) do
|
||||
if info.pilot == initiator then
|
||||
coa = info.coa
|
||||
info.matched = true -- for GC
|
||||
end
|
||||
end
|
||||
autoCSAR.createNewCSAR(initiator, coa)
|
||||
end
|
||||
|
||||
if event.id == 6 and autoCSAR.seaCSAR then -- eject, start tracking
|
||||
if event.initiator then
|
||||
if event.id == 33 then -- discard chair, connect pilot with seat
|
||||
for idx, info in pairs(autoCSAR.pilotInfo) do
|
||||
if info.seat == event.target then
|
||||
info.pilot = initiator
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if event.id == 6 then -- eject, start tracking, remember coa
|
||||
local coa = event.initiator:getCoalition()
|
||||
|
||||
-- see if pilot has ejector seat and prepare to connect one with the other
|
||||
local info = nil
|
||||
if event.target and event.target:isExist() then
|
||||
info = {}
|
||||
info.coa = coa
|
||||
info.seat = event.target
|
||||
table.insert(autoCSAR.pilotInfo, info)
|
||||
end
|
||||
|
||||
local uid = tonumber(event.initiator:getID())
|
||||
autoCSAR.trackedEjects[uid] = nil -- set to not handled (yet)
|
||||
|
||||
if autoCSAR.seaCSAR then
|
||||
-- see if this happened over open water and immediately
|
||||
-- create a seaCSAR
|
||||
|
||||
if autoCSAR.isOverWater(event.initiator) then
|
||||
autoCSAR.createNewCSAR(event.initiator)
|
||||
-- create a seaCSAR immediately
|
||||
if autoCSAR.isOverWater(initiator) then
|
||||
autoCSAR.createNewCSAR(initiator, initiator:getCoalition())
|
||||
-- mark this one as completed
|
||||
autoCSAR.trackedEjects[uid] = "processed" -- remember, so to not proc again
|
||||
if info then info.matched = true end -- discard this one too in next GC
|
||||
end
|
||||
|
||||
-- also mark this one as completed
|
||||
local uid = tonumber(event.initiator:getID())
|
||||
autoCSAR.trackedEjects[uid] = "processed"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
function autoCSAR.readConfigZone()
|
||||
@ -147,6 +182,19 @@ function autoCSAR.readConfigZone()
|
||||
end
|
||||
end
|
||||
|
||||
function autoCSAR.GC()
|
||||
timer.scheduleFunction(autoCSAR.GC, {}, timer.getTime() + 30 * 60) -- once every half hour
|
||||
local filtered = {}
|
||||
for idx, info in pairs(autoCSAR.pilotInfo) do
|
||||
if info.matched then
|
||||
-- skip it for next round
|
||||
else
|
||||
table.insert(filtered, info)
|
||||
end
|
||||
end
|
||||
autoCSAR.pilotInfo = filtered
|
||||
end
|
||||
|
||||
function autoCSAR.start()
|
||||
-- lib check
|
||||
if not dcsCommon.libCheck then
|
||||
@ -163,6 +211,9 @@ function autoCSAR.start()
|
||||
-- connect event handler
|
||||
world.addEventHandler(autoCSAR)
|
||||
|
||||
-- start GC
|
||||
timer.scheduleFunction(autoCSAR.GC, {}, timer.getTime() + 1)
|
||||
|
||||
trigger.action.outText("cfx autoCSAR v" .. autoCSAR.version .. " started.", 30)
|
||||
return true
|
||||
end
|
||||
|
||||
105
modules/bank.lua
Normal file
105
modules/bank.lua
Normal file
@ -0,0 +1,105 @@
|
||||
bank = {}
|
||||
bank.version = "0.0.0"
|
||||
bank.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
"cfxZones", -- Zones, of course
|
||||
}
|
||||
bank.acts = {}
|
||||
|
||||
function bank.addFunds(act, amt)
|
||||
if not act then act = "!!NIL!!" end
|
||||
if act == 1 then act = "red" end
|
||||
if act == 2 then act = "blue" end
|
||||
if act == 0 then act = "neutral" end
|
||||
act = string.lower(act)
|
||||
|
||||
local curVal = bank.acts[act]
|
||||
if not curVal then
|
||||
trigger.action.outText("+++Bank: no account <" .. act .. "> found. No transaction", 30)
|
||||
return false
|
||||
end
|
||||
|
||||
bank.acts[act] = curVal + amt
|
||||
return true
|
||||
end
|
||||
|
||||
function bank.withdawFunds(act, amt)
|
||||
if not act then act = "!!NIL!!" end
|
||||
if act == 1 then act = "red" end
|
||||
if act == 2 then act = "blue" end
|
||||
if act == 0 then act = "neutral" end
|
||||
act = string.lower(act)
|
||||
|
||||
local curVal = bank.acts[act]
|
||||
if not curVal then
|
||||
trigger.action.outText("+++Bank: no account <" .. act .. "> found. No transaction", 30)
|
||||
return false
|
||||
end
|
||||
if amt > curVal then return false end
|
||||
|
||||
bank.acts[act] = curVal - amt
|
||||
return true
|
||||
end
|
||||
|
||||
function bank.getBalance(act)
|
||||
if not act then act = "!!NIL!!" end
|
||||
if act == 1 then act = "red" end
|
||||
if act == 2 then act = "blue" end
|
||||
if act == 0 then act = "neutral" end
|
||||
act = string.lower(act)
|
||||
|
||||
local curVal = bank.acts[act]
|
||||
if not curVal then
|
||||
trigger.action.outText("+++Bank: no account <" .. act .. "> found. No transaction", 30)
|
||||
return false, 0
|
||||
end
|
||||
|
||||
return true, curVal
|
||||
end
|
||||
|
||||
function bank.openAccount(act, amount)
|
||||
if not amount then amount = 0 end
|
||||
if bank.acts[act] then return false end -- account exists
|
||||
bank.acts[act] = amount
|
||||
return true
|
||||
end
|
||||
|
||||
function bank.readConfigZone()
|
||||
local theZone = cfxZones.getZoneByName("bankConfig")
|
||||
if not theZone then
|
||||
theZone = cfxZones.createSimpleZone("bankConfig")
|
||||
end
|
||||
|
||||
-- set initial balances
|
||||
bank.red = theZone:getNumberFromZoneProperty ("red", 1000)
|
||||
bank.blue = theZone:getNumberFromZoneProperty ("blue", 1000)
|
||||
bank.neutral = theZone:getNumberFromZoneProperty ("neutral", 1000)
|
||||
|
||||
bank.acts["red"] = bank.red
|
||||
bank.acts["blue"] = bank.blue
|
||||
bank.acts["neutral"] = bank.neutral
|
||||
|
||||
bank.verbose = theZone.verbose
|
||||
end
|
||||
|
||||
function bank.start()
|
||||
-- lib check
|
||||
if not dcsCommon.libCheck then
|
||||
trigger.action.outText("bank requires dcsCommon", 30)
|
||||
return false
|
||||
end
|
||||
if not dcsCommon.libCheck("bank", bank.requiredLibs) then
|
||||
return false
|
||||
end
|
||||
|
||||
-- read config
|
||||
bank.readConfigZone()
|
||||
|
||||
trigger.action.outText("bank v" .. bank.version .. " started.", 30)
|
||||
return true
|
||||
end
|
||||
|
||||
if not bank.start() then
|
||||
trigger.action.outText("bank aborted: missing libraries", 30)
|
||||
bank = nil
|
||||
end
|
||||
467
modules/camp.lua
Normal file
467
modules/camp.lua
Normal file
@ -0,0 +1,467 @@
|
||||
camp = {}
|
||||
camp.ups = 1
|
||||
camp.version = "0.0.0"
|
||||
camp.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
"cfxZones", -- Zones, of course
|
||||
"cfxMX",
|
||||
"bank"
|
||||
}
|
||||
|
||||
--
|
||||
-- CURRENTLY REQUIRES SINGLE-UNIT PLAYER GROUPS
|
||||
--
|
||||
camp.camps = {} -- all camps on the map
|
||||
camp.roots = {} -- all player group comms roots
|
||||
|
||||
function camp.addCamp(theZone)
|
||||
camp.camps[theZone.name] = theZone
|
||||
end
|
||||
|
||||
function camp.getMyCurrentCamp(theUnit) -- returns first hit plaayer is in
|
||||
local p = theUnit:getPoint()
|
||||
for idx, theCamp in pairs(camp.camps) do
|
||||
if theCamp:pointInZone(p) then
|
||||
return theCamp
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function camp.createCampWithZone(theZone)
|
||||
-- look for all cloners inside my zone
|
||||
if theZone.verbose or camp.verbose then
|
||||
trigger.action.outText("+++camp: processing <" .. theZone.name .. ">, owner is <" .. theZone.owner .. ">", 30)
|
||||
end
|
||||
|
||||
local allZones = cfxZones.getAllZonesInsideZone(theZone)
|
||||
local cloners = {}
|
||||
local redCloners = {}
|
||||
local blueCloners = {}
|
||||
for idx, aZone in pairs(allZones) do
|
||||
if aZone:hasProperty("nocamp") then
|
||||
-- this zone cannot be part of a camp
|
||||
|
||||
elseif aZone:hasProperty("cloner") then
|
||||
-- this is a clone zone and part of my camp
|
||||
table.insert(cloners, aZone)
|
||||
if not aZone:hasProperty("blueOnly") then
|
||||
table.insert(redCloners, aZone)
|
||||
end
|
||||
if not aZone:hasProperty("redOnly") then
|
||||
table.insert(blueCloners, aZone)
|
||||
end
|
||||
if theZone.verbose or camp.verbose then
|
||||
trigger.action.outText("Cloner <" .. aZone.name .. "> is part of camp <" .. theZone.name .. ">", 30)
|
||||
end
|
||||
end
|
||||
end
|
||||
if #cloners < 1 then
|
||||
trigger.action.outText("+++camp: warning: camp <" .. theZone.name .. "> has no cloners, can't be improved or repaired", 30)
|
||||
else
|
||||
if camp.verbose or theZone.verbose then
|
||||
trigger.action.outText("Camp <" .. theZone.name .. ">: <" .. #cloners .. "> reinforcable points, <" .. #redCloners .. "> for red and <" .. #blueCloners .. "> blue", 30)
|
||||
end
|
||||
end
|
||||
theZone.cloners = cloners
|
||||
theZone.redCloners = redCloners
|
||||
theZone.blueCloners = blueCloners
|
||||
theZone.repairable = theZone:getBoolFromZoneProperty("repair", true)
|
||||
theZone.upgradable = theZone:getBoolFromZoneProperty("upgrade", true)
|
||||
theZone.repairCost = theZone:getNumberFromZoneProperty("repairCost", 100)
|
||||
theZone.upgradeCost = theZone:getNumberFromZoneProperty("upgradeCost", 3 * theZone.repairCost)
|
||||
end
|
||||
|
||||
--
|
||||
-- update and event
|
||||
--
|
||||
function camp.update()
|
||||
-- call me in a second to poll triggers
|
||||
timer.scheduleFunction(camp.update, {}, timer.getTime() + 1/camp.ups)
|
||||
end
|
||||
|
||||
function camp:onEvent(theEvent)
|
||||
|
||||
end
|
||||
|
||||
--
|
||||
-- Comms
|
||||
--
|
||||
function camp.processPlayers()
|
||||
-- install coms stump for all players. they will be switched in/out
|
||||
-- whenever it is apropriate
|
||||
for idx, gData in pairs(cfxMX.playerGroupByName) do
|
||||
gID = gData.groupId
|
||||
gName = gData.name
|
||||
local theRoot = missionCommands.addSubMenuForGroup(gID, "Ground 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"})
|
||||
local c1 = missionCommands.addCommandForGroup(gID, "REPAIRS: Purchase local repairs", theRoot, camp.redirectRepairs, {gName, gID, "repair"})
|
||||
local c2 = missionCommands.addCommandForGroup(gID, "UPGRADE: Purchase local upgrades", theRoot, camp.redirectUpgrades, {gName, gID, "upgrade"})
|
||||
end
|
||||
end
|
||||
|
||||
function camp.redirectRepairs(args)
|
||||
timer.scheduleFunction(camp.doRepairs, args, timer.getTime() + 0.1)
|
||||
end
|
||||
|
||||
function camp.redirectUpgrades(args)
|
||||
timer.scheduleFunction(camp.doUpgrades, args, timer.getTime() + 0.1)
|
||||
end
|
||||
|
||||
function camp.redirectTFunds(args)
|
||||
timer.scheduleFunction(camp.doTFunds, args, timer.getTime() + 0.1 )
|
||||
end
|
||||
|
||||
function camp.redirectFunds(args)
|
||||
timer.scheduleFunction(camp.doFunds, args, timer.getTime() + 0.1 )
|
||||
end
|
||||
|
||||
function camp.doTFunds(args)
|
||||
local gName = args[1]
|
||||
local gID = args[2]
|
||||
local theGroup = Group.getByName(gName)
|
||||
local coa = theGroup:getCoalition()
|
||||
local hasBalance, amount = bank.getBalance(coa)
|
||||
if not hasBalance then return end
|
||||
local msg = "\nYour faction currently has §" .. amount .. " available for repairs/upgrades.\n"
|
||||
|
||||
-- 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.repairable and theZone.upgradable then
|
||||
msg = msg .. " (§" .. theZone.repairCost .. "/§" .. theZone.upgradeCost .. ")"
|
||||
if camp.zoneNeedsRepairs(theZone, coa) then
|
||||
msg = msg .. " requests repairs and"
|
||||
else
|
||||
msg = msg .. " is running and"
|
||||
end
|
||||
|
||||
if camp.zoneNeedsUpgrades(theZone, coa) then
|
||||
msg = msg .. " can be upgraded"
|
||||
else
|
||||
msg = msg .. " is fully upgraded"
|
||||
end
|
||||
|
||||
elseif theZone.repairable then
|
||||
if camp.zoneNeedsRepairs(theZone, coa) then
|
||||
msg = msg .. " needs repairs (§" .. theZone.repairCost .. ")"
|
||||
else
|
||||
msg = msg .. " is fully operational"
|
||||
end
|
||||
|
||||
elseif theZone.upgradable then
|
||||
if camp.zoneNeedsUpgrades(theZone, coa) then
|
||||
msg = msg .. " can be upgraded (§" .. theZone.upgradeCost .. ")"
|
||||
else
|
||||
msg = msg .. " is fully upgraded"
|
||||
end
|
||||
else
|
||||
-- can be neither repaired nor upgraded
|
||||
msg = msg .. " is owned"
|
||||
end
|
||||
end
|
||||
end
|
||||
msg = msg .. "\n"
|
||||
trigger.action.outTextForGroup(gID, msg, 30)
|
||||
end
|
||||
|
||||
function camp.doFunds(args)
|
||||
local gName = args[1]
|
||||
local gID = args[2]
|
||||
local theGroup = Group.getByName(gName)
|
||||
local coa = theGroup:getCoalition()
|
||||
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 allUnits = theGroup:getUnits()
|
||||
local theUnit = allUnits[1] -- always first unit until we get playerCommands
|
||||
if not Unit.isExist(theUnit) or theUnit:getLife() < 1 or
|
||||
theUnit:inAir() or dcsCommon.getUnitSpeed(theUnit) > 1 then
|
||||
trigger.action.outTextForGroup(gID, msg, 30)
|
||||
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)
|
||||
return
|
||||
end
|
||||
|
||||
if camp.zoneNeedsRepairs(theZone, coa) then
|
||||
msg = msg .. "\nZone <" .. theZone.name .. "> needs repairs (§" .. theZone.repairCost .. " per repair)\n"
|
||||
elseif theZone.repairable then
|
||||
msg = msg .. "\nZone <" .. theZone.name .. "> has no outstanding repairs.\n"
|
||||
else
|
||||
-- say nothing
|
||||
end
|
||||
if camp.zoneNeedsUpgrades(theZone, coa) then
|
||||
msg = msg .. "\nZone <" .. theZone.name .. "> can be upgraded (§" .. theZone.upgradeCost .. " per upgrade)\n"
|
||||
elseif theZone.upgradable then
|
||||
msg = msg .. "\nZone <" .. theZone.name .. "> is fully upgraded.\n"
|
||||
end
|
||||
trigger.action.outTextForGroup(gID, msg, 30)
|
||||
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
|
||||
local myCloners = theZone.cloners
|
||||
|
||||
if not coa then
|
||||
trigger.action.outText("+++camp: warning: no coa on zoneNeedsRepair for zone <" .. theZone.name .. ">", 30)
|
||||
elseif coa == 1 then
|
||||
myCloners = theZone.redCloners
|
||||
elseif coa == 2 then
|
||||
myCloners = theZone.blueCloners
|
||||
end
|
||||
|
||||
if not theZone.repairable then return nil end
|
||||
for idx, theCloner in pairs(myCloners) do
|
||||
if theCloner.oSize and theCloner.oSize > 0 then
|
||||
local currSize = cloneZones.countLiveAIUnits(theCloner)
|
||||
if currSize > 0 and currSize < theCloner.oSize then
|
||||
if theZone.verbose then
|
||||
trigger.action.outText("+++camp: camp <" .. theZone.name .. "> has point <" .. theCloner.name .. "> that needs repair.", 30)
|
||||
end
|
||||
return theCloner
|
||||
else
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function camp.doRepairs(args)
|
||||
local gName = args[1]
|
||||
local gID = args[2]
|
||||
local theGroup = Group.getByName(gName)
|
||||
local coa = theGroup:getCoalition()
|
||||
local allUnits = theGroup:getUnits()
|
||||
local theUnit = allUnits[1] -- always first unit until we get playerCommands
|
||||
if not Unit.isExist(theUnit) then return end
|
||||
local pName = "<Error>"
|
||||
if theUnit.getPlayerName then pName = theUnit:getPlayerName() end
|
||||
if not pName then pName = "<Big Err>" end
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
return
|
||||
end
|
||||
|
||||
-- if we get here, we are inside a zone that can be repaired. see if it needs repair and then get repair cost and see if we have enough fund to repair
|
||||
if not camp.zoneNeedsRepairs(theZone, coa) then
|
||||
local msg = "\nZone <" .. theZone.name .. "> is already fully repaired.\n"
|
||||
if camp.zoneNeedsUpgrades(theZone, coa) then
|
||||
msg = msg .. "\nZone <" .. theZone.name .. "> can be upgraded.\n"
|
||||
end
|
||||
trigger.action.outTextForGroup(gID, msg, 30)
|
||||
return
|
||||
end
|
||||
|
||||
-- see if we have enough funds
|
||||
local hasBalance, amount = bank.getBalance(coa)
|
||||
if not hasBalance then
|
||||
trigger.action.outText("+++camp: no balance for upgrade!", 30)
|
||||
return
|
||||
end
|
||||
|
||||
if amount < theZone.repairCost then
|
||||
trigger.action.outTextForGroup(gID, "\nYou curently cannot afford repairs here\n", 30)
|
||||
return
|
||||
end
|
||||
|
||||
-- finally, let's repair
|
||||
camp.repairZone(theZone, coa)
|
||||
-- theCloner = camp.zoneNeedsRepairs(theZone)
|
||||
-- cloneZones.despawnAll(theCloner)
|
||||
-- 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 ..
|
||||
"> for §" .. theZone.repairCost .. ".\nFaction has §" .. remain .. " remaining funds.\n", 30)
|
||||
end
|
||||
|
||||
function camp.repairZone(theZone, coa)
|
||||
theCloner = camp.zoneNeedsRepairs(theZone, coa)
|
||||
if not theCloner then return end
|
||||
cloneZones.despawnAll(theCloner)
|
||||
cloneZones.spawnWithCloner(theCloner)
|
||||
end
|
||||
--
|
||||
-- UPGRADES
|
||||
--
|
||||
|
||||
function camp.zoneNeedsUpgrades(theZone, coa)
|
||||
-- return true if this zone 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
|
||||
|
||||
if not coa then
|
||||
trigger.action.outText("+++camp: warning: no coa on zoneNeedsUpgrades for zone <" .. theZone.name .. ">", 30)
|
||||
elseif coa == 1 then
|
||||
myCloners = theZone.redCloners
|
||||
elseif coa == 2 then
|
||||
myCloners = theZone.blueCloners
|
||||
end
|
||||
|
||||
for idx, theCloner in pairs(myCloners) do
|
||||
local currSize = cloneZones.countLiveAIUnits(theCloner)
|
||||
if currSize < 1 then
|
||||
if theZone.verbose then
|
||||
trigger.action.outText("+++camp: camp <" .. theZone.name .. "> has point <" .. theCloner.name .. "> that can be upgraded.", 30)
|
||||
end
|
||||
return theCloner
|
||||
else
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
function camp.doUpgrades(args)
|
||||
local gName = args[1]
|
||||
local gID = args[2]
|
||||
local theGroup = Group.getByName(gName)
|
||||
local coa = theGroup:getCoalition()
|
||||
local allUnits = theGroup:getUnits()
|
||||
local theUnit = allUnits[1] -- always first unit until we get playerCommands
|
||||
if not Unit.isExist(theUnit) then return end
|
||||
if theUnit:getLife() < 1 then return end
|
||||
local pName = "<Error>"
|
||||
if theUnit.getPlayerName then pName = theUnit:getPlayerName() end
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
return
|
||||
end
|
||||
|
||||
if camp.zoneNeedsRepairs(theZone, coa) then
|
||||
trigger.action.outTextForGroup(gID, "\nZone <" .. theZone.name .. "> requires repairs before it can be upgraded.\n", 30)
|
||||
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)
|
||||
return
|
||||
end
|
||||
|
||||
-- see if we have enough funds
|
||||
local hasBalance, amount = bank.getBalance(coa)
|
||||
if not hasBalance then
|
||||
trigger.action.outText("+++camp: no balance for upgrade!", 30)
|
||||
return
|
||||
end
|
||||
|
||||
if amount < theZone.upgradeCost then
|
||||
trigger.action.outTextForGroup(gID, "\nYou curently cannot afford an upgrade here\n", 30)
|
||||
return
|
||||
end
|
||||
|
||||
-- finally, let's upgrade
|
||||
--theCloner = camp.zoneNeedsUpgrades(theZone)
|
||||
--cloneZones.spawnWithCloner(theCloner)
|
||||
camp.upgradeZone(theZone, coa)
|
||||
-- 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 ..
|
||||
"> for §" .. theZone.upgradeCost .. ".\nFaction has §" .. remain .. " remaining funds.\n", 30)
|
||||
end
|
||||
|
||||
-- can be called externally
|
||||
function camp.upgradeZone(theZone, coa)
|
||||
theCloner = camp.zoneNeedsUpgrades(theZone, coa)
|
||||
if not theCloner then return end
|
||||
cloneZones.spawnWithCloner(theCloner)
|
||||
end
|
||||
--
|
||||
-- Config & Go
|
||||
--
|
||||
|
||||
function camp.readConfigZone()
|
||||
local theZone = cfxZones.getZoneByName("campConfig")
|
||||
if not theZone then
|
||||
theZone = cfxZones.createSimpleZone("campConfig")
|
||||
end
|
||||
|
||||
camp.verbose = theZone.verbose
|
||||
end
|
||||
|
||||
function camp.start()
|
||||
-- lib check
|
||||
if not dcsCommon.libCheck then
|
||||
trigger.action.outText("camp requires dcsCommon", 30)
|
||||
return false
|
||||
end
|
||||
if not dcsCommon.libCheck("camp", camp.requiredLibs) then
|
||||
return false
|
||||
end
|
||||
|
||||
-- read config
|
||||
camp.readConfigZone()
|
||||
|
||||
-- read zones
|
||||
local attrZones = cfxZones.getZonesWithAttributeNamed("camp")
|
||||
for k, aZone in pairs(attrZones) do
|
||||
camp.createCampWithZone(aZone) -- process attributes
|
||||
camp.addCamp(aZone) -- add to list
|
||||
end
|
||||
|
||||
-- process all players
|
||||
camp.processPlayers()
|
||||
|
||||
-- start update
|
||||
camp.update()
|
||||
|
||||
-- connect event handler
|
||||
world.addEventHandler(camp)
|
||||
|
||||
trigger.action.outText("camp v" .. camp.version .. " started.", 30)
|
||||
return true
|
||||
end
|
||||
|
||||
if not camp.start() then
|
||||
trigger.action.outText("camp aborted: missing libraries", 30)
|
||||
camp = nil
|
||||
end
|
||||
|
||||
--[[--
|
||||
Ideas:
|
||||
re-supply: will restore REMAINING units at all points with fresh
|
||||
units so that they can have full mags
|
||||
costs as much as a full upgrade? hald way between upgrade and repair
|
||||
--]]--
|
||||
@ -48,7 +48,8 @@ cfxZones.version = "4.3.1"
|
||||
- 4.3.0 - boolean supports maybe, random, rnd, ?
|
||||
- small optimization for randomInRange()
|
||||
- randomDelayFromPositiveRange also allows 0
|
||||
|
||||
- 4.3.1 - new drawText() for zones
|
||||
- dmlZones:getClosestZone() bridge
|
||||
--]]--
|
||||
|
||||
--
|
||||
@ -1208,6 +1209,11 @@ function cfxZones.getClosestZone(point, theZones)
|
||||
return closestZone, currDelta
|
||||
end
|
||||
|
||||
function dmlZones:getClosestZone(theZones)
|
||||
local closestZone, currDelta = cfxZones.getClosestZone(self:getPoint(), theZones)
|
||||
return closestZone, currDelta
|
||||
end
|
||||
|
||||
-- return a random zone from the table passed in zones
|
||||
function cfxZones.pickRandomZoneFrom(zones)
|
||||
if not zones then zones = cfxZones.zones end
|
||||
@ -2108,6 +2114,21 @@ function dmlZone:drawZone(lineColor, fillColor, markID)
|
||||
return cfxZones.drawZone(self, lineColor, fillColor, markID)
|
||||
end
|
||||
|
||||
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
|
||||
local markID = dcsCommon.numberUUID()
|
||||
local p = theZone:getPoint()
|
||||
local offset = {x = p.x, y = 0, z = p.z}
|
||||
trigger.action.textToAll(-1, markID, offset, lineColor , fillColor , fSize, true , theText)
|
||||
return markID
|
||||
end
|
||||
|
||||
function dmlZone:drawText(theText, fSize, lineColor, fillColor)
|
||||
return cfxZones.drawText(self, theText, fSize, lineColor, fillColor)
|
||||
end
|
||||
--
|
||||
-- ===================
|
||||
-- PROPERTY PROCESSING
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
cloneZones = {}
|
||||
cloneZones.version = "2.1.0"
|
||||
cloneZones.version = "2.2.0"
|
||||
cloneZones.verbose = false
|
||||
cloneZones.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
@ -44,6 +44,12 @@ cloneZones.respawnOnGroupID = true
|
||||
when pre-wipe is active
|
||||
2.1.0 - despawnIn option
|
||||
- inBuiltup option for rndLoc
|
||||
2.2.0 - oSize
|
||||
- countLiveUnits() performace optimization
|
||||
- new countLiveAIUnits()
|
||||
- damaged! output
|
||||
- health# output
|
||||
- persistence: persist oSize and set lastSize
|
||||
--]]--
|
||||
|
||||
--
|
||||
@ -337,7 +343,16 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
|
||||
if theZone:hasProperty("despawnIn") then
|
||||
theZone.despawnInMin, theZone.despawnInMax = theZone:getPositiveRangeFromZoneProperty("despawnIn", 2,2)
|
||||
end
|
||||
|
||||
-- damaged and health interface
|
||||
if theZone:hasProperty("damaged!") then
|
||||
theZone.damaged = theZone:getStringFromZoneProperty("damaged!")
|
||||
end
|
||||
if theZone:hasProperty("health#") then
|
||||
theZone.health = theZone:getStringFromZoneProperty("health#")
|
||||
end
|
||||
-- we end with clear plate
|
||||
theZone.lastSize = 0 -- no units here
|
||||
end
|
||||
|
||||
--
|
||||
@ -348,6 +363,7 @@ function cloneZones.despawnAll(theZone)
|
||||
if cloneZones.verbose or theZone.verbose then
|
||||
trigger.action.outText("+++clnZ: despawn all - wiping zone <" .. theZone.name .. ">", 30)
|
||||
end
|
||||
theZone.oSize = 0 -- original spawn size
|
||||
for idx, aGroup in pairs(theZone.mySpawns) do
|
||||
if aGroup:isExist() then
|
||||
if theZone.verbose then
|
||||
@ -1017,7 +1033,7 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
|
||||
trigger.action.outText("+++clnZ: spawning with template <" .. theZone.name .. "> for spawner <" .. spawnZone.name .. ">", 30)
|
||||
end
|
||||
-- theZone is the cloner with the TEMPLATE (source)
|
||||
-- spawnZone is the spawner with SETTINGS and DESTINATION (target location) where the clones are poofed into existence
|
||||
-- spawnZone is the actual spawner with SETTINGS and DESTINATION (target location) where the clones are poofed into existence
|
||||
local newCenter = spawnZone:getPoint() -- includes zone following updates
|
||||
local oCenter = theZone:getDCSOrigin() -- get original coords on map for cloning offsets
|
||||
-- calculate zoneDelta, is added to all vectors
|
||||
@ -1190,6 +1206,7 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
|
||||
cloneZones.resolveReferences(theZone, dataToSpawn)
|
||||
|
||||
-- now spawn all raw data
|
||||
spawnZone.oSize = 0 -- original size reset
|
||||
local groupCollector = {} -- to detect cross-group conflicts
|
||||
local unitCollector = {} -- to detect cross-group conflicts
|
||||
local theGroup = nil -- init to empty, on this level
|
||||
@ -1219,6 +1236,8 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
|
||||
-- SPAWN NOW!!!!
|
||||
theGroup = coalition.addGroup(rawData.CZctry, rawData.CZtheCat, rawData)
|
||||
table.insert(spawnedGroups, theGroup)
|
||||
-- increment oSize by number of spawns
|
||||
spawnZone.oSize = spawnZone.oSize + theGroup:getSize()
|
||||
|
||||
-- see if this is an auto-despawner
|
||||
if spawnZone.despawnInMin then
|
||||
@ -1465,6 +1484,8 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- reset lastSize to oSize
|
||||
spawnZone.lastSize = spawnZone.oSize
|
||||
local args = {}
|
||||
args.groups = spawnedGroups
|
||||
args.statics = spawnedStatics
|
||||
@ -1593,19 +1614,30 @@ function cloneZones.doClone(args)
|
||||
end
|
||||
end
|
||||
|
||||
function cloneZones.countLiveAIUnits(theZone)
|
||||
-- like countLiveUnits, but disregards statics
|
||||
if not theZone then return 0 end
|
||||
local count = 0
|
||||
if not theZone.mySpawns then return 0 end
|
||||
-- count units
|
||||
if theZone.mySpawns then
|
||||
for idx, aGroup in pairs(theZone.mySpawns) do
|
||||
if Group.isExist(aGroup) then
|
||||
count = count + aGroup:getSize()
|
||||
end
|
||||
end
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
function cloneZones.countLiveUnits(theZone)
|
||||
if not theZone then return 0 end
|
||||
local count = 0
|
||||
-- count units
|
||||
if theZone.mySpawns then
|
||||
for idx, aGroup in pairs(theZone.mySpawns) do
|
||||
if aGroup:isExist() then
|
||||
local allUnits = aGroup:getUnits()
|
||||
for idy, aUnit in pairs(allUnits) do
|
||||
if aUnit:isExist() and aUnit:getLife() >= 1 then
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
if Group.isExist(aGroup) then --aGroup:isExist() then
|
||||
count = count + aGroup:getSize()
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1715,6 +1747,28 @@ function cloneZones.update()
|
||||
-- can mess with empty, so we tell empty to skip
|
||||
end
|
||||
|
||||
-- handling of damaged! and #health
|
||||
if aZone.damaged or aZone.health then
|
||||
-- calculate current health
|
||||
local currSize = cloneZones.countLiveAIUnits(aZone)
|
||||
if aZone.oSize < 1 then
|
||||
if aZone.verbose or cloneZones.verbose then
|
||||
trigger.action.outText("+++clnZ: Warning: zero oZize for cloner <" .. aZone.name .. ">, no health info, no damage alert", 30)
|
||||
end
|
||||
else
|
||||
local percent = math.floor(currSize * 100 / aZone.oSize)
|
||||
if aZone.health then
|
||||
aZone:setFlagValue(aZone.health, percent)
|
||||
end
|
||||
if aZone.lastSize > currSize then
|
||||
if aZone.damaged then
|
||||
aZone:pollFlag(aZone.damaged, aZone.cloneMethod)
|
||||
end
|
||||
end
|
||||
end
|
||||
aZone.lastSize = currSize
|
||||
end
|
||||
|
||||
-- empty handling
|
||||
local isEmpty = cloneZones.countLiveUnits(aZone) < 1 and aZone.hasClones
|
||||
if isEmpty and (willSpawn == false) then
|
||||
@ -1885,7 +1939,8 @@ function cloneZones.saveData()
|
||||
local cData = {}
|
||||
local cName = theCloner.name
|
||||
cData.myUniqueCounter = theCloner.myUniqueCounter
|
||||
|
||||
cData.oSize = theCloner.oSize
|
||||
cData.lastSize = theCloner.lastSize
|
||||
-- mySpawns: all groups i'm curently observing for empty!
|
||||
-- myStatics: dto for objects
|
||||
local mySpawns = {}
|
||||
@ -1980,6 +2035,8 @@ function cloneZones.loadData()
|
||||
if cData.myUniqueCounter then
|
||||
theCloner.myUniqueCounter = cData.myUniqueCounter
|
||||
end
|
||||
if cData.oSize then theCloner.oSize = cData.oSize end
|
||||
if cData.lastSize then theCloner.lastSize = cData.lastSize end
|
||||
|
||||
local mySpawns = {}
|
||||
for idx, aName in pairs(cData.mySpawns) do
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
csarManager = {}
|
||||
csarManager.version = "3.2.5"
|
||||
csarManager.version = "3.2.7"
|
||||
csarManager.ups = 1
|
||||
|
||||
--[[-- VERSION HISTORY
|
||||
@ -41,6 +41,7 @@ csarManager.ups = 1
|
||||
3.2.5 - smoke callbacks
|
||||
- useRanks option
|
||||
3.2.6 - inBuiltup analogon to cloner
|
||||
3.2.7 - createCSARForParachutist now supports optional coa (autoCSAR)
|
||||
|
||||
|
||||
|
||||
@ -1330,8 +1331,8 @@ function csarManager.createCSARforUnit(theUnit, pilotName, radius, silent, score
|
||||
end
|
||||
end
|
||||
|
||||
function csarManager.createCSARForParachutist(theUnit, name) -- invoked with parachute guy on ground as theUnit
|
||||
local coa = theUnit:getCoalition()
|
||||
function csarManager.createCSARForParachutist(theUnit, name, coa) -- invoked with parachute guy on ground as theUnit
|
||||
if not coa then coa = theUnit:getCoalition() end
|
||||
local pos = theUnit:getPoint()
|
||||
-- unit DOES NOT HAVE GROUP!!! (unless water splashdown)
|
||||
-- create a CSAR mission now
|
||||
|
||||
@ -16,12 +16,13 @@ dcsCommon.version = "3.0.5"
|
||||
3.0.4 - getGroupLocation() hardened, optional verbose
|
||||
3.0.5 - new getNthItem()
|
||||
- new getFirstItem()
|
||||
|
||||
- arrayContainsString() can handle dicts
|
||||
- new pointXpercentYdegOffAB()
|
||||
--]]--
|
||||
|
||||
-- dcsCommon is a library of common lua functions
|
||||
-- for easy access and simple mission programming
|
||||
-- (c) 2021 - 2023 by Chritian Franz and cf/x AG
|
||||
-- (c) 2021 - 2024 by Christian Franz and cf/x AG
|
||||
|
||||
dcsCommon.verbose = false -- set to true to see debug messages. Lots of them
|
||||
dcsCommon.uuidStr = "uuid-"
|
||||
@ -1830,7 +1831,7 @@ dcsCommon.version = "3.0.5"
|
||||
|
||||
end;
|
||||
|
||||
function dcsCommon.pointInDirectionOfPointXYY(dir, dist, p) -- dir in rad, p in XYZ returns XYY
|
||||
function dcsCommon.pointInDirectionOfPointXYY(dir, dist, p) -- dir in rad, p in XYZ returns XZZ
|
||||
local fx = math.cos(dir)
|
||||
local fy = math.sin(dir)
|
||||
local p2 = {}
|
||||
@ -1840,6 +1841,15 @@ dcsCommon.version = "3.0.5"
|
||||
return p2
|
||||
end
|
||||
|
||||
function dcsCommon.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 dcsCommon.rotatePointAroundOriginRad(inX, inY, angle) -- angle in degrees
|
||||
local c = math.cos(angle)
|
||||
local s = math.sin(angle)
|
||||
@ -2078,7 +2088,7 @@ end
|
||||
if not theString then return false end
|
||||
if not caseSensitive then caseSensitive = false end
|
||||
if type(theArray) ~= "table" then
|
||||
trigger.action.outText("***arrayContainsString: theArray is not type table but <" .. type(theArray) .. ">", 30)
|
||||
trigger.action.outText("***wildArrayContainsString: theArray is not type table but <" .. type(theArray) .. ">", 30)
|
||||
end
|
||||
if not caseSensitive then theString = string.upper(theString) end
|
||||
|
||||
@ -2114,10 +2124,11 @@ end
|
||||
if not theArray then return false end
|
||||
if not theString then return false end
|
||||
if type(theArray) ~= "table" then
|
||||
trigger.action.outText("***arrayContainsString: theArray is not type table but <" .. type(theArray) .. ">", 30)
|
||||
trigger.action.outText("***arrayContainsString: theArray is not type <table> but <" .. type(theArray) .. ">", 30)
|
||||
end
|
||||
for i = 1, #theArray do
|
||||
if theArray[i] == theString then return true end
|
||||
for idx, item in pairs(theArray) do
|
||||
-- for i = 1, #theArray do
|
||||
if item == theString then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
@ -2418,7 +2429,7 @@ end
|
||||
if getmetatable(value) then
|
||||
if type(value) == "string" then
|
||||
else
|
||||
trigger.action.outText(prefix .. key (" .. type(value) .. ") .. " HAS META", 30)
|
||||
trigger.action.outText(prefix .. key .. (" .. type(value) .. ") .. " HAS META", 30)
|
||||
end
|
||||
end
|
||||
if type(value) == "table" then
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
factoryZone = {}
|
||||
factoryZone.version = "3.1.1"
|
||||
factoryZone.version = "3.1.2"
|
||||
factoryZone.verbose = false
|
||||
factoryZone.name = "factoryZone"
|
||||
|
||||
@ -18,6 +18,7 @@ factoryZone.name = "factoryZone"
|
||||
- defendMe? attribute
|
||||
- triggered 'shocked' mode via defendMe
|
||||
3.1.1 - fixed a big with persistence
|
||||
3.1.2 - fixed a verbosity bug
|
||||
|
||||
--]]--
|
||||
factoryZone.requiredLibs = {
|
||||
@ -253,14 +254,14 @@ function factoryZone.sendOutAttackers(aZone)
|
||||
-- bang on xxxP!
|
||||
if aZone.owner == 1 and aZone.redP then
|
||||
if aZone.verbose or factoryZone.verbose then
|
||||
trigger.action.outText("+++factZ: polling redP! <" .. aZone.redP .. "> for factrory <" .. aZone.name .. ">")
|
||||
trigger.action.outText("+++factZ: polling redP! <" .. aZone.redP .. "> for factrory <" .. aZone.name .. ">", 30)
|
||||
end
|
||||
aZone:pollFlag(aZone.redP, aZone.factoryMethod)
|
||||
end
|
||||
|
||||
if aZone.owner == 2 and aZone.blueP then
|
||||
if aZone.verbose or factoryZone.verbose then
|
||||
trigger.action.outText("+++factZ: polling blueP! <" .. aZone.blueP .. "> for factrory <" .. aZone.name .. ">")
|
||||
trigger.action.outText("+++factZ: polling blueP! <" .. aZone.blueP .. "> for factrory <" .. aZone.name .. ">", 30)
|
||||
end
|
||||
aZone:pollFlag(aZone.blueP, aZone.factoryMethod)
|
||||
end
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
cfxGroundTroops = {}
|
||||
cfxGroundTroops.version = "2.0.0"
|
||||
cfxGroundTroops.version = "2.0.1"
|
||||
cfxGroundTroops.ups = 1
|
||||
cfxGroundTroops.verbose = false
|
||||
cfxGroundTroops.requiredLibs = {
|
||||
@ -31,6 +31,8 @@ cfxGroundTroops.jtacCB = {} -- jtac callbacks, to be implemented
|
||||
- jtacSound
|
||||
- clanup
|
||||
- jtacVerbose
|
||||
2.0.1 - small fiex ti checkPileUp()
|
||||
|
||||
|
||||
an entry into the deployed troop table has the following attributes
|
||||
- group - the group
|
||||
@ -755,7 +757,8 @@ function cfxGroundTroops.checkPileUp()
|
||||
end
|
||||
|
||||
-- create a list of all piles
|
||||
for idx, oz in pairs(cfxOwnedZones.zones) do
|
||||
-- for idx, oz in pairs(cfxOwnedZones.zones) do
|
||||
for idx, oz in pairs(cfxOwnedZones.allManagedOwnedZones) do
|
||||
local newPile = {}
|
||||
newPile[1] = 0 -- no red inZone here
|
||||
newPile[2] = 0 -- no blue inZone here
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
cfxHeloTroops = {}
|
||||
cfxHeloTroops.version = "3.0.2"
|
||||
cfxHeloTroops.version = "3.0.3"
|
||||
cfxHeloTroops.verbose = false
|
||||
cfxHeloTroops.autoDrop = true
|
||||
cfxHeloTroops.autoPickup = false
|
||||
@ -39,6 +39,7 @@ cfxHeloTroops.requestRange = 500 -- meters
|
||||
- requestRange attribute
|
||||
3.0.1 - fixed a bug with legalTroops attribute
|
||||
3.0.2 - fixed a typo in in-air menu
|
||||
3.0.3 - pointInZone check for insertion rather than radius
|
||||
|
||||
--]]--
|
||||
--
|
||||
@ -585,7 +586,7 @@ function cfxHeloTroops.scoreWhenCapturing(theUnit)
|
||||
local theGroup = theUnit:getGroup()
|
||||
local ID = theGroup:getID()
|
||||
local nearestZone, dist = cfxOwnedZones.getNearestOwnedZoneToPoint(p)
|
||||
if nearestZone and dist < nearestZone.radius then
|
||||
if nearestZone and nearestZone:pointInZone(p) then -- dist < nearestZone.radius then
|
||||
-- we are inside an owned zone!
|
||||
if nearestZone.owner ~= coa then
|
||||
-- yup, combat drop!
|
||||
|
||||
110
modules/income.lua
Normal file
110
modules/income.lua
Normal file
@ -0,0 +1,110 @@
|
||||
income = {}
|
||||
income.version = "0.0.0"
|
||||
income.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
"cfxZones", -- Zones, of course
|
||||
"bank"
|
||||
}
|
||||
|
||||
income.sources = {}
|
||||
|
||||
|
||||
function income.addIncomeZone(theZone)
|
||||
income.sources[theZone.name] = theZone
|
||||
end
|
||||
|
||||
function income.createIncomeWithZone(theZone)
|
||||
theZone.income = theZone:getNumberFromZoneProperty("income")
|
||||
-- we may add enablers and prohibitors and shared income
|
||||
-- for example a building or upgrade must exist in order
|
||||
-- to provide income
|
||||
end
|
||||
|
||||
function income.getIncomeForZoneAndCoa(theZone, coa)
|
||||
-- process this zone's status (see which upgrades exist)
|
||||
-- and return the amount of income for this zone and coa
|
||||
-- currently very primitive: own it, get it
|
||||
if theZone.owner == coa then
|
||||
return theZone.income
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
function income.update()
|
||||
-- schedule next round
|
||||
timer.scheduleFunction(income.update, {}, timer.getTime() + income.interval)
|
||||
|
||||
-- base income
|
||||
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))
|
||||
end
|
||||
|
||||
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)
|
||||
has, balance = bank.getBalance(1)
|
||||
trigger.action.outTextForCoalition(1, "\n" .. income.tickMessage .. "\nNew balance: §" .. balance .. "\n", 30)
|
||||
has, balance = bank.getBalance(2)
|
||||
trigger.action.outTextForCoalition(2, "\n" .. income.tickMessage .. "\nNew balance: §" .. balance .. "\n", 30)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
function income.readConfigZone()
|
||||
local theZone = cfxZones.getZoneByName("incomeConfig")
|
||||
if not theZone then
|
||||
theZone = cfxZones.createSimpleZone("incomeConfig")
|
||||
end
|
||||
|
||||
income.base = theZone:getNumberFromZoneProperty ("base", 10)
|
||||
income.red = theZone:getNumberFromZoneProperty ("red", income.base)
|
||||
income.blue = theZone:getNumberFromZoneProperty ("blue", income.base)
|
||||
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.announceTicks = theZone:getBoolFromZoneProperty("announceTicks", true)
|
||||
income.verbose = theZone.verbose
|
||||
end
|
||||
|
||||
|
||||
function income.start()
|
||||
-- lib check
|
||||
if not dcsCommon.libCheck then
|
||||
trigger.action.outText("income requires dcsCommon", 30)
|
||||
return false
|
||||
end
|
||||
if not dcsCommon.libCheck("income", income.requiredLibs) then
|
||||
return false
|
||||
end
|
||||
|
||||
-- read config
|
||||
income.readConfigZone()
|
||||
|
||||
-- read income zones
|
||||
local attrZones = cfxZones.getZonesWithAttributeNamed("income")
|
||||
for k, aZone in pairs(attrZones) do
|
||||
income.createIncomeWithZone(aZone) -- process attributes
|
||||
income.addIncomeZone(aZone) -- add to list
|
||||
end
|
||||
|
||||
-- schedule first tick
|
||||
timer.scheduleFunction(income.update, {}, timer.getTime() + income.interval)
|
||||
|
||||
trigger.action.outText("income v" .. income.version .. " started.", 30)
|
||||
return true
|
||||
end
|
||||
|
||||
if not income.start() then
|
||||
trigger.action.outText("income aborted: missing libraries", 30)
|
||||
income = nil
|
||||
end
|
||||
@ -6,15 +6,30 @@ milHelo.requiredLibs = {
|
||||
"cfxMX",
|
||||
}
|
||||
milHelo.zones = {}
|
||||
milHelo.targetKeywords = {
|
||||
"milTarget", -- my own
|
||||
"camp", -- camps
|
||||
"airfield", -- airfields
|
||||
"FARP", -- FARPzones
|
||||
}
|
||||
|
||||
milHelo.targets = {}
|
||||
milHelo.flights = {} -- all currently active mil helo flights
|
||||
milHelo.ups = 1
|
||||
milHelo.missionTypes = {
|
||||
"cas", -- standard cas
|
||||
"patrol", -- orbit over zone for duration
|
||||
"insert", -- insert one of the ground groups in the src zone after landing
|
||||
"casz", -- engage in zone for target zone's radius
|
||||
-- missing csar
|
||||
}
|
||||
|
||||
function milHelo.addMilHeloZone(theZone)
|
||||
milHelo.zones[theZone.name] = theZone
|
||||
end
|
||||
|
||||
function milHelo.addMilTargetZone(theZone)
|
||||
milHelo.targets[theZone.name] = theZone
|
||||
milHelo.targets[theZone.name] = theZone -- overwrite if duplicate
|
||||
end
|
||||
|
||||
function milHelo.partOfGroupDataInZone(theZone, theUnits) -- move to mx?
|
||||
@ -51,15 +66,48 @@ end
|
||||
|
||||
function milHelo.readMilHeloZone(theZone) -- process attributes
|
||||
-- get mission type. part of milHelo
|
||||
theZone.msnType = string.lower(theZone:getStringFromZoneProperty("milHelo", "cas"))
|
||||
theZone.msnType = string.lower(theZone:getStringFromZoneProperty("milHelo", "cas"))
|
||||
if dcsCommon.arrayContainsString(milHelo.missionTypes, theZone.msnType) then
|
||||
-- great, mission type is known
|
||||
else
|
||||
trigger.action.outText("+++milH: zone <" .. theZone.name .. ">: unknown mission type <" .. theZone.msnType .. ">, defaulting to 'CAS'", 30)
|
||||
theZone.msnType = "cas"
|
||||
end
|
||||
|
||||
-- get all groups inside me
|
||||
local myGroups, count = milHelo.allGroupsInZoneByData(theZone)
|
||||
theZone.myGroups = myGroups
|
||||
theZone.groupCount = count
|
||||
theZone.hGroups = {}
|
||||
theZone.hCount = 0
|
||||
theZone.gGroups = {}
|
||||
theZone.gCount = 0
|
||||
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 == "helicopter" then
|
||||
theZone.hGroups[groupName] = data
|
||||
theZone.hCount = theZone.hCount + 1
|
||||
elseif catRaw == "plane" then
|
||||
theZone.fGroups[groupName] = data
|
||||
theZone.fCount = theZone.fCount + 1
|
||||
elseif catRaw == "vehicle" then
|
||||
theZone.gGroups[groupName] = data
|
||||
theZone.gCount = theZone.gCount + 1
|
||||
else
|
||||
trigger.action.outText("+++milH: ignored group <" .. groupName .. ">: unknown type <" .. catRaw .. ">", 30)
|
||||
end
|
||||
end
|
||||
theZone.coa = theZone:getCoalitionFromZoneProperty("coalition", 0)
|
||||
theZone.hot = theZone:getBoolFromZoneProperty("hot", true)
|
||||
theZone.hot = theZone:getBoolFromZoneProperty("hot", false)
|
||||
theZone.speed = theZone:getNumberFromZoneProperty("speed", 50) -- 110 mph
|
||||
theZone.alt = theZone:getNumberFromZoneProperty("alt", 100) -- we are always radar alt
|
||||
theZone.loiter = theZone:getNumberFromZoneProperty("loiter", 3600) -- 1 hour loiter default
|
||||
-- wipe all existing
|
||||
for groupName, data in pairs(myGroups) do
|
||||
local g = Group.getByName(groupName)
|
||||
@ -73,25 +121,24 @@ function milHelo.readMilHeloZone(theZone) -- process attributes
|
||||
end
|
||||
|
||||
function milHelo.readMilTargetZone(theZone)
|
||||
|
||||
-- can also be "camp", "farp", "airfield"
|
||||
theZone.casRadius = theZone:getNumberFromZoneProperty("casRadius", theZone.radius)
|
||||
if (not theZone.isCircle) and not theZone:hasProperty("casRadius") then
|
||||
-- often when we have a camp there is no cas radius, use 10km
|
||||
-- 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 10km", 30)
|
||||
end
|
||||
theZone.casRadius = 10000
|
||||
end
|
||||
if theZone.verbose or milHelo.verbose then
|
||||
trigger.action.outText("+++milH: processed TARGET zone <" .. theZone.name .. ">", 30)
|
||||
trigger.action.outText("+++milH: processed milHelo TARGET zone <" .. theZone.name .. ">", 30)
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- Spawning for a zone
|
||||
--
|
||||
--[[--
|
||||
function milHelo.getNthItem(theSet, n)
|
||||
local count = 1
|
||||
for key, value in pairs(theSet) do
|
||||
if count == n then return value end
|
||||
count = count + 1
|
||||
end
|
||||
return nil
|
||||
end
|
||||
--]]--
|
||||
|
||||
function milHelo.createCASTask(num, auto)
|
||||
if not auto then auto = false end
|
||||
@ -104,7 +151,6 @@ function milHelo.createCASTask(num, auto)
|
||||
task.auto = auto
|
||||
local params = {}
|
||||
params.priority = 0
|
||||
-- params.targetTypes = {"Helicopters", "Ground Units", "Light armed ships"}
|
||||
local targetTypes = {[1] = "Helicopters", [2] = "Ground Units", [3] = "Light armed ships",}
|
||||
params.targetTypes = targetTypes
|
||||
|
||||
@ -132,6 +178,29 @@ function milHelo.createROETask(num, roe)
|
||||
return task
|
||||
end
|
||||
|
||||
function milHelo.createEngageIZTask(num, theZone)
|
||||
local p = theZone:getPoint()
|
||||
if not num then num = 1 end
|
||||
local task = {}
|
||||
task.number = num
|
||||
task.enabled = true
|
||||
task.auto = false
|
||||
task.id = "EngageTargetsInZone"
|
||||
local params = {}
|
||||
targetTypes = {}
|
||||
targetTypes[1] = "All"
|
||||
params.targetTypes = targetTypes
|
||||
params.x = p.x
|
||||
params.y = p.z -- !!!!
|
||||
params.value = "All;"
|
||||
params.noTargetTypes = {}
|
||||
params.priority = 0
|
||||
local radius = theZone.casRadius
|
||||
params.zoneRadius = radius
|
||||
task.params = params
|
||||
return task
|
||||
end
|
||||
|
||||
function milHelo.createOrbitTask(num, duration, theZone)
|
||||
if not num then num = 1 end
|
||||
local task = {}
|
||||
@ -155,23 +224,72 @@ function milHelo.createOrbitTask(num, duration, theZone)
|
||||
return task
|
||||
end
|
||||
|
||||
function milHelo.createTakeOffWP(theZone)
|
||||
function milHelo.createLandTask(p, duration, num)
|
||||
if not num then num = 1 end
|
||||
local t = {}
|
||||
t.enabled = true
|
||||
t.auto = false
|
||||
t.id = "ControlledTask"
|
||||
t.number = num
|
||||
local params = {}
|
||||
t.params = params
|
||||
local ptsk = {}
|
||||
params.task = ptsk
|
||||
ptsk.id = "Land"
|
||||
local ptp = {}
|
||||
ptsk.params = ptp
|
||||
ptp.x = p.x
|
||||
ptp.y = p.z
|
||||
ptp.duration = "300" -- not sure why
|
||||
ptp.durationFlag = false -- off anyway
|
||||
local stopCon = {}
|
||||
stopCon.duration = duration
|
||||
params.stopCondition = stopCon
|
||||
return t
|
||||
end
|
||||
|
||||
function milHelo.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 milHelo.createTakeOffWP(theZone, engageInZone, engageZone)
|
||||
local WP = {}
|
||||
WP.alt = theZone.alt
|
||||
WP.alt_type = "RADIO"
|
||||
WP.alt = 500 -- theZone.alt
|
||||
WP.alt_type = "BARO"
|
||||
WP.properties = {}
|
||||
WP.properties.addopt = {}
|
||||
WP.action = "From Ground Area"
|
||||
if theZone.hot then WP.action = "From Ground Area Hot" end
|
||||
WP.speed = theZone.speed
|
||||
WP.speed = 0 -- theZone.speed
|
||||
WP.task = {}
|
||||
WP.task.id = "ComboTask"
|
||||
WP.task.params = {}
|
||||
local tasks = {}
|
||||
-- local casTask = milHelo.createCASTask(1)
|
||||
-- tasks[1] = casTask
|
||||
local roeTask = milHelo.createROETask(1,0) -- 0 = weapons free
|
||||
tasks[1] = roeTask
|
||||
local casTask = milHelo.createCASTask(1)
|
||||
tasks[1] = casTask
|
||||
local roeTask = milHelo.createROETask(2,0) -- 0 = weapons free
|
||||
tasks[2] = roeTask
|
||||
if engageInZone then
|
||||
if not engageZone then
|
||||
trigger.action.outText("+++milH: Warning - caz task with no engage zone!", 30)
|
||||
end
|
||||
local eiz = milHelo.createEngageIZTask(3, engageZone)
|
||||
tasks[3] = eiz
|
||||
end
|
||||
WP.task.params.tasks = tasks
|
||||
--
|
||||
WP.type = "TakeOffGround"
|
||||
@ -180,7 +298,7 @@ function milHelo.createTakeOffWP(theZone)
|
||||
WP.x = p.x
|
||||
WP.y = p.z
|
||||
WP.ETA = 0
|
||||
WP.ETA_locked = false
|
||||
WP.ETA_locked = true
|
||||
WP.speed_locked = true
|
||||
WP.formation_template = ""
|
||||
return WP
|
||||
@ -201,9 +319,9 @@ function milHelo.createOrbitWP(theZone, targetPoint)
|
||||
WP.task.params = {}
|
||||
-- start params construct
|
||||
local tasks = {}
|
||||
local casTask = milHelo.createCASTask(1, false)
|
||||
local casTask = milHelo.createCASTask(1)
|
||||
tasks[1] = casTask
|
||||
local oTask = milHelo.createOrbitTask(2, 3600, theZone)
|
||||
local oTask = milHelo.createOrbitTask(2, theZone.loiter, theZone)
|
||||
tasks[2] = oTask
|
||||
WP.task.params.tasks = tasks
|
||||
WP.type = "Turning Point"
|
||||
@ -217,41 +335,123 @@ function milHelo.createOrbitWP(theZone, targetPoint)
|
||||
return WP
|
||||
end
|
||||
|
||||
function milHelo.createLandWP(gName, theZone, targetZone)
|
||||
local toWP
|
||||
toWP = dcsCommon.createSimpleRoutePointData(targetZone:getPoint(), theZone.alt, theZone.speed)
|
||||
toWP.alt_type = "RADIO"
|
||||
|
||||
local task = {}
|
||||
task.id = "ComboTask"
|
||||
task.params = {}
|
||||
local ttsk = {}
|
||||
local p = targetZone:getPoint()
|
||||
ttsk[1] = milHelo.createLandTask(p, milHelo.landingDuration, 1)
|
||||
local command = "milHelo.landedCB('" .. gName .. "', '" .. targetZone:getName() .. "', '" .. theZone:getName() .. "')"
|
||||
ttsk[2] = milHelo.createCommandTask(command,2)
|
||||
task.params.tasks = ttsk
|
||||
toWP.task = task
|
||||
return toWP
|
||||
end
|
||||
|
||||
function milHelo.createOMWCallbackWP(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 = "RADIO"
|
||||
-- create a command waypoint
|
||||
local task = {}
|
||||
task.id = "ComboTask"
|
||||
task.params = {}
|
||||
local ttsk = {}
|
||||
local command = "milHelo.reachedWP('" .. gName .. "', '" .. number .. "', '" .. action .."')"
|
||||
ttsk[1] = milHelo.createCommandTask(command,1)
|
||||
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)
|
||||
local theRawData = dcsCommon.getNthItem(theZone.myGroups, 1)
|
||||
local n = dcsCommon.randomBetween(1, theZone.hCount)
|
||||
local theRawData = dcsCommon.getNthItem(theZone.hGroups, n)
|
||||
local gData = dcsCommon.clone(theRawData)
|
||||
--[[--
|
||||
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 = 10
|
||||
uData.alt_type = "RADIO"
|
||||
uData.speed = 0
|
||||
uData.unitId = nil
|
||||
end
|
||||
gData.groupId = nil
|
||||
|
||||
-- change task according to missionType in Zone
|
||||
-- we currently use CAS for all
|
||||
gData.task = "CAS"
|
||||
|
||||
-- create and process route
|
||||
local route = {}
|
||||
route.points = {}
|
||||
-- gData.route = route
|
||||
gData.route = route
|
||||
-- create take-off waypoint
|
||||
local wpTOff = milHelo.createTakeOffWP(theZone)
|
||||
local casInZone = theZone.msnType == "casz"
|
||||
if theZone.verbose and casInZone then
|
||||
trigger.action.outText("Setting up casZ for <" .. theZone.name .. "> to <" .. targetZone.name .. ">", 30)
|
||||
end
|
||||
|
||||
local wpTOff = milHelo.createTakeOffWP(theZone, casInZone, targetZone)
|
||||
|
||||
-- depending on mission, create an orbit or land WP
|
||||
local dest = targetZone:getPoint()
|
||||
local wpDest = milHelo.createOrbitWP(theZone, dest)
|
||||
-- move group to WP1 and add WP1 and WP2 to route
|
||||
-- dcsCommon.moveGroupDataTo(theGroup,
|
||||
-- fromWP.x,
|
||||
-- fromWP.y)
|
||||
|
||||
----
|
||||
dcsCommon.addRoutePointForGroupData(gData, wpTOff)
|
||||
dcsCommon.addRoutePointForGroupData(gData, wpDest)
|
||||
--]]--
|
||||
dcsCommon.dumpVar2Str("route", gData.route)
|
||||
local B = dest
|
||||
local A = theZone:getPoint()
|
||||
if theZone.msnType == "cas" or theZone.msnType == "patrol" then
|
||||
dcsCommon.addRoutePointForGroupData(gData, wpTOff)
|
||||
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)
|
||||
elseif theZone.msnType == "casz" then
|
||||
dcsCommon.addRoutePointForGroupData(gData, wpTOff)
|
||||
-- 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")
|
||||
dcsCommon.addRoutePointForGroupData(gData, omw1)
|
||||
local omw2 = milHelo.createOMWCallbackWP(gName, 3, B, theZone.alt, theZone.speed, "none")
|
||||
dcsCommon.addRoutePointForGroupData(gData, omw2)
|
||||
-- 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)
|
||||
elseif theZone.msnType == "insert" then
|
||||
local wpDest = milHelo.createLandWP(gName, theZone, targetZone)
|
||||
dcsCommon.addRoutePointForGroupData(gData, wpTOff)
|
||||
dcsCommon.addRoutePointForGroupData(gData, wpDest)
|
||||
end
|
||||
|
||||
-- make it a cty
|
||||
-- make coa a cty
|
||||
if theZone.coa == 0 then
|
||||
trigger.action.outText("+++milH: WARNING - zone <" .. theZone.name .. "> is NEUTRAL", 30)
|
||||
end
|
||||
@ -259,9 +459,162 @@ function milHelo.spawnForZone(theZone, targetZone)
|
||||
-- spawn
|
||||
local groupCat = Group.Category.HELICOPTER
|
||||
local theSpawnedGroup = coalition.addGroup(cty, groupCat, gData)
|
||||
local theFlight = {}
|
||||
theFlight.oName = oName
|
||||
theFlight.spawn = theSpawnedGroup
|
||||
theFlight.origin = theZone
|
||||
theFlight.destination = targetZone
|
||||
milHelo.flights[gName] = theFlight --theSpawnedGroup
|
||||
return theSpawnedGroup, gData
|
||||
end
|
||||
--
|
||||
-- mil helo landed callback (insertion)
|
||||
--
|
||||
function milHelo.insertTroops(theUnit, targetZone, srcZone)
|
||||
local theZone = srcZone
|
||||
local n = dcsCommon.randomBetween(1, theZone.gCount)
|
||||
local theRawData = dcsCommon.getNthItem(theZone.gGroups, n)
|
||||
-- local theRawData = dcsCommon.getNthItem(srcZone.gGroups, 1)
|
||||
if not theRawData then
|
||||
trigger.action.outText("+++milH: WARNING: no troops to insert for zone <" .. srcZone.name .. ">", 30)
|
||||
return
|
||||
end
|
||||
|
||||
local gData = dcsCommon.clone(theRawData)
|
||||
-- deploy in ring formation
|
||||
-- remove all routes
|
||||
-- mayhaps prepare for orders and formation
|
||||
|
||||
local p = theUnit:getPoint()
|
||||
gData.route = nil -- no more route. stand in place
|
||||
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.speed = 0
|
||||
uData.heading = 0
|
||||
uData.unitId = nil
|
||||
end
|
||||
gData.groupId = nil
|
||||
dcsCommon.moveGroupDataTo(gData, 0, 0) -- move to origin so we can arrange them
|
||||
|
||||
dcsCommon.arrangeGroupDataIntoFormation(gData, 20, nil, "CIRCLE_OUT")
|
||||
|
||||
dcsCommon.moveGroupDataTo(gData, p.x, p.z) -- move arranged group to helo
|
||||
|
||||
-- make coa a cty
|
||||
if theZone.coa == 0 then
|
||||
trigger.action.outText("+++milH: WARNING - zone <" .. theZone.name .. "> is NEUTRAL", 30)
|
||||
end
|
||||
local cty = dcsCommon.getACountryForCoalition(theZone.coa)
|
||||
-- spawn
|
||||
local groupCat = Group.Category.GROUND
|
||||
local theSpawnedGroup = coalition.addGroup(cty, groupCat, gData)
|
||||
|
||||
trigger.action.outText("Inserted troops <" .. gName .. ">", 30)
|
||||
|
||||
return theSpawnedGroup, gData
|
||||
end
|
||||
|
||||
function milHelo.replaceUnitsWithStatics(gName)
|
||||
|
||||
end
|
||||
|
||||
function milHelo.getRawDataFromGroupNamed(gName, oName)
|
||||
local theGroup = Group.getByName(gName)
|
||||
local groupName = gName
|
||||
local cat = theGroup:getCategory()
|
||||
-- access mxdata for livery because getDesc does not return the livery
|
||||
local liveries = {}
|
||||
local mxData = cfxMX.getGroupFromDCSbyName(oName)
|
||||
for idx, theUnit in pairs (mxData.units) do
|
||||
liveries[theUnit.name] = theUnit.livery_id
|
||||
end
|
||||
|
||||
local ctry
|
||||
local gID = theGroup:getID()
|
||||
local allUnits = theGroup:getUnits()
|
||||
local rawGroup = {}
|
||||
rawGroup.name = groupName
|
||||
local rawUnits = {}
|
||||
for idx, theUnit in pairs(allUnits) do
|
||||
local ir = {}
|
||||
local unitData = theUnit:getDesc()
|
||||
-- build record
|
||||
ir.heading = dcsCommon.getUnitHeading(theUnit)
|
||||
ir.name = theUnit:getName()
|
||||
ir.type = unitData.typeName -- warning: fields are called differently! typename vs type
|
||||
ir.livery_id = liveries[ir.name] -- getDesc does not return livery
|
||||
ir.groupId = gID
|
||||
ir.unitId = theUnit:getID()
|
||||
local up = theUnit:getPoint()
|
||||
ir.x = up.x
|
||||
ir.y = up.z -- !!! warning!
|
||||
-- see if any zones are linked to this unit
|
||||
ir.linkedZones = cfxZones.zonesLinkedToUnit(theUnit)
|
||||
|
||||
table.insert(rawUnits, ir)
|
||||
ctry = theUnit:getCountry()
|
||||
end
|
||||
rawGroup.ctry = ctry
|
||||
rawGroup.cat = cat
|
||||
rawGroup.units = rawUnits
|
||||
return rawGroup, cat, ctry
|
||||
end
|
||||
|
||||
function milHelo.spawnImpostorsFromData(rawData, cat, ctry)
|
||||
for idx, unitData in pairs(rawData.units) do
|
||||
-- build impostor record
|
||||
local ir = {}
|
||||
ir.heading = unitData.heading
|
||||
ir.type = unitData.type
|
||||
ir.name = dcsCommon.uuid(rawData.name) -- .. "-" .. tostring(impostors.uniqueID())
|
||||
ir.groupID = nil -- impostors.uniqueID()
|
||||
ir.unitId = nil -- impostors.uniqueID()
|
||||
ir.x = unitData.x
|
||||
ir.y = unitData.y
|
||||
ir.livery_id = unitData.livery_id
|
||||
-- spawn the impostor
|
||||
local theImp = coalition.addStaticObject(ctry, ir)
|
||||
end
|
||||
end
|
||||
|
||||
function milHelo.reachedWP(gName, wpNum, action)
|
||||
trigger.action.outText("MilH group <" .. gName .. " reached wp #" .. wpNum .. ".", 30)
|
||||
|
||||
end
|
||||
|
||||
function milHelo.landedCB(who, where, from) -- who group name, where a zone
|
||||
trigger.action.outText("milhelo landed CB for group <" .. who .. ">", 30)
|
||||
-- step 1: remove the flight
|
||||
local theGroup = Group.getByName(who)
|
||||
if theGroup then
|
||||
if Group.isExist(theGroup) then
|
||||
Group.destroy(theGroup)
|
||||
end
|
||||
else
|
||||
trigger.action.outText("+++milH: cannot find group <" .. who .. ">", 30)
|
||||
end
|
||||
|
||||
-- step 3: replace with static helo
|
||||
local aGroup = theGroup
|
||||
local theFlight = milHelo.flights[who]
|
||||
local oName = theFlight.oName
|
||||
local theZone = theFlight.origin
|
||||
if theZone.msn == "insertion" then
|
||||
-- create a static stand-in for scenery
|
||||
local rawData, cat, ctry = milHelo.getRawDataFromGroupNamed(who, oName)
|
||||
Group.destroy(aGroup)
|
||||
milHelo.spawnImpostorsFromData(rawData, cat, ctry)
|
||||
else
|
||||
-- remove group
|
||||
Group.destroy(aGroup)
|
||||
end
|
||||
|
||||
-- remove flight from list of active flights
|
||||
milHelo.flights[who] = nil
|
||||
end
|
||||
|
||||
--
|
||||
-- update and event
|
||||
--
|
||||
@ -269,10 +622,98 @@ function milHelo.update()
|
||||
timer.scheduleFunction(milHelo.update, {}, timer.getTime() + 1)
|
||||
end
|
||||
|
||||
function milHelo.onEvent(theEvent)
|
||||
|
||||
function milHelo.GCcollected(gName)
|
||||
-- do some housekeeping?
|
||||
trigger.action.outText("removed flight <" .. gName .. ">", 30)
|
||||
end
|
||||
|
||||
function milHelo.GC()
|
||||
timer.scheduleFunction(milHelo.GC, {}, timer.getTime() + 1)
|
||||
local filtered = {}
|
||||
for gName, theFlight in pairs(milHelo.flights) do
|
||||
local theGroup = Group.getByName(gName)
|
||||
if theGroup and Group.isExist(theGroup) then
|
||||
-- all fine, keep it
|
||||
filtered[gName] = theFlight
|
||||
else
|
||||
milHelo.GCcollected(gName)
|
||||
end
|
||||
end
|
||||
milHelo.flights = filtered
|
||||
end
|
||||
|
||||
function milHelo: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
|
||||
-- trigger.action.outText("event <" .. theEvent.id .. ">: group shenenigans for unit detected", 30)
|
||||
return
|
||||
end
|
||||
local gName = theGroup:getName()
|
||||
local theFlight = milHelo.flights[gName]
|
||||
if not theFlight then return end
|
||||
|
||||
local id = theEvent.id
|
||||
if id == 4 then
|
||||
-- flight landed
|
||||
-- did it land in target zone?
|
||||
local p = theUnit:getPoint()
|
||||
local srcZone = theFlight.origin
|
||||
local tgtZone = theFlight.destination
|
||||
if tgtZone:pointInZone(p) then
|
||||
trigger.action.outText("Flight <" .. gName .. "> originating from <" .. srcZone.name .. "> landed in zone <" .. tgtZone.name .. ">", 30)
|
||||
if srcZone.msnType == "insert" then
|
||||
trigger.action.outText("Commencing Troop Insertion", 30)
|
||||
milHelo.insertTroops(theUnit, tgtZone, srcZone)
|
||||
end
|
||||
else
|
||||
-- maybe its a return flight
|
||||
if srcZone:pointInZone(p) then
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
-- trigger.action.outText("Event <" .. theEvent.id .. "> for milHelo flight <" .. gName .. ">", 30)
|
||||
end
|
||||
|
||||
--
|
||||
-- API
|
||||
--
|
||||
function milHelo.getMilSources(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(milHelo.zones) do
|
||||
if theZone.owner == side then -- must be owned by same side
|
||||
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 milHelo.getMilTargets(side) -- 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
|
||||
if theZone.owner ~= side then -- must NOT be owned by same side
|
||||
table.insert(tgt, theZone)
|
||||
end
|
||||
end
|
||||
return tgt
|
||||
end
|
||||
--
|
||||
-- Config & start
|
||||
--
|
||||
@ -282,6 +723,7 @@ function milHelo.readConfigZone()
|
||||
theZone = cfxZones.createSimpleZone("milHeloConfig")
|
||||
end
|
||||
milHelo.verbose = theZone.verbose
|
||||
milHelo.landingDuration = theZone:getNumberFromZoneProperty("landingDuration", 180) -- seconds = 3 minutes
|
||||
end
|
||||
|
||||
|
||||
@ -305,11 +747,13 @@ function milHelo.start()
|
||||
milHelo.addMilHeloZone(aZone) -- add to list
|
||||
end
|
||||
|
||||
attrZones = cfxZones.getZonesWithAttributeNamed("milTarget")
|
||||
for k, aZone in pairs(attrZones) do
|
||||
milHelo.readMilTargetZone(aZone) -- process attributes
|
||||
milHelo.addMilTargetZone(aZone) -- add to list
|
||||
end
|
||||
for idx, keyWord in pairs(milHelo.targetKeywords) do
|
||||
attrZones = cfxZones.getZonesWithAttributeNamed(keyWord)
|
||||
for k, aZone in pairs(attrZones) do
|
||||
milHelo.readMilTargetZone(aZone) -- process attributes
|
||||
milHelo.addMilTargetZone(aZone) -- add to list
|
||||
end
|
||||
end
|
||||
|
||||
-- start update in 5 seconds
|
||||
timer.scheduleFunction(milHelo.update, {}, timer.getTime() + 1/milHelo.ups)
|
||||
@ -327,7 +771,21 @@ if not milHelo.start() then
|
||||
milHelo = nil
|
||||
end
|
||||
|
||||
--[[
|
||||
function milHelo.latestuff()
|
||||
trigger.action.outText("doing stuff", 30)
|
||||
local theZone = cfxZones.getZoneByName("milCAS") --dcsCommon.getFirstItem(milHelo.zones)
|
||||
local targetZone = cfxZones.getZoneByName("mh Target") -- dcsCommon.getFirstItem(milHelo.targets)
|
||||
milHelo.spawnForZone(theZone, targetZone)
|
||||
theZone = cfxZones.getZoneByName("milInsert") --dcsCommon.getNthItem(milHelo.zones, 2)
|
||||
milHelo.spawnForZone(theZone, targetZone)
|
||||
theZone = cfxZones.getZoneByName("doCASZ")
|
||||
targetZone = cfxZones.getZoneByName("milTarget Z")
|
||||
if not theZone then trigger.action.outText("Not theZone", 30) end
|
||||
if not targetZone then trigger.action.OutText("Not targetZone", 30) end
|
||||
milHelo.spawnForZone(theZone, targetZone)
|
||||
end
|
||||
|
||||
-- do some one-time stuff
|
||||
local theZone = dcsCommon.getFirstItem(milHelo.zones)
|
||||
local targetZone = dcsCommon.getFirstItem(milHelo.targets)
|
||||
milHelo.spawnForZone(theZone, targetZone)
|
||||
timer.scheduleFunction(milHelo.latestuff, {}, timer.getTime() + 1)
|
||||
--]]--
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
cfxOwnedZones = {}
|
||||
cfxOwnedZones.version = "2.2.0"
|
||||
cfxOwnedZones.version = "2.3.0"
|
||||
cfxOwnedZones.verbose = false
|
||||
cfxOwnedZones.announcer = true
|
||||
cfxOwnedZones.name = "cfxOwnedZones"
|
||||
@ -30,25 +30,30 @@ cfxOwnedZones.name = "cfxOwnedZones"
|
||||
- method support for global (config) output
|
||||
- moved drawZone to cfxZones
|
||||
2.2.0 - excludedTypes option in config
|
||||
|
||||
2.3.0 - include airfield zones (module) in collectZones()
|
||||
- if airfield is defined.
|
||||
- allManagedOwnedZones
|
||||
- gatherAllManagedOwnedZones()
|
||||
- commented out unused (?) methods
|
||||
- optmized getNearestEnemyOwnedZone
|
||||
- collectZones now uses gatherAllManagedOwnedZones
|
||||
- sideOwnsAll can use allManagedOwnedZones
|
||||
- per-zone local numCap
|
||||
- per-zone local numkeep
|
||||
- title attribute
|
||||
- code clean-up
|
||||
--]]--
|
||||
cfxOwnedZones.requiredLibs = {
|
||||
"dcsCommon",
|
||||
"cfxZones",
|
||||
}
|
||||
|
||||
cfxOwnedZones.zones = {}
|
||||
cfxOwnedZones.zones = {} -- ownedZones FROM THIS module
|
||||
cfxOwnedZones.allManagedOwnedZones = {} -- superset, indexed by name
|
||||
cfxOwnedZones.ups = 1
|
||||
cfxOwnedZones.initialized = false
|
||||
--[[--
|
||||
owned zones is a module that manages conquerable zones and keeps a record
|
||||
of who owns the zone based on rules
|
||||
|
||||
*** EXTENTDS ZONES ***
|
||||
|
||||
when a zone changes hands, a callback can be installed to be told of that fact
|
||||
callback has the format (zone, newOwner, formerOwner) with zone being the Zone, and new owner and former owners
|
||||
--]]--
|
||||
-- *** EXTENTDS ZONES *** --
|
||||
|
||||
cfxOwnedZones.conqueredCallbacks = {}
|
||||
|
||||
@ -100,11 +105,13 @@ function cfxOwnedZones.drawZoneInMap(aZone)
|
||||
if aZone.markID then
|
||||
trigger.action.removeMark(aZone.markID)
|
||||
end
|
||||
if aZone.hidden then return end
|
||||
if aZone.titleID then
|
||||
trigger.action.removeMark(aZone.titleID)
|
||||
end
|
||||
|
||||
local lineColor = aZone.redLine -- {1.0, 0, 0, 1.0} -- red
|
||||
local fillColor = aZone.redFill -- {1.0, 0, 0, 0.2} -- red
|
||||
local owner = aZone.owner -- cfxOwnedZones.getOwnerForZone(aZone)
|
||||
local owner = aZone.owner
|
||||
if owner == 2 then
|
||||
lineColor = aZone.blueLine -- {0.0, 0, 1.0, 1.0}
|
||||
fillColor = aZone.blueFill -- {0.0, 0, 1.0, 0.2}
|
||||
@ -112,7 +119,12 @@ function cfxOwnedZones.drawZoneInMap(aZone)
|
||||
lineColor = aZone.neutralLine -- {0.8, 0.8, 0.8, 1.0}
|
||||
fillColor = aZone.neutralFill -- {0.8, 0.8, 0.8, 0.2}
|
||||
end
|
||||
|
||||
|
||||
if aZone.title then
|
||||
aZone.titleID = aZone:drawText(aZone.title, 18, lineColor, {0, 0, 0, 0})
|
||||
end
|
||||
|
||||
if aZone.hidden then return end
|
||||
aZone.markID = aZone:drawZone(lineColor, fillColor) -- markID
|
||||
end
|
||||
|
||||
@ -154,6 +166,9 @@ function cfxOwnedZones.addOwnedZone(aZone)
|
||||
aZone.untargetable = aZone:getBoolFromZoneProperty("untargetable", false)
|
||||
|
||||
aZone.hidden = aZone:getBoolFromZoneProperty("hidden", false)
|
||||
-- numCap, numKeep
|
||||
aZone.numCap = aZone:getNumberFromZoneProperty("numCap", cfxOwnedZones.numCap)
|
||||
aZone.numKeep = aZone:getNumberFromZoneProperty("numKeep", cfxOwnedZones.numKeep)
|
||||
|
||||
-- individual colors, else default from config
|
||||
aZone.redLine = aZone:getRGBAVectorFromZoneProperty("redLine", cfxOwnedZones.redLine)
|
||||
@ -163,6 +178,31 @@ function cfxOwnedZones.addOwnedZone(aZone)
|
||||
aZone.neutralLine = aZone:getRGBAVectorFromZoneProperty("neutralLine", cfxOwnedZones.neutralLine)
|
||||
aZone.neutralFill = aZone:getRGBAVectorFromZoneProperty("neutralFill", cfxOwnedZones.neutralFill)
|
||||
|
||||
-- masterOwner
|
||||
if aZone:hasProperty("masterOwner") then
|
||||
local masterZone = aZone:getStringFromZoneProperty("masterOwner", "cfxNoneErr")
|
||||
local theMaster = cfxZones.getZoneByName(masterZone)
|
||||
if not theMaster then
|
||||
trigger.action.outText("+++owdZ: WARNING: owned zone <" .. aZone.name .. ">'s masterOwner <" .. masterZone .. "> does not exist, not connecting!", 30)
|
||||
else
|
||||
aZone.masterOwner = theMaster
|
||||
aZone.owner = theMaster.owner
|
||||
if aZone.verbose or cfxOwnedZones.verbose then
|
||||
trigger.action.outText("+++OwdZ: owned zone <" .. aZone.name .. "> inherits ownership from master zone <" .. masterZone .. ">", 30)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
aZone.announcer = aZone:getBoolFromZoneProperty("announcer", cfxZones.announcer)
|
||||
if aZone:hasProperty("announce") then
|
||||
aZone.announcer = aZone:getBoolFromZoneProperty("announce", cfxZones.announcer)
|
||||
end
|
||||
|
||||
-- title
|
||||
if aZone:hasProperty("title") then
|
||||
aZone.title = aZone:getStringFromZoneProperty("title")
|
||||
if aZone.title == "*" then aZone.title = aZone.name end
|
||||
end
|
||||
aZone.method = aZone:getStringFromZoneProperty("method", "inc")
|
||||
|
||||
cfxOwnedZones.zones[aZone] = aZone
|
||||
@ -178,23 +218,17 @@ end
|
||||
|
||||
function cfxOwnedZones.bangNeutral(value)
|
||||
if not cfxOwnedZones.neutralTriggerFlag then return end
|
||||
--local newVal = trigger.misc.getUserFlag(cfxOwnedZones.neutralTriggerFlag) + value
|
||||
--trigger.action.setUserFlag(cfxOwnedZones.neutralTriggerFlag, newVal)
|
||||
cfxZones.pollFlag(cfxOwnedZones.neutralTriggerFlag, cfxOwnedZones.method, cfxOwnedZones)
|
||||
end
|
||||
|
||||
function cfxOwnedZones.bangRed(value, theZone)
|
||||
if not cfxOwnedZones.redTriggerFlag then return end
|
||||
--local newVal = trigger.misc.getUserFlag(cfxOwnedZones.redTriggerFlag) + value
|
||||
--trigger.action.setUserFlag(cfxOwnedZones.redTriggerFlag, newVal)
|
||||
cfxZones.pollFlag(cfxOwnedZones.redTriggerFlag, cfxOwnedZones.method, cfxOwnedZones)
|
||||
end
|
||||
|
||||
function cfxOwnedZones.bangBlue(value, theZone)
|
||||
if not cfxOwnedZones.blueTriggerFlag then return end
|
||||
local newVal = trigger.misc.getUserFlag(cfxOwnedZones.blueTriggerFlag) + value
|
||||
-- trigger.action.setUserFlag(cfxOwnedZones.blueTriggerFlag, newVal)
|
||||
-- cfxZones.setFlagValue(cfxOwnedZones.blueTriggerFlag, newVal, cfxOwnedZones)
|
||||
cfxZones.pollFlag(cfxOwnedZones.blueTriggerFlag, cfxOwnedZones.method, cfxOwnedZones)
|
||||
end
|
||||
|
||||
@ -214,14 +248,15 @@ function cfxOwnedZones.zoneConquered(aZone, theSide, formerOwner) -- 0 = neutral
|
||||
local who = "REDFORCE"
|
||||
if theSide == 2 then who = "BLUEFORCE"
|
||||
elseif theSide == 0 then who = "NEUTRAL" end
|
||||
aZone.owner = theSide -- just to be sure
|
||||
|
||||
if cfxOwnedZones.announcer then
|
||||
if aZone.announcer then
|
||||
if theSide == 0 then
|
||||
trigger.action.outText(aZone.name .. " has become NEUTRAL", 30)
|
||||
else
|
||||
trigger.action.outText(who .. " have secured zone " .. aZone.name, 30)
|
||||
end
|
||||
aZone.owner = theSide -- just to be sure
|
||||
|
||||
-- play different sounds depending on who's won
|
||||
if theSide == 1 then
|
||||
trigger.action.outSoundForCoalition(1, cfxOwnedZones.winSound)
|
||||
@ -307,47 +342,38 @@ function cfxOwnedZones.update()
|
||||
if cfxOwnedZones.fixWingCap then
|
||||
allBlue = dcsCommon.combineTables(allBlue, coalition.getGroups(2, Group.Category.AIRPLANE))
|
||||
end
|
||||
|
||||
|
||||
-- WARNING: we only proc ownedZones, NOT airfield nor FARP or other
|
||||
for idz, theZone in pairs(cfxOwnedZones.zones) do
|
||||
theZone.numRed = 0
|
||||
theZone.numBlue = 0
|
||||
local lastOwner = theZone.owner
|
||||
if not lastOwner then
|
||||
trigger.action.outText("+++owdZ: WARNING - zone <" .. theZone.name .. "> has NIL owner", 30)
|
||||
return
|
||||
end
|
||||
if theZone.verbose then
|
||||
trigger.action.outText("Zone <" .. theZone.name .. "> lastOwner is <" .. lastOwner .. ">", 30)
|
||||
end
|
||||
local newOwner = 0 -- neutral is default
|
||||
-- count red units in zone
|
||||
for idx, aGroup in pairs(allRed) do
|
||||
if Group.isExist(aGroup) then
|
||||
if cfxOwnedZones.fastEval then
|
||||
-- we only check first unit that is alive
|
||||
local theUnit = dcsCommon.getGroupUnit(aGroup)
|
||||
if theUnit and (not theUnit:inAir()) and theZone:unitInZone(theUnit) then
|
||||
if cfxOwnedZones.excludedTypes then
|
||||
-- special carve-out for exclduding some
|
||||
-- unit types to prevent them from capping
|
||||
local uType = theUnit:getTypeName()
|
||||
local forbidden = false
|
||||
for idx, aType in pairs(cfxOwnedZones.excludedTypes) do
|
||||
if uType == aType then
|
||||
forbidden = true
|
||||
else
|
||||
end
|
||||
end
|
||||
if not forbidden then
|
||||
theZone.numRed = theZone.numRed + aGroup:getSize()
|
||||
end
|
||||
else
|
||||
theZone.numRed = theZone.numRed + aGroup:getSize()
|
||||
end
|
||||
end
|
||||
else -- full eval
|
||||
local allUnits = aGroup:getUnits()
|
||||
for idy, theUnit in pairs(allUnits) do
|
||||
if (not theUnit:inAir()) and theZone:unitInZone(theUnit) then
|
||||
-- theZone.numRed = theZone.numRed + 1
|
||||
if not theZone.masterOwner then
|
||||
for idx, aGroup in pairs(allRed) do
|
||||
if Group.isExist(aGroup) then
|
||||
if cfxOwnedZones.fastEval then
|
||||
-- we only check first unit that is alive
|
||||
local theUnit = dcsCommon.getGroupUnit(aGroup)
|
||||
if theUnit and (not theUnit:inAir()) and theZone:unitInZone(theUnit) then
|
||||
if cfxOwnedZones.excludedTypes then
|
||||
-- special carve-out for exclduding some
|
||||
-- unit types to prevent them from capping
|
||||
local uType = theUnit:getTypeName()
|
||||
local forbidden = false
|
||||
for idx, aType in pairs(cfxOwnedZones.excludedTypes) do
|
||||
if uType == aType then forbidden = true end
|
||||
if uType == aType then
|
||||
forbidden = true
|
||||
else
|
||||
end
|
||||
end
|
||||
if not forbidden then
|
||||
theZone.numRed = theZone.numRed + aGroup:getSize()
|
||||
@ -356,48 +382,47 @@ function cfxOwnedZones.update()
|
||||
theZone.numRed = theZone.numRed + aGroup:getSize()
|
||||
end
|
||||
end
|
||||
else -- full eval
|
||||
local allUnits = aGroup:getUnits()
|
||||
for idy, theUnit in pairs(allUnits) do
|
||||
if (not theUnit:inAir()) and theZone:unitInZone(theUnit) then
|
||||
if cfxOwnedZones.excludedTypes then
|
||||
-- special carve-out for exclduding some
|
||||
-- unit types to prevent them from capping
|
||||
local uType = theUnit:getTypeName()
|
||||
local forbidden = false
|
||||
for idx, aType in pairs(cfxOwnedZones.excludedTypes) do
|
||||
if uType == aType then forbidden = true end
|
||||
end
|
||||
if not forbidden then
|
||||
theZone.numRed = theZone.numRed + aGroup:getSize()
|
||||
end
|
||||
else
|
||||
theZone.numRed = theZone.numRed + aGroup:getSize()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- count blue units
|
||||
for idx, aGroup in pairs(allBlue) do
|
||||
if Group.isExist(aGroup) then
|
||||
if cfxOwnedZones.fastEval then
|
||||
-- we only check first unit that is alive
|
||||
local theUnit = dcsCommon.getGroupUnit(aGroup)
|
||||
if theUnit and (not theUnit:inAir()) and theZone:unitInZone(theUnit) then
|
||||
if cfxOwnedZones.excludedTypes then
|
||||
-- special carve-out for exclduding some
|
||||
-- unit types to prevent them from capping
|
||||
local uType = theUnit:getTypeName()
|
||||
local forbidden = false
|
||||
for idx, aType in pairs(cfxOwnedZones.excludedTypes) do
|
||||
if uType == aType then
|
||||
forbidden = true
|
||||
else
|
||||
end
|
||||
end
|
||||
if not forbidden then
|
||||
theZone.numBlue = theZone.numBlue + aGroup:getSize()
|
||||
end
|
||||
else
|
||||
theZone.numBlue = theZone.numBlue + aGroup:getSize()
|
||||
end
|
||||
end
|
||||
else
|
||||
local allUnits = aGroup:getUnits()
|
||||
for idy, theUnit in pairs(allUnits) do
|
||||
if (not theUnit:inAir()) and theZone:unitInZone(theUnit) then
|
||||
-- theZone.numBlue = theZone.numBlue + 1
|
||||
-- count blue units
|
||||
for idx, aGroup in pairs(allBlue) do
|
||||
if Group.isExist(aGroup) then
|
||||
if cfxOwnedZones.fastEval then
|
||||
-- we only check first unit that is alive
|
||||
local theUnit = dcsCommon.getGroupUnit(aGroup)
|
||||
if theUnit and (not theUnit:inAir()) and theZone:unitInZone(theUnit) then
|
||||
if cfxOwnedZones.excludedTypes then
|
||||
-- special carve-out for exclduding some
|
||||
-- unit types to prevent them from capping
|
||||
local uType = theUnit:getTypeName()
|
||||
local forbidden = false
|
||||
for idx, aType in pairs(cfxOwnedZones.excludedTypes) do
|
||||
if uType == aType then forbidden = true end
|
||||
if uType == aType then
|
||||
forbidden = true
|
||||
else
|
||||
end
|
||||
end
|
||||
if not forbidden then
|
||||
theZone.numBlue = theZone.numBlue + aGroup:getSize()
|
||||
@ -406,28 +431,50 @@ function cfxOwnedZones.update()
|
||||
theZone.numBlue = theZone.numBlue + aGroup:getSize()
|
||||
end
|
||||
end
|
||||
else
|
||||
local allUnits = aGroup:getUnits()
|
||||
for idy, theUnit in pairs(allUnits) do
|
||||
if (not theUnit:inAir()) and theZone:unitInZone(theUnit) then
|
||||
if cfxOwnedZones.excludedTypes then
|
||||
-- special carve-out for exclduding some
|
||||
-- unit types to prevent them from capping
|
||||
local uType = theUnit:getTypeName()
|
||||
local forbidden = false
|
||||
for idx, aType in pairs(cfxOwnedZones.excludedTypes) do
|
||||
if uType == aType then forbidden = true end
|
||||
end
|
||||
if not forbidden then
|
||||
theZone.numBlue = theZone.numBlue + aGroup:getSize()
|
||||
end
|
||||
else
|
||||
theZone.numBlue = theZone.numBlue + aGroup:getSize()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if theZone.verbose then
|
||||
trigger.action.outText("+++owdZ: zone <" .. theZone.name .. ">: red inside: <" .. theZone.numRed .. ">, blue inside: <>" .. theZone.numBlue, 30)
|
||||
end
|
||||
else
|
||||
-- zone has master owner, no counting done
|
||||
end
|
||||
|
||||
if theZone.verbose then
|
||||
trigger.action.outText("+++owdZ: zone <" .. theZone.name .. ">: red inside: <" .. theZone.numRed .. ">, blue inside: <>" .. theZone.numBlue, 30)
|
||||
end
|
||||
|
||||
-- trigger.action.outText(theZone.name .. " blue: " .. theZone.numBlue .. " red " .. theZone.numRed, 30)
|
||||
local lastOwner = theZone.owner
|
||||
local newOwner = 0 -- neutral is default
|
||||
if theZone.unbeatable then -- Parker Lewis can't lose. Neither this zone.
|
||||
newOwner = lastOwner
|
||||
end
|
||||
|
||||
-- determine new owner
|
||||
if theZone.unbeatable then
|
||||
-- we do nothing
|
||||
-- we do nothing
|
||||
elseif theZone.masterOwner then
|
||||
-- inherit from my master
|
||||
newOwner = theZone.masterOwner.owner
|
||||
elseif theZone.numRed < 1 and theZone.numBlue < 1 then
|
||||
-- no troops here. Become neutral?
|
||||
if cfxOwnedZones.numKeep < 1 then
|
||||
if theZone.numKeep < 1 then
|
||||
newOwner = lastOwner -- keep it, else turns neutral
|
||||
else
|
||||
-- noone here, zone becomes neutral
|
||||
@ -435,9 +482,9 @@ function cfxOwnedZones.update()
|
||||
end
|
||||
elseif theZone.numRed < 1 then
|
||||
-- only blue here. enough to keep?
|
||||
if theZone.numBlue >= cfxOwnedZones.numCap then
|
||||
if theZone.numBlue >= theZone.numCap then
|
||||
newOwner = 2 -- blue owns it
|
||||
elseif lastOwner == 2 and theZone.numBlue >= cfxOwnedZones.numKeep then
|
||||
elseif lastOwner == 2 and theZone.numBlue >= theZone.numKeep then
|
||||
-- enough to keep if owned before
|
||||
newOwner = 2
|
||||
else
|
||||
@ -445,9 +492,9 @@ function cfxOwnedZones.update()
|
||||
end
|
||||
elseif theZone.numBlue < 1 then
|
||||
-- only red here. enough to keep?
|
||||
if theZone.numRed >= cfxOwnedZones.numCap then
|
||||
if theZone.numRed >= theZone.numCap then
|
||||
newOwner = 1
|
||||
elseif lastOwner == 1 and theZone.numRed >= cfxOwnedZones.numKeep then
|
||||
elseif lastOwner == 1 and theZone.numRed >= theZone.numKeep then
|
||||
newOwner = 1
|
||||
else
|
||||
newOwner = 0
|
||||
@ -459,18 +506,18 @@ function cfxOwnedZones.update()
|
||||
if cfxOwnedZones.easyContest then
|
||||
-- this zone is immediately contested
|
||||
newOwner = 0 -- just to be explicit
|
||||
elseif cfxOwnedZones.numKeep < 1 then
|
||||
elseif theZone.numKeep < 1 then
|
||||
-- old owner keeps it until none left
|
||||
newOwner = lastOwner
|
||||
else
|
||||
if lastOwner == 1 then
|
||||
-- red can keep it as long as enough units here
|
||||
if theZone.numRed >= cfxOwnedZones.numKeep then
|
||||
if theZone.numRed >= theZone.numKeep then
|
||||
newOwner = 1
|
||||
end -- else 0
|
||||
elseif lastOwner == 2 then
|
||||
-- blue can keep it if enough units here
|
||||
if theZone.numBlue >= cfxOwnedZones.numKeep then
|
||||
if theZone.numBlue >= theZone.numKeep then
|
||||
newOwner = 2
|
||||
end -- else 0
|
||||
else -- stay 0
|
||||
@ -525,14 +572,14 @@ function cfxOwnedZones.update()
|
||||
|
||||
-- see if one side owns all and bang the flags if requiredLibs
|
||||
if cfxOwnedZones.allBlue and not cfxOwnedZones.hasAllBlue then
|
||||
if cfxOwnedZones.sideOwnsAll(2) then
|
||||
if cfxOwnedZones.sideOwnsAll(2) then -- ignores other owner-managed zones
|
||||
cfxZones.pollFlag(cfxOwnedZones.allBlue, cfxOwnedZones.method, cfxOwnedZones)
|
||||
cfxOwnedZones.hasAllBlue = true
|
||||
end
|
||||
end
|
||||
|
||||
if cfxOwnedZones.allRed and not cfxOwnedZones.hasAllRed then
|
||||
if cfxOwnedZones.sideOwnsAll(1) then
|
||||
if cfxOwnedZones.sideOwnsAll(1) then -- ignores other managed owner zones
|
||||
cfxZones.pollFlag(cfxOwnedZones.allRed, cfxOwnedZones.method, cfxOwnedZones)
|
||||
cfxOwnedZones.hasAllRed = true
|
||||
end
|
||||
@ -540,8 +587,10 @@ function cfxOwnedZones.update()
|
||||
|
||||
end
|
||||
|
||||
function cfxOwnedZones.sideOwnsAll(theSide)
|
||||
for key, aZone in pairs(cfxOwnedZones.zones) do
|
||||
function cfxOwnedZones.sideOwnsAll(theSide, useAllManaged)
|
||||
local themAll = cfxOwnedZones.zones
|
||||
if useAllManaged then themAll = cfxZones.allManagedOwnedZones end
|
||||
for key, aZone in pairs(themAll) do
|
||||
if aZone.owner ~= theSide then
|
||||
return false
|
||||
end
|
||||
@ -550,27 +599,44 @@ function cfxOwnedZones.sideOwnsAll(theSide)
|
||||
return true
|
||||
end
|
||||
|
||||
function cfxOwnedZones.hasOwnedZones()
|
||||
for idx, zone in pairs (cfxOwnedZones.zones) do
|
||||
return true -- even the first returns true
|
||||
end
|
||||
-- no owned zones
|
||||
return false
|
||||
end
|
||||
|
||||
-- getting closest owned zones etc
|
||||
-- required for groundTroops and factory attackers
|
||||
-- methods provided only for other modules (e.g. cfxGroundTroops or
|
||||
-- factoryZone
|
||||
--
|
||||
|
||||
function cfxOwnedZones.gatherAllManagedOwnedZones()
|
||||
-- we collect all zones with 'owner'
|
||||
local all = {}
|
||||
local pZones = cfxZones.zonesWithProperty("owner")
|
||||
for k, theZone in pairs(pZones) do
|
||||
all[theZone.name] = theZone
|
||||
end
|
||||
|
||||
-- and add all zones with airfield
|
||||
local pZones = cfxZones.zonesWithProperty("airfield")
|
||||
for k, theZone in pairs(pZones) do
|
||||
all[theZone.name] = theZone
|
||||
end
|
||||
-- and all zones with 'FARP'
|
||||
local pZones = cfxZones.zonesWithProperty("FARP")
|
||||
for k, theZone in pairs(pZones) do
|
||||
all[theZone.name] = theZone
|
||||
end
|
||||
|
||||
-- and all with ownAll?
|
||||
-- not yet
|
||||
cfxOwnedZones.allManagedOwnedZones = all
|
||||
end
|
||||
|
||||
-- collect zones can filter owned zones.
|
||||
-- by default it filters all zones that are in water
|
||||
-- includes all managed-owner zones
|
||||
function cfxOwnedZones.collectZones(mode)
|
||||
if not mode then mode = "land" end
|
||||
if mode == "land" then
|
||||
local landZones = {}
|
||||
for idx, theZone in pairs(cfxOwnedZones.zones) do
|
||||
for idx, theZone in pairs(cfxOwnedZones.allManagedOwnedZones) do
|
||||
p = theZone:getPoint()
|
||||
p.y = p.z
|
||||
local surfType = land.getSurfaceType(p)
|
||||
@ -580,64 +646,12 @@ function cfxOwnedZones.collectZones(mode)
|
||||
end
|
||||
end
|
||||
return landZones
|
||||
else
|
||||
return cfxOwnedZones.allManagedOwnedZones
|
||||
end
|
||||
|
||||
-- return all zones
|
||||
return cfxOwnedZones.zones
|
||||
--if not mode then mode = "OWNED" end
|
||||
-- Note: since cfxGroundTroops currently simply uses owner flag
|
||||
-- we cannot migrate to a differentiation between factory and
|
||||
-- owned. All produced attackers always attack owned zones.
|
||||
end
|
||||
|
||||
function cfxOwnedZones.getEnemyZonesFor(aCoalition)
|
||||
local enemyZones = {}
|
||||
local allZones = cfxOwnedZones.collectZones()
|
||||
local ourEnemy = dcsCommon.getEnemyCoalitionFor(aCoalition)
|
||||
for zKey, aZone in pairs(allZones) do
|
||||
if aZone.owner == ourEnemy then -- only check enemy owned zones
|
||||
-- note: will include untargetable zones
|
||||
table.insert(enemyZones, aZone)
|
||||
end
|
||||
end
|
||||
return enemyZones
|
||||
end
|
||||
|
||||
function cfxOwnedZones.getNearestOwnedZoneToPoint(aPoint)
|
||||
local shortestDist = math.huge
|
||||
local closestZone = nil
|
||||
local allZones = cfxOwnedZones.collectZones()
|
||||
|
||||
for zKey, aZone in pairs(allZones) do
|
||||
local zPoint = aZone:getPoint()
|
||||
currDist = dcsCommon.dist(zPoint, aPoint)
|
||||
if aZone.untargetable ~= true and
|
||||
currDist < shortestDist then
|
||||
shortestDist = currDist
|
||||
closestZone = aZone
|
||||
end
|
||||
end
|
||||
|
||||
return closestZone, shortestDist
|
||||
end
|
||||
|
||||
function cfxOwnedZones.getNearestOwnedZone(theZone)
|
||||
local shortestDist = math.huge
|
||||
local closestZone = nil
|
||||
local aPoint = theZone:getPoint()
|
||||
local allZones = cfxOwnedZones.collectZones()
|
||||
for zKey, aZone in pairs(allZones) do
|
||||
local zPoint = aZone:getPoint()
|
||||
currDist = dcsCommon.dist(zPoint, aPoint)
|
||||
if aZone.untargetable ~= true and currDist < shortestDist then
|
||||
shortestDist = currDist
|
||||
closestZone = aZone
|
||||
end
|
||||
end
|
||||
|
||||
return closestZone, shortestDist
|
||||
end
|
||||
end
|
||||
|
||||
-- getNearestEnemyOwnedZone invoked by cfxGroundTroops
|
||||
function cfxOwnedZones.getNearestEnemyOwnedZone(theZone, targetNeutral)
|
||||
if not targetNeutral then targetNeutral = false else targetNeutral = true end
|
||||
local shortestDist = math.huge
|
||||
@ -650,56 +664,20 @@ function cfxOwnedZones.getNearestEnemyOwnedZone(theZone, targetNeutral)
|
||||
for zKey, aZone in pairs(allZones) do
|
||||
if targetNeutral then
|
||||
-- return all zones that do not belong to us
|
||||
if aZone.owner ~= theZone.owner then
|
||||
if aZone.owner ~= theZone.owner and not aZone.untargetable then
|
||||
local aPoint = aZone:getPoint()
|
||||
currDist = dcsCommon.dist(aPoint, zPoint)
|
||||
if aZone.untargetable ~= true and currDist < shortestDist then
|
||||
if currDist < shortestDist then
|
||||
shortestDist = currDist
|
||||
closestZone = aZone
|
||||
end
|
||||
end
|
||||
else
|
||||
-- return zones that are taken by the Enenmy
|
||||
if aZone.owner == ourEnemy then -- only check own zones
|
||||
if aZone.owner == ourEnemy and not aZone.untargetable then -- only check own zones
|
||||
local aPoint = aZone:getPoint()
|
||||
currDist = dcsCommon.dist(zPoint, aPoint)
|
||||
if aZone.untargetable ~= true and currDist < shortestDist then
|
||||
shortestDist = currDist
|
||||
closestZone = aZone
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return closestZone, shortestDist
|
||||
end
|
||||
|
||||
function cfxOwnedZones.getNearestFriendlyZone(theZone, targetNeutral)
|
||||
if not targetNeutral then targetNeutral = false else targetNeutral = true end
|
||||
local shortestDist = math.huge
|
||||
local closestZone = nil
|
||||
local ourEnemy = dcsCommon.getEnemyCoalitionFor(theZone.owner)
|
||||
if not ourEnemy then return nil end -- we called for a neutral zone. they have no enemies nor friends, all zones would be legal.
|
||||
local zPoint = theZone:getPoint()
|
||||
local allZones = cfxOwnedZones.collectZones()
|
||||
|
||||
for zKey, aZone in pairs(allZones) do
|
||||
if targetNeutral then
|
||||
-- target all zones that do not belong to the enemy
|
||||
if aZone.owner ~= ourEnemy then
|
||||
local aPoint = aZone:getPoint()
|
||||
currDist = dcsCommon.dist(zPoint, aPoint)
|
||||
if aZone.untargetable ~= true and currDist < shortestDist then
|
||||
shortestDist = currDist
|
||||
closestZone = aZone
|
||||
end
|
||||
end
|
||||
else
|
||||
-- only target zones that are taken by us
|
||||
if aZone.owner == theZone.owner then -- only check own zones
|
||||
local aPoint = aZone:getPoint()
|
||||
currDist = dcsCommon.dist(zPoint, aPoint)
|
||||
if aZone.untargetable ~= true and currDist < shortestDist then
|
||||
if currDist < shortestDist then
|
||||
shortestDist = currDist
|
||||
closestZone = aZone
|
||||
end
|
||||
@ -710,11 +688,13 @@ function cfxOwnedZones.getNearestFriendlyZone(theZone, targetNeutral)
|
||||
return closestZone, shortestDist
|
||||
end
|
||||
|
||||
-- invoked by factory
|
||||
function cfxOwnedZones.enemiesRemaining(aZone)
|
||||
if cfxOwnedZones.getNearestEnemyOwnedZone(aZone) then return true end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- load / save data
|
||||
--
|
||||
@ -796,6 +776,9 @@ function cfxOwnedZones.readConfigZone(theZone)
|
||||
cfxOwnedZones.name = "cfxOwnedZones" -- just in case, so we can access with cfxZones
|
||||
cfxOwnedZones.verbose = theZone.verbose -- cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
|
||||
cfxOwnedZones.announcer = theZone:getBoolFromZoneProperty("announcer", true)
|
||||
if theZone:hasProperty("announce") then
|
||||
cfxZones.announcer = theZone:getBoolFromZoneProperty("announce", true)
|
||||
end
|
||||
|
||||
if theZone:hasProperty("r!") then
|
||||
cfxOwnedZones.redTriggerFlag = theZone:getStringFromZoneProperty("r!", "*<cfxnone>")
|
||||
@ -892,6 +875,9 @@ function cfxOwnedZones.init()
|
||||
cfxOwnedZones.addOwnedZone(aZone)
|
||||
end
|
||||
|
||||
-- gather ALL managed owner zones
|
||||
cfxOwnedZones.gatherAllManagedOwnedZones()
|
||||
|
||||
if persistence then
|
||||
-- sign up for persistence
|
||||
callbacks = {}
|
||||
@ -917,6 +903,11 @@ end
|
||||
masterOwner input for zones, overrides all else when not neutral
|
||||
|
||||
dont count zones that cant be conquered for allBlue/allRed
|
||||
|
||||
|
||||
noRed, noBlue options to prevent a zone to become that color
|
||||
|
||||
black color for dead. dead status to be defined. dead can't be capped and do not attact
|
||||
|
||||
|
||||
--]]--
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
cfxPlayerScore = {}
|
||||
cfxPlayerScore.version = "3.1.0"
|
||||
cfxPlayerScore.version = "3.2.0"
|
||||
cfxPlayerScore.name = "cfxPlayerScore" -- compatibility with flag bangers
|
||||
cfxPlayerScore.badSound = "Death BRASS.wav"
|
||||
cfxPlayerScore.scoreSound = "Quest Snare 3.wav"
|
||||
@ -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
|
||||
--]]--
|
||||
|
||||
cfxPlayerScore.requiredLibs = {
|
||||
@ -333,10 +333,16 @@ function cfxPlayerScore.updateScoreForPlayerImmediate(playerName, score)
|
||||
-- only on positive score
|
||||
if (score > 0) and pFaction > 0 then
|
||||
cfxPlayerScore.coalitionScore[pFaction] = cfxPlayerScore.coalitionScore[pFaction] + score
|
||||
if bank and bank.addFunds then
|
||||
bank.addFunds(pFaction, cfxPlayerScore.score2finance * score)
|
||||
end
|
||||
end
|
||||
else
|
||||
if pFaction > 0 then
|
||||
cfxPlayerScore.coalitionScore[pFaction] = cfxPlayerScore.coalitionScore[pFaction] + score
|
||||
if bank and bank.addFunds then
|
||||
bank.addFunds(pFaction, cfxPlayerScore.score2finance * score)
|
||||
end
|
||||
end
|
||||
end
|
||||
return thePlayerScore.score
|
||||
@ -1015,6 +1021,9 @@ function cfxPlayerScore.scheduledAward(args)
|
||||
theScore.score = theScore.score + theScore.scoreaccu
|
||||
desc = desc .. " score: " .. theScore.scoreaccu .. " for a new total of " .. theScore.score .. "\n"
|
||||
cfxPlayerScore.coalitionScore[playerSide] = cfxPlayerScore.coalitionScore[playerSide] + theScore.scoreaccu
|
||||
if bank and bank.addFunds then
|
||||
bank.addFunds(playerSide, cfxPlayerScore.score2finance * theScore.scoreaccu)
|
||||
end
|
||||
theScore.scoreaccu = 0
|
||||
hasAward = true
|
||||
end
|
||||
@ -1202,6 +1211,8 @@ function cfxPlayerScore.readConfigZone(theZone)
|
||||
if theZone:hasProperty("sharedData") then
|
||||
cfxPlayerScore.sharedData = theZone:getStringFromZoneProperty("sharedData", "cfxNameMissing")
|
||||
end
|
||||
|
||||
cfxPlayerScore.score2finance = theZone:getNumberFromZoneProperty("score2finance", 1) -- factor to convert points to bank finance
|
||||
end
|
||||
|
||||
--
|
||||
@ -1349,6 +1360,10 @@ 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)
|
||||
@ -1365,6 +1380,9 @@ 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 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)
|
||||
|
||||
@ -286,7 +286,11 @@ end
|
||||
|
||||
stopGap.kicks = {}
|
||||
function stopGap.kickplayer(args)
|
||||
if not stopGap.kickTheDead then return end
|
||||
if not stopGap.kickTheDead then
|
||||
-- trigger.action.outText("+++sg: Let em rest, no kick", 30)
|
||||
return
|
||||
end
|
||||
-- trigger.action.outText("Kick'em while they are down!", 30)
|
||||
local pName = args
|
||||
for i,slot in pairs(net.get_player_list()) do
|
||||
local nn = net.get_name(slot)
|
||||
|
||||
@ -256,7 +256,11 @@ function stopGap:onEvent(event)
|
||||
-- is now slotted into
|
||||
trigger.action.setUserFlag("SG"..gName, 0)
|
||||
end
|
||||
if id == 6 then -- eject
|
||||
-- trigger.action.outText("+++SG: not handled: Eject Eject Eject!", 30)
|
||||
end
|
||||
if (id == 9) or (id == 30) or (id == 5) then -- dead, lost, crash
|
||||
-- trigger.action.outText("+++sg: event <" .. id .. ">, handing off to kicker", 30)
|
||||
local pName = theUnit:getPlayerName()
|
||||
timer.scheduleFunction(stopGap.kickplayer, pName, timer.getTime() + 1)
|
||||
end
|
||||
@ -265,6 +269,7 @@ end
|
||||
|
||||
stopGap.kicks = {}
|
||||
function stopGap.kickplayer(args)
|
||||
-- trigger.action.outText("+++sg: enter kicker!", 30)
|
||||
if not stopGap.kickTheDead then return end
|
||||
local pName = args
|
||||
for i,slot in pairs(net.get_player_list()) do
|
||||
|
||||
173
modules/xpStrat.lua
Normal file
173
modules/xpStrat.lua
Normal file
@ -0,0 +1,173 @@
|
||||
xpStrat = {}
|
||||
-- AI strategy module for expansion.
|
||||
xpStrat.version = "0.0.0"
|
||||
xpStrat.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
"cfxZones", -- Zones, of course
|
||||
"milHelo", -- for helo attack and capture missions
|
||||
}
|
||||
xpStrat.zones = {} -- all the zones that interest me
|
||||
xpStrat.AI = {} -- red, blue -- if true, that side has pure ai
|
||||
|
||||
function xpStrat.addXPZone(theZone)
|
||||
xpStrat.zones[theZone.name] = theZone
|
||||
end
|
||||
|
||||
--
|
||||
-- Strategy
|
||||
--
|
||||
function xpStrat.fullAIStrategy(coa, cname)
|
||||
if xpStrat.verbose then
|
||||
trigger.action.outText("FULL AI Strategy for (" .. coa .. "/" .. cname .. ")", 30)
|
||||
end
|
||||
end
|
||||
|
||||
function xpStrat.getMilHSource(coa, msnType, nearZone)
|
||||
if not msnType then
|
||||
msnType = dcsCommon.pickRandom(milHelo.missionTypes)
|
||||
elseif msnType == "*" then
|
||||
msnType = nil -- get all.
|
||||
end
|
||||
|
||||
-- get all sources for coa and msnTypes
|
||||
local sources = milHelo.getMilSources(coa, msnType)
|
||||
if #sources < 1 then
|
||||
trigger.action.outText("Strat: no sources found for <" .. msnType .. "> helo mission", 30)
|
||||
return nil
|
||||
end
|
||||
|
||||
local theSource = nil
|
||||
if nearZone then
|
||||
theSource = nearZone:getClosestZone(sources)
|
||||
else
|
||||
-- pick one by random
|
||||
theSource = dcsCommon.pickRandom(sources)
|
||||
end
|
||||
return theSource
|
||||
end
|
||||
|
||||
function xpStrat.createHeloMission(coa, msnType, theSource, theTarget) -- msnType = "*" means any, nil means pick random
|
||||
if not theSource then
|
||||
theSource = xpStrat.getMilHSource(coa, msnType)
|
||||
end
|
||||
if not theSource then
|
||||
trigger.action.outText("Strat: cannot find coa <" .. coa .. "> source for <" .. msnType .. "> mission", 30)
|
||||
return nil
|
||||
end
|
||||
msnType = theSource.msnType
|
||||
|
||||
-- now gather all destinations
|
||||
if not theTarget then
|
||||
local targets = milHelo.getMilTargets(coa)
|
||||
if #targets < 1 then
|
||||
trigger.action.outText("Strat: no destinations for side " .. side, 30)
|
||||
return nil
|
||||
end
|
||||
|
||||
-- TODO: choose nearest target to source
|
||||
-- and prefer neutral
|
||||
theTarget = theSource:getClosestZone(targets)--targets[1]
|
||||
end
|
||||
|
||||
-- if we get here, we have a source and target
|
||||
|
||||
trigger.action.outText("Strat: identified Coa <" .. coa .. "> - Starting <" .. msnType .. "> mission from <" .. theSource.name .. "> to <" .. theTarget.name .. ">)", 30)
|
||||
|
||||
return theSource, theTarget, msnType
|
||||
end
|
||||
|
||||
function xpStrat.playerAIStrategy(coa, cname)
|
||||
if xpStrat.verbose then
|
||||
trigger.action.outText("Player-assisted AI Strategy for (" .. coa .. "/" .. cname .. ")", 30)
|
||||
end
|
||||
|
||||
-- strategy in general (ha!, pun)
|
||||
-- check which missions are done first
|
||||
-- select one mission unless still running and initiate own support flights
|
||||
-- select one aggressive flight and start it, no matter what
|
||||
end
|
||||
|
||||
function xpStrat.update()
|
||||
-- schedule next round
|
||||
timer.scheduleFunction(xpStrat.update, {}, timer.getTime() + xpStrat.interval)
|
||||
|
||||
local sides = {"red", "blue"}
|
||||
for idx, sideName in pairs(sides) do
|
||||
local coa = 1
|
||||
if sideName == "blue" then coa = 2 end
|
||||
|
||||
if xpStrat.AI[sideName] then
|
||||
xpStrat.fullAIStrategy(coa, sideName)
|
||||
else
|
||||
xpStrat.playerAIStrategy(coa, sideName)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
function xpStrat.readConfigZone()
|
||||
local theZone = cfxZones.getZoneByName("expansionConfig")
|
||||
if not theZone then
|
||||
theZone = cfxZones.createSimpleZone("expansionConfig")
|
||||
end
|
||||
xpStrat.redAI = theZone:getBoolFromZoneProperty("redAI", true) -- is red player or AI?
|
||||
xpStrat.AI["red"] = xpStrat.redAI
|
||||
xpStrat.blueAI = theZone:getBoolFromZoneProperty("blueAI", true) -- is red player or AI?
|
||||
xpStrat.AI["blue"] = xpStrat.blueAI
|
||||
|
||||
xpStrat.interval = theZone:getNumberFromZoneProperty("interval", 600)
|
||||
xpStrat.difficulty = theZone:getNumberFromZoneProperty("difficulty", 1)
|
||||
|
||||
xpStrat.verbose = theZone.verbose
|
||||
end
|
||||
|
||||
|
||||
function xpStrat.init()
|
||||
-- gather data etc
|
||||
|
||||
trigger.action.outText("'Expansion' Core v" .. xpStrat.version .. " (" .. dcsCommon.getMapName() .. ") started.", 30)
|
||||
local msg = ""
|
||||
if xpStrat.redAI then msg = msg .. "\nRed side controlled by AI General"
|
||||
else msg = msg .. "\nRed side controlled by Player-Assisted General" end
|
||||
if xpStrat.blueAI then msg = msg .. "\nBlue side controlled by AI General"
|
||||
else msg = msg .. "\nBlue side controlled by Player-Assisted General" end
|
||||
msg = msg .. "\ndifficulty level set to " .. xpStrat.difficulty
|
||||
msg = msg .. "\n"
|
||||
trigger.action.outText(msg, 30)
|
||||
|
||||
-- schedule first round of AI in 10 seconds
|
||||
timer.scheduleFunction(xpStrat.update, {}, timer.getTime() + 10)
|
||||
end
|
||||
|
||||
function xpStrat.start()
|
||||
-- lib check
|
||||
if not dcsCommon.libCheck then
|
||||
trigger.action.outText("xpStrat requires dcsCommon", 30)
|
||||
return false
|
||||
end
|
||||
if not dcsCommon.libCheck("xpStrat", xpStrat.requiredLibs) then
|
||||
return false
|
||||
end
|
||||
|
||||
-- read config
|
||||
xpStrat.readConfigZone()
|
||||
|
||||
-- read income zones
|
||||
--[[-- local attrZones = cfxZones.getZonesWithAttributeNamed("income")
|
||||
for k, aZone in pairs(attrZones) do
|
||||
income.createIncomeWithZone(aZone) -- process attributes
|
||||
income.addIncomeZone(aZone) -- add to list
|
||||
end
|
||||
--]]--
|
||||
-- schedule init for 5 seconds after mission start
|
||||
timer.scheduleFunction(xpStrat.init, {}, timer.getTime() + 5)
|
||||
|
||||
trigger.action.outText("xpStrat v" .. xpStrat.version .. " started.", 30)
|
||||
return true
|
||||
end
|
||||
|
||||
if not xpStrat.start() then
|
||||
trigger.action.outText("xpStrat aborted: missing libraries", 30)
|
||||
xpStrat = nil
|
||||
end
|
||||
Loading…
x
Reference in New Issue
Block a user