-- ==================================================================================== -- 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