mirror of
https://github.com/akaAgar/the-universal-mission-for-dcs-world.git
synced 2025-11-25 19:31:01 +00:00
Initial commit
This commit is contained in:
146
Script/DCS extensions/Converter.lua
Normal file
146
Script/DCS extensions/Converter.lua
Normal file
@@ -0,0 +1,146 @@
|
||||
-- ====================================================================================
|
||||
-- (DCS LUA ADD-ON) CONVERTER - UNITS CONVERSION FUNCTIONS
|
||||
--
|
||||
-- DCSEx.converter.celsiusToFahrenheit(t)
|
||||
-- DCSEx.converter.degreesToRadians(degrees)
|
||||
-- DCSEx.converter.fahrenheitToCelsius(fahrenheit)
|
||||
-- DCSEx.converter.feetToMeters(feet)
|
||||
-- DCSEx.converter.kelvinToCelsius(t)
|
||||
-- DCSEx.converter.kelvinToFahrenheit(t)
|
||||
-- DCSEx.converter.kmphToMps(kmph)
|
||||
-- DCSEx.converter.knotsToMps(knots)
|
||||
-- DCSEx.converter.metersToFeet(meters)
|
||||
-- DCSEx.converter.metersToNM(meters)
|
||||
-- DCSEx.converter.mpsToKmph(mps)
|
||||
-- DCSEx.converter.mpsToKnots(mps)
|
||||
-- DCSEx.converter.nmToMeters(nm)
|
||||
-- DCSEx.converter.radiansToDegrees(radians)
|
||||
-- ====================================================================================
|
||||
|
||||
DCSEx.converter = {}
|
||||
|
||||
-------------------------------------
|
||||
-- Converts Celsius degrees to Fahrenheit
|
||||
-- @param t Temperature in Celsius degrees
|
||||
-- @return Temperature in Fahrenheit degrees
|
||||
-------------------------------------
|
||||
function DCSEx.converter.celsiusToFahrenheit(t)
|
||||
return t * (9 / 5) + 32
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Converts angle in degrees to radians.
|
||||
-- @param degrees Angle in degrees
|
||||
-- @return Angle in radians
|
||||
-------------------------------------
|
||||
function DCSEx.converter.degreesToRadians(degrees)
|
||||
return degrees * math.pi / 180
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Converts Fahrenheit degrees to Celsius
|
||||
-- @param fahrenheit Temperature in Fahrenheit degrees
|
||||
-- @return Temperature in Celsius degrees
|
||||
-------------------------------------
|
||||
function DCSEx.converter.fahrenheitToCelsius(fahrenheit)
|
||||
return (fahrenheit - 32) * (5 / 9)
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Converts feet to meters.
|
||||
-- @param feet Distance in feet
|
||||
-- @return Distance in meters
|
||||
-------------------------------------
|
||||
function DCSEx.converter.feetToMeters(feet)
|
||||
return feet * 0.3048
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Converts Kelvin degrees to Celsius
|
||||
-- @param kelvin Temperature in Kelvin degrees
|
||||
-- @return Temperature in Celsius degrees
|
||||
-------------------------------------
|
||||
function DCSEx.converter.kelvinToCelsius(t)
|
||||
return t - 273.15
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Converts Kelvin degrees to Fahrenheit
|
||||
-- @param kelvin Temperature in Kelvin degrees
|
||||
-- @return Temperature in Fahrenheit degrees
|
||||
-------------------------------------
|
||||
function DCSEx.converter.kelvinToFahrenheit(t)
|
||||
return DCSEx.converter.celsiusToFahrenheit(DCSEx.converter.kelvinToCelsius(t))
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Converts kilometers per hour to meters per second.
|
||||
-- @param kmph speed in km/h
|
||||
-- @return speed in m/s
|
||||
-------------------------------------
|
||||
function DCSEx.converter.kmphToMps(kmph)
|
||||
return kmph / 3.6
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Converts knots to meters per second.
|
||||
-- @param knots speed in knots
|
||||
-- @return speed in m/s
|
||||
-------------------------------------
|
||||
function DCSEx.converter.knotsToMps(knots)
|
||||
return knots * 1852 / 3600
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Converts meters to feet.
|
||||
-- @param meters distance in meters
|
||||
-- @return distance in feet
|
||||
-------------------------------------
|
||||
function DCSEx.converter.metersToFeet(meters)
|
||||
return meters / 0.3048
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Converts meters to nautical miles.
|
||||
-- @param meters distance in meters
|
||||
-- @return distance in nautical miles
|
||||
-------------------------------------
|
||||
function DCSEx.converter.metersToNM(meters)
|
||||
return meters / 1852
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Converts meters per second to kilometers per hour.
|
||||
-- @param mps speed in m/s
|
||||
-- @return speed in km/h
|
||||
-------------------------------------
|
||||
function DCSEx.converter.mpsToKmph(mps)
|
||||
return mps * 3.6
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Converts meters per second to knots.
|
||||
-- @param mps speed in m/s
|
||||
-- @return speed in knots
|
||||
-------------------------------------
|
||||
function DCSEx.converter.mpsToKnots(mps)
|
||||
return mps * 3600 / 1852
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Converts nautical miles to meters.
|
||||
-- @param nm distance in nautical miles
|
||||
-- @return distance in meters
|
||||
-------------------------------------
|
||||
function DCSEx.converter.nmToMeters(nm)
|
||||
return nm * 1852
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Converts angle in radians to degrees.
|
||||
-- @param degrees Angle in radians
|
||||
-- @return Angle in degrees
|
||||
-------------------------------------
|
||||
function DCSEx.converter.radiansToDegrees(radians)
|
||||
return radians * 180 / math.pi
|
||||
end
|
||||
374
Script/DCS extensions/DCS.lua
Normal file
374
Script/DCS extensions/DCS.lua
Normal file
@@ -0,0 +1,374 @@
|
||||
-- ====================================================================================
|
||||
-- DCSTOOLS - FUNCTIONS LINKED TO DCS WORLD RULES AND TABLES
|
||||
-- ====================================================================================
|
||||
-- DCSEx.dcs.getBRAA(point, refPoint, showAltitude, metricSystem)
|
||||
-- DCSEx.dcs.getCJTFForCoalition(coalitionID)
|
||||
-- DCSEx.dcs.getCoalitionAsString(coalitionID)
|
||||
-- DCSEx.dcs.getCoalitionColor(coalitionID, alpha)
|
||||
-- DCSEx.dcs.getGroupCenterPoint(group)
|
||||
-- DCSEx.dcs.getGroupIDAsNumber(group)
|
||||
-- DCSEx.dcs.getNearestObject(refPoint, objectTable)
|
||||
-- DCSEx.dcs.getNearestObjects(refPoint, objectTable, maxCount)
|
||||
-- DCSEx.dcs.getNearestPoints(refPoint, pointsTable, maxCount)
|
||||
-- DCSEx.dcs.getOppositeCoalition(coalitionID)
|
||||
-- DCSEx.dcs.getPlayerUnitsInGroup(group)
|
||||
-- DCSEx.dcs.getPlayerUnitsInGroupByID(groupID)
|
||||
-- DCSEx.dcs.getRadioModulationName(modulationID)
|
||||
-- DCSEx.dcs.getObjectIDAsNumber(obj)
|
||||
-- DCSEx.dcs.getUnitTypeFromFamily(unitFamily)
|
||||
-- DCSEx.dcs.getUnitFamilyForDecade(unitFamily, decade) -- TODO: remove?
|
||||
-- ====================================================================================
|
||||
|
||||
DCSEx.dcs = { }
|
||||
|
||||
-- TODO: add description and update file header
|
||||
function DCSEx.dcs.doNothing()
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Gets a BRAA (bearing, range, altitude, aspect) string about a point
|
||||
-- Format is "[bearing to unit] for [distance] at [altitude]"
|
||||
-- @param unit A unit
|
||||
-- @param refPoint2 Reference point for the bearing and distance
|
||||
-- @param showAltitude Should altitude be displayed?
|
||||
-- @param metricSystem Should metric system units be used?
|
||||
-- @return BRAA, as a string
|
||||
-------------------------------------
|
||||
function DCSEx.dcs.getBRAA(point, refPoint, showAltitude, metricSystem)
|
||||
showAltitude = showAltitude or false
|
||||
metricSystem = metricSystem or false
|
||||
|
||||
if not point.z then point = DCSEx.math.vec2ToVec3(point, "land") end
|
||||
if not refPoint.z then refPoint = DCSEx.math.vec2ToVec3(refPoint, "land") end
|
||||
|
||||
local braa = tostring(DCSEx.math.getBearing(point, refPoint))
|
||||
local distance = DCSEx.math.getDistance2D(point, refPoint)
|
||||
if metricSystem then
|
||||
distance = distance / 1000.0
|
||||
else
|
||||
distance = DCSEx.converter.metersToNM(distance)
|
||||
end
|
||||
|
||||
braa = braa .. " for " .. tostring(math.ceil(distance))
|
||||
|
||||
if showAltitude then
|
||||
local altitude = point.y
|
||||
if metricSystem then
|
||||
altitude = math.floor(point.y / 100) * 100
|
||||
else
|
||||
altitude = math.floor(DCSEx.converter.metersToFeet(point.y) / 1000) * 1000
|
||||
end
|
||||
braa = braa .. " at " .. tostring(altitude)
|
||||
end
|
||||
|
||||
return braa
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns the CJTF country for a given coalition
|
||||
-- @param A coalition ID
|
||||
-- @return A country ID (country.id.CJTF_BLUE or country.id.CJTF_RED)
|
||||
-------------------------------------
|
||||
function DCSEx.dcs.getCJTFForCoalition(coalitionID)
|
||||
if coalitionID == coalition.side.RED then return country.id.CJTF_RED end
|
||||
|
||||
return country.id.CJTF_BLUE
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns the name of a coalition, as a string ("blue", "red" or "neutral")
|
||||
-- @param A coalition ID
|
||||
-- @return A string
|
||||
-------------------------------------
|
||||
function DCSEx.dcs.getCoalitionAsString(coalitionID)
|
||||
if coalitionID == coalition.side.NEUTRAL then return "neutral" end
|
||||
if coalitionID == coalition.side.RED then return "red" end
|
||||
if coalitionID == coalition.side.BLUE then return "blue" end
|
||||
return nil
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns the RGBA color table for the given coalition, accoding to NATO symbology colors
|
||||
-- @param coalitionID A coalition side
|
||||
-- @param alpha (optional) Alpha. Default is 1
|
||||
-- @return A RGBA color table
|
||||
-------------------------------------
|
||||
function DCSEx.dcs.getCoalitionColor(coalitionID, alpha)
|
||||
alpha = alpha or 1.0
|
||||
|
||||
if coalitionID == coalition.side.RED then return {0.97, 0.52, 0.51, alpha}
|
||||
elseif coalitionID == coalition.side.BLUE then return {0.52, 0.87, 0.99, alpha}
|
||||
else
|
||||
return {0.5, 0.5, 0.5, alpha}
|
||||
end
|
||||
end
|
||||
|
||||
-- TODO: description
|
||||
function DCSEx.dcs.getFirstUnitCallsign(group)
|
||||
if not group then return nil end
|
||||
|
||||
local units = group:getUnits()
|
||||
if not units then return nil end
|
||||
|
||||
local unit0 = units[1]
|
||||
if not unit0 then return nil end
|
||||
return unit0:getCallsign()
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns a vec3 point at the center of all units of a group
|
||||
-- @param group A group object
|
||||
-- @return A vec3
|
||||
-------------------------------------
|
||||
function DCSEx.dcs.getGroupCenterPoint(group)
|
||||
if not group then return nil end
|
||||
local units = group:getUnits()
|
||||
if not units or #units == 0 then return nil end
|
||||
|
||||
local centerPoint = { x = 0, y = 0, z = 0}
|
||||
for _,u in pairs(units) do
|
||||
local uPoint = u:getPoint()
|
||||
centerPoint.x = centerPoint.x + uPoint.x
|
||||
centerPoint.y = centerPoint.y + uPoint.y
|
||||
centerPoint.z = centerPoint.z + uPoint.z
|
||||
end
|
||||
|
||||
centerPoint.x = centerPoint.x / #units
|
||||
centerPoint.y = centerPoint.y / #units
|
||||
centerPoint.z = centerPoint.z / #units
|
||||
|
||||
return centerPoint
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns the ID of a group as a number (here to fix a bug where sometimes ID is returned as a string)
|
||||
-- @param group A group table
|
||||
-- @return An ID (as an number) or nil if group is nil or has no ID
|
||||
-------------------------------------
|
||||
function DCSEx.dcs.getGroupIDAsNumber(group)
|
||||
if not group then
|
||||
return nil
|
||||
end
|
||||
|
||||
return tonumber(group:getID())
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns the object nearest (in a 2D plane) from a given point
|
||||
-- @param refPoint A reference point, as a vec2 or vec3
|
||||
-- @param objectTable A table of DCS objects
|
||||
-- @return The object nearest from the point, or nil if no object was found
|
||||
-------------------------------------
|
||||
function DCSEx.dcs.getNearestObject(refPoint, objectTable)
|
||||
local nearestObjects = DCSEx.dcs.getNearestObjects(refPoint, objectTable, 1)
|
||||
if #nearestObjects == 0 then return nil end
|
||||
return nearestObjects[1]
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns the nearest objects (in a 2D plane) from a given point
|
||||
-- @param refPoint A reference point, as a vec2 or vec3
|
||||
-- @param objectTable A table of DCS objects
|
||||
-- @param maxCount (optional) Maximum number of objects to return
|
||||
-- @return A table of objects, sorted by distance
|
||||
-------------------------------------
|
||||
function DCSEx.dcs.getNearestObjects(refPoint, objectTable, maxCount)
|
||||
if not refPoint then return DCSEx.table.deepCopy(objectTable) end
|
||||
|
||||
refPoint = DCSEx.math.vec3ToVec2(refPoint)
|
||||
local sortedObjects = {}
|
||||
local sortedKeys = {}
|
||||
|
||||
for i,o in pairs(objectTable) do
|
||||
local distance = DCSEx.math.getDistance2D(refPoint, o:getPoint())
|
||||
sortedObjects[distance] = i
|
||||
table.insert(sortedKeys, distance)
|
||||
end
|
||||
|
||||
table.sort(sortedKeys)
|
||||
|
||||
local sortedList = {}
|
||||
for _,v in ipairs(sortedKeys) do
|
||||
if maxCount and #sortedList >= maxCount then break end
|
||||
table.insert(sortedList, objectTable[sortedObjects[v]])
|
||||
end
|
||||
|
||||
return sortedList
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns the nearest points (in a 2D plane) from a given point
|
||||
-- @param refPoint A reference point, as a vec2 or vec3
|
||||
-- @param objectTable A table of points (vec2 or vec3)
|
||||
-- @param maxCount (optional) Maximum number of points to return
|
||||
-- @return A table of points, sorted by distance
|
||||
-------------------------------------
|
||||
function DCSEx.dcs.getNearestPoints(refPoint, pointsTable, maxCount)
|
||||
if not refPoint then return DCSEx.table.deepCopy(pointsTable) end
|
||||
|
||||
refPoint = DCSEx.math.vec3ToVec2(refPoint)
|
||||
local sortedPoints = {}
|
||||
local sortedKeys = {}
|
||||
|
||||
for i,pt in pairs(pointsTable) do
|
||||
local distance = DCSEx.math.getDistance2D(refPoint, pt)
|
||||
sortedPoints[distance] = i
|
||||
table.insert(sortedKeys, distance)
|
||||
end
|
||||
|
||||
table.sort(sortedKeys)
|
||||
|
||||
local sortedList = {}
|
||||
for _,v in ipairs(sortedKeys) do
|
||||
if maxCount and #sortedList >= maxCount then break end
|
||||
table.insert(sortedList, pointsTable[sortedPoints[v]])
|
||||
end
|
||||
|
||||
return sortedList
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns the coalition opposed to the provided coalition (coalition.side.NEUTRAL still returns NEUTRAL)
|
||||
-- @param group A coalition
|
||||
-- @return Another coalition
|
||||
-------------------------------------
|
||||
function DCSEx.dcs.getOppositeCoalition(coalitionID)
|
||||
if coalitionID == coalition.side.RED then return coalition.side.BLUE end
|
||||
if coalitionID == coalition.side.BLUE then return coalition.side.RED end
|
||||
return coalition.side.NEUTRAL
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns all player-controlled units in a group
|
||||
-- @param group A group object
|
||||
-- @return A table of unit objects
|
||||
-------------------------------------
|
||||
function DCSEx.dcs.getPlayerUnitsInGroup(group)
|
||||
if not group then return nil end
|
||||
local units = group:getUnits()
|
||||
if not units then return { } end
|
||||
|
||||
local players = {}
|
||||
for _,u in pairs(units) do
|
||||
if u:getPlayerName() then
|
||||
table.insert(players, u)
|
||||
end
|
||||
end
|
||||
|
||||
return players
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns all player-controlled units in the group with the given ID
|
||||
-- @param groupID A group ID
|
||||
-- @return A table of unit objects
|
||||
-------------------------------------
|
||||
function DCSEx.dcs.getPlayerUnitsInGroupByID(groupID)
|
||||
return DCSEx.dcs.getPlayerUnitsInGroup(DCSEx.world.getGroupByID(groupID))
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns a radio modulation type as a string
|
||||
-- @param modulationID A modulation ID (from radio.modulation enum)
|
||||
-- @return A string
|
||||
-------------------------------------
|
||||
function DCSEx.dcs.getRadioModulationName(modulationID)
|
||||
if modulationID == radio.modulation.FM then return "FM" end
|
||||
return "AM"
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns a remplacement unit family for given family if it's not available in this decade (e.g. SAMs in the 1940s). Else returns the original family.
|
||||
-- @param unitFamily An unit family
|
||||
-- @param decade (optional) A decade, or the current decade from env.mission.date.Year
|
||||
-- @return An unit family
|
||||
-------------------------------------
|
||||
function DCSEx.dcs.getUnitFamilyForDecade(unitFamily, decade)
|
||||
-- TODO
|
||||
-- decade = decade or envMission.getDecade()
|
||||
|
||||
-- if decade < 1990 then
|
||||
-- if unitFamily == DCSEx.enums.unitFamily.UAVs then
|
||||
-- unitFamily = DCSEx.enums.unitFamily.AttackHelicopters
|
||||
-- end
|
||||
-- end
|
||||
|
||||
-- if decade < 1970 then
|
||||
-- if unitFamily == DCSEx.enums.unitFamily.AWACS then
|
||||
-- unitFamily = DCSEx.enums.unitFamily.Transports
|
||||
-- elseif unitFamily == DCSEx.enums.unitFamily.SAMShort then
|
||||
-- unitFamily = DCSEx.enums.unitFamily.MobileAAA
|
||||
-- elseif unitFamily == DCSEx.enums.unitFamily.SAMShortIR then
|
||||
-- unitFamily = DCSEx.enums.unitFamily.MobileAAA
|
||||
-- end
|
||||
-- end
|
||||
|
||||
-- if decade < 1960 then
|
||||
-- if unitFamily == DCSEx.enums.unitFamily.AttackHelicopters then
|
||||
-- unitFamily = DCSEx.enums.unitFamily.Fighters
|
||||
-- elseif unitFamily == DCSEx.enums.unitFamily.MANPADS then
|
||||
-- unitFamily = DCSEx.enums.unitFamily.Infantry
|
||||
-- elseif unitFamily == DCSEx.enums.unitFamily.SAMLong then
|
||||
-- unitFamily = DCSEx.enums.unitFamily.StaticAAA
|
||||
-- elseif unitFamily == DCSEx.enums.unitFamily.SAMMedium then
|
||||
-- unitFamily = DCSEx.enums.unitFamily.StaticAAA
|
||||
-- elseif unitFamily == DCSEx.enums.unitFamily.SSMissiles then
|
||||
-- unitFamily = DCSEx.enums.unitFamily.Artillery
|
||||
-- elseif unitFamily == DCSEx.enums.unitFamily.TransportHelicopters then
|
||||
-- unitFamily = DCSEx.enums.unitFamily.Transports
|
||||
-- end
|
||||
-- end
|
||||
|
||||
-- if decade < 1950 then
|
||||
-- if unitFamily == DCSEx.enums.unitFamily.MobileAAA then
|
||||
-- unitFamily = DCSEx.enums.unitFamily.APC
|
||||
-- elseif unitFamily == DCSEx.enums.unitFamily.Tankers then
|
||||
-- unitFamily = DCSEx.enums.unitFamily.Transports
|
||||
-- end
|
||||
-- end
|
||||
|
||||
return unitFamily
|
||||
end
|
||||
|
||||
-- TODO: description
|
||||
function DCSEx.dcs.getUnitTypeFromFamily(unitFamily)
|
||||
return math.floor(unitFamily / 100)
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns the ID of an object as a number (here to fix a bug where sometimes ID is returned as a string)
|
||||
-- @param obj An object (unit, static object...)
|
||||
-- @return An ID (as an number) or nil if unit is nil or has no ID
|
||||
-------------------------------------
|
||||
function DCSEx.dcs.getObjectIDAsNumber(obj)
|
||||
if not obj then return nil end
|
||||
return tonumber(obj:getID())
|
||||
end
|
||||
|
||||
-- TODO: description & file header
|
||||
function DCSEx.dcs.loadMission(fileName)
|
||||
net.dostring_in("mission", string.format("a_load_mission(\"%s\")", fileName))
|
||||
end
|
||||
|
||||
-- TODO: description & file header
|
||||
-- function DCSEx.dcs.isMultiplayer()
|
||||
-- if #net.get_player_list() > 0 then return true end
|
||||
-- if dcs and dcs.isServer() == true then return true end
|
||||
-- return false
|
||||
-- end
|
||||
|
||||
-- TODO: a_end_mission
|
||||
|
||||
-- TODO: description & file header
|
||||
function DCSEx.dcs.outPicture(fileName, durationSeconds, clearView, startDelay, horizontalAlign, verticalAlign, size, sizeUnits)
|
||||
clearView = clearView or false
|
||||
startDelay = startDelay or 0
|
||||
horizontalAlign = horizontalAlign or 1
|
||||
verticalAlign = verticalAlign or 1
|
||||
size = size or 100
|
||||
sizeUnits = sizeUnits or 0
|
||||
|
||||
if clearView then clearView = "true" else clearView = "false" end
|
||||
|
||||
net.dostring_in("mission", string.format("a_out_picture(getValueResourceByKey(\"%s\"), %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")", fileName, durationSeconds, clearView, startDelay, horizontalAlign, verticalAlign, size, sizeUnits))
|
||||
end
|
||||
123
Script/DCS extensions/Enums.lua
Normal file
123
Script/DCS extensions/Enums.lua
Normal file
@@ -0,0 +1,123 @@
|
||||
DCSEx.enums = {}
|
||||
|
||||
-------------------------------------
|
||||
-- Line types for map markers. The enum is missing from DCS
|
||||
-------------------------------------
|
||||
DCSEx.enums.lineType = {
|
||||
NO_LINE = 0,
|
||||
SOLID = 1,
|
||||
DASHED = 2,
|
||||
DOTTED = 3,
|
||||
DOT_DASH = 4,
|
||||
LONG_DASH = 5,
|
||||
TWO_DASH = 6,
|
||||
}
|
||||
|
||||
-------------------------------------
|
||||
-- Event to check to see if a task/objective is complete
|
||||
-------------------------------------
|
||||
DCSEx.enums.spawnPointType = {
|
||||
LAND_LARGE = 1,
|
||||
LAND_MEDIUM = 2,
|
||||
LAND_SMALL = 3,
|
||||
SEA = 4,
|
||||
}
|
||||
|
||||
-------------------------------------
|
||||
-- Event to check to see if a task/objective is complete
|
||||
-------------------------------------
|
||||
DCSEx.enums.taskEvent = {
|
||||
DESTROY = 1,
|
||||
DESTROY_SCENERY = 2,
|
||||
LAND = 3,
|
||||
}
|
||||
|
||||
-------------------------------------
|
||||
-- Families of mission tasks
|
||||
-------------------------------------
|
||||
DCSEx.enums.taskFamily = {
|
||||
ANTISHIP = 1,
|
||||
-- CAP = 2, -- TODO
|
||||
-- CAS = 3, -- TODO
|
||||
GROUND_ATTACK = 2, -- 4
|
||||
-- HELICOPTER = XXX, -- 5
|
||||
-- HELO_HUNT = XXX, -- 6
|
||||
INTERCEPTION = 3, -- 7
|
||||
-- OCA = XXX, -- 8
|
||||
SEAD = 4, --9
|
||||
STRIKE = 5, -- 10
|
||||
}
|
||||
|
||||
-------------------------------------
|
||||
-- Special events for tasks
|
||||
-------------------------------------
|
||||
DCSEx.enums.taskFlag = {
|
||||
ALLOW_JTAC = 1,
|
||||
DESTROY_TRACK_RADARS_ONLY = 2,
|
||||
MOVING = 3,
|
||||
ON_ROADS = 4,
|
||||
SCENERY_TARGET = 5
|
||||
}
|
||||
|
||||
-------------------------------------
|
||||
-- Enumerates the various time periods during which DCS World missions can take place
|
||||
-------------------------------------
|
||||
DCSEx.enums.timePeriod = {
|
||||
WORLD_WAR_2 = 1,
|
||||
KOREA_WAR = 2,
|
||||
VIETNAM_WAR = 3,
|
||||
COLD_WAR = 4,
|
||||
MODERN = 5
|
||||
}
|
||||
|
||||
-------------------------------------
|
||||
-- Type of unit families. Hundreds digit must match the value in the Unit.Category enum
|
||||
-------------------------------------
|
||||
DCSEx.enums.unitFamily = {
|
||||
AIRDEFENSE_MANPADS = 251,
|
||||
AIRDEFENSE_AAA_MOBILE = 252,
|
||||
AIRDEFENSE_AAA_STATIC = 253,
|
||||
AIRDEFENSE_SAM_LONG = 254,
|
||||
AIRDEFENSE_SAM_MEDIUM = 255,
|
||||
AIRDEFENSE_SAM_SHORT = 256,
|
||||
AIRDEFENSE_SAM_SHORT_IR = 257,
|
||||
|
||||
PLANE_ATTACK = 001,
|
||||
PLANE_AWACS = 002,
|
||||
PLANE_BOMBER = 003,
|
||||
PLANE_FIGHTER = 004,
|
||||
PLANE_INTERCEPTOR = 005,
|
||||
PLANE_TANKER = 006,
|
||||
PLANE_TRANSPORT = 007,
|
||||
PLANE_UAV = 008,
|
||||
|
||||
GROUND_APC = 201,
|
||||
GROUND_ARTILLERY = 202,
|
||||
GROUND_INFANTRY = 203,
|
||||
GROUND_MBT = 204,
|
||||
GROUND_SS_MISSILE = 205,
|
||||
GROUND_UNARMED = 206,
|
||||
|
||||
HELICOPTER_ATTACK = 101,
|
||||
HELICOPTER_TRANSPORT = 102,
|
||||
|
||||
SHIP_CARGO = 301,
|
||||
SHIP_CARRIER = 302,
|
||||
SHIP_CRUISER = 303,
|
||||
SHIP_FRIGATE = 304,
|
||||
SHIP_LIGHT = 305,
|
||||
SHIP_MISSILE_BOAT = 306,
|
||||
SHIP_SUBMARINE = 307,
|
||||
|
||||
STATIC_SCENERY = 401,
|
||||
STATIC_STRUCTURE = 402
|
||||
}
|
||||
|
||||
DCSEx.enums.victoryCondition = {
|
||||
DESTROY = 1,
|
||||
DESTROY_NO_AIR_DEFENSE = 2,
|
||||
DESTROY_SCENERY = 3,
|
||||
DESTROY_TRACK_RADARS_ONLY = 4, -- for SEAD tasks
|
||||
|
||||
LAND_NEAR = 5,
|
||||
}
|
||||
131
Script/DCS extensions/EnvMission.lua
Normal file
131
Script/DCS extensions/EnvMission.lua
Normal file
@@ -0,0 +1,131 @@
|
||||
-- ====================================================================================
|
||||
-- (DCS LUA ADD-ON) ENVMISSION - FUNCTIONS RELATED TO THE ENV.MISSION TABLE
|
||||
--
|
||||
-- DCSEx.envMission.getDecade(yearOffset)
|
||||
-- DCSEx.envMission.getDistanceToNearestPlayerSpawnPoint(point)
|
||||
-- DCSEx.envMission.getGroup(groupID)
|
||||
-- DCSEx.envMission.getGroups(sideID)
|
||||
-- DCSEx.envMission.getPlayerGroups(coalitionId)
|
||||
-- ====================================================================================
|
||||
|
||||
DCSEx.envMission = {}
|
||||
|
||||
-------------------------------------
|
||||
-- Returns the decade during which the mission takes place (1940 to 2010)
|
||||
-- @param yearOffset An offset to apply to the actual year
|
||||
-- @return The decade, as a number
|
||||
-------------------------------------
|
||||
function DCSEx.envMission.getDecade(yearOffset)
|
||||
return DCSEx.math.clamp(math.floor((env.mission.date.Year + (yearOffset or 0)) / 10) * 10, 1940, 2010)
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns the distance to the nearest player spawn point
|
||||
-- @param coalition Coalition the players belong to
|
||||
-- @param point A vec3 or vec2
|
||||
-- @return The distance, in meters, to the nearest player spawn point, or nil if no player spawn points are present
|
||||
-------------------------------------
|
||||
function DCSEx.envMission.getDistanceToNearestPlayerSpawnPoint(coalition, point)
|
||||
point = DCSEx.math.vec3ToVec2(point)
|
||||
|
||||
local playerSlots = DCSEx.envMission.getPlayerGroups(coalition)
|
||||
if #playerSlots == 0 then return nil end
|
||||
|
||||
local nearestPlayer = 999999999999999999999
|
||||
for _,p in pairs(playerSlots) do
|
||||
local dist = DCSEx.math.getDistance2D(point, p)
|
||||
if dist < nearestPlayer then nearestPlayer = dist end
|
||||
end
|
||||
return nearestPlayer
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Gets information about a group
|
||||
-- @param groupID Group ID
|
||||
-- @return Missiondata group table or nil if ID doesn't exist
|
||||
-------------------------------------
|
||||
function DCSEx.envMission.getGroup(groupID)
|
||||
local allGroups = DCSEx.envMission.getGroups()
|
||||
|
||||
for _, g in ipairs(allGroups) do
|
||||
if g.groupId == groupID then
|
||||
return g
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Gets all unit groups
|
||||
-- @param sideID Coalition ID (coalition.side.*), or nil to return unit groups from all coalitions
|
||||
-- @return Table of missiondata group tables
|
||||
-------------------------------------
|
||||
function DCSEx.envMission.getGroups(sideID)
|
||||
-- Group data are located in coalition[COAL_ID_AS_STRING].country[N][UNIT_CATEGORY_AS_STRING].group[]
|
||||
-- (e.g. coalition["blue"].country[1]["plane"].group[1])
|
||||
|
||||
local validCoalitions = { [0] = "neutrals", [1] = "red", [2] = "blue"} -- by default, search all coalitions
|
||||
if sideID == coalition.side.BLUE then
|
||||
validCoalitions = { [2] = "blue"}
|
||||
elseif sideID == coalition.side.RED then
|
||||
validCoalitions = { [1] = "red" }
|
||||
elseif sideID == coalition.side.NEUTRAL then
|
||||
validCoalitions = { [0] = "neutrals" }
|
||||
end
|
||||
|
||||
local groups = {}
|
||||
|
||||
for coalID, coalName in pairs(validCoalitions) do -- In each coalition...
|
||||
if env.mission.coalition[coalName] then -- (if coalition exists)
|
||||
for _, countryData in pairs(env.mission.coalition[coalName].country) do -- search each country
|
||||
for _, uType in pairs({"helicopter", "plane", "ship", "static", "vehicle"}) do -- for each unit category
|
||||
if countryData[uType] then -- (if unit category exists)
|
||||
for _,g in pairs(countryData[uType].group) do -- for each group
|
||||
local groupInfo = DCSEx.table.deepCopy(g)
|
||||
groupInfo.coalition = coalID -- store coalition side for later reference
|
||||
table.insert(groups, groupInfo)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return groups
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Gets all player groups
|
||||
-- @param coalitionId Coalition ID (coalition.side.*), or nil to return unit groups from all coalitions
|
||||
-- @return Table of missiondata group tables
|
||||
-------------------------------------
|
||||
function DCSEx.envMission.getPlayerGroups(coalitionId)
|
||||
local allGroups = DCSEx.envMission.getGroups(coalitionId)
|
||||
|
||||
local playerGroups = {}
|
||||
|
||||
for _, g in ipairs(allGroups) do
|
||||
local isPlayerGroup = false
|
||||
|
||||
for _, u in pairs(g.units) do
|
||||
if u.skill == "Player" or u.skill == "Client" then
|
||||
isPlayerGroup = true
|
||||
end
|
||||
end
|
||||
|
||||
if isPlayerGroup then
|
||||
table.insert(playerGroups, DCSEx.table.deepCopy(g))
|
||||
end
|
||||
end
|
||||
|
||||
return playerGroups
|
||||
end
|
||||
|
||||
-- TODO: description & file header
|
||||
function DCSEx.envMission.setBriefing(side, text, picture)
|
||||
text = text or ""
|
||||
text = text:gsub("\n", "\\n")
|
||||
picture = picture or ""
|
||||
net.dostring_in("mission", string.format("a_set_briefing(\"%s\", getValueResourceByKey(\"%s\"), \"%s\")", DCSEx.dcs.getCoalitionAsString(side):lower(), picture, text))
|
||||
end
|
||||
106
Script/DCS extensions/IO.lua
Normal file
106
Script/DCS extensions/IO.lua
Normal file
@@ -0,0 +1,106 @@
|
||||
-- ====================================================================================
|
||||
-- DCSEx.IO - HANDLES READING/WRITING FILES
|
||||
-- ====================================================================================
|
||||
-- DCSEx.io.canReadAndWrite()
|
||||
-- DCSEx.io.load(fileName)
|
||||
-- DCSEx.io.save(fileName, values)
|
||||
-- ====================================================================================
|
||||
|
||||
DCSEx.io = {}
|
||||
|
||||
do
|
||||
-------------------------------------
|
||||
-- Returns true if the IO table has been unsanitized (allowing IO operations)
|
||||
-- and false if it hasn't been
|
||||
--
|
||||
-- @return A boolean
|
||||
-------------------------------------
|
||||
function DCSEx.io.canReadAndWrite()
|
||||
return io ~= nil
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Loads a table from a text file
|
||||
--
|
||||
-- @param fileName Name of the file to read
|
||||
-- @param obfuscate Should the file contents be obfuscated?
|
||||
-- @return A table, or nil if something went wrong
|
||||
-------------------------------------
|
||||
-- function DCSEx.io.load(fileName, obfuscate)
|
||||
-- obfuscate = obfuscate or false -- TODO: obfuscation
|
||||
|
||||
-- -- IO table is sanitized, cannot read/write to disk
|
||||
-- if not DCSEx.io.canReadAndWrite() then return nil end
|
||||
|
||||
-- local saveFile = io.open(fileName, "r")
|
||||
-- if not saveFile then return nil end
|
||||
|
||||
-- local values = {}
|
||||
-- local rawText = saveFile:read("*all")
|
||||
-- for k, v in string.gmatch(rawText, "(%w+)=(%w+)") do
|
||||
-- local numval = tonumber(v)
|
||||
-- if numval then
|
||||
-- values[k] = tonumber(v)
|
||||
-- else
|
||||
-- values[k] = v
|
||||
-- -- trigger.action.outText("GET value \""..k.."\" AT \""..tostring(v).."\"", 1)
|
||||
-- end
|
||||
-- end
|
||||
-- saveFile:close()
|
||||
|
||||
-- return values
|
||||
-- end
|
||||
|
||||
function DCSEx.io.load(fileName)
|
||||
-- IO table is sanitized, cannot read/write to disk
|
||||
if not DCSEx.io.canReadAndWrite() then return nil end
|
||||
|
||||
local saveFile = io.open(fileName, "r")
|
||||
if not saveFile then return nil end
|
||||
local str = saveFile:read("*all")
|
||||
saveFile:close()
|
||||
|
||||
return str
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Saves a table to a text file
|
||||
--
|
||||
-- @param fileName Name of the file to write to
|
||||
-- @param values Key/value table containing the values to save
|
||||
-- @param obfuscate Should the file contents be obfuscated?
|
||||
-- @return True if everything went right, false otherwise
|
||||
-------------------------------------
|
||||
-- function DCSEx.io.save(fileName, values, obfuscate)
|
||||
-- obfuscate = obfuscate or false -- TODO: obfuscation
|
||||
|
||||
-- -- IO table is sanitized, cannot read/write to disk
|
||||
-- if not DCSEx.io.canReadAndWrite() then return false end
|
||||
|
||||
-- -- No values or not a table
|
||||
-- if values == nil then return false end
|
||||
-- if type(values) ~= "table" then return false end
|
||||
|
||||
-- local saveFile = io.open(fileName, "w")
|
||||
-- if not saveFile then return false end
|
||||
|
||||
-- for k,v in pairs(values) do
|
||||
-- saveFile:write(k.."="..tostring(v).."\n")
|
||||
-- -- trigger.action.outText("SET value \""..k.."\" TO \""..tostring(v).."\"", 1)
|
||||
-- end
|
||||
-- saveFile:close()
|
||||
|
||||
-- return true
|
||||
-- end
|
||||
function DCSEx.io.save(fileName, str)
|
||||
-- IO table is sanitized, cannot read/write to disk
|
||||
if not DCSEx.io.canReadAndWrite() then return false end
|
||||
|
||||
local saveFile = io.open(fileName, "w")
|
||||
if not saveFile then return false end
|
||||
saveFile:write(str)
|
||||
saveFile:close()
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
351
Script/DCS extensions/Math.lua
Normal file
351
Script/DCS extensions/Math.lua
Normal file
@@ -0,0 +1,351 @@
|
||||
-- ====================================================================================
|
||||
-- (DCS LUA ADD-ON) MATH - EXTENSION TO THE "MATH" TABLE
|
||||
--
|
||||
-- (Constant) DCSEx.math.TWO_PI
|
||||
-- DCSEx.math.addVec(vecA, vecB)
|
||||
-- DCSEx.math.clamp(val, min, max)
|
||||
-- DCSEx.math.getBearing(point, refPoint)
|
||||
-- DCSEx.math.getDistance2D(vec2a, vec2b)
|
||||
-- DCSEx.math.getDistance3D(vec3a, vec3b)
|
||||
-- DCSEx.math.getRelativeHeading(point, refObject)
|
||||
-- DCSEx.math.getVec2FromAngle(angle)
|
||||
-- DCSEx.math.isPointInsideCircle(center, radius, vec2)
|
||||
-- DCSEx.math.isPointInsidePolygon(polygon, vec2)
|
||||
-- DCSEx.math.isSamePoint(pointA, pointB)
|
||||
-- DCSEx.math.lerp(val0, val1, t)
|
||||
-- DCSEx.math.multVec2(vec2, mult)
|
||||
-- DCSEx.math.normalizeVec2(vec2)
|
||||
-- DCSEx.math.randomBoolean()
|
||||
-- DCSEx.math.randomFloat(min, max)
|
||||
-- DCSEx.math.randomPointAtDistance(point, distance)
|
||||
-- DCSEx.math.randomPointInCircle(center, radius, minRadius, surfaceType)
|
||||
-- DCSEx.math.randomSign()
|
||||
-- DCSEx.math.toBoolean(val)
|
||||
-- DCSEx.math.vec2ToVec3(vec2, y)
|
||||
-- DCSEx.math.vec3ToVec2(vec3)
|
||||
-- ====================================================================================
|
||||
|
||||
DCSEx.math = {}
|
||||
|
||||
-------------------------------------
|
||||
-- Constants
|
||||
-------------------------------------
|
||||
DCSEx.math.TWO_PI = math.pi * 2
|
||||
|
||||
-------------------------------------
|
||||
-- Returns the sum of two vec2 or vec3
|
||||
-- @param vecA A vector
|
||||
-- @param vecB Another vector
|
||||
-- @return The sum of both vectors
|
||||
-------------------------------------
|
||||
function DCSEx.math.addVec(vecA, vecB)
|
||||
if vecA.z then
|
||||
return { x = vecA.x + vecB.x, y = vecA.y + vecB.y, z = vecA.z + vecB.z }
|
||||
end
|
||||
|
||||
return { x = vecA.x + vecB.x, y = vecA.y + vecB.y }
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Clamp a number value between min and max
|
||||
-- @param value The value to clamp
|
||||
-- @param min Minimum allowed value
|
||||
-- @param max Maximum allowed value
|
||||
-- @return Clamped value
|
||||
-------------------------------------
|
||||
function DCSEx.math.clamp(val, min, max)
|
||||
return math.max(min, math.min(max, val))
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Gets the bearing between two vectors, in degrees
|
||||
-- @param point A vec2/vec3
|
||||
-- @param refPoint Vec2/vec3 to use as a reference point
|
||||
-- @return Bearing, as a number, in degrees
|
||||
-------------------------------------
|
||||
function DCSEx.math.getBearing(point, refPoint)
|
||||
refPoint = refPoint or {x = 0, y = 0}
|
||||
if point.z then point = DCSEx.math.vec3ToVec2(point) end -- Point is a vec3, convert it
|
||||
if refPoint.z then refPoint = DCSEx.math.vec3ToVec2(refPoint) end -- Point is a vec3, convert it
|
||||
|
||||
local bearing = math.atan2(point.y - refPoint.y, point.x - refPoint.x)
|
||||
|
||||
if bearing < 0 then
|
||||
bearing = bearing + DCSEx.math.TWO_PI
|
||||
end
|
||||
|
||||
return math.floor(bearing * (180 / math.pi))
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns the pythagorean distance between two 2D points or the length of a single vector
|
||||
-- @param vec2a A 2D point
|
||||
-- @param vec2b (optional) Another 2D point
|
||||
-- @return Distance between the points
|
||||
-------------------------------------
|
||||
function DCSEx.math.getDistance2D(vec2a, vec2b)
|
||||
vec2b = vec2b or { x = 0, y = 0 }
|
||||
if vec2a.z then vec2a = DCSEx.math.vec3ToVec2(vec2a) end
|
||||
if vec2b.z then vec2b = DCSEx.math.vec3ToVec2(vec2b) end
|
||||
|
||||
return math.sqrt((vec2a.x - vec2b.x) ^ 2 + (vec2a.y - vec2b.y) ^ 2)
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns the pythagorean distance between two 3D points or the length of a single vector
|
||||
-- @param vec3a A 3D point
|
||||
-- @param vec3b (optional) Another 3D point
|
||||
-- @return Distance between the points
|
||||
-------------------------------------
|
||||
function DCSEx.math.getDistance3D(vec3a, vec3b)
|
||||
vec3b = vec3b or { x = 0, y = 0, z = 0 }
|
||||
|
||||
return math.sqrt((vec3a.x - vec3b.x) ^ 2 + (vec3a.y - vec3b.y) ^ 2 + (vec3a.z - vec3b.z) ^ 2)
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns the relative heading difference between refObject and a given point
|
||||
-- @param point The point for which to check the relative heading
|
||||
-- @param refObject The reference object against which relative heading should be measured
|
||||
-- @param format (optional) Return format. Possible formats are "clock" (1 o'clock...) or "cardinal" (NNW...)
|
||||
-- @return A number in degrees, or a string if a special format was provided
|
||||
-------------------------------------
|
||||
function DCSEx.math.getRelativeHeading(point, refObject, format)
|
||||
if not point then return nil end
|
||||
if not refObject then return nil end
|
||||
|
||||
local unitpos = refObject:getPosition()
|
||||
local bearing = DCSEx.math.getBearing(point, unitpos:getPoint())
|
||||
|
||||
local unitBearing = math.atan2(unitpos.x.z, unitpos.x.x)
|
||||
if unitBearing < 0 then unitBearing = unitBearing + DCSEx.math.TWO_PI end
|
||||
unitBearing = math.floor(unitBearing * (180 / math.pi))
|
||||
|
||||
local relBearing = bearing - unitBearing
|
||||
if relBearing < 0 then relBearing = relBearing + 360 end
|
||||
|
||||
if format == "oclock" then
|
||||
local oClock = math.floor(relBearing / 30)
|
||||
if oClock <= 0 then oClock = 12 end
|
||||
|
||||
return tostring(oClock).." o'clock"
|
||||
elseif format == "cardinal" then
|
||||
return "TODO" -- TODO: cardinal
|
||||
end
|
||||
|
||||
return math.floor(relBearing)
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns an normalized vec2 from an angle/bearing in radians
|
||||
-- @param unit Angle/bearing in radians
|
||||
-- @return A normalized vec2
|
||||
-------------------------------------
|
||||
function DCSEx.math.getVec2FromAngle(angle)
|
||||
return {x = math.cos(angle), y = math.sin(angle)}
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Is a point inside a circle?
|
||||
-- @param center The center of the circle, as a vec2
|
||||
-- @param radius The radius of the circle
|
||||
-- @param vec2 A vec2
|
||||
-- @return True if vec2 is inside the circle, false otherwise
|
||||
-------------------------------------
|
||||
function DCSEx.math.isPointInsideCircle(center, radius, vec2)
|
||||
return (vec2.x - center.x) ^ 2 + (vec2.y - center.y) ^ 2 < radius ^ 2
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Is a point inside a polygon?
|
||||
-- @param vec2[] A polygon, as a table of vec2
|
||||
-- @param vec2 A vec2
|
||||
-- @return True if vec2 is inside the polygon, false otherwise
|
||||
-------------------------------------
|
||||
function DCSEx.math.isPointInsidePolygon(polygon, vec2)
|
||||
if not polygon or not vec2 then return false end
|
||||
|
||||
local oddNodes = false
|
||||
local j = #polygon
|
||||
for i = 1, #polygon do
|
||||
if (polygon[i].y < vec2.y and polygon[j].y >= vec2.y or polygon[j].y < vec2.y and polygon[i].y >= vec2.y) then
|
||||
if
|
||||
(polygon[i].x + (vec2.y - polygon[i].y) / (polygon[j].y - polygon[i].y) * (polygon[j].x - polygon[i].x) <
|
||||
vec2.x)
|
||||
then
|
||||
oddNodes = not oddNodes
|
||||
end
|
||||
end
|
||||
j = i
|
||||
end
|
||||
|
||||
return oddNodes
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Compares two 2D or 3D points
|
||||
-- @param pointA a Point2 or Point3
|
||||
-- @param pointB another Point2 or Point3
|
||||
-- @return True if points are the same, false otherwise
|
||||
-------------------------------------
|
||||
function DCSEx.math.isSamePoint(pointA, pointB)
|
||||
if pointA.x ~= pointB.x then return false end
|
||||
if pointA.y ~= pointB.y then return false end
|
||||
if pointA.z or pointB.z then
|
||||
if pointA.z ~= pointB.z then return false end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Linearly interpolates between two numbers
|
||||
-- @param val0 Value vers l=0
|
||||
-- @param val1 Value vers l=1
|
||||
-- @param t Interpolation between 0 and 1
|
||||
-- @return A number value
|
||||
-------------------------------------
|
||||
function DCSEx.math.lerp(val0, val1, t)
|
||||
return val0 + t * (val1 - val0);
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Multiplies both the x and y components of a vec2 by a floating-point value
|
||||
-- @param vec2 A vec2
|
||||
-- @param mult A floating-point value
|
||||
-- @return A vec2
|
||||
-------------------------------------
|
||||
function DCSEx.math.multVec2(vec2, mult)
|
||||
return {x = vec2.x * mult, y = vec2.y * mult}
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns an normalized vec2
|
||||
-- @param unit A vec2
|
||||
-- @return A normalized vec2
|
||||
-------------------------------------
|
||||
function DCSEx.math.normalizeVec2(vec2)
|
||||
local length = DCSEx.math.getDistance2D(vec2)
|
||||
return {x = vec2.x / length, y = vec2.y / length}
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns a random boolean
|
||||
-- @return A boolean
|
||||
-------------------------------------
|
||||
function DCSEx.math.randomBoolean()
|
||||
return DCSEx.math.random(1, 2) == 1
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns a random floating-point number between min and max
|
||||
-- @param min Minimum floating-point value
|
||||
-- @param max Maximum floating-point value
|
||||
-- @return A number
|
||||
-------------------------------------
|
||||
function DCSEx.math.randomFloat(min, max)
|
||||
if min >= max then
|
||||
return min
|
||||
end
|
||||
|
||||
return min + math.random() * (max - min)
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns a random vec2 at a given distance of another vec2
|
||||
-- @param point Reference point
|
||||
-- @param distance Distance from the reference point
|
||||
-- @return A vec2
|
||||
-------------------------------------
|
||||
function DCSEx.math.randomPointAtDistance(point, distance)
|
||||
local angle = math.random() * math.pi * 2.0
|
||||
|
||||
local x = point.x + math.cos(angle) * distance
|
||||
local y = point.y + math.sin(angle) * distance
|
||||
return { x = x, y = y }
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns a random vec2 in circle of a given center and radius
|
||||
-- @param center Center of the circle as a vec2
|
||||
-- @param radius Radius of the circle
|
||||
-- @param minRadius (optional) Minimum inner radius circle in which points should not be spawned
|
||||
-- @param surfaceType (optional) If not nil, point must be of given surface type
|
||||
-- @return A vec2 or nil if no point was found
|
||||
-------------------------------------
|
||||
function DCSEx.math.randomPointInCircle(center, radius, minRadius, surfaceType)
|
||||
local minRadius = minRadius or 0
|
||||
local dist = minRadius + math.random() * (radius - minRadius)
|
||||
local angle = math.random() * math.pi * 2.0
|
||||
|
||||
local point = nil
|
||||
for i=1,36 do
|
||||
local x = center.x + math.cos(angle) * dist
|
||||
local y = center.y + math.sin(angle) * dist
|
||||
|
||||
point = { x = x, y = y }
|
||||
|
||||
if not surfaceType then return point end
|
||||
if land.getSurfaceType(point) == surfaceType then return point end
|
||||
angle = angle + 0.174533 -- Increment angle by 10° (in radians)
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns a random sign as a number, -1 or 1
|
||||
-- @return -1 50% of the time, 1 50% of the time
|
||||
-------------------------------------
|
||||
function DCSEx.math.randomSign()
|
||||
if math.random() < .5 then
|
||||
return -1
|
||||
end
|
||||
return 1
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Converts a value to a boolean
|
||||
-- @param val Value to convert
|
||||
-- @return A boolean, or nil if val was nil
|
||||
-------------------------------------
|
||||
function DCSEx.math.toBoolean(val)
|
||||
if val == nil then return nil end
|
||||
if not val then return false end
|
||||
if val == 0 then return false end
|
||||
if val:lower() == "false" or val:lower() == "no" or val:lower() == "off" then return false end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Converts a vec2 to a vec3
|
||||
-- @param vec2 A vec2
|
||||
-- @param y (Optional) A value for the vec3's y component or "land" to use land height
|
||||
-- @return A vec3 where v3.x=v2.x, v3.y=y and v3.z=v2.y
|
||||
-------------------------------------
|
||||
function DCSEx.math.vec2ToVec3(vec2, y)
|
||||
-- Value was already a vec3
|
||||
if vec2.z then
|
||||
return {x = vec2.x, y = vec2.y, z = vec2.z}
|
||||
end
|
||||
|
||||
y = y or 0
|
||||
if y == "land" then y = land.getHeight(vec2) end
|
||||
|
||||
return {x = vec2.x, y = y, z = vec2.y}
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Converts a vec3 to a vec2
|
||||
-- @param vec3 A vec3
|
||||
-- @return A vec2 where v2.x=v3.x and v2.y=v3.z
|
||||
-------------------------------------
|
||||
function DCSEx.math.vec3ToVec2(vec3)
|
||||
-- Value was already a vec2
|
||||
if not vec3.z then
|
||||
return {x = vec3.x, y = vec3.y}
|
||||
end
|
||||
|
||||
return {x = vec3.x, y = vec3.z}
|
||||
end
|
||||
87
Script/DCS extensions/String.lua
Normal file
87
Script/DCS extensions/String.lua
Normal file
@@ -0,0 +1,87 @@
|
||||
-- ====================================================================================
|
||||
-- (DCS LUA ADD-ON) STRING - EXTENSION TO THE "STRING" TABLE
|
||||
--
|
||||
-- DCSEx.string.firstToUpper(str)
|
||||
-- DCSEx.string.getReadingTime(message)
|
||||
-- DCSEx.string.split(str, separator)
|
||||
-- DCSEx.string.startsWith(haystack, needle)
|
||||
-- DCSEx.string.trim(str)
|
||||
-- ====================================================================================
|
||||
|
||||
DCSEx.string = {}
|
||||
|
||||
-------------------------------------
|
||||
-- Uppercases the fist letter of a string
|
||||
-- @param str A string
|
||||
-- @return A string, with the first letter cast to upper case
|
||||
-------------------------------------
|
||||
function DCSEx.string.firstToUpper(str)
|
||||
return (str:gsub("^%l", DCSEx.string.upper))
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Estimates the time (in seconds) required to read a string
|
||||
-- @param message A text message
|
||||
-- @return A duration in seconds
|
||||
-------------------------------------
|
||||
function DCSEx.string.getReadingTime(message)
|
||||
message = message or ""
|
||||
message = tostring(message)
|
||||
|
||||
return DCSEx.math.clamp(#message / 8.7, 3.0, 15.0) -- 10.7 letters per second, minimum length 3 seconds, max length 15 seconds
|
||||
end
|
||||
|
||||
-- TODO: description, update file header
|
||||
function DCSEx.string.join(table, separator)
|
||||
local joinedString = ""
|
||||
|
||||
for i=1,#table do
|
||||
joinedString = joinedString..tostring(table[i])
|
||||
if i < #table then
|
||||
joinedString = joinedString..separator
|
||||
end
|
||||
end
|
||||
|
||||
return joinedString
|
||||
end
|
||||
|
||||
-- TODO: description, file header
|
||||
-- Code from https://stackoverflow.com/questions/10989788/format-integer-in-lua
|
||||
function DCSEx.string.toStringThousandsSeparator(number)
|
||||
local i, j, minus, int, fraction = tostring(number):find('([-]?)(%d+)([.]?%d*)')
|
||||
int = int:reverse():gsub("(%d%d%d)", "%1,")
|
||||
return minus .. int:reverse():gsub("^,", "") .. fraction
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Splits a string
|
||||
-- @param str The string to split
|
||||
-- @param separator The string to split
|
||||
-- @return A table of split strings
|
||||
-------------------------------------
|
||||
function DCSEx.string.split(str, separator)
|
||||
separator = separator or "%s"
|
||||
|
||||
local t = {}
|
||||
for s in DCSEx.string.gmatch(str, "([^"..separator.."]+)") do table.insert(t, s) end
|
||||
return t
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Does a string starts with the given substring?
|
||||
-- @param haystack The string
|
||||
-- @param needle The substring to look for
|
||||
-- @return True if it starts with the substring, false otherwise
|
||||
-------------------------------------
|
||||
function DCSEx.string.startsWith(haystack, needle)
|
||||
return haystack:sub(1, #needle) == needle
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Trims a string
|
||||
-- @param str A string
|
||||
-- @return A string
|
||||
-------------------------------------
|
||||
function DCSEx.string.trim(str)
|
||||
return (str:gsub("^%s*(.-)%s*$", "%1"))
|
||||
end
|
||||
222
Script/DCS extensions/Table.lua
Normal file
222
Script/DCS extensions/Table.lua
Normal file
@@ -0,0 +1,222 @@
|
||||
-- ====================================================================================
|
||||
-- (DCS LUA ADD-ON) TABLE - EXTENSION TO THE "TABLE" TABLE
|
||||
--
|
||||
-- DCSEx.table.contains(t, val)
|
||||
-- DCSEx.table.containsKey(t, k)
|
||||
-- DCSEx.table.containsAll(t, values)
|
||||
-- DCSEx.table.containsAny(t, values)
|
||||
-- DCSEx.table.countNonNils(t)
|
||||
-- DCSEx.table.deepCopy(orig)
|
||||
-- DCSEx.table.dump(t)
|
||||
-- DCSEx.table.getKeys(t)
|
||||
-- DCSEx.table.getKeyFromValue(t, val)
|
||||
-- DCSEx.table.getRandom(t)
|
||||
-- DCSEx.table.getRandomIndex(t)
|
||||
-- DCSEx.table.shuffle(t)
|
||||
-- ====================================================================================
|
||||
|
||||
DCSEx.table = {}
|
||||
|
||||
-------------------------------------
|
||||
-- Returns true if table t contains value val
|
||||
-- @param t A table
|
||||
-- @param val A value
|
||||
-- @return True if the table contains the value, false otherwise
|
||||
-------------------------------------
|
||||
function DCSEx.table.contains(t, val)
|
||||
if not t then return false end
|
||||
|
||||
for _,v in pairs(t) do
|
||||
if v == val then return true end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns true if table t contains key k
|
||||
-- @param t A table
|
||||
-- @param k A key
|
||||
-- @return True if the table contains the key, false otherwise
|
||||
-------------------------------------
|
||||
function DCSEx.table.containsKey(t, k)
|
||||
if not t then return false end
|
||||
if t[k] then return true end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns true if table t contains all values in table values
|
||||
-- @param t A table
|
||||
-- @param values A table of values
|
||||
-- @return True if the table contains all values, false otherwise
|
||||
-------------------------------------
|
||||
function DCSEx.table.containsAll(t, values)
|
||||
if not t then return false end
|
||||
|
||||
for _,v in pairs(values) do
|
||||
if not DCSEx.table.contains(t, v) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns true if table t contains at least one value in table values
|
||||
-- @param t A table
|
||||
-- @param values A table of values
|
||||
-- @return True if the table contains at least one value, false otherwise
|
||||
-------------------------------------
|
||||
function DCSEx.table.containsAny(t, values)
|
||||
if not t then return false end
|
||||
|
||||
for _,v in pairs(values) do
|
||||
if DCSEx.table.contains(t, v) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function DCSEx.table.containsAllKeys(t, keys)
|
||||
if not t then return false end
|
||||
|
||||
for _,k in pairs(keys) do
|
||||
if not DCSEx.table.containsKey(t, k) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function DCSEx.table.containsAnyKeys(t, keys)
|
||||
if not t then return false end
|
||||
|
||||
for _,k in pairs(keys) do
|
||||
if DCSEx.table.containsKey(t, k) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns the number of non-nils elements in a table
|
||||
-- @param t A table
|
||||
-- @return A number
|
||||
-------------------------------------
|
||||
function DCSEx.table.countNonNils(t)
|
||||
local count = 0
|
||||
for _,v in pairs(t) do
|
||||
if v ~= nil then count = count + 1 end
|
||||
end
|
||||
|
||||
return count
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns a deep copy of the table, doesn't work with recursive tables (code from http://lua-users.org/wiki/CopyTable)
|
||||
-- @param orig A table
|
||||
-- @return A deep copied clone of the table
|
||||
-------------------------------------
|
||||
function DCSEx.table.deepCopy(orig)
|
||||
-- Not a table
|
||||
if type(orig) ~= "table" then
|
||||
return orig
|
||||
end
|
||||
|
||||
local copy = {}
|
||||
for orig_key, orig_value in next, orig, nil do
|
||||
copy[DCSEx.table.deepCopy(orig_key)] = DCSEx.table.deepCopy(orig_value)
|
||||
end
|
||||
setmetatable(copy, DCSEx.table.deepCopy(getmetatable(orig)))
|
||||
|
||||
return copy
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Dumps the content of a table as a string
|
||||
-- @param orig A table
|
||||
-- @return A string representaton of the table
|
||||
-------------------------------------
|
||||
function DCSEx.table.dump(t)
|
||||
if type(t) == 'table' then
|
||||
local s = '{ '
|
||||
for k,v in pairs(t) do
|
||||
if type(k) ~= 'number' then k = '"'..k..'"' end
|
||||
s = s .. '['..k..'] = ' .. DCSEx.table.dump(v) .. ','
|
||||
end
|
||||
return s .. '} '
|
||||
else
|
||||
return tostring(t)
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns the key associated to a value in a table, or nil if not found
|
||||
-- @param t A table
|
||||
-- @param val A value
|
||||
-- @return The key associated to this value in the table, or nil
|
||||
-------------------------------------
|
||||
function DCSEx.table.getKeyFromValue(t, val)
|
||||
for k, v in pairs(t) do
|
||||
if v == val then return k end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns all the keys in an associative table
|
||||
-- @param t A table
|
||||
-- @return An array of keys
|
||||
-------------------------------------
|
||||
function DCSEx.table.getKeys(t)
|
||||
local keys = {}
|
||||
local n = 1
|
||||
|
||||
for k,_ in pairs(t) do
|
||||
keys[n] = k
|
||||
n = n + 1
|
||||
end
|
||||
|
||||
table.sort(keys)
|
||||
return keys
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns a random value from a numerically-indexed table
|
||||
-- @param t A table
|
||||
-- @return A random element from the table
|
||||
-------------------------------------
|
||||
function DCSEx.table.getRandom(t)
|
||||
return t[math.random(#t)]
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns a random index from a numerically-indexed table
|
||||
-- @param t A table
|
||||
-- @return A random index from the table
|
||||
-------------------------------------
|
||||
function DCSEx.table.getRandomIndex(t)
|
||||
return math.random(#t)
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Randomly shuffles a numerically-indexed table
|
||||
-- @param t A table
|
||||
-- @return A table with shuffled values
|
||||
-------------------------------------
|
||||
function DCSEx.table.shuffle(t)
|
||||
local len, random = #t, math.random
|
||||
for i = len, 2, -1 do
|
||||
local j = random(1, i)
|
||||
t[i], t[j] = t[j], t[i]
|
||||
end
|
||||
return t
|
||||
end
|
||||
213
Script/DCS extensions/UnitCallsignMaker.lua
Normal file
213
Script/DCS extensions/UnitCallsignMaker.lua
Normal file
@@ -0,0 +1,213 @@
|
||||
DCSEx.unitCallsignMaker = {}
|
||||
|
||||
do
|
||||
local CALLSIGN_TYPE = {
|
||||
DEFAULT = 1,
|
||||
A10 = 2,
|
||||
AH64 = 3,
|
||||
AWACS = 4,
|
||||
B1B = 5,
|
||||
B52 = 6,
|
||||
F15E = 7,
|
||||
F16 = 8,
|
||||
F18 = 9,
|
||||
JTAC = 10,
|
||||
TANKER = 11,
|
||||
TRANSPORT = 12,
|
||||
}
|
||||
|
||||
local CALLSIGNS = {
|
||||
[CALLSIGN_TYPE.DEFAULT] = {
|
||||
[1] = "Enfield",
|
||||
[2] = "Springfield",
|
||||
[3] = "Uzi",
|
||||
[4] = "Colt",
|
||||
[5] = "Dodge",
|
||||
[6] = "Ford",
|
||||
[7] = "Chevy",
|
||||
[8] = "Pontiac",
|
||||
},
|
||||
[CALLSIGN_TYPE.A10] = {
|
||||
[9] = "Hawg",
|
||||
[10] = "Boar",
|
||||
[11] = "Pig",
|
||||
[12] = "Tusk",
|
||||
},
|
||||
[CALLSIGN_TYPE.AH64] = {
|
||||
[9] = "Army Air",
|
||||
[10] = "Apache",
|
||||
[11] = "Crow",
|
||||
[12] = "Sioux",
|
||||
[13] = "Gatling",
|
||||
[14] = "Gunslinger",
|
||||
[15] = "Hammerhead",
|
||||
[16] = "Bootleg",
|
||||
[17] = "Palehorse",
|
||||
[18] = "Carnivor",
|
||||
[19] = "Saber",
|
||||
},
|
||||
[CALLSIGN_TYPE.AWACS] = {
|
||||
[1] = "Overlord",
|
||||
[2] = "Magic",
|
||||
[3] = "Wizard",
|
||||
[4] = "Focus",
|
||||
[5] = "Darkstar",
|
||||
},
|
||||
[CALLSIGN_TYPE.B1B] = {
|
||||
[9] = "Bone",
|
||||
[10] = "Dark",
|
||||
[11] = "Vader",
|
||||
},
|
||||
[CALLSIGN_TYPE.B52] = {
|
||||
[9] = "Buff",
|
||||
[10] = "Dump",
|
||||
[11] = "Kenworth",
|
||||
},
|
||||
[CALLSIGN_TYPE.F15E] = {
|
||||
[9] = "Dude",
|
||||
[10] = "Thud",
|
||||
[11] = "Gunny",
|
||||
[12] = "Trek",
|
||||
[13] = "Sniper",
|
||||
[14] = "Sled",
|
||||
[15] = "Best",
|
||||
[16] = "Jazz",
|
||||
[17] = "Rage",
|
||||
[18] = "Tahoe",
|
||||
},
|
||||
[CALLSIGN_TYPE.F16] = {
|
||||
[9] = "Viper",
|
||||
[10] = "Venom",
|
||||
[11] = "Lobo",
|
||||
[12] = "Cowboy",
|
||||
[13] = "Python",
|
||||
[14] = "Rattler",
|
||||
[15] = "Panther",
|
||||
[16] = "Wolf",
|
||||
[17] = "Weasel",
|
||||
[18] = "Wild",
|
||||
[19] = "Ninja",
|
||||
[20] = "Jedi",
|
||||
},
|
||||
[CALLSIGN_TYPE.F18] = {
|
||||
[9] = "Hornet",
|
||||
[10] = "Squid",
|
||||
[11] = "Ragin",
|
||||
[12] = "Roman",
|
||||
[13] = "Sting",
|
||||
[14] = "Jury",
|
||||
[15] = "Joker",
|
||||
[16] = "Ram",
|
||||
[17] = "Hawk",
|
||||
[18] = "Devil",
|
||||
[19] = "Check",
|
||||
[20] = "Snake",
|
||||
},
|
||||
[CALLSIGN_TYPE.JTAC] = {
|
||||
[1] = "Axeman",
|
||||
[2] = "Darknight",
|
||||
[3] = "Warrior",
|
||||
[4] = "Pointer",
|
||||
[5] = "Eyeball",
|
||||
[6] = "Moonbeam",
|
||||
[7] = "Whiplash",
|
||||
[8] = "Finger",
|
||||
[9] = "Pinpoint",
|
||||
[10] = "Ferret",
|
||||
[11] = "Shaba",
|
||||
[12] = "Playboy",
|
||||
[13] = "Hammer",
|
||||
[14] = "Jaguar",
|
||||
[15] = "Deathstar",
|
||||
[16] = "Anvil",
|
||||
[17] = "Firefly",
|
||||
[18] = "Mantis",
|
||||
[19] = "Badger"
|
||||
},
|
||||
[CALLSIGN_TYPE.TANKER] = {
|
||||
[1] = "Texaco",
|
||||
[2] = "Arco",
|
||||
[3] = "Shell",
|
||||
},
|
||||
[CALLSIGN_TYPE.TRANSPORT] = {
|
||||
[9] = "Heavy",
|
||||
[10] = "Trash",
|
||||
[11] = "Cargo",
|
||||
[12] = "Ascot",
|
||||
},
|
||||
}
|
||||
|
||||
local currentCallsigns = {}
|
||||
for _,i in pairs(CALLSIGN_TYPE) do
|
||||
currentCallsigns[i] = { 1, 1, 1, 1, 1, 1, 1, 1, 1 }
|
||||
end
|
||||
|
||||
local function getCallsignTypeByUnitType(unitType)
|
||||
if not unitType then return CALLSIGN_TYPE.DEFAULT end
|
||||
|
||||
-- TODO: complete
|
||||
if unitType == "AH-64A" or unitType == "AH-64D" or unitType == "AH-64D_BLK_II" then return CALLSIGN_TYPE.A10 end
|
||||
if unitType == "A-10A" or unitType == "A-10C" or unitType == "A-10C_2" then return CALLSIGN_TYPE.A10 end
|
||||
if unitType == "F-15E" or unitType == "F-15ESE" then return CALLSIGN_TYPE.F15E end
|
||||
if unitType == "F-16A" or unitType == "F-16A MLU" or unitType == "F-16C bl.50" or unitType == "F-16C bl.52d" or unitType == "F-16C_50" then return CALLSIGN_TYPE.F16 end
|
||||
if unitType == "FA-18C" or unitType == "FA-18C_hornet" then return CALLSIGN_TYPE.F18 end
|
||||
-- if unitType == "A-50" or unitType == "E-2C" or unitType == "E-3A" then return CALLSIGN_TYPE.AWACS end
|
||||
|
||||
local unitDesc = Unit.getDescByName(unitType)
|
||||
if not unitDesc then return CALLSIGN_TYPE.DEFAULT end
|
||||
|
||||
if unitDesc.attributes["AWACS"] then return CALLSIGN_TYPE.AWACS end
|
||||
if unitDesc.attributes["Tankers"] then return CALLSIGN_TYPE.TANKER end
|
||||
|
||||
return CALLSIGN_TYPE.DEFAULT
|
||||
end
|
||||
|
||||
local function incrementCallsign(callsignName, callsignNumber)
|
||||
if not callsignName then return end
|
||||
callsignNumber = math.max(1, callsignNumber or 1)
|
||||
|
||||
for k,_ in pairs(CALLSIGNS) do
|
||||
for i,_ in pairs(CALLSIGNS[k]) do
|
||||
if CALLSIGNS[k][i]:lower() == callsignName:lower() then
|
||||
currentCallsigns[k][i] = math.max(currentCallsigns[k][i], callsignNumber + 1)
|
||||
if (currentCallsigns[k][i] > 9) then currentCallsigns[k][i] = 1 end -- More than 9? Loop back to 1.
|
||||
-- TUM.log("Callsign "..CALLSIGNS[k][i].." set to "..tostring(callsignNumber))
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function DCSEx.unitCallsignMaker.getCallsign(unitType)
|
||||
local csType = getCallsignTypeByUnitType(unitType)
|
||||
|
||||
local csNameIndex = DCSEx.table.getRandom(DCSEx.table.getKeys(CALLSIGNS[csType]))
|
||||
if not currentCallsigns[csType][csNameIndex] then currentCallsigns[csType][csNameIndex] = 1 end
|
||||
local csNumber = currentCallsigns[csType][csNameIndex]
|
||||
|
||||
currentCallsigns[csType][csNameIndex] = currentCallsigns[csType][csNameIndex] + 1
|
||||
if currentCallsigns[csType][csNameIndex] > 9 then currentCallsigns[csType][csNameIndex] = 1 end
|
||||
|
||||
local callsignTable = {
|
||||
[1] = csNameIndex,
|
||||
[2] = csNumber,
|
||||
-- [3] = 1,
|
||||
["name"] = CALLSIGNS[csType][csNameIndex]..tostring(csNumber),
|
||||
}
|
||||
|
||||
return callsignTable
|
||||
end
|
||||
|
||||
do
|
||||
local missionGroups = DCSEx.envMission.getGroups()
|
||||
for _,g in ipairs(missionGroups) do
|
||||
if g.units and g.units[1] then
|
||||
local unit = g.units[1]
|
||||
if unit.callsign and unit.callsign[1] and unit.callsign.name then
|
||||
local callsignName = unit.callsign.name:sub(1, #unit.callsign.name - 2)
|
||||
incrementCallsign(callsignName, unit.callsign[2])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
453
Script/DCS extensions/UnitGroupMaker.lua
Normal file
453
Script/DCS extensions/UnitGroupMaker.lua
Normal file
@@ -0,0 +1,453 @@
|
||||
-- ====================================================================================
|
||||
-- DCSEX.UNITGROUMAKER - CREATES AND ADDS GROUPS TO THE GAME WORLD
|
||||
--
|
||||
-- (local) createGroupTable(groupID, groupCategory, options)
|
||||
-- (local) getDefaultUnitSpread(groupCategory)
|
||||
-- (local) getNextGroupID()
|
||||
-- (local) getNextUnitID()
|
||||
-- (local) setAircraftTaskAwacs(groupTable)
|
||||
-- (local) setAircraftTaskOrbit(groupTable, options)
|
||||
-- (local) setCommand(groupTable, actionID, actionValue)
|
||||
-- (local) setOption(groupTable, optionID, optionValue)
|
||||
-- DCSEx.unitGroupMaker.create(coalitionID, groupCategory, vec2, unitTypes, options)
|
||||
-- DCSEx.unitGroupMaker.initialize()
|
||||
-- ====================================================================================
|
||||
|
||||
DCSEx.unitGroupMaker = {}
|
||||
|
||||
do
|
||||
local nextGroupID = 1 -- ID of the next generated group
|
||||
local nextUnitID = 1 -- ID of the next generated unit
|
||||
|
||||
local function createGroupTable(groupID, groupCategory, options)
|
||||
local groupTable = {
|
||||
groupId = groupID,
|
||||
hidden = options.hidden,
|
||||
name = options.name,
|
||||
route = {points = {}},
|
||||
start_time = 0,
|
||||
taskSelected = true,
|
||||
uncontrollable = true,
|
||||
visible = false,
|
||||
units = {},
|
||||
}
|
||||
|
||||
if groupCategory == Group.Category.GROUND then
|
||||
groupTable.task = "Ground Nothing"
|
||||
groupTable.tasks = {}
|
||||
elseif groupCategory == Group.Category.AIRPLANE or groupCategory == Group.Category.HELICOPTER then
|
||||
groupTable.uncontrolled = options.uncontrolled or false
|
||||
end
|
||||
|
||||
return groupTable
|
||||
end
|
||||
|
||||
local function getDefaultUnitSpread(groupCategory)
|
||||
if groupCategory == Group.Category.AIRPLANE or groupCategory == Group.Category.HELICOPTER then
|
||||
return 150 -- TODO: improve
|
||||
elseif groupCategory == Group.Category.SHIP then
|
||||
return math.random(250, 400)
|
||||
else
|
||||
return math.random(20, 30)
|
||||
end
|
||||
end
|
||||
|
||||
-- Returns the next available group ID and increment the number
|
||||
local function getNextGroupID()
|
||||
nextGroupID = nextGroupID + 1
|
||||
return nextGroupID - 1
|
||||
end
|
||||
|
||||
-- Returns the next available unit ID and increment the number
|
||||
local function getNextUnitID()
|
||||
nextUnitID = nextUnitID + 1
|
||||
return nextUnitID - 1
|
||||
end
|
||||
|
||||
local function setAircraftTaskAwacs(groupTable)
|
||||
groupTable.frequency = 251000000
|
||||
groupTable.task = "AWACS"
|
||||
|
||||
table.insert(groupTable.route.points[1].task.params.tasks,
|
||||
{
|
||||
["enabled"] = true,
|
||||
["auto"] = true,
|
||||
["id"] = "AWACS",
|
||||
["number"] = #groupTable.route.points[1].task.params.tasks + 1,
|
||||
["params"] = { },
|
||||
})
|
||||
|
||||
return groupTable
|
||||
end
|
||||
|
||||
local function setAircraftTaskCAP(groupTable)
|
||||
groupTable.task = "CAP"
|
||||
|
||||
table.insert(groupTable.route.points[1].task.params.tasks,
|
||||
{
|
||||
["enabled"] = true,
|
||||
["auto"] = true,
|
||||
["id"] = "EngageTargets",
|
||||
["number"] = #groupTable.route.points[1].task.params.tasks + 1,
|
||||
["params"] = {
|
||||
maxDist = DCSEx.converter.nmToMeters(60),
|
||||
maxDistEnabled = false,
|
||||
-- targetTypes = { "Planes", "Helicopters" },
|
||||
targetTypes = { "Fighters", "Interceptors", "Multirole fighters" },
|
||||
priority = 0
|
||||
},
|
||||
})
|
||||
|
||||
return groupTable
|
||||
end
|
||||
|
||||
local function setAircraftTaskOrbit(groupTable, options)
|
||||
-- TODO: oval orbit
|
||||
table.insert(groupTable.route.points[#groupTable.route.points].task.params.tasks,
|
||||
{
|
||||
["enabled"] = true,
|
||||
["auto"] = false,
|
||||
["id"] = "Orbit",
|
||||
["number"] = #groupTable.route.points[#groupTable.route.points].task.params.tasks + 1,
|
||||
["params"] =
|
||||
{
|
||||
["altitude"] = options.altitude,
|
||||
["pattern"] = "Circle",
|
||||
["speed"] = options.speed,
|
||||
},
|
||||
})
|
||||
|
||||
return groupTable
|
||||
end
|
||||
|
||||
local function setCommand(groupTable, actionID, actionValue)
|
||||
table.insert(
|
||||
groupTable.route.points[1].task.params.tasks,
|
||||
{
|
||||
["auto"] = false,
|
||||
["enabled"] = true,
|
||||
["id"] = "WrappedAction",
|
||||
["number"] = #groupTable.route.points[1].task.params.tasks + 1,
|
||||
["params"] = { ["action"] = { ["id"] = actionID, ["params"] = { ["value"] = actionValue, }, }, },
|
||||
})
|
||||
end
|
||||
|
||||
local function setOption(groupTable, optionID, optionValue)
|
||||
table.insert(
|
||||
groupTable.route.points[1].task.params.tasks,
|
||||
{
|
||||
["auto"] = false,
|
||||
["enabled"] = true,
|
||||
["id"] = "WrappedAction",
|
||||
["number"] = #groupTable.route.points[1].task.params.tasks + 1,
|
||||
["params"] = { ["action"] = { ["id"] = "Option", ["params"] = { ["name"] = optionID, ["value"] = optionValue, }, }, },
|
||||
})
|
||||
end
|
||||
|
||||
function DCSEx.unitGroupMaker.createStatic(side, point2, typeName, shapeName, heading, dead)
|
||||
heading = heading or DCSEx.converter.degreesToRadians(math.random(0, 359))
|
||||
dead = dead or false
|
||||
local unitID = getNextUnitID()
|
||||
|
||||
local staticObj = {
|
||||
["heading"] = 0,
|
||||
["groupId"] = getNextGroupID(),
|
||||
["shape_name"] = shapeName,
|
||||
["type"] = typeName,
|
||||
["unitId"] = unitID,
|
||||
["rate"] = 100,
|
||||
["name"] = "Static #"..tostring(unitID),
|
||||
["category"] = "Fortifications",
|
||||
["y"] = point2.y,
|
||||
["x"] = point2.x,
|
||||
["dead"] = dead,
|
||||
}
|
||||
|
||||
coalition.addStaticObject(DCSEx.dcs.getCJTFForCoalition(side), staticObj)
|
||||
|
||||
return unitID
|
||||
end
|
||||
|
||||
function DCSEx.unitGroupMaker.create(coalitionID, groupCategory, vec2, unitTypes, options)
|
||||
if not unitTypes or #unitTypes == 0 then return nil end -- No unit types provided
|
||||
if type(unitTypes) == "string" then unitTypes = { unitTypes } end -- Single unit type provided, make it a table of size 1
|
||||
|
||||
local aircraftDB = nil -- Aircraft entry in the DB for airplane/helicopter units
|
||||
local destVec2 = nil -- Destination point (for moving units)
|
||||
local groupID = getNextGroupID() -- Get a new unique ID for the group
|
||||
local isAirUnit = false
|
||||
|
||||
local hidden = false
|
||||
if not TUM.DEBUG_MODE and coalitionID ~= TUM.settings.getPlayerCoalition() then
|
||||
hidden = true
|
||||
end
|
||||
|
||||
-- Setup options
|
||||
options = options or {}
|
||||
--options.heading = nil
|
||||
options.altitude = options.altitude or 0
|
||||
options.altitudeType = options.altitudeType or "BARO"
|
||||
options.hidden = hidden
|
||||
options.isMoving = false
|
||||
options.livery = options.livery or "default"
|
||||
options.name = DCSEx.unitNamesMaker.getName(groupID, unitTypes)
|
||||
options.pointAction = "Turning Point"
|
||||
options.skill = options.skill or "Average"
|
||||
options.speed = 5.5555556
|
||||
options.spreadDistance = options.spreadDistance or getDefaultUnitSpread(groupCategory)
|
||||
options.spreadOffset = options.spreadOffset or 0
|
||||
|
||||
-- Movement point (for units with a second WP)
|
||||
if options.moveTo then
|
||||
options.isMoving = true
|
||||
destVec2 = DCSEx.table.deepCopy(options.moveTo)
|
||||
elseif options.moveBy then
|
||||
options.isMoving = true
|
||||
local angle = DCSEx.converter.degreesToRadians(math.random(0, 359))
|
||||
destVec2 = { x = vec2.x + math.cos(angle) * options.moveBy, y = vec2.y + math.sin(angle) * options.moveBy }
|
||||
end
|
||||
|
||||
-- Category specific options
|
||||
if groupCategory == Group.Category.GROUND then
|
||||
options.pointAction = "Off Road"
|
||||
|
||||
local desc = Unit.getDescByName(unitTypes[1])
|
||||
if desc and desc.attributes and desc.attributes.Infantry then options.speed = 1.66667 end
|
||||
|
||||
-- Check position and formation for moving ground units
|
||||
if options.isMoving then
|
||||
if options.onRoad then
|
||||
options.pointAction = "On Road"
|
||||
vec2 = DCSEx.world.getClosestPointOnRoadsVec2(vec2)
|
||||
destVec2 = DCSEx.world.getClosestPointOnRoadsVec2(destVec2)
|
||||
else
|
||||
options.pointAction = options.formation or DCSEx.table.getRandom({"Rank", "Cone", "Vee", "Diamond", "EchelonL", "EchelonR"})
|
||||
end
|
||||
end
|
||||
|
||||
options.livery = "default" -- TODO: getSeasonalLivery()
|
||||
elseif groupCategory == Group.Category.AIRPLANE or groupCategory == Group.Category.HELICOPTER then
|
||||
isAirUnit = true
|
||||
|
||||
-- Plane/helicopter groups always use a single unit type
|
||||
for i=1,#unitTypes do
|
||||
unitTypes[i] = unitTypes[1]
|
||||
end
|
||||
|
||||
-- Get unit info from aircraft database
|
||||
aircraftDB = Library.aircraft[unitTypes[1]]
|
||||
if not aircraftDB then return nil end -- Unit wasn't found in the database, abort group creation
|
||||
|
||||
-- Pick a random livery if available
|
||||
-- if aircraftDB.liveries and aircraftDB.liveries[coalitionID] then
|
||||
-- options.livery = table.getRandom(aircraftDB.liveries[coalitionID])
|
||||
-- end
|
||||
|
||||
options.altitude = aircraftDB.altitude or DCSEx.converter.feetToMeters(15000)
|
||||
options.altitudeType = "BARO"
|
||||
options.speed = DCSEx.math.randomFloat(0.9, 1.1) * (aircraftDB.speed or 250)
|
||||
end
|
||||
|
||||
-- First unit of the group is a template, use a group template instead of enumerating the types in unitTypes
|
||||
local groupTemplate = nil
|
||||
if Library.groupTemplates[unitTypes[1]] then
|
||||
groupTemplate = DCSEx.table.deepCopy(Library.groupTemplates[unitTypes[1]])
|
||||
unitTypes = {}
|
||||
for i=1,#groupTemplate do
|
||||
table.insert(unitTypes, groupTemplate[i].name)
|
||||
end
|
||||
end
|
||||
|
||||
-- Create group table
|
||||
local groupTable = createGroupTable(groupID, groupCategory, options)
|
||||
groupTable.x = vec2.x
|
||||
groupTable.y = vec2.y
|
||||
|
||||
-- Initial waypoint
|
||||
table.insert(
|
||||
groupTable.route.points,
|
||||
{
|
||||
action = options.pointAction,
|
||||
alt = options.altitude,
|
||||
alt_type = options.altitudeType,
|
||||
ETA = 0.0,
|
||||
ETA_locked = false,
|
||||
formation_template = "",
|
||||
name = "WP1",
|
||||
speed = options.speed,
|
||||
speed_locked = true,
|
||||
task = {id = "ComboTask", params = {tasks = {}}},
|
||||
type = "Turning Point",
|
||||
x = vec2.x,
|
||||
y = vec2.y
|
||||
}
|
||||
)
|
||||
|
||||
if options.takeOff and (groupCategory == Group.Category.AIRPLANE or groupCategory == Group.Category.HELICOPTER) then
|
||||
groupTable.route.points[1].alt = 250
|
||||
groupTable.route.points[1].alt_type = "RADIO"
|
||||
end
|
||||
|
||||
if destVec2 then -- There's a destination, add a second waypoint
|
||||
local destPoint = {
|
||||
action = options.pointAction,
|
||||
alt_type = options.altitudeType,
|
||||
alt = options.altitude,
|
||||
ETA = 0.0,
|
||||
ETA_locked = false,
|
||||
formation_template = "",
|
||||
name = "WP2",
|
||||
speed = options.speed,
|
||||
task = {
|
||||
id = "ComboTask",
|
||||
params = { tasks = { } }
|
||||
},
|
||||
type = "Turning Point",
|
||||
x = destVec2.x,
|
||||
y = destVec2.y,
|
||||
speed_locked = true
|
||||
}
|
||||
|
||||
-- Ground/ship groups loop between their waypoints
|
||||
if groupCategory == Group.Category.GROUND or groupCategory == Group.Category.SHIP then
|
||||
if not options.noLoop then
|
||||
table.insert(
|
||||
destPoint.task.params.tasks,
|
||||
{
|
||||
enabled = true,
|
||||
auto = false,
|
||||
id = "GoToWaypoint",
|
||||
number = 1,
|
||||
params = {
|
||||
fromWaypointIndex = 2,
|
||||
nWaypointIndx = 1
|
||||
}
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(groupTable.route.points, destPoint)
|
||||
end
|
||||
|
||||
-- Add various options/commands
|
||||
if options.disableWeapons then setOption(groupTable, AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.WEAPON_HOLD) end -- Values are from the AI.Option.Ground tables, but they're the same for all (ROE=0, WEAPON_HOLD=4)
|
||||
if options.immortal then setCommand(groupTable, "SetImmortal", true) end
|
||||
if options.invisible then setCommand(groupTable, "SetInvisible", true) end
|
||||
if options.silenced then setOption(groupTable, AI.Option.Air.id.SILENCE, true) end
|
||||
if options.unlimitedFuel then setCommand(groupTable, "SetUnlimitedFuel", true) end
|
||||
|
||||
local groupCallsign = nil
|
||||
|
||||
if isAirUnit then
|
||||
if options.taskAwacs then setAircraftTaskAwacs(groupTable) end
|
||||
if options.taskCAP then setAircraftTaskCAP(groupTable) end
|
||||
setAircraftTaskOrbit(groupTable, options)
|
||||
groupCallsign = DCSEx.unitCallsignMaker.getCallsign(unitTypes[1])
|
||||
groupTable.name = groupCallsign.name
|
||||
end
|
||||
|
||||
local unitsID = {}
|
||||
|
||||
-----------------------
|
||||
-- Create units
|
||||
-----------------------
|
||||
for i=1,#unitTypes do
|
||||
local unitID = getNextUnitID()
|
||||
table.insert(unitsID, unitID)
|
||||
|
||||
local unitHeading, unitOffset
|
||||
|
||||
if groupTemplate then -- Use offset and heading from the group
|
||||
unitHeading = groupTemplate[i].heading
|
||||
unitOffset = { x = groupTemplate[i].dx, y = groupTemplate[i].dy }
|
||||
else -- Create offset and heading
|
||||
unitHeading = options.heading or DCSEx.math.randomFloat(0, DCSEx.math.TWO_PI)
|
||||
unitOffset =
|
||||
DCSEx.math.multVec2(
|
||||
DCSEx.math.getVec2FromAngle(unitHeading),
|
||||
options.spreadDistance * (options.spreadOffset + i - 1)
|
||||
)
|
||||
end
|
||||
|
||||
local unitType = unitTypes[i]
|
||||
|
||||
local unitTable = {
|
||||
coldAtStart = false,
|
||||
heading = unitHeading,
|
||||
livery = options.livery,
|
||||
name = options.name.." #"..tostring(i),
|
||||
playerCanDrive = false,
|
||||
skill = options.skill,
|
||||
transportable = {randomTransportable = false},
|
||||
type = unitType,
|
||||
unitId = unitID,
|
||||
x = vec2.x + unitOffset.x,
|
||||
y = vec2.y + unitOffset.y
|
||||
}
|
||||
|
||||
if isAirUnit and aircraftDB then
|
||||
unitTable.hardpoint_racks = true
|
||||
unitTable.psi = 1.7703702498393
|
||||
|
||||
unitTable.callsign = DCSEx.table.deepCopy(groupCallsign)
|
||||
unitTable.callsign.name = unitTable.callsign.name..tostring(i)
|
||||
unitTable.callsign[3] = i
|
||||
unitTable.name = unitTable.callsign.name
|
||||
|
||||
-- Special properties for unit
|
||||
if aircraftDB.properties then
|
||||
unitTable.AddPropAircraft = DCSEx.table.deepCopy(aircraftDB.properties)
|
||||
end
|
||||
|
||||
-- Common payload (fuel, gun ammo, etc)
|
||||
if aircraftDB.payload then
|
||||
unitTable.payload = DCSEx.table.deepCopy(aircraftDB.payload)
|
||||
end
|
||||
|
||||
if aircraftDB.pylons then
|
||||
-- TODO: pylons according to task
|
||||
if options.taskAttack and aircraftDB.pylons.attack then
|
||||
unitTable.payload.pylons = DCSEx.table.deepCopy(aircraftDB.pylons.attack)
|
||||
elseif options.taskCAP and aircraftDB.pylons.cap then
|
||||
unitTable.payload.pylons = DCSEx.table.deepCopy(aircraftDB.pylons.cap)
|
||||
elseif options.taskSEAD and aircraftDB.pylons.sead then
|
||||
unitTable.payload.pylons = DCSEx.table.deepCopy(aircraftDB.pylons.sead)
|
||||
elseif options.taskStrike and aircraftDB.pylons.strike then
|
||||
unitTable.payload.pylons = DCSEx.table.deepCopy(aircraftDB.pylons.strike)
|
||||
else
|
||||
unitTable.payload.pylons = DCSEx.table.deepCopy(aircraftDB.pylons.default)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(groupTable.units, unitTable)
|
||||
end
|
||||
|
||||
coalition.addGroup(DCSEx.dcs.getCJTFForCoalition(coalitionID), groupCategory, groupTable)
|
||||
|
||||
return {
|
||||
callsign = groupCallsign,
|
||||
groupID = groupID,
|
||||
unitsID = unitsID,
|
||||
unitTypeNames = DCSEx.table.deepCopy(unitTypes) -- Not always the same as the unitTypes input parameter, because of group templates
|
||||
}
|
||||
end
|
||||
|
||||
------------------------------------------------------
|
||||
-- INITIALIZE THE GROUP MAKER
|
||||
-- Look for maximum groupID and unitID already in use to make sure we don't use an existing ID when spawning units
|
||||
------------------------------------------------------
|
||||
local groupData = DCSEx.envMission.getGroups()
|
||||
|
||||
for _,g in ipairs(groupData) do
|
||||
if g.groupId >= nextGroupID then
|
||||
nextGroupID = g.groupId + 1
|
||||
end
|
||||
|
||||
for __,u in ipairs(g.units) do
|
||||
if u.unitId >= nextUnitID then
|
||||
nextUnitID = u.unitId + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
92
Script/DCS extensions/UnitNamesMaker.lua
Normal file
92
Script/DCS extensions/UnitNamesMaker.lua
Normal file
@@ -0,0 +1,92 @@
|
||||
DCSEx.unitNamesMaker = {}
|
||||
|
||||
do
|
||||
local function getSectionNameSuffixAir()
|
||||
return DCSEx.table.getRandom({"squadron", "wing"})
|
||||
end
|
||||
|
||||
local function getSectionNameSuffixGround()
|
||||
return DCSEx.table.getRandom({"bataillon", "brigade", "corps", "division", "regiment"})
|
||||
end
|
||||
|
||||
local function getSectionNameSuffixNaval()
|
||||
return DCSEx.table.getRandom({"fleet", "force"})
|
||||
end
|
||||
|
||||
local function getDefaultSectionName()
|
||||
return "unit "..getSectionNameSuffixGround()
|
||||
end
|
||||
|
||||
local function getNameByDesc(unitDesc)
|
||||
if not unitDesc then return getDefaultSectionName() end
|
||||
|
||||
if unitDesc.attributes["UAVs"] then
|
||||
return DCSEx.table.getRandom({"unmanned"}).." "..getSectionNameSuffixAir()
|
||||
end
|
||||
if unitDesc.attributes["Bombers"] then
|
||||
return DCSEx.table.getRandom({"bomber"}).." "..getSectionNameSuffixAir()
|
||||
end
|
||||
if unitDesc.attributes["Fighters"] or unitDesc.attributes["Interceptors"] or unitDesc.attributes["Battle airplanes"] then
|
||||
return DCSEx.table.getRandom({"fighter", "fighting"}).." "..getSectionNameSuffixAir()
|
||||
end
|
||||
if unitDesc.attributes["Planes"] then
|
||||
return DCSEx.table.getRandom({"air", "cargo", "transport"}).." "..getSectionNameSuffixAir()
|
||||
end
|
||||
|
||||
if unitDesc.attributes["Attack helicopters"] then
|
||||
return DCSEx.table.getRandom({"assault", "rotary"}).." "..getSectionNameSuffixAir()
|
||||
end
|
||||
if unitDesc.attributes["Helicopters"] then
|
||||
return DCSEx.table.getRandom({"tactical"}).." "..getSectionNameSuffixAir()
|
||||
end
|
||||
|
||||
if unitDesc.attributes["Aircraft Carriers"] then
|
||||
return DCSEx.table.getRandom({"carrier"}).." "..getSectionNameSuffixNaval()
|
||||
end
|
||||
if unitDesc.attributes["Unarmed ships"] then
|
||||
return DCSEx.table.getRandom({"cargo", "logistical"}).." "..getSectionNameSuffixNaval()
|
||||
end
|
||||
if unitDesc.attributes["Ships"] then
|
||||
return DCSEx.table.getRandom({"expeditionary", "assault"}).." "..getSectionNameSuffixNaval()
|
||||
end
|
||||
|
||||
if unitDesc.attributes["MANPADS AUX"] or unitDesc.attributes["MANPADS"] or unitDesc.attributes["Armed Air Defence"] or unitDesc.attributes["SAM elements"] or unitDesc.attributes["SAM related"] or unitDesc.attributes["SAM"] then
|
||||
return DCSEx.table.getRandom({"air defense"}).." "..getSectionNameSuffixGround()
|
||||
end
|
||||
|
||||
if unitDesc.attributes["Tanks"] or unitDesc.attributes["HeavyArmoredUnits"] or unitDesc.attributes["LightArmoredUnits"] then
|
||||
return DCSEx.table.getRandom({"armor", "heavy"}).." "..getSectionNameSuffixGround()
|
||||
end
|
||||
if unitDesc.attributes["LightArmoredUnits"] then
|
||||
return DCSEx.table.getRandom({"armored", "mechanized"}).." "..getSectionNameSuffixGround()
|
||||
end
|
||||
if unitDesc.attributes["Ground vehicles"] then
|
||||
return DCSEx.table.getRandom({"engineer","logistical","support","tactical"}).." "..getSectionNameSuffixGround()
|
||||
end
|
||||
if unitDesc.attributes["Infantry"] then
|
||||
return DCSEx.table.getRandom({"infantry"}).." "..getSectionNameSuffixGround()
|
||||
end
|
||||
|
||||
return getDefaultSectionName()
|
||||
end
|
||||
|
||||
function DCSEx.unitNamesMaker.getName(groupID, unitTypes)
|
||||
local nameNumber = tostring(groupID)
|
||||
|
||||
if (nameNumber:sub(-1) == "1") then nameNumber = nameNumber.."st"
|
||||
elseif (nameNumber:sub(-1) == "2") then nameNumber = nameNumber.."nd"
|
||||
elseif (nameNumber:sub(-1) == "3") then nameNumber = nameNumber.."rd"
|
||||
else nameNumber = nameNumber.."th"
|
||||
end
|
||||
|
||||
local nameSection = getDefaultSectionName()
|
||||
if unitTypes and #unitTypes > 0 then
|
||||
local desc = Unit.getDescByName(unitTypes[1])
|
||||
if desc then
|
||||
nameSection = getNameByDesc(desc)
|
||||
end
|
||||
end
|
||||
|
||||
return nameNumber.." "..nameSection
|
||||
end
|
||||
end
|
||||
497
Script/DCS extensions/World.lua
Normal file
497
Script/DCS extensions/World.lua
Normal file
@@ -0,0 +1,497 @@
|
||||
-- ====================================================================================
|
||||
-- WORLDTOOLS - FUNCTIONS RELATED TO THE GAME WORLD
|
||||
-- ====================================================================================
|
||||
-- DCSEx.world.collidesWithScenery(vec2, radius)
|
||||
-- DCSEx.world.findSpawnPoint(vec2, minRadius, maxRadius, surfaceType, radiusWithoutScenery)
|
||||
-- DCSEx.world.getAllPlayers()
|
||||
-- DCSEx.world.getAllSceneryBuildings(minHealth)
|
||||
-- DCSEx.world.getAllUnits(unitCategory)
|
||||
-- DCSEx.world.getClosestPointOnRoadsVec2(vec2)
|
||||
-- DCSEx.world.getCoordinatesAsString(point)
|
||||
-- DCSEx.world.getCurrentMarkerID()
|
||||
-- DCSEx.world.getGroupByID(groupID)
|
||||
-- DCSEx.world.getNextMarkerID()
|
||||
-- DCSEx.world.getSceneriesInZone(center, radius, minHealth)
|
||||
-- DCSEx.world.getTerrainHeightDiff(coord, searchRadius)
|
||||
-- DCSEx.world.getUnitByID(unitID)
|
||||
-- DCSEx.world.isGroupAlive(g, unitsMustBeInAir)
|
||||
-- DCSEx.world.setUnitLifePercent(unitID, life)
|
||||
-- ====================================================================================
|
||||
|
||||
DCSEx.world = { }
|
||||
|
||||
do
|
||||
-- TODO: get max marker already in use from envMission
|
||||
local nextMarkerId = 1 -- Next map marker ID
|
||||
|
||||
function DCSEx.world.collidesWithScenery(vec2, radius)
|
||||
local foundOne = false
|
||||
radius = radius or 8
|
||||
|
||||
local volS = {
|
||||
id = world.VolumeType.SPHERE,
|
||||
params = {
|
||||
point = DCSEx.math.vec2ToVec3(vec2, "land"),
|
||||
radius = radius
|
||||
}
|
||||
}
|
||||
|
||||
local ifFound = function(foundItem, val)
|
||||
foundOne = true
|
||||
return true
|
||||
end
|
||||
|
||||
world.searchObjects(Object.Category.SCENERY, volS, ifFound)
|
||||
|
||||
return foundOne
|
||||
end
|
||||
|
||||
-- function DCSEx.world.findSpawnPoint(vec2, minRadius, maxRadius, surfaceType, radiusWithoutScenery, territorySide, expandSearch)
|
||||
-- expandSearch = expandSearch or true
|
||||
|
||||
-- for _=0,16 do
|
||||
-- for _=0,16 do
|
||||
-- local spawnPoint = nil
|
||||
|
||||
-- spawnPoint = DCSEx.math.randomPointInCircle(
|
||||
-- vec2,
|
||||
-- DCSEx.converter.nmToMeters(maxRadius),
|
||||
-- DCSEx.converter.nmToMeters(minRadius),
|
||||
-- surfaceType)
|
||||
|
||||
-- if spawnPoint and radiusWithoutScenery then
|
||||
-- if DCSEx.world.collidesWithScenery(spawnPoint, radiusWithoutScenery) then
|
||||
-- spawnPoint = nil
|
||||
-- end
|
||||
-- end
|
||||
|
||||
-- if spawnPoint and territorySide then
|
||||
-- if scramble.territories.getOwner(spawnPoint) ~= territorySide then
|
||||
-- spawnPoint = nil
|
||||
-- end
|
||||
-- end
|
||||
|
||||
-- if spawnPoint then return spawnPoint end
|
||||
-- end
|
||||
|
||||
-- if not expandSearch then return nil end
|
||||
|
||||
-- minRadius = minRadius * 0.9
|
||||
-- maxRadius = maxRadius * 1.2
|
||||
-- end
|
||||
|
||||
-- return nil
|
||||
-- end
|
||||
|
||||
|
||||
-------------------------------------
|
||||
-- Returns a table of all player-controlled units currently in the game
|
||||
-- @return A table of unit objects
|
||||
-------------------------------------
|
||||
function DCSEx.world.getAllPlayers()
|
||||
local players = {}
|
||||
|
||||
for coalitionID=0,2 do
|
||||
local coalPlayers = coalition.getPlayers(coalitionID)
|
||||
for _,p in pairs(coalPlayers) do
|
||||
table.insert(players, p)
|
||||
end
|
||||
end
|
||||
|
||||
return players
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns a table of all map scenery buildings.
|
||||
-- This function is rather CPU-consuming, better run it once on mission start and store the result in table.
|
||||
-- @param minHealth Minimum health a building must have to be included in the table
|
||||
-- @return A table of scenery objects
|
||||
-------------------------------------
|
||||
function DCSEx.world.getAllSceneryBuildings(minHealth)
|
||||
minHealth = minHealth or 0
|
||||
local sceneries = {}
|
||||
|
||||
local scenerySearchvolume = {
|
||||
id = world.VolumeType.SPHERE,
|
||||
params = { point = { x = env.mission.map.centerX, y = 0, z = env.mission.map.centerY }, radius = 10000000 }
|
||||
}
|
||||
|
||||
local function ifSceneryFound(foundScenery, val)
|
||||
local desc = foundScenery:getDesc()
|
||||
if desc and desc.attributes and desc.attributes.Buildings then
|
||||
if desc.life >= minHealth then
|
||||
table.insert(sceneries, foundScenery)
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
world.searchObjects(Object.Category.SCENERY, scenerySearchvolume, ifSceneryFound)
|
||||
|
||||
return sceneries
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns all units belonging to a given category
|
||||
-- @param coalitionID Coalition ID (coalition.side.XXX) or nil to search all coalitions
|
||||
-- @param unitCategory An unit category (Group.Category.XXX)
|
||||
-- @return A table of unit tables
|
||||
-------------------------------------
|
||||
function DCSEx.world.getAllUnits(coalitionID, unitCategory)
|
||||
local units = {}
|
||||
|
||||
local searchedCoalitions = { 0, 1, 2 }
|
||||
if coalitionID then searchedCoalitions = { coalitionID } end
|
||||
|
||||
for _,c in pairs(searchedCoalitions) do -- Enumerate coalitions
|
||||
for _, g in pairs(coalition.getGroups(c, unitCategory)) do
|
||||
for __, u in pairs(g:getUnits()) do
|
||||
table.insert(units, u)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return units
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns the closest point to roads as a vec2
|
||||
-- @param vec2 Coordinates to look for
|
||||
-- @return A vec2 with the closest point on roads
|
||||
-------------------------------------
|
||||
function DCSEx.world.getClosestPointOnRoadsVec2(vec2)
|
||||
local roadX, roadY = land.getClosestPointOnRoads("roads", vec2.x, vec2.y)
|
||||
return {x = roadX, y = roadY}
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns the LL/MGRS coordinates of a point, as a string
|
||||
-- Based on code by Bushmanni - https://forums.eagle.ru/showthread.php?t=99480
|
||||
-- @param point The point, as a vec2 or vec3
|
||||
-- @param hideElevation (optional) Show elevation NOT be displayed? Default: false
|
||||
-- @return A string
|
||||
-------------------------------------
|
||||
function DCSEx.world.getCoordinatesAsString(point, hideElevation)
|
||||
hideElevation = hideElevation or false
|
||||
|
||||
if not point.z then point = DCSEx.math.vec2ToVec3(point, "land") end
|
||||
local cooString = ""
|
||||
|
||||
local LLposN, LLposE, alt = coord.LOtoLL(point)
|
||||
local LLposfixN, LLposdegN = math.modf(LLposN)
|
||||
LLposdegN = LLposdegN * 60
|
||||
local LLposdegN2, LLposdegN3 = math.modf(LLposdegN)
|
||||
local LLposdegN3Decimal = LLposdegN3 * 1000
|
||||
LLposdegN3 = LLposdegN3 * 60
|
||||
|
||||
local LLposfixE, LLposdegE = math.modf(LLposE)
|
||||
LLposdegE = LLposdegE * 60
|
||||
local LLposdegE2, LLposdegE3 = math.modf(LLposdegE)
|
||||
local LLposdegE3Decimal = LLposdegE3 * 1000
|
||||
LLposdegE3 = LLposdegE3 * 60
|
||||
|
||||
local LLns = "N"
|
||||
if LLposfixN < 0 then
|
||||
LLns = "S"
|
||||
end
|
||||
local LLew = "E"
|
||||
if LLposfixE < 0 then
|
||||
LLew = "W"
|
||||
end
|
||||
|
||||
local LLposNstring = LLns .. " " .. string.format("%.2i°%.2i'%.2i''", LLposfixN, LLposdegN2, LLposdegN3)
|
||||
local LLposEstring = LLew .. " " .. string.format("%.3i°%.2i'%.2i''", LLposfixE, LLposdegE2, LLposdegE3)
|
||||
cooString = "L/L: " .. LLposNstring .. " " .. LLposEstring
|
||||
|
||||
local LLposNstring = LLns .. " " .. string.format("%.2i°%.2i.%.3i", LLposfixN, LLposdegN2, LLposdegN3Decimal)
|
||||
local LLposEstring = LLew .. " " .. string.format("%.3i°%.2i.%.3i", LLposfixE, LLposdegE2, LLposdegE3Decimal)
|
||||
cooString = cooString .. "\nL/L: " .. LLposNstring .. " " .. LLposEstring
|
||||
|
||||
local mgrs = coord.LLtoMGRS(LLposN, LLposE)
|
||||
local mgrsString =
|
||||
mgrs.MGRSDigraph .. " " .. mgrs.UTMZone .. " " .. tostring(mgrs.Easting) .. " " .. tostring(mgrs.Northing)
|
||||
cooString = cooString .. "\nMGRS: " .. mgrsString
|
||||
|
||||
if not hideElevation then
|
||||
cooString = cooString .. "\nELEVATION: " .. math.floor(alt * 3.281) .. "ft / " .. math.floor(alt) .. " meters"
|
||||
end
|
||||
|
||||
return cooString
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns the last map marker ID generated by DCSEx.world.getNextMarkerID(), if any
|
||||
-- @return A numeric ID, or nil
|
||||
-------------------------------------
|
||||
function DCSEx.world.getCurrentMarkerID()
|
||||
if nextMarkerId == 1 then return nil end
|
||||
return nextMarkerId - 1
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Searches and return a group by its ID
|
||||
-- @param groupID ID of the group
|
||||
-- @return A group table, or nil if no group with this ID was found
|
||||
-------------------------------------
|
||||
function DCSEx.world.getGroupByID(groupID)
|
||||
for coalitionID = 1, 2 do
|
||||
for _, grp in pairs(coalition.getGroups(coalitionID)) do
|
||||
if DCSEx.dcs.getGroupIDAsNumber(grp) == groupID then
|
||||
return grp
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-- TODO: description
|
||||
function DCSEx.world.getMarkerByText(text, coalition)
|
||||
if not text then return nil end
|
||||
text = text:lower()
|
||||
local markers = DCSEx.world.getMarkPanels()
|
||||
|
||||
for _,m in pairs(markers) do
|
||||
local markerText = m.text or ""
|
||||
markerText = markerText:lower()
|
||||
if markerText == text then
|
||||
if coalition == nil or m.coalition == coalition then
|
||||
return m
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Returns a new, unique, map marker ID
|
||||
-- @return A numeric ID
|
||||
-------------------------------------
|
||||
function DCSEx.world.getNextMarkerID()
|
||||
nextMarkerId = nextMarkerId + 1
|
||||
return nextMarkerId - 1
|
||||
end
|
||||
|
||||
-- TODO: description, file header
|
||||
function DCSEx.world.getPlayersInAir(side)
|
||||
local players = {}
|
||||
if side then
|
||||
players = coalition.getPlayers(side)
|
||||
else
|
||||
players = DCSEx.world.getAllPlayers()
|
||||
end
|
||||
|
||||
local playersInAir = {}
|
||||
for _,p in ipairs(players) do
|
||||
if p:inAir() then
|
||||
table.insert(playersInAir, p)
|
||||
end
|
||||
end
|
||||
|
||||
return playersInAir
|
||||
end
|
||||
|
||||
-- TODO: description, file header
|
||||
function DCSEx.world.getSpawnPoint(zone, surfaceType, safeRadius)
|
||||
safeRadius = safeRadius or 100
|
||||
|
||||
-- Only two surface types are really useful to us: land and water
|
||||
if surfaceType == land.SurfaceType.SHALLOW_WATER then
|
||||
surfaceType = land.SurfaceType.WATER
|
||||
elseif surfaceType == land.SurfaceType.ROAD or surfaceType == land.SurfaceType.RUNWAY then
|
||||
surfaceType = land.SurfaceType.LAND
|
||||
end
|
||||
|
||||
local loopsLeft = 512
|
||||
|
||||
while loopsLeft > 0 do
|
||||
local basePoint = DCSEx.zones.getRandomPointInside(zone)
|
||||
|
||||
if not surfaceType then -- Nil surfaceType means: any point is fine
|
||||
return basePoint
|
||||
end
|
||||
|
||||
if surfaceType == land.SurfaceType.WATER then
|
||||
if basePoint and land.getSurfaceType(basePoint) == land.SurfaceType.WATER then
|
||||
return basePoint
|
||||
end
|
||||
else
|
||||
if safeRadius <= 0 then
|
||||
return basePoint
|
||||
end
|
||||
|
||||
basePoint = DCSEx.math.vec2ToVec3(basePoint, "land")
|
||||
|
||||
local spawnPoints = Disposition.getSimpleZones(basePoint, math.max(1852, safeRadius * 5), safeRadius, 1)
|
||||
if #spawnPoints > 0 and land.getSurfaceType(spawnPoints[1]) == land.SurfaceType.LAND then
|
||||
return spawnPoints[1]
|
||||
end
|
||||
end
|
||||
|
||||
loopsLeft = loopsLeft - 1
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-- TODO: description
|
||||
function DCSEx.world.getSceneriesInZone(center, radius, minHealth)
|
||||
minHealth = minHealth or 0
|
||||
local sceneries = {}
|
||||
|
||||
local scenerySearchvolume = {
|
||||
id = world.VolumeType.SPHERE,
|
||||
params = { point = { x = center.x, y = 0, z = center.y }, radius = radius }
|
||||
}
|
||||
|
||||
local function ifSceneryFound(foundScenery, val)
|
||||
local desc = foundScenery:getDesc()
|
||||
if desc and desc.attributes and desc.attributes.Buildings then
|
||||
if desc.life >= minHealth then
|
||||
table.insert(sceneries, foundScenery)
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
world.searchObjects(Object.Category.SCENERY, scenerySearchvolume, ifSceneryFound)
|
||||
|
||||
return sceneries
|
||||
end
|
||||
|
||||
-- TODO: description
|
||||
function DCSEx.world.getTerrainHeightDiff(coord, searchRadius)
|
||||
local samples = {}
|
||||
searchRadius = searchRadius or 5
|
||||
|
||||
samples[#samples + 1] = land.getHeight(coord)
|
||||
for i = 0, 360, 30 do
|
||||
samples[#samples + 1] = land.getHeight({x = (coord.x + (math.sin(math.rad(i))*searchRadius)), y = (coord.y + (math.cos(math.rad(i))*searchRadius))})
|
||||
if searchRadius >= 20 then -- if search radius is sorta large, take a sample halfway between center and outer edge
|
||||
samples[#samples + 1] = land.getHeight({x = (coord.x + (math.sin(math.rad(i))*(searchRadius/2))), y = (coord.y + (math.cos(math.rad(i))*(searchRadius/2)))})
|
||||
end
|
||||
end
|
||||
local tMax, tMin = 0, 1000000
|
||||
for _, height in pairs(samples) do
|
||||
if height > tMax then
|
||||
tMax = height
|
||||
end
|
||||
if height < tMin then
|
||||
tMin = height
|
||||
end
|
||||
end
|
||||
|
||||
return tMax - tMin
|
||||
end
|
||||
|
||||
function DCSEx.world.getUnitsCenter(units)
|
||||
if not units or #units == 0 then return { x = 0, y = 0 } end
|
||||
|
||||
local center = { x = 0, y = 0 }
|
||||
for _,u in pairs(units) do
|
||||
local uPt2 = DCSEx.math.vec3ToVec2(u:getPoint())
|
||||
center.x = center.x + uPt2.x
|
||||
center.y = center.y + uPt2.y
|
||||
end
|
||||
|
||||
center.x = center.x / #units
|
||||
center.y = center.y / #units
|
||||
|
||||
return center
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Searches and return an unit by its ID
|
||||
-- @param unitID ID of the unit
|
||||
-- @return An unit, or nil if no unit with this ID was found
|
||||
-------------------------------------
|
||||
function DCSEx.world.getUnitByID(unitID)
|
||||
for coalitionID = 1, 2 do
|
||||
for _, g in pairs(coalition.getGroups(coalitionID)) do
|
||||
local units = g:getUnits()
|
||||
for _, u in pairs(units) do
|
||||
if DCSEx.dcs.getObjectIDAsNumber(u) == unitID then
|
||||
return u
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-------------------------------------
|
||||
-- Searches and return a coalition static object by its ID
|
||||
-- @param unitID ID of the static object
|
||||
-- @return An static object, or nil if no unit with this ID was found
|
||||
-------------------------------------
|
||||
function DCSEx.world.getStaticObjectByID(staticID)
|
||||
for coalitionID = 1,2 do
|
||||
for _,s in pairs(coalition.getStaticObjects(coalitionID)) do
|
||||
if DCSEx.dcs.getObjectIDAsNumber(s) == staticID then
|
||||
return s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-- TODO: description
|
||||
function DCSEx.world.isGroupAlive(g, unitsMustBeInAir)
|
||||
if not g then return false end
|
||||
if not g:isExist() then return false end
|
||||
if g:getSize() == 0 then return false end
|
||||
|
||||
unitsMustBeInAir = unitsMustBeInAir or false
|
||||
|
||||
local atLeastOneActiveUnit = false
|
||||
local units = g:getUnits()
|
||||
if not units or #units == 0 then return false end
|
||||
for _,u in pairs(units) do
|
||||
if u:isExist() and u:getLife() > 0 then
|
||||
if u:inAir() or not unitsMustBeInAir then
|
||||
atLeastOneActiveUnit = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return atLeastOneActiveUnit
|
||||
end
|
||||
|
||||
-- TODO: description & file header
|
||||
function DCSEx.world.setUnitLifePercent(unitID, life)
|
||||
net.dostring_in("mission", string.format("a_unit_set_life_percentage(%d, %f)", unitID, life))
|
||||
end
|
||||
|
||||
-- TODO: description & file header
|
||||
function DCSEx.world.explodeUnit(unitID, amount)
|
||||
net.dostring_in("mission", string.format("a_explosion_unit(%d, %f)", unitID, amount))
|
||||
end
|
||||
|
||||
function DCSEx.world.destroyGroupByID(groupID)
|
||||
if not groupID then return end
|
||||
local g = DCSEx.world.getGroupByID(groupID)
|
||||
if g then g:destroy() end
|
||||
end
|
||||
|
||||
-- function DCSEx.world.destroySceneryInZone(zone, destructionPercent)
|
||||
-- destructionPercent = destructionPercent or 0
|
||||
-- net.dostring_in("mission", string.format("a_scenery_destruction_zone(%d, %f)", zone.zoneId, destructionPercent))
|
||||
-- end
|
||||
|
||||
-- function DCSEx.world.highlightUnit(unitID, enabled)
|
||||
-- if enabled then
|
||||
-- enabled = "true"
|
||||
-- else
|
||||
-- enabled = "false"
|
||||
-- end
|
||||
|
||||
-- net.dostring_in("mission", string.format("a_unit_highlight(%d, %s)", unitID, enabled))
|
||||
-- end
|
||||
|
||||
-- function DCSEx.world.shellingZone(zone, tnt, shellsCount)
|
||||
-- net.dostring_in("mission", string.format("a_shelling_zone(%d, %f, %d)", zone.zoneId, tnt, shellsCount))
|
||||
-- end
|
||||
end
|
||||
305
Script/DCS extensions/Zones.lua
Normal file
305
Script/DCS extensions/Zones.lua
Normal file
@@ -0,0 +1,305 @@
|
||||
-- ====================================================================================
|
||||
-- ZONETOOLS - FUNCTIONS RELATED TO MAP TRIGGER ZONES
|
||||
-- ====================================================================================
|
||||
-- DCSEx.zones.drawOnMap(zoneTable, lineColor, fillColor, lineType, drawName, readonly)
|
||||
-- DCSEx.zones.getAll()
|
||||
-- DCSEx.zones.getByName(name)
|
||||
-- DCSEx.zones.getCenter(zoneTable)
|
||||
-- DCSEx.zones.getProperty(zoneTable, propertyName)
|
||||
-- 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.getSurfaceArea(zoneTable)
|
||||
-- DCSEx.zones.isPointInside(zoneTable, point)
|
||||
-- ====================================================================================
|
||||
|
||||
DCSEx.zones = { }
|
||||
|
||||
-- TODO: function description
|
||||
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 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
|
||||
Reference in New Issue
Block a user