366 lines
14 KiB
Lua

-- ====================================================================================
-- DCSEX.ZONES - FUNCTIONS RELATED TO MAP TRIGGER ZONES
-- ====================================================================================
-- DCSEx.zones.drawOnMap(zoneTable, lineColor, fillColor, lineType, drawName, readOnly)
-- DCSEx.zones.getAirbases(zone, coalID, allowShips)
-- DCSEx.zones.getAll()
-- DCSEx.zones.getByName(name)
-- DCSEx.zones.getCenter(zoneTable)
-- DCSEx.zones.getProperty(zoneTable, propertyName, defaultValue)
-- DCSEx.zones.getPropertyBoolean(zoneTable, propertyName, defaultValue)
-- DCSEx.zones.getPropertyFloat(zoneTable, propertyName, defaultValue, min, max)
-- DCSEx.zones.getPropertyInt(zoneTable, propertyName, defaultValue, min, max)
-- DCSEx.zones.getPropertyParse(zoneTable, propertyName, stringTable, valueTable, defaultValue)
-- DCSEx.zones.getPropertyTable(zoneTable, propertyName)
-- DCSEx.zones.getRadius(zoneTable, useMaxForQuads)
-- DCSEx.zones.getRandomPointInside(zoneTable, surfaceType)
-- DCSEx.zones.getSurfaceArea(zoneTable)
-- DCSEx.zones.isPointInside(zoneTable, point)
-- ====================================================================================
DCSEx.zones = { }
-------------------------------------
-- Draws a zone on the F10, visible for all players
-------------------------------------
-- @param zoneTable The zone to draw
-- @param lineColor Line color as a RGBA table
-- @param fillColor Fill color as a RGBA table
-- @param lineType Type of line from the DCSEx.enums.lineType enum
-- @param drawName Should the name of the zone be drawn too (default: false)
-- @param drawName Should the zone marker be read only? (default: true)
-------------------------------------
function DCSEx.zones.drawOnMap(zoneTable, lineColor, fillColor, lineType, drawName, readOnly)
drawName = drawName or false
readOnly = readOnly or true
if not zoneTable then return end
local markerID = DCSEx.world.getNextMarkerID()
-- Draw shapes on the F10 map
if zoneTable.type == 2 then -- Zone is a quad
trigger.action.quadToAll(
-1,
markerID,
DCSEx.math.vec2ToVec3(zoneTable.verticies[1]),
DCSEx.math.vec2ToVec3(zoneTable.verticies[2]),
DCSEx.math.vec2ToVec3(zoneTable.verticies[3]),
DCSEx.math.vec2ToVec3(zoneTable.verticies[4]),
lineColor,
fillColor,
lineType,
readOnly
)
else -- Zone is a circle
trigger.action.circleToAll(
-1,
markerID,
DCSEx.math.vec2ToVec3(zoneTable),
zoneTable.radius,
lineColor,
fillColor,
lineType,
readOnly
)
end
if drawName then
local markerIDText = DCSEx.world.getNextMarkerID()
trigger.action.textToAll(-1, markerIDText, DCSEx.math.vec2ToVec3(zoneTable), { 1, 1, 1, 1 }, { 0, 0, 0, .5 }, 18, readOnly, zoneTable.name)
return { markerID, markerIDText }
end
return markerID
end
-------------------------------------
-- Returns all airbases in the zone
-------------------------------------
-- @param zoneTable Table of the zone in which to search
-- @param coalID Coalition (from the coalition.side enum) the airbase must belong to. Default is nil, which means "all coalitions"
-- @param allowShips Should ships be allowed?
-- @return Table of airbases
-------------------------------------
function DCSEx.zones.getAirbases(zoneTable, coalID, allowShips)
coalID = coalID or nil
allowShips = allowShips or false
local coalitionSides = { coalition.side.RED, coalition.side.BLUE }
if coalID then coalitionSides = { coalID } end
local validAirbases = {}
for _,side in ipairs(coalitionSides) do
for _,ab in ipairs(coalition.getAirbases(side)) do
local abDesc = ab:getDesc()
local isValid = true
if ab:getDesc().category == Airbase.Category.HELIPAD then
isValid = false
elseif ab:getDesc().category == Airbase.Category.SHIP and not allowShips then
isValid = false
end
if isValid then
if DCSEx.zones.isPointInside(zoneTable, ab:getPoint()) then
table.insert(validAirbases, ab)
end
end
end
end
return validAirbases
end
-------------------------------------
-- Returns all trigger zones
-------------------------------------
-- @return Table of zones
-------------------------------------
function DCSEx.zones.getAll()
if not env.mission.triggers then
return {}
end
if not env.mission.triggers.zones then
return {}
end
return DCSEx.table.deepCopy(env.mission.triggers.zones)
end
-------------------------------------
-- Finds and return a trigger zone by a certain name
-------------------------------------
-- @param name Case-insensitive name of the zone
-- @return Zone table or nil if no zone with this name was found
-------------------------------------
function DCSEx.zones.getByName(name)
if not name then return nil end
if not env.mission.triggers then return nil end
if not env.mission.triggers.zones then return nil end
name = name:lower()
for _, z in pairs(env.mission.triggers.zones) do
if z.name:lower() == name then
return DCSEx.table.deepCopy(z)
end
end
return nil
end
-------------------------------------
-- Returns the center of a zone
-------------------------------------
-- @param zoneTable The zone table, returned by TMMissionData.getZones() or TMMissionData.getZoneByName(name)
-- @return A vec2
-------------------------------------
function DCSEx.zones.getCenter(zoneTable)
if not zoneTable then return nil end
local x = zoneTable.x or 0
local y = zoneTable.y or 0
if zoneTable.type == 2 then -- Zone is a quad
x = (zoneTable.verticies[1].x + zoneTable.verticies[2].x + zoneTable.verticies[3].x + zoneTable.verticies[4].x) / 4
y = (zoneTable.verticies[1].y + zoneTable.verticies[2].y + zoneTable.verticies[3].y + zoneTable.verticies[4].y) / 4
end
return { x = x, y = y }
end
-------------------------------------
-- Returns the value of the property of a trigger zone, as a string
-------------------------------------
-- @param zoneTable The zone table, returned by DCSEx.zones.getAll() or DCSEx.zones.getByName(name)
-- @param propertyName Case-insensitive name of the property
-- @return The value of the property or nil if it doesn't exist
-------------------------------------
function DCSEx.zones.getProperty(zoneTable, propertyName, defaultValue)
if not propertyName then return defaultValue end
if not zoneTable then return defaultValue end
if not zoneTable.properties then return defaultValue end
propertyName = propertyName:lower()
for _, p in pairs(zoneTable.properties) do
if p.key:lower() == propertyName then
return (p.value or defaultValue):lower()
end
end
return defaultValue
end
-------------------------------------
-- Returns the value of the property of a trigger zone, parsed against a case-insensitive table of strings
-------------------------------------
-- @param zoneTable The zone table, returned by DCSEx.zones.getAll() or DCSEx.zones.getByName(name)
-- @param propertyName Case-insensitive name of the property
-- @param defaultValue Default value to return if no match was found
-- @return A boolean
-------------------------------------
function DCSEx.zones.getPropertyBoolean(zoneTable, propertyName, defaultValue)
return DCSEx.zones.getPropertyParse(
zoneTable,
propertyName,
{"true", "yes", "1", "on", "enabled", "false", "no", "0", "off", "disabled"},
{true, true, true, true, true, false, false, false, false, false},
defaultValue)
end
-------------------------------------
-- Returns the value of the property of a trigger zone, as a float
-------------------------------------
-- @param zoneTable The zone table, returned by DCSEx.zones.getAll() or DCSEx.zones.getByName(name)
-- @param propertyName Case-insensitive name of the property
-- @param defaultValue Default value to return if no match was found
-- @param min Minimum value
-- @param max Maximum value
-- @return A float
-------------------------------------
function DCSEx.zones.getPropertyFloat(zoneTable, propertyName, defaultValue, min, max)
local value = tonumber(DCSEx.zones.getProperty(zoneTable, propertyName))
if not value then return defaultValue end
if min then value = math.max(min, value) end
if max then value = math.min(max, value) end
return value
end
-------------------------------------
-- Returns the value of the property of a trigger zone, as an integer
-------------------------------------
-- @param zoneTable The zone table, returned by DCSEx.zones.getAll() or DCSEx.zones.getByName(name)
-- @param propertyName Case-insensitive name of the property
-- @param defaultValue Default value to return if no match was found
-- @param min Minimum value
-- @param max Maximum value
-- @return An integer
-------------------------------------
function DCSEx.zones.getPropertyInt(zoneTable, propertyName, defaultValue, min, max)
local value = DCSEx.zones.getPropertyFloat(zoneTable, propertyName, defaultValue, min, max)
if not value then return nil end
return math.floor(value)
end
-------------------------------------
-- Gets the value of a property of a trigger zone and parse it according to two correspondance tables
-------------------------------------
-- @param zoneTable The zone table, returned by DCSEx.zones.getAll() or DCSEx.zones.getByName(name)
-- @param propertyName Case-insensitive name of the property
-- @param stringTable A table of strings
-- @param valueTable A values, matching the strings table's indices
-- @param defaultValue Default value to return if no match was found
-- @return A value
-------------------------------------
function DCSEx.zones.getPropertyParse(zoneTable, propertyName, stringTable, valueTable, defaultValue)
local value = DCSEx.zones.getProperty(zoneTable, propertyName) or ""
value = value:lower()
for i,_ in ipairs(stringTable) do
if value == stringTable[i]:lower() then
return valueTable[i]
end
end
return defaultValue
end
-------------------------------------
-- Returns the value of the property of a trigger zone, as a table of comma-separated lowercase strings
-------------------------------------
-- @param zoneTable The zone table, returned by DCSEx.zones.getAll() or DCSEx.zones.getByName(name)
-- @param propertyName Case-insensitive name of the property
-- @return An table
-------------------------------------
function DCSEx.zones.getPropertyTable(zoneTable, propertyName)
local value = DCSEx.zones.getProperty(zoneTable, propertyName)
if not value then return {} end
return string.split(value:lower(), ",")
end
-------------------------------------
-- Returns the radius of a zone, in meter
-------------------------------------
-- @param zoneTable The zone table, returned by DCSEx.zones.getAll() or DCSEx.zones.getByName(name)
-- @param useMaxForQuads If true, return largest distance between the center and a vertex. If false (default value), returns the mean distance. Only used if the zone is a quad.
-- @return An table
-------------------------------------
function DCSEx.zones.getRadius(zoneTable, useMaxForQuads)
if not zoneTable then return 0 end
useMaxForQuads = useMaxForQuads or false
local radius = 0
if zoneTable.type == 2 then -- Zone is a quad
for _,v in ipairs(zoneTable.verticies) do
if useMaxForQuads then
radius = math.max(radius, DCSEx.math.getDistance2D(zoneTable, v))
else
radius = radius + DCSEx.math.getDistance2D(zoneTable, v)
end
end
if #zoneTable.verticies > 0 and not useMaxForQuads then
radius = radius / #zoneTable.verticies
end
else
radius = zoneTable.radius
end
return radius
end
-- TODO: description + file header
function DCSEx.zones.getRandomPointInside(zoneTable, surfaceType)
local radius = DCSEx.zones.getRadius(zoneTable)
for _=1,64 do
local point = DCSEx.math.randomPointInCircle(zoneTable, radius, surfaceType)
if zoneTable.type == 2 then
if not DCSEx.zones.isPointInside(zoneTable, point) then point = nil end
end
if point then return point end
end
return nil
end
-------------------------------------
-- Returns the surface area of a zone
-------------------------------------
-- @param zoneTable The zone table, returned by TMMissionData.getZones() or TMMissionData.getZoneByName(name)
-- @return A number, in squared meters
-------------------------------------
function DCSEx.zones.getSurfaceArea(zoneTable)
if not zoneTable then return 0 end
if zoneTable.type == 2 then -- Zone is a quad
if not zoneTable.verticies then return 0 end
local area = zoneTable.verticies[1].x * zoneTable.verticies[2].y + zoneTable.verticies[2].x * zoneTable.verticies[3].y + zoneTable.verticies[3].x * zoneTable.verticies[4].y + zoneTable.verticies[4].x * zoneTable.verticies[1].y - zoneTable.verticies[2].x * zoneTable.verticies[1].y - zoneTable.verticies[3].x * zoneTable.verticies[2].y - zoneTable.verticies[4].x * zoneTable.verticies[3].y - zoneTable.verticies[1].x * zoneTable.verticies[4].y
return math.abs(area) / 2
else -- Zone is a circle
if not zoneTable.radius then return 0 end
return (zoneTable.radius ^ 2) * math.pi
end
end
-------------------------------------
-- Returns true if a point is inside a zone
-------------------------------------
-- @param zoneTable The zone table, returned by TMMissionData.getZones() or TMMissionData.getZoneByName(name)
-- @param point A point, as a vec3 or vec2
-- @return True if the point is inside the zone, false otherwise
-------------------------------------
function DCSEx.zones.isPointInside(zoneTable, point)
if not point then return false end
if point.z then point = DCSEx.math.vec3ToVec2(point) end -- Point was a vec3, convert to vec2
if zoneTable.type == 2 then -- Zone is a quad
return DCSEx.math.isPointInsidePolygon(zoneTable.verticies, point)
else -- Zone is a circle
return DCSEx.math.isPointInsideCircle({x = zoneTable.x, y = zoneTable.y}, zoneTable.radius, point)
end
end