DML/modules/FARPZones.lua
2022-01-19 20:55:23 +01:00

455 lines
13 KiB
Lua

FARPZones = {}
FARPZones.version = "1.0.2"
--[[--
Version History
1.0.0 - Initial Version
1.0.1 - support "none" as defender types
- default types for defenders to none
1.0.2 - hiddenRed, hiddenBlue, hiddenGrey
--]]--
FARPZones.requiredLibs = {
"dcsCommon", -- common is of course needed for everything
-- pretty stupid to check for this since we
-- need common to invoke the check, but anyway
"cfxZones", -- Zones, of course
-- "cfxCommander", -- to make troops do stuff
-- "cfxGroundTroops", -- generic when dropping troops
}
-- *** DOES NOT EXTEND ZONES, USES OWN STRUCT ***
-- *** SETS ZONE.OWNER IF PRESENT, POSSIBLE CONFLICT
-- *** WITH OWNED ZONES
-- *** DOES NOT WORK WITH AIRFIELDS!
-- *** USE OWNED ZONES AND SPAWNZONES FOR AIRFIELDS
--[[--
Functioning, capturable FARPS with all services. To use,
place a FARP on a map, an a Zone that contains the FARP,
then add the following attributes
Z O N E A T T R I B U T E S
- FARP <anything>: indicate that this is a FARP ZONE. Must
contain at least one FARP inside the zone. The first FARP
found will become main FARP to determine ownership
- rPhiHDef - r, phi, heading separated by coma, eg
<120, 245, 0> that defines radius and Phi (in degrees) for
the center position, and heading for the group of defenders
for this FARP. r, phi relative to ZONE center, not FARP
- redDefenders - Type Strings for defender vehicles, coma
separated. e.g. "BTR-80,BTR-80" will create two BTR-80
vehicles when owned by RED
- blueDefenders - Type Strings for defender vehicles, coma
separated when owned by blue
- formation - formation for defenders, e.g. "grid".
optional, defaults to "circle-out". Span raidus is 100m
- rPhiHRes - r, phi and H separated by coma to determine
the location and heading of FARP Resopurces (services).
They always auto-gen all vehicles for all services. They
always spawn as "line_V" through center with radius 50 meters
Optional. Will spawn around zone center else. Remember that
all these vehicles MUST be within 150m of FARP Center
- hidden - if true, no circle on map, else (default) visible
to all with owner color
--]]--
FARPZones.resourceTypes = {
"M978 HEMTT Tanker", -- BLUE fuel
"M 818", -- BLUE ammo
"M 818", -- Blue Power (repair???)
"Hummer", -- BLUE ATC
"ATMZ-5", -- RED refuel
"KAMAZ Truck", -- rearming
"SKP-11", -- communication
"ZiL-131 APA-80", -- Power
}
FARPZones.spinUpDelay = 30 -- seconds until FARP becomes operational after capture
FARPZones.allFARPZones = {}
FARPZones.startingUp = false
-- FARP ZONE ACCESS
function FARPZones.addFARPZone(aFARP)
FARPZones.allFARPZones[aFARP.zone] = aFARP
end
function FARPZones.removeFARPZone(aFARP)
FARPZones.allFARPZones[aFARP.zone] = nil
end
function FARPZones.getFARPForZone(aZone)
return FARPZones.allFARPZones[aZone]
end
function FARPZones.getFARPZoneForFARP(aFarp)
-- find the first FARP zone that associates with
-- aFARP (an airField)
for idx, aFarpZone in pairs(FARPZones.allFARPZones) do
local associatedFarps = aFarpZone.myFarps
for itoo, assocF in pairs(associatedFarps) do
if assocF == aFarp then return aFarpZone end
end
end
return nil
end
function FARPZones.createFARPFromZone(aZone)
-- WARNING: WILL SET ZONE.OWNER
local theFarp = {}
theFarp.zone = aZone
theFarp.name = aZone.name
-- find the FARPS that belong to this zone
local thePoint = aZone.point
local mapFarps = dcsCommon.getAirbasesInRangeOfPoint(
thePoint,
aZone.radius,
1 -- FARPS = Helipads
)
-- only #1 is significant for owner
theFarp.myFarps = mapFarps
theFarp.owner = 0 -- start with neutral
aZone.owner = 0
if #mapFarps == 0 then
trigger.action.outText("***Farp Zones: no FARP found for zone " .. aZone.name, 30)
else
for idx, aFarp in pairs(mapFarps) do
-- trigger.action.outText("Associated FARP " .. aFarp:getName() .. " with FARP Zone " .. aZone.name, 30)
end
theFarp.mainFarp = theFarp.myFarps[1]
theFarp.point = theFarp.mainFarp:getPoint() -- this is FARP, not zone!!!
theFarp.owner = theFarp.mainFarp:getCoalition()
aZone.owner = theFarp.owner
end
-- get r and phi for defenders
local rPhi = cfxZones.getVectorFromZoneProperty(
aZone,
"rPhiHDef",
3)
-- trigger.action.outText("*** DEF rPhi are " .. rPhi[1] .. " and " .. rPhi[2], 30)
-- get r and phi for facilities
-- create a new defenderzone for this
local r = rPhi[1]
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)
theFarp.defZone = cfxZones.createSimpleZone(aZone.name .. "-Def", {x=dx, y = 0, z=dz}, 100)
theFarp.defHeading = rPhi[3]
rPhi = cfxZones.getVectorFromZoneProperty(
aZone,
"rPhiHRes",
3) -- optional, will reterurn {0,0} else
-- trigger.action.outText("*** RES rPhi are " .. rPhi[1] .. " and " .. rPhi[2] .. " heading " .. rPhi[3], 30)
r = rPhi[1]
phi = rPhi[2] * 0.0174533 -- 1 degree = 0.0174533 rad
dx = aZone.point.x + r * math.cos(phi)
dz = aZone.point.z + r * math.sin(phi)
theFarp.resZone = cfxZones.createSimpleZone(aZone.name .. "-Res", {x=dx, y = 0, z=dz}, 50)
theFarp.resHeading = rPhi[3]
-- get redDefenders - defenders produced when red owned
theFarp.redDefenders = cfxZones.getStringFromZoneProperty(aZone, "redDefenders", "none")
-- get blueDefenders - defenders produced when blue owned
theFarp.blueDefenders = cfxZones.getStringFromZoneProperty(aZone, "blueDefenders", "none")
-- get formation for defenders
theFarp.formation = cfxZones.getStringFromZoneProperty(aZone, "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")
return theFarp
end
--[[--
function FARPZones.drawFarp(theFarp)
local theZone = theFarp.zone
local theOwner = theFarp.owner
FARPZones.drawZoneInMap(theZone, theOwner)
end
--]]--
function FARPZones.drawFARPCircleInMap(theFarp)
if not theFarp then return end
if theFarp.zone and theFarp.zone.markID then
-- remove previous mark
trigger.action.removeMark(theFarp.zone.markID)
theFarp.zone.markID = nil
end
if theFarp.hideRed and
theFarp.owner == 1 then
-- hide only when red
return
end
if theFarp.hideBlue and
theFarp.owner == 2 then
-- hide only when blue
return
end
if theFarp.hideGrey and
theFarp.owner == 0 then
-- hide only when blue
return
end
if theFarp.hidden then
return
end
-- owner is 0 = neutral, 1 = red, 2 = blue
-- will save markID in zone's markID
-- should be able to only show owned
-- draws 2km radius circle around main (first) FARP
local aZone = theFarp.zone
local thePoint = theFarp.point
local owner = theFarp.owner
local lineColor = {1.0, 0, 0, 1.0} -- red
local fillColor = {1.0, 0, 0, 0.2} -- red
if owner == 2 then
lineColor = {0.0, 0, 1.0, 1.0}
fillColor = {0.0, 0, 1.0, 0.2}
elseif owner == 0 then
lineColor = {0.8, 0.8, 0.8, 1.0}
fillColor = {0.8, 0.8, 0.8, 0.2}
end
local theShape = 2 -- circle
local markID = dcsCommon.numberUUID()
trigger.action.circleToAll(-1, markID, thePoint, 2000, lineColor, fillColor, 1, true, "")
aZone.markID = markID
end
function FARPZones.drawZoneInMap(aZone, owner)
-- owner is 0 = neutral, 1 = red, 2 = blue
-- will save markID in zone's markID
-- should be moved to cfxZones
-- should be able to only show owned
if aZone.markID then
trigger.action.removeMark(aZone.markID)
end
local lineColor = {1.0, 0, 0, 1.0} -- red
local fillColor = {1.0, 0, 0, 0.2} -- red
if owner == 2 then
lineColor = {0.0, 0, 1.0, 1.0}
fillColor = {0.0, 0, 1.0, 0.2}
elseif owner == 0 then
lineColor = {0.8, 0.8, 0.8, 1.0}
fillColor = {0.8, 0.8, 0.8, 0.2}
end
local theShape = 2 -- circle
local markID = dcsCommon.numberUUID()
trigger.action.circleToAll(-1, markID, aZone.point, aZone.radius, lineColor, fillColor, 1, true, "")
aZone.markID = markID
end
function FARPZones.scheduedProduction(args)
-- args contain [aFarp, owner]
-- make sure that owner is still the same
-- and if so, branch to produce vehicles
-- ***write-though to zone.owner
local theFarp = args[1]
local owner = args[2]
-- make sure the farp wasn't conquered in the meantime
if owner == theFarp.mainFarp:getCoalition() then
-- ok, still same owner , go ahead and spawn
theFarp.owner = owner
theFarp.zone.owner = owner
FARPZones.produceVehicles(theFarp)
trigger.action.outTextForCoalition(theFarp.owner, "FARP " .. theFarp.name .. " has become operational!", 30)
trigger.action.outSoundForCoalition(theFarp.owner, "Quest Snare 3.wav")
end
end
function FARPZones.produceVehicles(theFarp)
-- first, remove anything that may still be there
if theFarp.defenders and theFarp.defenders:isExist() then
theFarp.defenders:destroy()
end
if theFarp.resources and theFarp.resources:isExist() then
theFarp.resources:destroy()
end
theFarp.defenders = nil
theFarp.resources = nil
-- spawn defenders
local owner = theFarp.owner -- coalition
local theTypes = theFarp.redDefenders
if owner == 2 then theTypes = theFarp.blueDefenders end
local unitTypes = dcsCommon.splitString(theTypes, ",")
if #unitTypes < 1 then
table.insert(unitTypes, "Soldier M4") -- make it one m4 trooper as fallback
end
-- trigger.action.outText("*** ENTER produce vehicles, will produce " .. theTypes , 30)
local theCoalition = theFarp.owner
if theTypes ~= "none" then
local theGroup = cfxZones.createGroundUnitsInZoneForCoalition (
theCoalition,
theFarp.name .. "-D" .. theFarp.count, -- must be unique
theFarp.defZone,
unitTypes,
theFarp.formation,
theFarp.defHeading)
-- we do not add these troops to ground troop management
theFarp.defenders = theGroup -- but we retain a handle just in case
end
unitTypes = FARPZones.resourceTypes
local theGroup = cfxZones.createGroundUnitsInZoneForCoalition (
theCoalition,
theFarp.name .. "-R" .. theFarp.count, -- must be unique
theFarp.resZone,
unitTypes,
"line_v",
theFarp.resHeading)
theFarp.resources = theGroup
-- update unique counter
theFarp.count = theFarp.count + 1
end
--
-- EVENT PROCESSING
--
FARPZones.myEvents = {10, } -- 10: S_EVENT_BASE_CAPTURED
function FARPZones.isInteresting(eventID)
-- return true if we are interested in this event, false else
for key, evType in pairs(FARPZones.myEvents) do
if evType == eventID then return true end
end
return false
end
function FARPZones.preProcessor(event)
if not event then return false end
if not event.place then return false end
return FARPZones.isInteresting(event.id)
end
function FARPZones.postProcessor(event)
-- don't do anything
end
function FARPZones.somethingHappened(event)
-- *** writes to zone.owner
local theUnit = event.initiator
local ID = event.id
trigger.action.outText("FZ: something happened", 30)
local aFarp = event.place
local zonedFarp = FARPZones.getFARPZoneForFARP(aFarp)
if not zonedFarp then
trigger.action.outText("Hand change, NOT INTERESTING", 30)
return
end
local newOwner = aFarp:getCoalition()
local blueRed = "Red"
if newOwner == 2 then blueRed = "Blue" end
trigger.action.outText("FARP " .. zonedFarp.zone.name .. " captured by " .. blueRed .."!", 30)
trigger.action.outSound("Quest Snare 3.wav")
zonedFarp.owner = newOwner
zonedFarp.zone.owner = newOwner
-- better: sound winm and lose to different sides
-- update color in map
-- FARPZones.drawFarp(zonedFarp)
FARPZones.drawFARPCircleInMap(zonedFarp)
-- remove all existing resources immediately,
-- no more service available
if zonedFarp.resources and zonedFarp.resources:isExist() then
zonedFarp.resources:destroy()
zonedFarp.resources = nil
end
-- now schedule operational after spin-up delay
timer.scheduleFunction(
FARPZones.scheduedProduction,
{zonedFarp, newOwner}, -- pass farp struct and current owner
timer.getTime() + FARPZones.spinUpDelay
)
end
--
-- Start
--
function FARPZones.start()
-- check libs
if not dcsCommon.libCheck("cfx FARP Zones",
FARPZones.requiredLibs) then
return false
end
FARPZones.startingUp = true
-- install callbacks for FARP-relevant events
dcsCommon.addEventHandler(FARPZones.somethingHappened,
FARPZones.preProcessor,
FARPZones.postProcessor)
-- collect all FARP Zones
local theZones = cfxZones.getZonesWithAttributeNamed("FARP")
for k, aZone in pairs(theZones) do
local aFARP = FARPZones.createFARPFromZone(aZone) -- read attributes from DCS
FARPZones.addFARPZone(aFARP) -- add to managed zones
-- FARPZones.drawFarp(aFARP)
FARPZones.drawFARPCircleInMap(aFARP) -- mark in map
FARPZones.produceVehicles(aFARP) -- allocate initial vehicles
--trigger.action.outText("processed FARP " .. aZone.name .. " now owned by " .. aZone.owner, 30)
end
FARPZones.startingUp = false
trigger.action.outText("cf/x FARP Zones v" .. FARPZones.version .. " started", 30)
return true
end
-- let's get rolling
if not FARPZones.start() then
trigger.action.outText("cf/x FARP Zones aborted: missing libraries", 30)
FARPZones = nil
end
--[[--
Improvements:
per FARP/Helipad in zone: create resources (i.e. support multi 4-Pad FARPS out of the box
make hidden farps only appear for owning side
--]]--