Merge branch 'FF/Ops' into FF/OpsDev

This commit is contained in:
Frank
2025-11-15 10:14:15 +01:00
31 changed files with 4513 additions and 2595 deletions
+1
View File
@@ -79,6 +79,7 @@
do -- FSM
--- FSM class
-- @type FSM
-- @field #string ClassName Name of the class.
-- @field Core.Scheduler#SCHEDULER CallScheduler Call scheduler.
+83 -10
View File
@@ -51,8 +51,8 @@
-- you specify in the draw panel.
--
-- # Mark on F10 map
--
-- The ponints of the PATHLINE can be marked on the F10 map with the @{#PATHLINE.MarkPoints}(`true`) function. The mark points contain information of the surface type, land height and
--
-- The points of the PATHLINE can be marked on the F10 map with the @{#PATHLINE.MarkPoints}(`true`) function. The mark points contain information of the surface type, land height and
-- water depth.
--
-- To remove the marks, use @{#PATHLINE.MarkPoints}(`false`).
@@ -76,17 +76,12 @@ PATHLINE = {
-- @field #number landHeight Land height in meters.
-- @field #number depth Water depth in meters.
-- @field #number markerID Marker ID.
-- @field #number lineID Marker of pathline ID.
--- Segment of line.
-- @type PATHLINE.Segment
-- @field #PATHLINE.Point p1 First point.
-- @field #PATHLINE.Point p2 Second point.
-- @field #number lineID Line marker ID.
--- PATHLINE class version.
-- @field #string version
PATHLINE.version="0.3.0"
PATHLINE.version="0.2.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@@ -362,6 +357,30 @@ function PATHLINE:GetPoint2DFromIndex(n)
return nil
end
--- Calculate the length of the line.
-- @param #PATHLINE self
-- @param #boolean Project2D Calculate 2D distance between points.
-- @return #number Length in meters.
function PATHLINE:GetLength(Project2D)
local l=0
local np=#self.points
for i=1,np-1 do
local p1=self.points[i] --#PATHLINE.Point
local p2=self.points[i+1] --#PATHLINE.Point
if Project2D then
l=l+UTILS.VecDist2D(p1.vec2, p2.vec2)
else
l=l+UTILS.VecDist3D(p1.vec3, p2.vec3)
end
end
return l
end
--- Mark points on F10 map.
-- @param #PATHLINE self
@@ -373,7 +392,13 @@ function PATHLINE:MarkPoints(Switch)
local point=_point --#PATHLINE.Point
if Switch==false then
if point.markerID then
UTILS.RemoveMark(point.markerID)
end
else
if point.markerID then
UTILS.RemoveMark(point.markerID)
end
@@ -392,6 +417,54 @@ function PATHLINE:MarkPoints(Switch)
end
end
return self
end
--- Draw line on F10 map.
-- @param #PATHLINE self
-- @param #number Recipient Recipent of the line: -1=All.
-- @param #table Color Color as RGB table plus alpha value. Default {1, 0, 0, 1.0}.
-- @param #number LineType Line type: 1=Solid (default).
-- @return #PATHLINE self
function PATHLINE:DrawLine(Recipient, Color, LineType)
-- Input
Recipient= Recipient or -1
Color= Color or {1,0,0, 1.0}
LineType=LineType or 1
local ReadOnly=false
local np=#self.points
for i=1,np-1 do
local p1=self.points[i] --#PATHLINE.Point
local p2=self.points[i+1] --#PATHLINE.Point
p1.lineID = UTILS.GetMarkID()
trigger.action.lineToAll(Recipient, p1.lineID, p1.vec3, p2.vec3, Color, LineType, ReadOnly, "")
end
return self
end
--- Remove line on F10 map.
-- @param #PATHLINE self
-- @param #number Delay Delay in seconds before line is removed.
-- @return #PATHLINE self
function PATHLINE:UnDrawLine(Delay)
local np=#self.points
for _,_point in pairs(self.points) do
local p=_point --#PATHLINE.Point
if p.lineID then
UTILS.RemoveMark(p.lineID, Delay)
end
end
return self
end
+35
View File
@@ -1654,6 +1654,7 @@ do -- COORDINATE
if AirbaseCategory == Airbase.Category.SHIP or AirbaseCategory == Airbase.Category.HELIPAD then
RoutePoint.linkUnit = AirbaseID
RoutePoint.helipadId = AirbaseID
RoutePoint.airdromeId = airbase:IsAirdrome() and AirbaseID or nil
elseif AirbaseCategory == Airbase.Category.AIRDROME then
RoutePoint.airdromeId = AirbaseID
else
@@ -2076,6 +2077,40 @@ do -- COORDINATE
return Path, Way, GotPath
end
--- Returns a table of coordinates to a destination using only roads or railroads.
-- The first point is the closest point on road of the given coordinate.
-- By default, the last point is the closest point on road of the ToCoord. Hence, the coordinate itself and the final ToCoord are not necessarily included in the path.
-- @param #COORDINATE self
-- @param #COORDINATE ToCoord Coordinate of destination.
-- @param #boolean IncludeEndpoints (Optional) Include the coordinate itself and the ToCoordinate in the path.
-- @param #boolean Railroad (Optional) If true, path on railroad is returned. Default false.
-- @return Core.Pathline#PATHLINE Pathline containing the points on road. If no path on road can be found, nil is returned or just the endpoints.
function COORDINATE:GetPathlineOnRoad(ToCoord, IncludeEndpoints, Railroad)
-- Set road type.
local RoadType="roads"
if Railroad==true then
RoadType="railroads"
end
-- DCS API function returning a table of vec2.
local path = land.findPathOnRoads(RoadType, self.x, self.z, ToCoord.x, ToCoord.z)
if IncludeEndpoints then
path=path or {}
table.insert(path, 1, self:GetVec2())
table.insert(path, ToCoord:GetVec2())
end
local pathline=nil
if path then
pathline=PATHLINE:NewFromVec2Array(RoadType, path)
end
return pathline
end
--- Gets the surface type at the coordinate.
-- @param #COORDINATE self
File diff suppressed because it is too large Load Diff
+246 -65
View File
@@ -133,7 +133,10 @@ VECTOR = {
--- VECTOR class version.
-- @field #string version
VECTOR.version="0.0.2"
VECTOR.version="0.1.0"
--- VECTOR unique ID
_VECTORID=0
--- VECTOR private index.
-- @field #VECTOR __index
@@ -144,7 +147,7 @@ VECTOR.__index = VECTOR
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: 3D rotation
-- TODO: Markers
-- DONE: Markers
-- TODO: Documentation
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -168,6 +171,10 @@ function VECTOR:New(x, y, z)
else
self=setmetatable({x=x or 0, y=y or 0, z=z or 0}, VECTOR)
end
_VECTORID=_VECTORID+1
self.uid=_VECTORID
return self
end
@@ -275,7 +282,7 @@ end
-- @param #VECTOR self
-- @param #number Latitude Latitude in decimal degrees.
-- @param #number Longitude Longitude in decimal degrees.
-- @param #number altitude (Optional) Altitude in meters. Default is the land height at the 2D position.
-- @param #number Altitude (Optional) Altitude in meters. Default is the land height at the 2D position.
-- @return #VECTOR self
function VECTOR:NewFromLLDD(Latitude, Longitude, Altitude)
@@ -284,13 +291,11 @@ function VECTOR:NewFromLLDD(Latitude, Longitude, Altitude)
-- Convert vec3 to coordinate object.
self=VECTOR:NewFromVec(vec3)
-- -- Adjust height
-- if Altitude==nil then
-- self.y=self:GetSurfaceHeight()
-- else
-- self.y=Altitude
-- end
-- Adjust height
if Altitude then
self.y=Altitude
end
return self
end
@@ -526,18 +531,29 @@ function VECTOR:GetLatitudeLongitude()
end
--- Get MGRS coordinates of this vector.
--- Get MGRS information of this vector.
--
-- `MGRS = {UTMZone = string, MGRSDigraph = string, Easting = number, Northing = number}`
--
-- @param #VECTOR self
-- @param #VECTOR Vector Vector from which the heading is requested.
-- @return #number Latitude
-- @return #number Longitude
-- @return #table MGRS table with `UTMZone`, `MGRSDiGraph`, `Easting` and `Northing` keys.
function VECTOR:GetMGRS()
local lat, long=self:GetLatitudeLongitude()
local mrgs=coord.LLtoMGRS(lat, long)
local mgrs=coord.LLtoMGRS(lat, long)
-- Example table returned by coord.LLtoMGRS
--[[
MGRS = {
UTMZone = string,
MGRSDigraph = string,
Easting = number,
Northing = number
}
]]
return mrgs.Easing, mrgs.Northing
return mgrs
end
--- Get the difference of the heading of this vector w.
@@ -569,7 +585,7 @@ end
-- @param #VECTOR Vector The destination vector.
-- @param #number Fraction The fraction (0,1) where the new vector is created. Default 0.5, *i.e.* in the middle.
-- @return #VECTOR Vector between this and the other vector.
function VECTOR:GetIntermediateCoordinate(Vector, Fraction)
function VECTOR:GetIntermediateVector(Vector, Fraction)
local f=Fraction or 0.5
@@ -581,13 +597,7 @@ function VECTOR:GetIntermediateCoordinate(Vector, Fraction)
-- Set/scale the length.
vec:SetLength(f*length)
--TODO: Not sure what this was supposed to do?!
-- if f>1 then
-- local norm=UTILS.VecNorm(vec)
-- f=Fraction/norm
-- end
-- Get to the desired position.
vec=self+vec
@@ -638,14 +648,13 @@ end
--- Set z-component of vector. The z-axis points to the East.
-- @param #VECTOR self
-- @param #number z Value of z. Default 0.
-- @param #VECTOR self
-- @return #VECTOR self
function VECTOR:SetZ(z)
self.z=z or 0
return self
end
--- Add a vector to this. This function works for DCS#Vec2, DCS#Vec3, VECTOR, COORDINATE objects.
-- Note that if you want to add a VECTOR, you can also simply use the `+` operator.
-- @param #VECTOR self
@@ -666,8 +675,9 @@ function VECTOR:AddVec(Vec)
return self
end
--- Substract a vector from this one. This function works for DCS#Vec2, DCS#Vec3, VECTOR, COORDINATE objects.
-- Note that if you want to add a VECTOR, you can also simply use the `-` operator.
--- Subtract a vector from this one. This function works for DCS#Vec2, DCS#Vec3, VECTOR, COORDINATE objects.
--
-- **Note** that if you want to add a VECTOR, you can also simply use the `-` operator.
-- @param #VECTOR self
-- @param DCS#Vec3 Vec Vector to substract. Can also be a DCS#Vec2, DCS#Vec3, COORDINATE or VECTOR object.
-- @return #VECTOR self
@@ -818,10 +828,14 @@ function VECTOR:Rotate2D(Angle, Copy)
local x = X*sinPhi + Y*cosPhi
-- Create new vector.
-- TODO: Copy argument
local vector=VECTOR:New(x, self.y, z)
return vector
if Copy then
local vector=VECTOR:New(x, self.y, z)
return vector
else
self:SetX(x)
self:SetZ(z)
return self
end
end
@@ -857,7 +871,7 @@ end
-- * RUNWAY = 5
--
-- @param #VECTOR self
-- @return #number Surface Type
-- @return #number Surface type
function VECTOR:GetSurfaceType()
local vec2=self:GetVec2()
@@ -867,6 +881,32 @@ function VECTOR:GetSurfaceType()
return s
end
--- Get the name of surface type at the vector.
--
-- * LAND = 1
-- * SHALLOW_WATER = 2
-- * WATER = 3
-- * ROAD = 4
-- * RUNWAY = 5
--
-- @param #VECTOR self
-- @return #string Surface type name
function VECTOR:GetSurfaceTypeName()
local vec2=self:GetVec2()
local s=land.getSurfaceType(vec2)
for name,id in land.SurfaceType() do
if id==s then
return name
end
end
return "unknown"
end
--- Check if a given vector has line of sight with this vector.
-- @param #VECTOR self
-- @param #VECTOR Vec The other vector.
@@ -884,8 +924,7 @@ end
--- Get a vector on the closest road.
-- @param #VECTOR self
-- @param #VECTOR Vec The other vector.
-- @return #VECTOR Closest vector on a road.
-- @return #VECTOR Closest vector to a road.
function VECTOR:GetClosestRoad()
local vec2=self:GetVec2()
@@ -900,19 +939,36 @@ function VECTOR:GetClosestRoad()
return road
end
--- Get a vector on the closest railroad.
-- @param #VECTOR self
-- @return #VECTOR Closest vector to a railroad.
function VECTOR:GetClosestRailroad()
local vec2=self:GetVec2()
local x,y=land.getClosestPointOnRoads('railroads', vec2.x, vec2.y)
local road=nil
if x and y then
road=VECTOR:New(x, y)
end
return road
end
--- Get the path on road from this vector to a given other vector.
-- @param #VECTOR self
-- @param #VECTOR Vec The destination vector.
-- @return Core.Path#PATHLINE Pathline with points on road.
-- @return Core.Pathline#PATHLINE Pathline with points on road.
function VECTOR:GetPathOnRoad(Vec)
local vec2=self:GetVec2()
local vec1=self:GetVec2()
local vec2=Vec:GetVec2()
local path=nil
local vec2points=land.findPathOnRoads("roads", vec2.x , vec2.y, vec2.x, vec2.y)
local vec2points=land.findPathOnRoads("roads", vec1.x , vec1.y, vec2.x, vec2.y)
local path=nil
if vec2points then
path=PATHLINE:NewFromVec2Array("Road", vec2points)
end
@@ -924,7 +980,7 @@ end
--- Get profile of the land between the two passed points.
-- @param #VECTOR self
-- @param #VECTOR Vec3 The 3D destination vector. If a 2D vector is passed, `y` is set to the land height.
-- @return Core.Path#PATHLINE Pathline with points of the profile.
-- @return Core.Pathline#PATHLINE Pathline with points of the profile.
function VECTOR:GetProfile(Vec3)
local vec3=self:GetVec3()
@@ -1022,36 +1078,104 @@ function VECTOR:GetTemperaturAndPressure()
return t,p
end
--- Creates a smoke at this vector.
-- @param #VECTOR self
-- @param #number Color Color of the smoke: 0=Green, 1=Red, 2=White, 3=Orange, 4=Blue. Default 0.
-- @param #number Duration (Optional) Duration of the smoke in seconds. Default nil.
-- @return #string Name of the smoke object. Can be used to stop it.
function VECTOR:Smoke(Color, Duration)
local vec3=self:GetVec3()
Color=Color or 0
-- Create a name for the smoke object
local name=string.format("Vector-Smoke-%d", self.uid)
-- Create smoke at this position
trigger.action.smoke(vec3, Color, name)
if Duration and Duration>0 then
self:StopSmoke(name, Duration)
end
return name
end
--- Creates a large smoke and fire effect of a specified type and density at this vector.
--
-- * 1 = small smoke and fire
-- * 2 = medium smoke and fire
-- * 3 = large smoke and fire
-- * 4 = huge smoke and fire
-- * 5 = small smoke
-- * 6 = medium smoke
-- * 7 = large smoke
-- * 8 = huge smoke
--
-- @param #VECTOR self
-- @param #number Preset Preset of smoke. Default `BIGSMOKEPRESET.LargeSmokeAndFire`.
-- @param #number Density Density between [0,1]. Default 0.5.
-- @return #string Name of the smoke. Can be used to stop it.
function VECTOR:SmokeAndFire(Preset, Density)
-- @param #number Duration (Optional) Duration of the smoke and fire in seconds.
-- @return #string Name of the smoke. Can be used to stop it.
function VECTOR:SmokeAndFire(Preset, Density, Duration)
Preset=Preset or BIGSMOKEPRESET.LargeSmokeAndFire
Density=Density or 0.5
local vec3=self:GetVec3()
--TODO: Get a unique name or pass name as parameter?
-- Get a name of this smoke & fire object
local name=string.format("Vector-Fire-%d", self.uid)
trigger.action.effectSmokeBig(vec3, Preset, Density, Name)
trigger.action.effectSmokeBig(vec3, Preset, Density, name)
if Duration and Duration>0 then
self:StopSmoke(name, Duration)
end
return Name
return name
end
--- Stop smoke or fire effect.
-- @param #VECTOR self
-- @param #string Name Name of the smoke object.
-- @param #number Delay Delay in seconds before the smoke is stopped.
-- @return #VECTOR self
function VECTOR:StopSmoke(Name, Delay)
if Delay and Delay>0 then
TIMER:New(VECTOR.StopSmoke, self, Name):Start(Delay)
else
if Name then
trigger.action.effectSmokeStop(Name)
else
env.error(string.format("No name provided in VECTOR.StopSmoke function!"))
end
end
return self
end
--- Creates an illumination bomb at the specified point.
-- @param #VECTOR self
-- @param #number Power The power in Candela (cd). Should be between 1 and 1000000. Default 1000 cd.
-- @param #number Altitude (Optional) Altitude [m] at which the illumination bomb is created.
-- @return #VECTOR self
function VECTOR:IlluminationBomb(Power)
function VECTOR:IlluminationBomb(Power, Altitude)
local vec3=self:GetVec3()
if Altitude then
vec3.y=Altitude
end
-- Create an illumination bomb.
trigger.action.illuminationBomb(vec3, Power or 1000)
return self
end
--- Creates an explosion at a given point at the specified power.
@@ -1085,16 +1209,73 @@ end
--- Creates a arrow from this VECTOR to another vector on the F10 map.
-- @param #VECTOR self
-- @param #VECTOR Vector The vector defining the endpoint.
-- @return #VECTOR self
function VECTOR:ArrowToAll(Vector)
-- @param #number Coalition Coalition Id: -1=All, 0=Neutral, 1=Red, 2=Blue. Default -1.
-- @param #table Color RGB color with alpha {r, g, b, alpha}. Default {1, 0, 0, 0.7}.
-- @param #table FillColor RGB color with alpha {r, g, b, alpha}. Default {1, 0, 0, 0.5}.
-- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot Dash, 5=Long Dash, 6=Two Dash. Default 1.
-- @return #number Marker ID. Can be used to remove the drawing.
function VECTOR:ArrowTo(Vector, Coalition, Color, FillColor, LineType)
local vec3Start=self:GetVec3()
local vec3End=self:GetVec3()
local vec3Start=Vector:GetVec3()
trigger.action.arrowToAll(coalition , id, vec3Start, vec3End, color, fillColor , lineType, readOnly, "")
local id=UTILS.GetMarkID()
Coalition=Coalition or -1
Color=Color or {1,0,0,0.7}
FillColor=FillColor or {1,0,0,0.5}
LineType=LineType or 1
local readOnly=false
trigger.action.arrowToAll(Coalition , id, vec3Start, vec3End, Color, FillColor , LineType, readOnly, "")
return self
return id
end
--- Create mark on F10 map.
-- @param #VECTOR self
-- @param #string MarkText Free format text that shows the marking clarification.
-- @param #number Recipient Recipient of the mark: -1=All (default), 0=Neutral, 1=Red, 2=Blue. Can also be a `GROUP` object.
-- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false.
-- @return #number Mark ID.
function VECTOR:Mark(MarkText, Recipient, ReadOnly)
Recipient=Recipient or -1
if type(Recipient)=="number" then
local MarkID = UTILS.GetMarkID()
if Recipient==-1 then
trigger.action.markToAll(MarkID, MarkText, self:GetVec3(), ReadOnly, "")
elseif Recipient==0 then
trigger.action.markToCoalition(MarkID, MarkText, self:GetVec3(), coalition.side.NEUTRAL, ReadOnly, "")
elseif Recipient==1 then
trigger.action.markToCoalition(MarkID, MarkText, self:GetVec3(), coalition.side.RED, ReadOnly, "")
elseif Recipient==2 then
trigger.action.markToCoalition(MarkID, MarkText, self:GetVec3(), coalition.side.BLUE, ReadOnly, "")
end
return MarkID
elseif type(Recipient)=="table" then
local MarkID = UTILS.GetMarkID()
local group=Recipient --Wrapper.Group#GROUP
trigger.action.markToGroup( MarkID, MarkText, self:GetVec3(), group:GetID(), ReadOnly, "")
return MarkID
end
return nil
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Private Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -1119,7 +1300,7 @@ function VECTOR.__add(a,b)
return c
end
--- Meta function to substract vectors.
--- Meta function to subtract vectors.
-- @param #VECTOR a Vector a.
-- @param #VECTOR b Vector b.
-- @return #VECTOR Returns a new VECTOR c with c[i]=a[i]-b[i] for i=x,y,z.
@@ -1152,17 +1333,21 @@ function VECTOR.__mul(a, b)
return c
end
--- Meta function for dividing vectors by scalars.
--- Meta function for dividing a vector by a scalar or by another vector.
-- @param #VECTOR a Vector a.
-- @param #number b Number by which the components of the vector are divided.
-- @return #VECTOR Returns a new VECTOR c with c[i]=a[i]/b for i=x,y,z.
-- @param #VECTOR b Vector b. Can also be a #number.
-- @return #VECTOR Returns a new VECTOR c with c[i]=a[i]/b or c[i]=a[i]/b[i] for i=x,y,z.
function VECTOR.__div(a, b)
assert(VECTOR._IsVector(a) and type(b) == "number", "div: wrong argument types (expected <vector> and <number>)")
env.info("FF __div")
assert(VECTOR._IsVector(a) and (type(b) == "number" or VECTOR._IsVector(b)), "div: wrong argument types (expected <vector> and (<number> or <vector>))")
local c=VECTOR:New(a.x/b, a.y/b, a.z/b)
local c=nil --#VECTOR
if type(b) == "number" then
c=VECTOR:New(a.x/b, a.y/b, a.z/b)
else
c=VECTOR:New(a.x/b.x, a.y/b.y, a.z/b.z)
end
return c
end
@@ -1179,10 +1364,6 @@ end
-- @param #VECTOR b Vector b.
-- @return #boolean If `true`, both vectors are equal
function VECTOR.__eq(a, b)
--assert(VECTOR._IsVector(a) and VECTOR._IsVector(b), "ERROR in VECTOR.__eq: wrong argument types: (expected <vector> and <vector>)")
env.info("FF __eq",showMessageBox)
BASE:I(a)
BASE:I(b)
return a.x==b.x and a.y==b.y and a.z==b.z
end
@@ -1191,7 +1372,7 @@ end
-- @param #VECTOR self
-- @return #string String representation of vector.
function VECTOR:__tostring()
local text=string.format("(x=%.1f, y=%.1f, z=%.1f) |v|=%.1f Phi=%4.1f°", self.x, self.y, self.z, self:GetLength(), self:GetHeading(false))
local text=string.format("VECTOR: x=%.1f, y=%.1f, z=%.1f |v|=%.1f Phi=%4.1f°", self.x, self.y, self.z, self:GetLength(), self:GetHeading(false))
return text
end
@@ -320,8 +320,8 @@ function SCORING:New( GameName, SavePath, AutoSave )
-- Create the CSV file.
self.AutoSavePath = SavePath
self.AutoSave = AutoSave or true
if self.AutoSave == true then
self.AutoSave = (AutoSave == nil or AutoSave == true) and true or false
if self.AutoSavePath and self.AutoSave == true then
self:OpenCSV( GameName )
end
@@ -3340,7 +3340,6 @@ function WAREHOUSE:FindAssetInDB(group)
if aid~=nil then
local asset=_WAREHOUSEDB.Assets[aid]
self:T2({asset=asset})
if asset==nil then
self:_ErrorMessage(string.format("ERROR: Asset for group %s not found in the data base!", group:GetName()), 0)
end
@@ -3918,7 +3917,7 @@ end
-- @param #string assignment A free to choose string specifying an assignment for the asset. This can be used with the @{#WAREHOUSE.OnAfterNewAsset} function.
-- @param #table other (Optional) Table of other useful data. Can be collected via WAREHOUSE.OnAfterNewAsset() function for example
function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribute, forcecargobay, forceweight, loadradius, skill, liveries, assignment, other)
self:T({group=group, ngroups=ngroups, forceattribute=forceattribute, forcecargobay=forcecargobay, forceweight=forceweight})
--self:T({group=group:GetName(), ngroups=ngroups, forceattribute=forceattribute, forcecargobay=forcecargobay, forceweight=forceweight})
-- Set default.
local n=ngroups or 1
@@ -4446,7 +4445,6 @@ end
-- @param #WAREHOUSE.Queueitem Request Information table of the request.
-- @return #boolean If true, request is granted.
function WAREHOUSE:onbeforeRequest(From, Event, To, Request)
self:T3({warehouse=self.alias, request=Request})
-- Distance from warehouse to requesting warehouse.
local distance=self:GetCoordinate():Get2DDistance(Request.warehouse:GetCoordinate())
@@ -6153,9 +6151,6 @@ function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrol
-- Uncontrolled spawning.
template.uncontrolled=uncontrolled
-- Debug info.
self:T2({airtemplate=template})
-- Spawn group.
local group=_DATABASE:Spawn(template) --Wrapper.Group#GROUP
@@ -8601,6 +8596,8 @@ function WAREHOUSE:_DeleteStockItem(stockitem)
local item=self.stock[i] --#WAREHOUSE.Assetitem
if item.uid==stockitem.uid then
table.remove(self.stock,i)
-- remove also from warehouse DB
_WAREHOUSEDB.Assets[stockitem.uid]=nil
break
end
end
+7
View File
@@ -20,6 +20,13 @@ _DATABASE:_RegisterCargos()
_DATABASE:_RegisterZones()
_DATABASE:_RegisterAirbases()
--- Function that writes to DCS log file
-- @param #string text Formatted text.
-- @param ... Format passed to string.format().
function printf(text, ...)
env.info(string.format(text, ...))
end
--- Check if os etc is available.
BASE:I("Checking de-sanitization of os, io and lfs:")
local __na = false
+45 -7
View File
@@ -39,7 +39,7 @@
--
-- # The BEACONS Concept
--
-- This class is desinged to make information about beacons of a map/theatre easier accessible. The information contains location, type and frequencies of all or specific beacons of the map.
-- This class is designed to make information about beacons of a map/theatre easier accessible. The information contains location, type and frequencies of all or specific beacons of the map.
--
-- **Note** that try to avoid hard coding stuff in Moose since DCS is updated frequently and things change. Therefore, the main source of information is either a file `beacons.lua` that can be
-- found in the installation directory of DCS for each map or a table that the user needs to provide.
@@ -90,7 +90,7 @@ BEACONS = {
--- BEACONS class version.
-- @field #string version
BEACONS.version="0.0.4"
BEACONS.version="0.1.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
@@ -104,7 +104,7 @@ BEACONS.version="0.0.4"
-- Constructor(s)
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new BECAONS class instance from a given table.
--- Create a new BEACONS class instance from a given table.
-- @param #BEACONS self
-- @param #table BeaconTable Table with beacon info.
-- @return #BEACONS self
@@ -159,7 +159,7 @@ function BEACONS:NewFromTable(BeaconTable)
end
--- Create a new BECAONS class instance from a given file.
--- Create a new BEACONS class instance from a given file.
-- @param #BEACONS self
-- @param #string FileName Full path to the file containing the map beacons.
-- @return #BEACONS self
@@ -238,18 +238,56 @@ function BEACONS:GetClosestBeacon(Coordinate, TypeID, DistMax, ExcludeList)
return beacon
end
--- Find closest beacons to a given coordinate.
-- @param #BEACONS self
-- @param Core.Point#COORDINATE Coordinate The reference coordinate.
-- @param #number Nmax Max number of beacons. Default 5.
-- @param #number TypeID (Optional) Only search for specific beacon types, *e.g.* `BEACON.Type.TACAN`.
-- @param #number DistMax (Optional) Max search distance in meters.
-- @return #table Table of #BEACONS.Beacon closest beacons.
function BEACONS:GetClosestBeacons(Coordinate, Nmax, TypeID, DistMax)
Nmax=Nmax or 5
local closest={}
for i=1,Nmax do
local beacon=self:GetClosestBeacon(Coordinate, TypeID, DistMax, closest)
if beacon then
table.insert(closest, beacon)
else
break
end
end
return closest
end
--- Get table of all beacons, optionally of a given type.
-- @param #BEACONS self
-- @param #number TypeID (Optional) Only return specific beacon types, *e.g.* `BEACON.Type.TACAN`.
-- @param #number TypeID (Optional) Only return specific beacon types, *e.g.* `BEACON.Type.TACAN`. Can be handed in as tanle of beacon types.
-- @return #table Table of beacons. Each element is of type #BEACON.Beacon.
function BEACONS:GetBeacons(TypeID)
local beacons={}
local keys = {}
if TypeID~=nil and type(TypeID) ~= "table" then
TypeID = {TypeID}
end
for _,_typeid in pairs(TypeID or {}) do
if _typeid ~= nil then
keys[_typeid] = _typeid
end
end
for _,_beacon in pairs(self.beacons) do
local bc=_beacon --#BEACONS.Beacon
if TypeID==nil or TypeID==bc.type then
if TypeID==nil or keys[bc.type] ~= nil then
table.insert(beacons, bc)
end
+20 -5
View File
@@ -2,8 +2,8 @@
--
-- **Main Features:**
--
-- * Stuff
-- * More Stuff
-- * Navigation Fixes
-- * Navigation Aids
--
-- ===
--
@@ -100,7 +100,7 @@ NAVFIX.Type={
--- NAVFIX class version.
-- @field #string version
NAVFIX.version="0.0.1"
NAVFIX.version="0.1.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
@@ -231,6 +231,21 @@ function NAVFIX:NewFromNavFix(Name, Type, NavFix, Distance, Bearing, Reciprocal)
return self
end
--- Create a new NAVFIX class instance from BEACONS.Beacon data.
-- @param #NAVFIX self
-- @param Navigation.Beacons#BEACONS.Beacon Beacon The beacon data.
-- @return #NAVFIX self
function NAVFIX:NewFromBeacon(Beacon)
local frequency, unit = BEACONS:_GetFrequency(Beacon.frequency)
frequency = string.format("%.3f",frequency)
if Beacon.typeName == "TACAN" then
frequency = Beacon.channel
unit = "X"
end
self = NAVFIX:NewFromVector(string.format("%s %s %s",Beacon.typeName,frequency,unit),Beacon.typeName,Beacon.vec3)
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -348,7 +363,7 @@ end
--- Set whether this fix is compulsory.
-- @param #NAVFIX self
-- @param #boolean Compulsory If `true`, this is a compusory fix. If `false` or nil, it is non-compulsory.
-- @param #boolean Compulsory If `true`, this is a compulsory fix. If `false` or nil, it is non-compulsory.
-- @return #NAVFIX self
function NAVFIX:SetCompulsory(Compulsory)
self.isCompulsory=Compulsory
@@ -492,7 +507,7 @@ NAVAID = {
--- NAVAID class version.
-- @field #string version
NAVAID.version="0.0.1"
NAVAID.version="0.1.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
+112 -20
View File
@@ -3,6 +3,8 @@
-- **Main Features:**
--
-- * Get radio frequencies of airbases
-- * Find closest airbase radios
-- * Mark radio frequencies on F10 map
--
-- ===
--
@@ -37,7 +39,7 @@
--
-- # The RADIOS Concept
--
-- This class is desinged to make information about radios of a map/theatre easier accessible. The information contains mostly the frequencies of airbases of the map.
-- This class is designed to make information about radios of a map/theatre easier accessible. The information contains mostly the frequencies of airbases of the map.
--
-- **Note** that try to avoid hard coding stuff in Moose since DCS is updated frequently and things change. Therefore, the main source of information is either a file `radio.lua` that can be
-- found in the installation directory of DCS for each map or a table that the user needs to provide.
@@ -46,7 +48,7 @@
--
-- A new `RADIOS` object can be created with the @{#RADIOS.NewFromFile}(*radio_lua_file*) function.
--
-- local radios=RADIOS:NewFromFile("<DCS_Install_Directory>\Mods\terrains\<Map_Name>\radios.lua")
-- local radios=RADIOS:NewFromFile("<DCS_Install_Directory>\Mods\terrains\<Map_Name>\radio.lua")
-- radios:MarkerShow()
--
-- This will load the radios from the `<DCS_Install_Directory>` for the specific map and place markers on the F10 map. This is the first step you should do to ensure that the file
@@ -64,7 +66,7 @@
-- @field #RADIOS
RADIOS = {
ClassName = "RADIOS",
verbose = 0,
verbose = 0,
radios = {},
}
@@ -78,6 +80,9 @@ RADIOS = {
-- @field #table sceneObjects Scenery objects.
-- @field #string name Name of the airbase.
-- @field Wrapper.Airbase#AIRBASE airbase Airbase.
-- @field Core.Point#COORDINATE coordinate The COORDINATE of the radio.
-- @field DCS#Vec3 vec3 3D vector.
-- @field #number markerID Marker ID.
--- Radio item data structure.
-- @type RADIOS.Frequency
@@ -87,7 +92,7 @@ RADIOS = {
--- RADIOS class version.
-- @field #string version
RADIOS.version="0.0.0"
RADIOS.version="0.1.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
@@ -108,32 +113,41 @@ function RADIOS:NewFromTable(RadioTable)
-- Inherit everything from BASE class.
self=BASE:Inherit(self, BASE:New()) -- #RADIOS
local airbasenames=AIRBASE.GetAllAirbaseNames()
--local airbasenames=AIRBASE.GetAllAirbaseNames()
-- Get all airdromes
local airdromes=AIRBASE.GetAllAirbases(nil, Airbase.Category.AIRDROME)
for _,_radio in pairs(RadioTable) do
local radio=_radio --#RADIOS.Radio
--UTILS.PrintTableToLog(radio)
--UTILS.PrintTableToLog(radio.callsign)
-- The table structure of callsign is a bit awkward. We need to get the airbase name.
local cs=radio.callsign[1]
if cs and cs.common then
radio.name=cs.common[1]
elseif cs and cs.nato then
radio.name=cs.nato[1]
else
radio.name="Unknown"
-- Note that unfortunately, the callsign does not always correspond to the airbase name.
if false then
local cs=radio.callsign[1]
if cs and cs.common then
radio.name=cs.common[1]
elseif cs and cs.nato then
radio.name=cs.nato[1]
else
radio.name="Unknown"
end
radio.name=self:_GetAirbaseName(airbasenames, radio.name)
radio.airbase=AIRBASE:FindByName(radio.name)
end
--UTILS.PrintTableToLog(radio.callsign)
-- Each radio item has a key radioId = 'airfield106_0', where 106 is the UID of the airbase.
-- So we can use that to get the airbase.
local aid = tonumber(string.match(radio.radioId, "airfield(%d+)_"))
radio.name=self:_GetAirbaseName(airbasenames, radio.name)
radio.airbase=AIRBASE:FindByName(radio.name)
-- Get airbase
radio.airbase=self:_GetAirbaseByID(airdromes, aid)
-- Set other stuff
if radio.airbase then
radio.coordinate=radio.airbase:GetCoordinate()
radio.vec3=radio.airbase:GetVec3()
radio.name=radio.airbase:GetName()
end
-- Add to table
@@ -198,6 +212,66 @@ function RADIOS:GetCoordinate(radio)
return radio.coordinate
end
--- Find closest radio to a given coordinate.
-- @param #RADIOS self
-- @param Core.Point#COORDINATE Coordinate The reference coordinate.
-- @param #number DistMax (Optional) Max search distance in meters.
-- @param #table ExcludeList (Optional) List of radios to exclude.
-- @return #RADIOS.Radio The closest radio.
function RADIOS:GetClosestRadio(Coordinate, DistMax, ExcludeList)
local radio=nil --#RADIOS.Radio
local distmin=math.huge
ExcludeList=ExcludeList or {}
for _,_radio in pairs(self.radios) do
local ra=_radio --#RADIOS.Radio
if (not UTILS.IsInTable(ExcludeList, ra, "radioId")) then
local dist=Coordinate:Get2DDistance(ra.coordinate)
if dist<distmin and (DistMax==nil or dist<=DistMax) then
distmin=dist
radio=ra
end
end
end
return radio
end
--- Find closest radios to a given coordinate.
-- @param #RADIOS self
-- @param Core.Point#COORDINATE Coordinate The reference coordinate.
-- @param #number Nmax Max number of radios. Default 5.
-- @param #number DistMax (Optional) Max search distance in meters.
-- @return #table Table of #RADIOS.Radio closest radios.
function RADIOS:GetClosestRadios(Coordinate, Nmax, DistMax)
Nmax=Nmax or 5
local closest={}
for i=1,Nmax do
local radio=self:GetClosestRadio(Coordinate, DistMax, closest)
if radio then
table.insert(closest, radio)
else
break
end
end
return closest
end
--- Add markers for all radios on the F10 map.
-- @param #RADIOS self
-- @param #RADIOS.Radio Radio (Optional) Only this specifc radio.
@@ -325,6 +399,24 @@ function RADIOS:_GetAirbaseName(airbasenames, name)
return "Unknown"
end
--- Get name of frequency band.
-- @param #RADIOS self
-- @param #table airbases Table of airbases.
-- @param #number aid Airbase ID.
-- @return Wrapper.Airbase#AIRBASE Airbase matching the ID or nil.
function RADIOS:_GetAirbaseByID(airbases, aid)
for _,_airbase in pairs(airbases) do
local airbase=_airbase --Wrapper.Airbase#AIRBASE
local id=airbase:GetID(true)
if id==aid then
return airbase
end
end
return nil
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+51 -16
View File
@@ -1,9 +1,10 @@
--- **NAVIGATION** - Beacons of the map/theatre.
--- **NAVIGATION** - Towns of the map/theatre.
--
-- **Main Features:**
--
-- * Find towns of map
-- * Road and rail connections
-- * Find closest town to a given coordinate
--
-- ===
--
@@ -38,7 +39,7 @@
--
-- # The TOWNS Concept
--
-- This class is desinged to make information about towns of a map/theatre easier accessible. The information contains location and road/rail connections of the towns.
-- This class is designed to make information about towns of a map/theatre easier accessible. The information contains location and road/rail connections of the towns.
--
-- **Note** that try to avoid hard coding stuff in Moose since DCS is updated frequently and things change. Therefore, the main source of information is either a file `towns.lua` that can be
-- found in the installation directory of DCS for each map or a table that the user needs to provide.
@@ -47,7 +48,7 @@
--
-- A new `TOWNS` object can be created with the @{#TOWNS.NewFromFile}(*towns_lua_file*) function.
--
-- local towns=TOWNS:NewFromFile("<DCS_Install_Directory>\Mods\terrains\<Map_Name>\towns.lua")
-- local towns=TOWNS:NewFromFile("<DCS_Install_Directory>\Mods\terrains\<Map_Name>\map\towns.lua")
-- towns:MarkerShow()
--
-- This will load the towns from the `<DCS_Install_Directory>` for the specific map and place markers on the F10 map. This is the first step you should do to ensure that the file
@@ -83,16 +84,16 @@ TOWNS = {
--- TOWNS class version.
-- @field #string version
TOWNS.version="0.0.1"
TOWNS.version="0.1.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: A lot...
-- TODO: Road connection
-- TODO: Rail connection
-- TODO: Connection between towns
-- DONE: Road connection
-- DONE: Rail connection
-- DONE: Connection between towns
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor(s)
@@ -193,14 +194,15 @@ function TOWNS:GetCoordRail(town)
return town.coordRail
end
--- Get road connection between two towns.
--- Get road or rail connection between two towns.
-- @param #TOWNS self
-- @param #TOWNS.Town townA The town data structure.
-- @param #TOWNS.Town townB The town data structure.
-- @return #table Table containing path coordinates.
function TOWNS:GetConnectionRoad(townA, townB)
-- @param #boolean Railroad If `true`, find rail road connection
-- @return Core.Pathline#PATHLINE Pathline connecting the two towns on road.
function TOWNS:GetConnectionRoad(townA, townB, Railroad)
local path=townA.coordRoad:GetPathOnRoad(townB.coordRoad)
local path=townA.coordRoad:GetPathlineOnRoad(townB.coordRoad, false, Railroad)
return path
end
@@ -208,26 +210,59 @@ end
--- Find closest town to a given coordinate.
-- @param #TOWNS self
-- @param Core.Point#COORDINATE Coordinate The reference coordinate.
-- @param #number DistMax (Optional) Max search distance in meters.
-- @param #table ExcludeList (Optional) List of towns excluded from the search.
-- @return #TOWNS.Town The closest town.
function TOWNS:GetClosestTown(Coordinate)
function TOWNS:GetClosestTown(Coordinate, DistMax, ExcludeList)
local Town=nil --#TOWNS.Town
local distmin=math.huge
ExcludeList=ExcludeList or {}
for _,_town in pairs(self.towns) do
local town=_town --#TOWNS.Town
local dist=Coordinate:Get2DDistance(town.coordinate)
if (not UTILS.IsInTable(ExcludeList, town, "name")) then
if dist<distmin then
distmin=dist
Town=town
local dist=Coordinate:Get2DDistance(town.coordinate)
if dist<distmin then
distmin=dist
Town=town
end
end
end
return Town
end
--- Find closest towns to a given coordinate.
-- @param #TOWNS self
-- @param Core.Point#COORDINATE Coordinate The reference coordinate.
-- @param #number Nmax Max number of towns. Default 5.
-- @param #number DistMax (Optional) Max search distance in meters.
-- @return #table Table of #TOWNS.Town closest towns.
function TOWNS:GetClosestTowns(Coordinate, Nmax, DistMax)
Nmax=Nmax or 5
local closest={}
for i=1,Nmax do
local town=self:GetClosestTown(Coordinate, DistMax, closest)
if town then
table.insert(closest, town)
else
break
end
end
return closest
end
--- Get table of all towns, optionally of a given type.
-- @param #TOWNS self
-- @return #table Table of towns. Each element is of type #TOWN.Town.
+44 -14
View File
@@ -5498,15 +5498,15 @@ function AIRBOSS:_GetAircraftAoA( playerData )
aoa.OnSpeedMin = self:_AoAUnit2Deg( playerData, 14.0 ) -- 14.17 --14.5 units -- VNAO Edit - Original value 14.5
aoa.Fast = self:_AoAUnit2Deg( playerData, 13.5 ) -- 13.33 --14.0 units -- VNAO Edit - Original value 14
aoa.FAST = self:_AoAUnit2Deg( playerData, 12.5 ) -- 11.67 --13.0 units -- VNAO Edit - Original value 13
elseif goshawk then
elseif goshawk then --These parameters edited by CIRCUIT to support new VNAO flight model
-- T-45C Goshawk parameters.
aoa.SLOW = 8.00 -- 19
aoa.Slow = 7.75 -- 18
aoa.OnSpeedMax = 7.25 -- 17.5
aoa.OnSpeed = 7.00 -- 17
aoa.OnSpeedMin = 6.75 -- 16.5
aoa.Fast = 6.25 -- 16
aoa.FAST = 6.00 -- 15
aoa.SLOW = 9.5 -- 8.00 -- 19
aoa.Slow = 9.25 -- 7.75 -- 18
aoa.OnSpeedMax = 9.0 --7.25 -- 17.5
aoa.OnSpeed = 8.5 -- 7.00 -- 17
aoa.OnSpeedMin = 8.25 -- 6.75 -- 16.5
aoa.Fast = 7.75 -- 6.25 -- 16
aoa.FAST = 5.5 -- 6.00 -- 15
elseif skyhawk then
-- A-4E-C Skyhawk parameters from https://forums.eagle.ru/showpost.php?p=3703467&postcount=390
-- Note that these are arbitrary UNITS and not degrees. We need a conversion formula!
@@ -8161,8 +8161,14 @@ function AIRBOSS:_CheckPlayerStatus()
-- TODO: This might cause problems if the CCA is set to be very small!
if unit:IsInZone( self.zoneCCA ) then
-- VNAO Edit - Added wrapped up call to LSO grading
if playerData.step==AIRBOSS.PatternStep.WAKE then-- VNAO Edit - Added
local hornet = playerData.actype == AIRBOSS.AircraftCarrier.HORNET
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOE
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOF
or playerData.actype == AIRBOSS.AircraftCarrier.GROWLER
local tomcat = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B
-- VNAO Edit - Added wrapped up call to LSO grading Hornet
if playerData.step==AIRBOSS.PatternStep.WAKE and hornet then-- VNAO Edit - Added
if math.abs(playerData.unit:GetRoll())>35 and math.abs(playerData.unit:GetRoll())<=40 then-- VNAO Edit - Added
playerData.wrappedUpAtWakeLittle = true -- VNAO Edit - Added
elseif math.abs(playerData.unit:GetRoll()) >40 and math.abs(playerData.unit:GetRoll())<=45 then-- VNAO Edit - Added
@@ -8186,6 +8192,30 @@ function AIRBOSS:_CheckPlayerStatus()
end -- VNAO Edit - Added
end-- VNAO Edit - Added
-- VNAO Edit - Added wrapped up call to LSO grading Tomcat
if playerData.step==AIRBOSS.PatternStep.WAKE and tomcat then-- VNAO Edit - Added
if math.abs(playerData.unit:GetRoll())>35 and math.abs(playerData.unit:GetRoll())<=40 then-- VNAO Edit - Added
playerData.wrappedUpAtWakeLittle = true -- VNAO Edit - Added
elseif math.abs(playerData.unit:GetRoll()) >40 and math.abs(playerData.unit:GetRoll())<=45 then-- VNAO Edit - Added
playerData.wrappedUpAtWakeFull = true-- VNAO Edit - Added
elseif math.abs(playerData.unit:GetRoll()) >45 then-- VNAO Edit - Added
playerData.wrappedUpAtWakeUnderline = true -- VNAO Edit - Added
elseif math.abs(playerData.unit:GetRoll()) <12 and math.abs(playerData.unit:GetRoll()) >=5 then -- VNAO Edit - Added a new AA comment based on discussion with Lipps today, and going to replace the AA at the X with the original LUL comments
playerData.AAatWakeLittle = true -- VNAO Edit - Added
elseif math.abs(playerData.unit:GetRoll()) <5 and math.abs(playerData.unit:GetRoll()) >=2 then -- VNAO Edit - Added a new AA comment based on discussion with Lipps today, and going to replace the AA at the X with the original LUL comments
playerData.AAatWakeFull = true -- VNAO Edit - Added
elseif math.abs(playerData.unit:GetRoll()) <2 then -- VNAO Edit - Added a new AA comment based on discussion with Lipps today, and going to replace the AA at the X with the original LUL comments
playerData.AAatWakeUnderline = true -- VNAO Edit - Added
else -- VNAO Edit - Added
end -- VNAO Edit - Added
if math.abs(playerData.unit:GetAoA())>= 15 then -- VNAO Edit - Added
playerData.AFU = true -- VNAO Edit - Added
elseif math.abs(playerData.unit:GetAoA())<= 5 then -- VNAO Edit - Added
playerData.AFU = true -- VNAO Edit - Added
else -- VNAO Edit - Added
end -- VNAO Edit - Added
end-- VNAO Edit - Added
-- Display aircraft attitude and other parameters as message text.
if playerData.attitudemonitor then
@@ -12229,8 +12259,8 @@ function AIRBOSS:GetHeadingIntoWind_new( vdeck, magnetic, coord )
local magvar= magnetic and self.magvar or 0
-- Ship heading so cross wind is min for the given wind.
-- local intowind = (540 + (windto - magvar + math.deg(theta) )) % 360 -- VNAO Edit: Using old heading into wind algorithm
local intowind = self:GetHeadingIntoWind_old(vdeck,magnetic) -- VNAO Edit: Using old heading into wind algorithm
local intowind = (540 + (windto - magvar + math.deg(theta) )) % 360
return intowind, v
end
@@ -12682,7 +12712,8 @@ function AIRBOSS:_LSOgrade( playerData )
local nL=count(G, '_')/2
local nS=count(G, '%(')
local nN=N-nS-nL
if TIG=="_OK_" then nL = nL -1 end --Circuit added to prevent grade deduction for perfect groove
-- Groove time 15-18.99 sec for a unicorn. Or 60-65 for V/STOL unicorn.
local Tgroove=playerData.Tgroove
@@ -12712,7 +12743,6 @@ function AIRBOSS:_LSOgrade( playerData )
else
if vtol then
-- Add AV-8B Harrier devation allowances due to lower groundspeed and 3x conventional groove time, this allows to maintain LSO tolerances while respecting the deviations are not unsafe.--Pene testing
-- Large devaitions still result in a No Grade, A Unicorn still requires a clean pass with no deviation.
+6 -7
View File
@@ -839,8 +839,8 @@ function ARMYGROUP:Status()
local ammo=self:GetAmmoElement(element)
-- Output text for element.
text=text..string.format("\n[%d] %s: status=%s, life=%.1f/%.1f, guns=%d, rockets=%d, bombs=%d, missiles=%d, cargo=%d/%d kg",
i, name, status, life, life0, ammo.Guns, ammo.Rockets, ammo.Bombs, ammo.Missiles, element.weightCargo, element.weightMaxCargo)
text=text..string.format("\n[%d] %s: status=%s, life=%.1f/%.1f, guns=%d, cannons=%d, rockets=%d, missiles=%d, cargo=%d/%d kg",
i, name, status, life, life0, ammo.Guns, ammo.Cannons, ammo.Rockets, ammo.Missiles, element.weightCargo, element.weightMaxCargo)
end
if #self.elements==0 then
text=text.." none!"
@@ -1571,7 +1571,7 @@ end
-- @param Core.Zone#ZONE Zone The zone to return to.
-- @param #number Formation Formation of the group.
function ARMYGROUP:onafterRTZ(From, Event, To, Zone, Formation)
self:T2(self.lid.."onafterRTZ")
self:T(self.lid.."onafterRTZ")
-- Zone.
local zone=Zone or self.homezone
@@ -1841,8 +1841,6 @@ function ARMYGROUP:_UpdateEngageTarget()
-- Check if target moved more than 100 meters or we do not have line of sight.
if dist>100 or los==false then
--env.info("FF Update Engage Target Moved "..self.engage.Target:GetName())
-- Update new position.
self.engage.Coordinate:UpdateFromVec3(vec3)
@@ -1852,13 +1850,14 @@ function ARMYGROUP:_UpdateEngageTarget()
-- Remove current waypoint
self:RemoveWaypointByID(self.engage.Waypoint.uid)
local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.9)
-- Get new coordinate where to go.
local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.95)
-- Add waypoint after current.
self.engage.Waypoint=self:AddWaypoint(intercoord, self.engage.Speed, uid, self.engage.Formation, true)
-- Set if we want to resume route after reaching the detour waypoint.
self.engage.Waypoint.detour=0
self.engage.Waypoint.detour=1
end
+99 -25
View File
@@ -324,6 +324,10 @@
--
-- A ground attack mission can be created with the @{#AUFTRAG.NewGROUNDATTACK}() function.
--
-- ## NAVALENGAGEMENT
--
-- A naval engagement mission can be created with the @{#AUFTRAG.NewNAVALENGAGEMENT}() function.
--
-- # Assigning Missions
--
-- An AUFTRAG can be assigned to groups (FLIGHTGROUP, ARMYGROUP, NAVYGROUP), legions (AIRWING, BRIGADE, FLEET) or to a COMMANDER.
@@ -443,6 +447,7 @@ _AUFTRAGSNR=0
-- @field #string HOVER Hover.
-- @field #string LANDATCOORDINATE Land at coordinate.
-- @field #string GROUNDATTACK Ground attack.
-- @field #string NAVALENGAGEMENT Naval engagement (similar to GROUNDATTACK).
-- @field #string CARGOTRANSPORT Cargo transport.
-- @field #string RELOCATECOHORT Relocate a cohort from one legion to another.
-- @field #string AIRDEFENSE Air defense.
@@ -491,6 +496,7 @@ AUFTRAG.Type={
HOVER="Hover",
LANDATCOORDINATE="Land at Coordinate",
GROUNDATTACK="Ground Attack",
NAVALENGAGEMENT="Naval Engagement",
CARGOTRANSPORT="Cargo Transport",
RELOCATECOHORT="Relocate Cohort",
AIRDEFENSE="Air Defence",
@@ -515,6 +521,7 @@ AUFTRAG.Type={
-- @field #string BARRAGE Barrage.
-- @field #string HOVER Hover.
-- @field #string GROUNDATTACK Ground attack.
-- @field #string NAVALENGAGEMENT Naval engagement.
-- @field #string FERRY Ferry mission.
-- @field #string RELOCATECOHORT Relocate cohort.
-- @field #string AIRDEFENSE Air defense.
@@ -537,6 +544,7 @@ AUFTRAG.SpecialTask={
ARMORATTACK="AmorAttack",
HOVER="Hover",
GROUNDATTACK="Ground Attack",
NAVALENGAGEMENT="Naval Engagement",
FERRY="Ferry",
RELOCATECOHORT="Relocate Cohort",
AIRDEFENSE="Air Defense",
@@ -666,7 +674,7 @@ AUFTRAG.Category={
--- AUFTRAG class version.
-- @field #string version
AUFTRAG.version="1.2.1"
AUFTRAG.version="1.3.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@@ -2210,7 +2218,7 @@ end
-- **Note** that it is recommended to set the weapon range via the `OPSGROUP:AddWeaponRange()` function as this cannot be retrieved from the DCS API.
-- @param #AUFTRAG self
-- @param Core.Point#COORDINATE Target Center of the firing solution.
-- @param #number Nshots Number of shots to be fired. Default `#nil`.
-- @param #number Nshots Number of shots to be fired. Default `#nil`. If value is in (0,1), it is interpreted as per cent of available ammo.
-- @param #number Radius Radius of the shells in meters. Default 100 meters.
-- @param #number Altitude Altitude in meters. Can be used to setup a Barrage. Default `#nil`.
-- @return #AUFTRAG self
@@ -2382,11 +2390,13 @@ function AUFTRAG:NewARMORATTACK(Target, Speed, Formation)
return mission
end
--- **[GROUND]** Create a GROUNDATTACK mission. Ground group(s) will go to a target object and attack.
--- **[GROUND]** Create a GROUNDATTACK mission. Ground group(s) will go to a target object and attack at their own discretion.
-- Unfortunately, the "Attack Group" and "Attack Unit" tasks do not work for ground and naval groups (only for aircraft).
-- Therefore, we resort to this workaround, which guides the attacking group to the vicinity of the target. Then they start shooting on their own, once they detect the target.
-- @param #AUFTRAG self
-- @param Wrapper.Positionable#POSITIONABLE Target The target to attack. Can be a GROUP, UNIT or STATIC object.
-- @param #number Speed Speed in knots. Default max.
-- @param #string Formation The attack formation, e.g. "Wedge", "Vee" etc. Default `ENUMS.Formation.Vehicle.Vee`.
-- @param #string Formation The attack formation, e.g. "Wedge", "Vee" etc. Default `ENUMS.Formation.Vehicle.Vee`. Only working for ground, not naval!
-- @return #AUFTRAG self
function AUFTRAG:NewGROUNDATTACK(Target, Speed, Formation)
@@ -2413,6 +2423,38 @@ function AUFTRAG:NewGROUNDATTACK(Target, Speed, Formation)
return mission
end
--- **[NAVAL]** Create a NAVALENGAGEMENT mission. Naval group(s) will go to a target object and attack at their own discretion.
-- Unfortunately, the "Attack Group" and "Attack Unit" tasks do not work for ground and naval groups (only for aircraft).
-- Therefore, we resort to this workaround, which guides the attacking group to the vicinity of the target. Then they start shooting on their own, once they detect the target.
-- @param #AUFTRAG self
-- @param Wrapper.Positionable#POSITIONABLE Target The target to attack. Can be a GROUP, UNIT or STATIC object.
-- @param #number Speed Speed in knots. Default max.
-- @param #number Depth The attack depth in meters. Only for submarines!
-- @return #AUFTRAG self
function AUFTRAG:NewNAVALENGAGEMENT(Target, Speed, Depth)
local mission=AUFTRAG:New(AUFTRAG.Type.NAVALENGAGEMENT)
mission:_TargetFromObject(Target)
mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.NAVALENGAGEMENT)
-- Defaults.
mission.optionROE=ENUMS.ROE.OpenFire
mission.optionAlarm=ENUMS.AlarmState.Auto
mission.missionFraction=0.70
mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed) or nil
mission.missionAltitude=Depth or 0
mission.categories={AUFTRAG.Category.NAVAL}
mission.DCStask=mission:GetDCSMissionTask()
mission.DCStask.params.speed=mission.missionSpeed and UTILS.KmphToMps(mission.missionSpeed) or nil
return mission
end
--- **[AIR, GROUND, NAVAL]** Create a RECON mission.
-- @param #AUFTRAG self
-- @param Core.Set#SET_ZONE ZoneSet The recon zones.
@@ -2705,33 +2747,35 @@ function AUFTRAG:NewFromTarget(Target, MissionType)
local mission=nil --#AUFTRAG
if MissionType==AUFTRAG.Type.ANTISHIP then
mission=self:NewANTISHIP(Target, Altitude)
mission=self:NewANTISHIP(Target)
elseif MissionType==AUFTRAG.Type.ARTY then
mission=self:NewARTY(Target, Nshots, Radius)
mission=self:NewARTY(Target, 0.3) -- use 30% of the available ammo
elseif MissionType==AUFTRAG.Type.BAI then
mission=self:NewBAI(Target, Altitude)
mission=self:NewBAI(Target)
elseif MissionType==AUFTRAG.Type.BOMBCARPET then
mission=self:NewBOMBCARPET(Target, Altitude, CarpetLength)
mission=self:NewBOMBCARPET(Target)
elseif MissionType==AUFTRAG.Type.BOMBING then
mission=self:NewBOMBING(Target, Altitude)
mission=self:NewBOMBING(Target)
elseif MissionType==AUFTRAG.Type.BOMBRUNWAY then
mission=self:NewBOMBRUNWAY(Target, Altitude)
mission=self:NewBOMBRUNWAY(Target)
elseif MissionType==AUFTRAG.Type.STRAFING then
mission=self:NewSTRAFING(Target, Altitude)
mission=self:NewSTRAFING(Target)
elseif MissionType==AUFTRAG.Type.CAS then
mission=self:NewCAS(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000), Altitude, Speed, Target:GetAverageCoordinate(), Heading, Leg, TargetTypes)
mission=self:NewCAS(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000), nil, nil, Target:GetAverageCoordinate())
elseif MissionType==AUFTRAG.Type.CASENHANCED then
mission=self:NewCASENHANCED(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000), Altitude, Speed, RangeMax, NoEngageZoneSet, TargetTypes)
mission=self:NewCASENHANCED(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000))
elseif MissionType==AUFTRAG.Type.INTERCEPT then
mission=self:NewINTERCEPT(Target)
elseif MissionType==AUFTRAG.Type.SEAD then
mission=self:NewSEAD(Target, Altitude)
mission=self:NewSEAD(Target)
elseif MissionType==AUFTRAG.Type.STRIKE then
mission=self:NewSTRIKE(Target, Altitude)
mission=self:NewSTRIKE(Target)
elseif MissionType==AUFTRAG.Type.ARMORATTACK then
mission=self:NewARMORATTACK(Target, Speed)
mission=self:NewARMORATTACK(Target)
elseif MissionType==AUFTRAG.Type.GROUNDATTACK then
mission=self:NewGROUNDATTACK(Target, Speed, Formation)
mission=self:NewGROUNDATTACK(Target)
elseif MissionType==AUFTRAG.Type.NAVALENGAGEMENT then
mission=self:NewNAVALENGAGEMENT(Target)
else
return nil
end
@@ -2848,7 +2892,7 @@ function AUFTRAG:NewAUTO(EngageGroup)
if auftrag==AUFTRAG.Type.ANTISHIP then
mission=AUFTRAG:NewANTISHIP(Target)
elseif auftrag==AUFTRAG.Type.ARTY then
mission=AUFTRAG:NewARTY(Target)
mission=AUFTRAG:NewARTY(Target, 0.2)
elseif auftrag==AUFTRAG.Type.AWACS then
mission=AUFTRAG:NewAWACS(Coordinate, Altitude,Speed,Heading,Leg)
elseif auftrag==AUFTRAG.Type.BAI then
@@ -4399,7 +4443,7 @@ function AUFTRAG:onafterStatus(From, Event, To)
-- Group info.
if self.verbose>=3 then
-- Data on assigned groups.
local text=string.format("Assets [N=%d,Nassigned=%s, Ndead=%s]:", self.Nassets or 0, self.Nassigned or 0, self.Ndead or 0)
local text=string.format("Assets [N=%d, Nassigned=%s, Ndead=%s]:", self.Nassets or 0, self.Nassigned or 0, self.Ndead or 0)
for i,_asset in pairs(self.assets or {}) do
local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem
text=text..string.format("\n[%d] %s: spawned=%s, requested=%s, reserved=%s", i, asset.spawngroupname, tostring(asset.spawned), tostring(asset.requested), tostring(asset.reserved))
@@ -4440,7 +4484,7 @@ function AUFTRAG:Evaluate()
local owndamage=self.Ncasualties/self.Nelements*100
-- Current number of mission targets.
local Ntargets=self:CountMissionTargets()
local Ntargets=self:CountMissionTargets(true)
local Ntargets0=self:GetTargetInitialNumber()
local Life=self:GetTargetLife()
@@ -4891,16 +4935,25 @@ function AUFTRAG:CheckGroupsDone()
self:T2(self.lid..string.format("CheckGroupsDone: Mission is still in state %s [FSM=%s] and reinfoce=%d. Mission NOT DONE!", self.status, self:GetState(), self.reinforce))
return false
end
local NopsgroupsAlive=self:CountOpsGroups()
local NopsgroupsDone=self:CountOpsGroupsInStatus(AUFTRAG.GroupStatus.DONE)+self:CountOpsGroupsInStatus(AUFTRAG.GroupStatus.CANCELLED)
-- It could be that all groups were destroyed on the way to the mission execution waypoint.
-- TODO: would be better to check if everybody is dead by now.
if self:IsStarted() and self:CountOpsGroups()==0 then
if self:IsStarted() and NopsgroupsAlive==0 then
self:T(self.lid..string.format("CheckGroupsDone: Mission is STARTED state %s [FSM=%s] but count of alive OPSGROUP is zero. Mission DONE!", self.status, self:GetState()))
return true
end
if (self:IsStarted() or self:IsExecuting()) and (fsmState == AUFTRAG.Status.STARTED or fsmState == AUFTRAG.Status.EXECUTING) and self:CountOpsGroups()>0 then
self:T(self.lid..string.format("CheckGroupsDone: Mission is STARTED state %s [FSM=%s] and count of alive OPSGROUP > zero. Mission NOT DONE!", self.status, self:GetState()))
-- Every group alive is done or cancelled
if NopsgroupsAlive==NopsgroupsDone then
self:T(self.lid..string.format("CheckGroupsDone: Mission is in state %s [FSM=%s] but all groups [=%d] are done or cancelled. Mission DONE!", self.status, self:GetState(), NopsgroupsAlive))
return true
end
if (self:IsStarted() or self:IsExecuting()) and (fsmState == AUFTRAG.Status.STARTED or fsmState == AUFTRAG.Status.EXECUTING) and NopsgroupsAlive>0 then
self:T(self.lid..string.format("CheckGroupsDone: Mission is in state %s [FSM=%s] and count of alive OPSGROUP > zero. Mission NOT DONE!", self.status, self:GetState()))
return false
end
@@ -5494,8 +5547,9 @@ end
--- Count alive mission targets.
-- @param #AUFTRAG self
-- @param #boolean OnlyReallyAlive (Optional) If `true`, count only really alive targets (units, groups) but not coordinates or zones.
-- @return #number Number of alive target units.
function AUFTRAG:CountMissionTargets()
function AUFTRAG:CountMissionTargets(OnlyReallyAlive)
local N=0
@@ -5503,7 +5557,7 @@ function AUFTRAG:CountMissionTargets()
local Coalitions=self.coalition and UTILS.GetCoalitionEnemy(self.coalition, true) or nil
if self.engageTarget then
N=self.engageTarget:CountTargets(Coalitions)
N=self.engageTarget:CountTargets(Coalitions, OnlyReallyAlive)
end
return N
@@ -6642,6 +6696,26 @@ function AUFTRAG:GetDCSMissionTask()
table.insert(DCStasks, DCStask)
elseif self.type==AUFTRAG.Type.NAVALENGAGEMENT then
---------------------------
-- NAVAL ENGAGEMENT Mission --
---------------------------
local DCStask={}
DCStask.id=AUFTRAG.SpecialTask.NAVALENGAGEMENT
-- We create a "fake" DCS task and pass the parameters to the NAVYGROUP.
local param={}
param.target=self:GetTargetData()
param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed) or nil
param.altitude=self.missionAltitude or 0
DCStask.params=param
table.insert(DCStasks, DCStask)
elseif self.type==AUFTRAG.Type.AMMOSUPPLY then
-------------------------
File diff suppressed because it is too large Load Diff
+7 -3
View File
@@ -35,7 +35,7 @@
-- @field #number Nsuccess Number of successful missions.
-- @field #number Nfailure Number of failed mission.
-- @field #table assetNumbers Asset numbers. Each entry is a table of data type `#CHIEF.AssetNumber`.
-- @extends Ops.Intel#INTEL
-- @extends Ops.Intelligence#INTEL
--- *In preparing for battle I have always found that plans are useless, but planning is indispensable* -- Dwight D Eisenhower
--
@@ -48,7 +48,7 @@
--
-- # Territory
--
-- The chief class allows you to define boarder zones, conflict zones and attack zones.
-- The chief class allows you to define border zones, conflict zones and attack zones.
--
-- ## Border Zones
--
@@ -332,7 +332,7 @@ CHIEF.Strategy = {
--- CHIEF class version.
-- @field #string version
CHIEF.version="0.6.1"
CHIEF.version="0.7.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@@ -2897,6 +2897,8 @@ function CHIEF:_GetMissionPerformanceFromTarget(Target)
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.ARTY, 30))
else
-- Everything else
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BAI, 100))
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.GROUNDATTACK, 50))
@@ -2912,6 +2914,8 @@ function CHIEF:_GetMissionPerformanceFromTarget(Target)
---
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.ANTISHIP, 100))
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.NAVALENGAGEMENT, 50))
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.ARTY, 30))
else
self:E(self.lid.."ERROR: Unknown Group category!")
+28 -18
View File
@@ -629,32 +629,42 @@ end
--- Remove assets from pool. Not that assets must not be spawned or already reserved or requested.
-- @param #COHORT self
-- @param #number N Number of assets to be removed. Default 1.
-- @param #number Delay Delay in seconds before assets are removed.
-- @return #COHORT self
function COHORT:RemoveAssets(N)
function COHORT:RemoveAssets(N, Delay)
self:T2(self.lid..string.format("Remove %d assets of Cohort", N))
N=N or 1
local n=0
for i=#self.assets,1,-1 do
local asset=self.assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem
if Delay and Delay>0 then
-- Delayed call
self:ScheduleOnce(Delay, COHORT.RemoveAssets, self, N, 0)
else
self:T2(self.lid..string.format("Checking removing asset %s", asset.spawngroupname))
if not (asset.requested or asset.spawned or asset.isReserved) then
self:T2(self.lid..string.format("Removing asset %s", asset.spawngroupname))
table.remove(self.assets, i)
n=n+1
else
self:T2(self.lid..string.format("Could NOT Remove asset %s", asset.spawngroupname))
N=N or 1
local n=0
for i=#self.assets,1,-1 do
local asset=self.assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem
self:T2(self.lid..string.format("Checking removing asset %s", asset.spawngroupname))
if not (asset.requested or asset.spawned or asset.isReserved) then
self:T2(self.lid..string.format("Removing asset %s", asset.spawngroupname))
-- Remove from warehouse and warehouse DB
asset.legion:_DeleteStockItem(asset)
table.remove(self.assets, i)
n=n+1
else
self:T2(self.lid..string.format("Could NOT Remove asset %s", asset.spawngroupname))
end
if n>=N then
break
end
end
if n>=N then
break
end
self:T(self.lid..string.format("Removed %d/%d assets. New asset count=%d", n, N, #self.assets))
end
self:T(self.lid..string.format("Removed %d/%d assets. New asset count=%d", n, N, #self.assets))
return self
end
+10 -5
View File
@@ -1901,7 +1901,7 @@ function LEGION:_TacticalOverview()
for _,mtype in pairs(AUFTRAG.Type) do
local n=self:CountMissionsInQueue(mtype)
if n>0 then
local N=self:CountMissionsInQueue(mtype)
local N=self:CountMissionsInQueue(mtype, true)
text=text..string.format(" - %s: %d [Running=%d]\n", mtype, n, N)
end
end
@@ -2057,18 +2057,23 @@ end
--- Count missions in mission queue.
-- @param #LEGION self
-- @param #table MissionTypes Types on mission to be checked. Default *all* possible types `AUFTRAG.Type`.
-- @param #boolean OnlyRunning If `true`, only count running missions.
-- @return #number Number of missions that are not over yet.
function LEGION:CountMissionsInQueue(MissionTypes)
function LEGION:CountMissionsInQueue(MissionTypes, OnlyRunning)
MissionTypes=MissionTypes or AUFTRAG.Type
local N=0
for _,_mission in pairs(self.missionqueue) do
local mission=_mission --Ops.Auftrag#AUFTRAG
if (not OnlyRunning) or (mission.statusLegion~=AUFTRAG.Status.PLANNED) then
-- Check if this mission type is requested.
if mission:IsNotOver() and AUFTRAG.CheckMissionType(mission.type, MissionTypes) then
N=N+1
-- Check if this mission type is requested.
if mission:IsNotOver() and AUFTRAG.CheckMissionType(mission.type, MissionTypes) then
N=N+1
end
end
end
+25 -19
View File
@@ -86,12 +86,14 @@ NAVYGROUP = {
-- @field Ops.Target#TARGET Target The target.
-- @field Core.Point#COORDINATE Coordinate Last known coordinate of the target.
-- @field Ops.OpsGroup#OPSGROUP.Waypoint Waypoint the waypoint created to go to the target.
-- @field #number Speed Speed in knots.
-- @field #number Depth Depth of the engagement (submarines).
-- @field #number roe ROE backup.
-- @field #number alarmstate Alarm state backup.
--- NavyGroup version.
-- @field #string version
NAVYGROUP.version="1.0.3"
NAVYGROUP.version="1.0.4"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@@ -1081,7 +1083,7 @@ function NAVYGROUP:onafterSpawned(From, Event, To)
text=text..string.format("Elements = %d\n", #self.elements)
text=text..string.format("Waypoints = %d\n", #self.waypoints)
text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On))
text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d/T=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Missiles, self.ammo.Torpedos)
text=text..string.format("Ammo = %d (G=%d/C=%d/R=%d/M=%d/T=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Cannons, self.ammo.Rockets, self.ammo.Missiles, self.ammo.Torpedos)
text=text..string.format("FSM state = %s\n", self:GetState())
text=text..string.format("Is alive = %s\n", tostring(self:IsAlive()))
text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated()))
@@ -1602,8 +1604,10 @@ end
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Wrapper.Group#GROUP Group the group to be engaged.
function NAVYGROUP:onafterEngageTarget(From, Event, To, Target)
-- @param Ops.Target#TARGET Target The target to be engaged. Can also be a GROUP or UNIT object.
-- @param #number Speed Attack speed in knots.
-- @param #number Depth The depth in meters. Only for submarins.
function NAVYGROUP:onafterEngageTarget(From, Event, To, Target, Speed, Depth)
self:T(self.lid.."Engaging Target")
if Target:IsInstanceOf("TARGET") then
@@ -1615,11 +1619,9 @@ function NAVYGROUP:onafterEngageTarget(From, Event, To, Target)
-- Target coordinate.
self.engage.Coordinate=UTILS.DeepCopy(self.engage.Target:GetCoordinate())
local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.9)
-- Get a coordinate close to the target.
local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.8)
-- Backup ROE and alarm state.
self.engage.roe=self:GetROE()
self.engage.alarmstate=self:GetAlarmstate()
@@ -1629,11 +1631,17 @@ function NAVYGROUP:onafterEngageTarget(From, Event, To, Target)
self:SwitchROE(ENUMS.ROE.OpenFire)
-- ID of current waypoint.
local uid=self:GetWaypointCurrent().uid
local uid=self:GetWaypointCurrentUID()
-- Set formation.
self.engage.Depth=Depth or 0
-- Set speed.
self.engage.Speed=Speed
-- Add waypoint after current.
self.engage.Waypoint=self:AddWaypoint(intercoord, nil, uid, Formation, true)
self.engage.Waypoint=self:AddWaypoint(intercoord, Speed, uid, Depth, true)
-- Set if we want to resume route after reaching the detour waypoint.
self.engage.Waypoint.detour=1
@@ -1656,24 +1664,22 @@ function NAVYGROUP:_UpdateEngageTarget()
-- Check if target moved more than 100 meters.
if dist>100 then
--env.info("FF Update Engage Target Moved "..self.engage.Target:GetName())
-- Update new position.
self.engage.Coordinate:UpdateFromVec3(vec3)
-- ID of current waypoint.
local uid=self:GetWaypointCurrent().uid
local uid=self:GetWaypointCurrentUID()
-- Remove current waypoint
self:RemoveWaypointByID(self.engage.Waypoint.uid)
local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.9)
local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.8)
-- Add waypoint after current.
self.engage.Waypoint=self:AddWaypoint(intercoord, nil, uid, Formation, true)
self.engage.Waypoint=self:AddWaypoint(intercoord, self.engage.Speed, uid, self.engage.Depth, true)
-- Set if we want to resume route after reaching the detour waypoint.
self.engage.Waypoint.detour=0
self.engage.Waypoint.detour=1
end
@@ -1709,8 +1715,8 @@ function NAVYGROUP:onafterDisengage(From, Event, To)
local task=self:GetTaskCurrent()
-- Get if current task is ground attack.
if task and task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK then
self:T(self.lid.."Disengage with current task GROUNDATTACK ==> Task Done!")
if task and (task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK or task.dcstask.id==AUFTRAG.SpecialTask.NAVALENGAGEMENT) then
self:T(self.lid.."Disengage with current task GROUNDATTACK/NAVALENGAGEMENT ==> Task Done!")
self:TaskDone(task)
end
+57 -17
View File
@@ -405,7 +405,9 @@ OPSGROUP.TaskType={
--- Ammo data.
-- @type OPSGROUP.Ammo
-- @field #number Total Total amount of ammo.
-- @field #number Guns Amount of gun shells.
-- @field #number Shells Amount of shells (guns + cannons).
-- @field #number Guns Amount of gun shells (caliber < 25).
-- @field #number Cannons Amount of cannon shells (caliber >= 25).
-- @field #number Bombs Amount of bombs.
-- @field #number Rockets Amount of rockets.
-- @field #number Torpedos Amount of torpedos.
@@ -1195,8 +1197,8 @@ function OPSGROUP:GetDCSObject()
return self.dcsgroup
end
--- Set detection on or off.
-- If detection is on, detected targets of the group will be evaluated and FSM events triggered.
--- Make a target (unit, group, opsgroup) known to this group.
-- This is useing the DCS function `controller.knowTarget`.
-- @param #OPSGROUP self
-- @param Wrapper.Positionable#POSITIONABLE TargetObject The target object.
-- @param #boolean KnowType Make type known.
@@ -4519,6 +4521,25 @@ function OPSGROUP:_UpdateTask(Task, Mission)
if target then
self:EngageTarget(target, speed, Task.dcstask.params.formation)
end
elseif Task.dcstask.id==AUFTRAG.SpecialTask.NAVALENGAGEMENT then
---
-- Task "Naval Engagement" Mission.
---
-- Engage target.
local target=Task.dcstask.params.target --Ops.Target#TARGET
-- Set speed. Default max.
local speed=self.speedMax and UTILS.KmphToKnots(self.speedMax) or nil
if Task.dcstask.params.speed then
speed=UTILS.MpsToKnots(Task.dcstask.params.speed)
end
if target then
self:EngageTarget(target, speed, Task.dcstask.params.altitude)
end
elseif Task.dcstask.id==AUFTRAG.SpecialTask.PATROLRACETRACK then
@@ -4745,7 +4766,7 @@ function OPSGROUP:_UpdateTask(Task, Mission)
elseif weaponType==ENUMS.WeaponFlag.AnyRocket then
nAmmo=ammo.Rockets
elseif weaponType==ENUMS.WeaponFlag.Cannons then
nAmmo=ammo.Guns
nAmmo=ammo.Cannons
end
--TODO: Update target location while we're at it anyway.
@@ -4892,7 +4913,7 @@ function OPSGROUP:onafterTaskCancel(From, Event, To, Task)
done=true
elseif Task.dcstask.id==AUFTRAG.SpecialTask.ONGUARD or Task.dcstask.id==AUFTRAG.SpecialTask.ARMOREDGUARD then
done=true
elseif Task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK or Task.dcstask.id==AUFTRAG.SpecialTask.ARMORATTACK then
elseif Task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK or Task.dcstask.id==AUFTRAG.SpecialTask.ARMORATTACK or Task.dcstask.id==AUFTRAG.SpecialTask.NAVALENGAGEMENT then
done=true
elseif Task.dcstask.id==AUFTRAG.SpecialTask.NOTHING then
done=true
@@ -5724,7 +5745,7 @@ end
function OPSGROUP:onafterMissionDone(From, Event, To, Mission)
-- Debug info.
local text=string.format("Mission %s DONE!", Mission.name)
local text=string.format("Mission DONE %s!", Mission.name)
self:T(self.lid..text)
-- Set group status.
@@ -6237,11 +6258,12 @@ function OPSGROUP:RouteToMission(mission, delay)
self:T(self.lid.."Already in mission zone ==> TaskExecute()")
self:TaskExecute(waypointtask)
-- TODO: Calling PassingWaypoint here is probably better as it marks the mission waypoint as passed!
--self:PassingWaypoint(waypoint)
self:PassingWaypoint(waypoint)
return
elseif d<25 then
self:T(self.lid.."Already within 25 meters of mission waypoint ==> TaskExecute()")
self:TaskExecute(waypointtask)
self:PassingWaypoint(waypoint)
return
end
@@ -7856,7 +7878,7 @@ function OPSGROUP:_Spawn(Delay, Template)
self:ScheduleOnce(Delay, OPSGROUP._Spawn, self, 0, Template)
else
-- Debug output.
self:T2({Template=Template})
--self:T2({Template=Template})
if self:IsArmygroup() and self.ValidateAndRepositionGroundUnits then
UTILS.ValidateAndRepositionGroundUnits(Template.units)
@@ -11196,11 +11218,11 @@ function OPSGROUP:_CheckAmmoStatus()
self:OutOfAmmo()
end
-- Guns.
if self.outofGuns and ammo.Guns>0 then
-- Guns (changed to shells)
if self.outofGuns and ammo.Shells>0 then
self.outofGuns=false
end
if ammo.Guns==0 and self.ammo.Guns>0 and not self.outofGuns then
if ammo.Shells==0 and self.ammo.Shells>0 and not self.outofGuns then
self.outofGuns=true
self:OutOfGuns()
end
@@ -13376,7 +13398,9 @@ function OPSGROUP:GetAmmoTot()
local Ammo={} --#OPSGROUP.Ammo
Ammo.Total=0
Ammo.Shells=0
Ammo.Guns=0
Ammo.Cannons=0
Ammo.Rockets=0
Ammo.Bombs=0
Ammo.Torpedos=0
@@ -13397,7 +13421,9 @@ function OPSGROUP:GetAmmoTot()
-- Add up total.
Ammo.Total=Ammo.Total+ammo.Total
Ammo.Shells=Ammo.Shells+ammo.Shells
Ammo.Guns=Ammo.Guns+ammo.Guns
Ammo.Cannons=Ammo.Cannons+ammo.Cannons
Ammo.Rockets=Ammo.Rockets+ammo.Rockets
Ammo.Bombs=Ammo.Bombs+ammo.Bombs
Ammo.Torpedos=Ammo.Torpedos+ammo.Torpedos
@@ -13430,6 +13456,8 @@ function OPSGROUP:GetAmmoUnit(unit, display)
-- Init counter.
local nammo=0
local nshells=0
local nguns=0
local ncannons=0
local nrockets=0
local nmissiles=0
local nmissilesAA=0
@@ -13452,10 +13480,10 @@ function OPSGROUP:GetAmmoUnit(unit, display)
local ammotable=unit:GetAmmo()
if ammotable then
local weapons=#ammotable
--self:I(ammotable)
--self:I(ammotable)
--UTILS.PrintTableToLog(ammotable)
-- Loop over all weapons.
for w=1,weapons do
@@ -13463,9 +13491,9 @@ function OPSGROUP:GetAmmoUnit(unit, display)
-- Number of current weapon.
local Nammo=ammotable[w]["count"]
-- Range in meters. Seems only to exist for missiles (not shells).
local rmin=ammotable[w]["desc"]["rangeMin"] or 0
local rmax=ammotable[w]["desc"]["rangeMaxAltMin"] or 0
-- Range in meters. Seems only to exist for missiles (not shells).
local rmin=ammotable[w]["desc"]["rangeMin"] or 0
local rmax=ammotable[w]["desc"]["rangeMaxAltMin"] or 0
-- Type name of current weapon.
local Tammo=ammotable[w]["desc"]["typeName"]
@@ -13487,6 +13515,16 @@ function OPSGROUP:GetAmmoUnit(unit, display)
-- Add up all shells.
nshells=nshells+Nammo
-- Add small and large caliber shells for guns and cannons
if ammotable[w]["desc"]["warhead"] and ammotable[w]["desc"]["warhead"]["caliber"] then
local caliber=ammotable[w]["desc"]["warhead"]["caliber"]
if caliber<25 then
nguns=nguns+Nammo
else
ncannons=ncannons+Nammo
end
end
-- Debug info.
text=text..string.format("- %d shells of type %s, range=%d - %d meters\n", Nammo, _weaponName, rmin, rmax)
@@ -13565,7 +13603,9 @@ function OPSGROUP:GetAmmoUnit(unit, display)
local ammo={} --#OPSGROUP.Ammo
ammo.Total=nammo
ammo.Guns=nshells
ammo.Shells=nshells
ammo.Guns=nguns
ammo.Cannons=ncannons
ammo.Rockets=nrockets
ammo.Bombs=nbombs
ammo.Torpedos=ntorps
+96 -31
View File
@@ -21,7 +21,7 @@
-- ===
-- @module Ops.PlayerTask
-- @image OPS_PlayerTask.jpg
-- @date Last Update May 2025
-- @date Last Update Oct 2025
do
@@ -59,6 +59,8 @@ do
-- @field #string FinalState
-- @field #string TypeName
-- @field #number PreviousCount
-- @field #boolean CanSmoke
-- @field #boolean ShowThreatDetails
-- @extends Core.Fsm#FSM
@@ -94,11 +96,13 @@ PLAYERTASK = {
NextTaskFailure = {},
FinalState = "none",
PreviousCount = 0,
CanSmoke = true,
ShowThreatDetails = true,
}
--- PLAYERTASK class version.
-- @field #string version
PLAYERTASK.version="0.1.28"
PLAYERTASK.version="0.1.29"
--- Generic task condition.
-- @type PLAYERTASK.Condition
@@ -231,6 +235,7 @@ function PLAYERTASK:New(Type, Target, Repeat, Times, TTSType)
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #boolen Silent If true, suppress message output on cancel.
--- On After "Planned" event. Task has been planned.
-- @function [parent=#PLAYERTASK] OnAfterPilotPlanned
@@ -468,6 +473,26 @@ function PLAYERTASK:SetSubType(Type)
return self
end
--- [USER] Set if a task can have a smoke marker.
-- @param #PLAYERTASK self
-- @param #boolean OnOff If true (default) it can be smoke, false if not.
-- @return #PLAYERTASK self
function PLAYERTASK:SetCanSmoke(OnOff)
self:T(self.lid.."AddSSetCanSmokeubType")
self.CanSmoke = OnOff
return self
end
--- [USER] Set if a task can show threat details.
-- @param #PLAYERTASK self
-- @param #boolean OnOff If true (default) it can be shown, false if not.
-- @return #PLAYERTASK self
function PLAYERTASK:SetShowThreatDetails(OnOff)
self:T(self.lid.."SetShowThreatDetails")
self.ShowThreatDetails = OnOff
return self
end
--- [USER] Get task sub type description from this task.
-- @param #PLAYERTASK self
-- @return #string Type or nil
@@ -1173,11 +1198,12 @@ end
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param #boolean Silent
-- @return #PLAYERTASK self
function PLAYERTASK:onafterCancel(From, Event, To)
function PLAYERTASK:onafterCancel(From, Event, To, Silent)
self:T({From, Event, To})
if self.TaskController then
self.TaskController:__TaskCancelled(-1,self)
self.TaskController:__TaskCancelled(-1,self, Silent)
end
self.timestamp = timer.getAbsTime()
self.FinalState = "Cancelled"
@@ -1606,7 +1632,7 @@ do
--
-- The event is triggered when a task was cancelled manually. Use @{#PLAYERTASKCONTROLLER.OnAfterTaskCancelled}()` to link into this event:
--
-- function taskmanager:OnAfterTaskCancelled(From, Event, To, Task)
-- function taskmanager:OnAfterTaskCancelled(From, Event, To, Task, Silent)
-- ... your code here ...
-- end
--
@@ -1770,12 +1796,15 @@ PLAYERTASKCONTROLLER.Messages = {
THREATMEDIUM = "medium",
THREATLOW = "low",
THREATTEXT = "%s\nThreat: %s\nTargets left: %d\nCoord: %s",
NOTHREATTEXT = "%s\nNo target information available.",
ELEVATION = "\nTarget Elevation: %s %s",
METER = "meter",
FEET = "feet",
THREATTEXTTTS = "%s, %s. Target information for %s. Threat level %s. Targets left %d. Target location %s.",
NOTHREATTEXTTTS = "%s, %s. No target information available.",
MARKTASK = "%s, %s, copy, task %03d location marked on map!",
SMOKETASK = "%s, %s, copy, task %03d location smoked!",
NOSMOKETASK = "%s, %s, negative, task %03d location cannot be smoked!",
FLARETASK = "%s, %s, copy, task %03d location illuminated!",
ABORTTASK = "All stations, %s, %s has aborted %s task %03d!",
UNKNOWN = "Unknown",
@@ -1854,12 +1883,15 @@ PLAYERTASKCONTROLLER.Messages = {
THREATMEDIUM = "mittel",
THREATLOW = "niedrig",
THREATTEXT = "%s\nGefahrstufe: %s\nZiele: %d\nKoord: %s",
NOTHREATTEXT = "%s\nKeine Zielinformation verfügbar.",
ELEVATION = "\nZiel Höhe: %s %s",
METER = "Meter",
FEET = "Fuss",
THREATTEXTTTS = "%s, %s. Zielinformation zu %s. Gefahrstufe %s. Ziele %d. Zielposition %s.",
NOTHREATTEXTTTS = "%s, %s. Keine Zielinformation verfügbar.",
MARKTASK = "%s, %s, verstanden, Zielposition %03d auf der Karte markiert!",
SMOKETASK = "%s, %s, verstanden, Zielposition %03d mit Rauch markiert!",
NOSMOKETASK = "%s, %s, negativ, Zielposition %03d kann nicht markiert werden!",
FLARETASK = "%s, %s, verstanden, Zielposition %03d beleuchtet!",
ABORTTASK = "%s, an alle, %s hat Auftrag %s %03d abgebrochen!",
UNKNOWN = "Unbekannt",
@@ -1920,7 +1952,7 @@ PLAYERTASKCONTROLLER.Messages = {
--- PLAYERTASK class version.
-- @field #string version
PLAYERTASKCONTROLLER.version="0.1.70"
PLAYERTASKCONTROLLER.version="0.1.71"
--- Create and run a new TASKCONTROLLER instance.
-- @param #PLAYERTASKCONTROLLER self
@@ -2061,6 +2093,7 @@ function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter)
-- @param #string Event Event.
-- @param #string To To state.
-- @param Ops.PlayerTask#PLAYERTASK Task
-- @param #boolean Silent If true suppress message output.
--- On After "TaskFailed" event. Task has failed.
-- @function [parent=#PLAYERTASKCONTROLLER] OnAfterTaskFailed
@@ -2741,11 +2774,12 @@ end
--- [User] Manually cancel a specific task
-- @param #PLAYERTASKCONTROLLER self
-- @param Ops.PlayerTask#PLAYERTASK Task The task to be cancelled
-- @param Ops.PlayerTask#PLAYERTASK Task The task to be cancelled.
-- @param #boolean Silent If true suppress message output.
-- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:CancelTask(Task)
function PLAYERTASKCONTROLLER:CancelTask(Task,Silent)
self:T(self.lid.."CancelTask")
Task:__Cancel(-1)
Task:__Cancel(-1,Silent)
return self
end
@@ -3734,6 +3768,7 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client)
local Elevation = Coordinate:GetLandHeight() or 0 -- meters
local CoordText = ""
local CoordTextLLDM = nil
local ShowThreatInfo = task.ShowThreatDetails
local LasingDrone = self:_FindLasingDroneForTaskID(task.PlayerTaskNr)
if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A then
CoordText = Coordinate:ToStringA2G(Client,nil,self.ShowMagnetic)
@@ -3757,7 +3792,12 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client)
local clientlist, clientcount = task:GetClients()
local ThreatGraph = "[" .. string.rep( "", ThreatLevel ) .. string.rep( "", 10 - ThreatLevel ) .. "]: "..ThreatLevel
local ThreatLocaleText = self.gettext:GetEntry("THREATTEXT",self.locale)
text = string.format(ThreatLocaleText, taskname, ThreatGraph, targets, CoordText)
if ShowThreatInfo == true then
text = string.format(ThreatLocaleText, taskname, ThreatGraph, targets, CoordText)
else
ThreatLocaleText = self.gettext:GetEntry("NOTHREATTEXT",self.locale)
text = string.format(ThreatLocaleText, taskname)
end
local settings = _DATABASE:GetPlayerSettings(playername) or _SETTINGS -- Core.Settings#SETTINGS
local elevationmeasure = self.gettext:GetEntry("FEET",self.locale)
if settings:IsMetric() then
@@ -3880,9 +3920,19 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client)
end
--self:I(self.lid.." | ".. CoordText)
end
local ttstext
local ThreatLocaleTextTTS = self.gettext:GetEntry("THREATTEXTTTS",self.locale)
local ttstext = string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name,ttstaskname,ThreatLevelText, targets, CoordText)
-- THREATTEXT = "%s\nThreat: %s\nTargets left: %d\nCoord: %s",
-- THREATTEXTTTS = "%s, %s. Target information for %s. Threat level %s. Targets left %d. Target location %s.",
if ShowThreatInfo == true then
ttstext = string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name,ttstaskname,ThreatLevelText, targets, CoordText)
else
ThreatLocaleTextTTS = self.gettext:GetEntry("NOTHREATTEXTTTS",self.locale)
ttstext = string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name)
end
-- POINTERTARGETLASINGTTS = ". Pointer over target and lasing."
if task.Type == AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then
if LasingDrone and LasingDrone.playertask.inreach and LasingDrone:IsLasing() then
local lasingtext = self.gettext:GetEntry("POINTERTARGETLASINGTTS",self.locale)
@@ -3951,15 +4001,25 @@ function PLAYERTASKCONTROLLER:_SmokeTask(Group, Client)
local text = ""
if self.TasksPerPlayer:HasUniqueID(playername) then
local task = self.TasksPerPlayer:ReadByID(playername) -- Ops.PlayerTask#PLAYERTASK
task:SmokeTarget()
local textmark = self.gettext:GetEntry("SMOKETASK",self.locale)
text = string.format(textmark, ttsplayername, self.MenuName or self.Name, task.PlayerTaskNr)
self:T(self.lid..text)
--local m=MESSAGE:New(text,"10","Tasking"):ToAll()
if self.UseSRS then
self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2)
if task.CanSmoke == true then
task:SmokeTarget()
local textmark = self.gettext:GetEntry("SMOKETASK",self.locale)
text = string.format(textmark, ttsplayername, self.MenuName or self.Name, task.PlayerTaskNr)
self:T(self.lid..text)
--local m=MESSAGE:New(text,"10","Tasking"):ToAll()
if self.UseSRS then
self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2)
end
self:__TaskTargetSmoked(5,task)
else
local textmark = self.gettext:GetEntry("NOSMOKETASK",self.locale)
text = string.format(textmark, ttsplayername, self.MenuName or self.Name, task.PlayerTaskNr)
self:T(self.lid..text)
--local m=MESSAGE:New(text,"10","Tasking"):ToAll()
if self.UseSRS then
self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2)
end
end
self:__TaskTargetSmoked(5,task)
else
text = self.gettext:GetEntry("NOACTIVETASK",self.locale)
end
@@ -4793,22 +4853,27 @@ end
-- @param #string Event
-- @param #string To
-- @param Ops.PlayerTask#PLAYERTASK Task
-- @param #boolean Silent If true, suppress message output on cancel.
-- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:onafterTaskCancelled(From, Event, To, Task)
function PLAYERTASKCONTROLLER:onafterTaskCancelled(From, Event, To, Task, Silent)
self:T({From, Event, To})
self:T(self.lid.."TaskCancelled")
local canceltxt = self.gettext:GetEntry("TASKCANCELLED",self.locale)
local canceltxttts = self.gettext:GetEntry("TASKCANCELLEDTTS",self.locale)
local taskname = string.format(canceltxt, Task.PlayerTaskNr, tostring(Task.Type))
if not self.NoScreenOutput then
self:_SendMessageToClients(taskname,15)
--local m = MESSAGE:New(taskname,15,"Tasking"):ToCoalition(self.Coalition)
if Silent ~= true then
local canceltxt = self.gettext:GetEntry("TASKCANCELLED",self.locale)
local canceltxttts = self.gettext:GetEntry("TASKCANCELLEDTTS",self.locale)
local taskname = string.format(canceltxt, Task.PlayerTaskNr, tostring(Task.Type))
if self.NoScreenOutput ~= true then
self:_SendMessageToClients(taskname,15)
--local m = MESSAGE:New(taskname,15,"Tasking"):ToCoalition(self.Coalition)
end
if self.UseSRS then
taskname = string.format(canceltxttts, self.MenuName or self.Name, Task.PlayerTaskNr, tostring(Task.TTSType))
self.SRSQueue:NewTransmission(taskname,nil,self.SRS,nil,2)
end
end
if self.UseSRS then
taskname = string.format(canceltxttts, self.MenuName or self.Name, Task.PlayerTaskNr, tostring(Task.TTSType))
self.SRSQueue:NewTransmission(taskname,nil,self.SRS,nil,2)
end
local clients=Task:GetClientObjects()
for _,client in pairs(clients) do
self:_RemoveMenuEntriesForTask(Task,client)
+12 -6
View File
@@ -2006,9 +2006,10 @@ end
--- Count alive objects.
-- @param #TARGET self
-- @param #TARGET.Object Target Target objective.
-- @param #table Coalitions (Optional) Only count targets of the given coalition(s).
-- @param #table Coalitions (Optional) Only count targets of the given coalition(s).
-- @param #boolean OnlyReallyAlive (Optional) If `true`, count only really alive targets (units, groups) but not coordinates or zones.
-- @return #number Number of alive target objects.
function TARGET:CountObjectives(Target, Coalitions)
function TARGET:CountObjectives(Target, Coalitions, OnlyReallyAlive)
local N=0
@@ -2067,13 +2068,17 @@ function TARGET:CountObjectives(Target, Coalitions)
-- No target, where we can check the alive status, so we assume it is alive. Changed this because otherwise target count is 0 if we pass a coordinate.
-- This is also more consitent with the life and is alive status.
N=N+1
if not OnlyReallyAlive then
N=N+1
end
elseif Target.Type==TARGET.ObjectType.ZONE then
-- No target, where we can check the alive status, so we assume it is alive. Changed this because otherwise target count is 0 if we pass a coordinate.
-- This is also more consitent with the life and is alive status.
N=N+1
if not OnlyReallyAlive then
N=N+1
end
elseif Target.Type==TARGET.ObjectType.OPSZONE then
@@ -2093,15 +2098,16 @@ end
--- Count alive targets.
-- @param #TARGET self
-- @param #table Coalitions (Optional) Only count targets of the given coalition(s).
-- @param #boolean OnlyReallyAlive (Optional) If `true`, count only really alive targets (units, groups) but not coordinates or zones.
-- @return #number Number of alive target objects.
function TARGET:CountTargets(Coalitions)
function TARGET:CountTargets(Coalitions, OnlyReallyAlive)
local N=0
for _,_target in pairs(self.targets) do
local Target=_target --#TARGET.Object
N=N+self:CountObjectives(Target, Coalitions)
N=N+self:CountObjectives(Target, Coalitions, OnlyReallyAlive)
end
File diff suppressed because it is too large Load Diff
+48 -52
View File
@@ -3318,48 +3318,6 @@ function UTILS.AdjustHeading360(Heading)
return Heading
end
--- Transfroms a given 2D vector 3D.
-- This takes care of ED's different conventions for 2D and 3D coordinate systems.
-- @param DCS#Vec2 Vec Vector to be transformed. Can be any table/object that has at least x and y and optionally a z component.
-- @param #boolean OnSurface If `true`, new vector's y-component (alt) is at surface height. Otherwise, it is set to 0.
-- @return DCS#Vec3 Vector in 3D with x-, y- and z-components.
function UTILS.VecTo3D(Vec, OnSurface)
local vec={x=0, y=0, z=0} --DCS#Vec3
if Vec.z then
-- Vector is 3D already ==> Nothing to do.
vec.x=Vec.x
vec.y=Vec.y
vec.z=Vec.z
else
-- Vector is 2D
vec.x=Vec.x
vec.y=OnSurface and land.getHeight({x=Vec.x, y=Vec.y}) or 0
vec.z=Vec.y
end
return vec
end
--- Transfroms a given 3D (or 2D) vector to 2D.
-- This takes care of ED's different conventions for 2D and 3D coordinate systems.
-- @param DCS#Vec3 Vec Vector to be transformed. Can be any table/object that has at least x and y and optionally a z component.
-- @return DCS#Vec2 Vector in 2D with x- and y-components.
function UTILS.VecTo2D(Vec)
local vec={x=0, y=0} --DCS#Vec2
if Vec.z then
vec.x=Vec.x
vec.y=Vec.z
else
vec.x=Vec.x
vec.y=Vec.y
end
return vec
end
--- Create a BRAA NATO call string BRAA between two GROUP objects
-- @param Wrapper.Group#GROUP FromGrp GROUP object
-- @param Wrapper.Group#GROUP ToGrp GROUP object
@@ -4388,18 +4346,56 @@ function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,
local FarpVec2 = Coordinate:GetVec2()
if NumberPads > 1 then
local Grid = UTILS.GenerateGridPoints(FarpVec2, NumberPads, SpacingX, SpacingY)
local Grid = UTILS.GenerateGridPoints(FarpVec2, NumberPads, SpacingX, SpacingY)
local groupData = {
["visible"] = true,
["hidden"] = false,
["units"] = {},
["y"] = 0, -- Group center latitude
["x"] = 0, -- Group center longitude
["name"] = Name,
}
local unitData = {
["category"] = "Heliports",
["type"] = STypeName, -- FARP type
["y"] = 0, -- Latitude coordinate (meters)
["x"] = 0, -- Longitude coordinate (meters)
["name"] = Name,
["heading"] = 0, -- Heading in radians
["heliport_modulation"] = mod, -- 0 = AM, 1 = FM
["heliport_frequency"] = freq, -- Radio frequency in MHz
["heliport_callsign_id"] = callsign, -- Callsign ID
["dead"] = false,
["shape_name"] = SShapeName,
["dynamicSpawn"] = DynamicSpawns,
["allowHotStart"] = HotStart,
}
for id,gridpoint in ipairs(Grid) do
-- Spawn FARP
local location = COORDINATE:NewFromVec2(gridpoint)
local newfarp = SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country) -- "Invisible FARP" "FARP"
newfarp:InitShape(SShapeName) -- "invisiblefarp" "FARPS"
newfarp:InitFARP(callsign,freq,mod,DynamicSpawns,HotStart)
local spawnedfarp = newfarp:SpawnFromCoordinate(location,0,Name.."-"..id)
table.insert(ReturnObjects,spawnedfarp)
PopulateStorage(Name.."-"..id,liquids,equip,airframes)
end
local UnitTemplate = UTILS.DeepCopy(unitData)
UnitTemplate.x = gridpoint.x
UnitTemplate.y = gridpoint.y
if id > 1 then UnitTemplate.name = Name.."-"..id end
table.insert(groupData.units,UnitTemplate)
if id==1 then
groupData.x = gridpoint.x
groupData.y = gridpoint.y
end
end
--BASE:I("Spawning FARP")
--UTILS.PrintTableToLog(groupData,1)
local Static=coalition.addGroup(Country, -1, groupData)
-- Currently DCS >= 2.8 does not trigger birth events if FARPS are spawned!
-- We create such an event. The airbase is registered in Core.Event
local Event = {
id = EVENTS.Birth,
time = timer.getTime(),
initiator = Static
}
-- Create BIRTH event.
world.onEvent(Event)
PopulateStorage(Name.."-1",liquids,equip,airframes)
else
-- Spawn FARP
local newfarp = SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country) -- "Invisible FARP" "FARP"
@@ -2855,7 +2855,7 @@ do -- Route methods
-- @param Core.Zone#ZONE Zone The zone where to route to.
-- @param #boolean Randomize Defines whether to target point gets randomized within the Zone.
-- @param #number Speed The speed in m/s. Default is 5.555 m/s = 20 km/h.
-- @param Core.Base#FORMATION Formation The formation string.
-- @param DCS#FORMATION Formation The formation string.
function CONTROLLABLE:TaskRouteToZone( Zone, Randomize, Speed, Formation )
self:F2( Zone )
@@ -2915,7 +2915,7 @@ do -- Route methods
-- @param #CONTROLLABLE self
-- @param DCS#Vec2 Vec2 The Vec2 where to route to.
-- @param #number Speed The speed in m/s. Default is 5.555 m/s = 20 km/h.
-- @param Core.Base#FORMATION Formation The formation string.
-- @param DCS#FORMATION Formation The formation string.
function CONTROLLABLE:TaskRouteToVec2( Vec2, Speed, Formation )
local DCSControllable = self:GetDCSObject()
@@ -108,8 +108,9 @@ DYNAMICCARGO.State = {
-- @type DYNAMICCARGO.AircraftTypes
DYNAMICCARGO.AircraftTypes = {
["CH-47Fbl1"] = "CH-47Fbl1",
["Mi-8MTV2"] = "CH-47Fbl1",
["Mi-8MT"] = "CH-47Fbl1",
["Mi-8MTV2"] = "Mi-8MTV2",
["Mi-8MT"] = "Mi-8MT",
["C-130J-30"] = "C-130J-30",
}
--- Helo types possible.
@@ -122,23 +123,29 @@ DYNAMICCARGO.AircraftDimensions = {
["length"] = 11,
["ropelength"] = 30,
},
["Mi-8MTV2"] = {
["Mi-8MTV2"] = {
["width"] = 6,
["height"] = 6,
["length"] = 15,
["ropelength"] = 30,
},
["Mi-8MT"] = {
["Mi-8MT"] = {
["width"] = 6,
["height"] = 6,
["length"] = 15,
["ropelength"] = 30,
},
["C-130J-30"] = {
["width"] = 4,
["height"] = 12,
["length"] = 35,
["ropelength"] = 0,
},
}
--- DYNAMICCARGO class version.
-- @field #string version
DYNAMICCARGO.version="0.0.9"
DYNAMICCARGO.version="0.1.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@@ -527,7 +534,10 @@ function DYNAMICCARGO:_UpdatePosition()
---------------
-- REMOVED Cargo
---------------
if self.timer and self.timer:IsRunning() then self.timer:Stop() end
if self.timer and self.timer:IsRunning() then
self.timer:Stop()
self.timer=nil
end
self:T(self.lid.." dead! " ..self.CargoState.."-> REMOVED")
self.CargoState = DYNAMICCARGO.State.REMOVED
_DATABASE:CreateEventDynamicCargoRemoved(self)
@@ -535,6 +545,24 @@ function DYNAMICCARGO:_UpdatePosition()
return self
end
--- [USER] Destroy a DYNAMICCARGO object.
-- @param #DYNAMICCARGO self
-- @param #boolean GenerateEvent Set to false to remove an item silently. Defaults to true.
-- @return #boolean Return Returns nil if the object could not be found, else returns true.
function DYNAMICCARGO:Destroy(GenerateEvent)
local DCSObject = self:GetDCSObject()
if DCSObject then
local GenerateEvent = (GenerateEvent ~= nil and GenerateEvent == false) and false or true
if GenerateEvent and GenerateEvent == true then
self:CreateEventDead( timer.getTime(), DCSObject )
end
DCSObject:destroy()
self:_UpdatePosition()
return true
end
return nil
end
--- [Internal] Track helos for loaded/unloaded decision making.
-- @param Wrapper.Client#CLIENT client
-- @return #boolean IsIn
+41 -12
View File
@@ -1058,9 +1058,12 @@ function GROUP:GetTypeName()
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local GroupTypeName = DCSGroup:getUnit(1):getTypeName()
--self:T3( GroupTypeName )
return( GroupTypeName )
local unit = DCSGroup:getUnit(1)
if unit then
local GroupTypeName = unit:getTypeName()
--self:T3( GroupTypeName )
return( GroupTypeName )
end
end
return nil
@@ -1075,9 +1078,12 @@ function GROUP:GetNatoReportingName()
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local GroupTypeName = DCSGroup:getUnit(1):getTypeName()
--self:T3( GroupTypeName )
return UTILS.GetReportingName(GroupTypeName)
local unit = DCSGroup:getUnit(1)
if unit then
local GroupTypeName = unit:getTypeName()
--self:T3( GroupTypeName )
return UTILS.GetReportingName(GroupTypeName)
end
end
return "Bogey"
@@ -1093,9 +1099,12 @@ function GROUP:GetPlayerName()
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local PlayerName = DCSGroup:getUnit(1):getPlayerName()
--self:T3( PlayerName )
return( PlayerName )
local unit = DCSGroup:getUnit(1)
if unit then
local PlayerName = unit:getPlayerName()
--self:T3( PlayerName )
return( PlayerName )
end
end
return nil
@@ -1111,9 +1120,12 @@ function GROUP:GetCallsign()
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local GroupCallSign = DCSGroup:getUnit(1):getCallsign()
--self:T3( GroupCallSign )
return GroupCallSign
local unit = DCSGroup:getUnit(1)
if unit then
local GroupCallSign = unit:getCallsign()
--self:T3( GroupCallSign )
return GroupCallSign
end
end
BASE:E( { "Cannot GetCallsign", Positionable = self, Alive = self:IsAlive() } )
@@ -1152,6 +1164,23 @@ function GROUP:GetVec3()
return nil
end
--- Returns the current {@Core.Vector#VECTOR} of the first Unit in the GROUP.
-- @param #GROUP self
-- @return Core.Vector#VECTOR Vector of the first Unit of the GROUP or nil if cannot be found.
function GROUP:GetVector()
-- Get first unit.
local unit=self:GetUnit(1)
if unit then
local vector=unit:GetVector()
return vector
end
self:E("ERROR: Cannot get Vector of group "..tostring(self.GroupName))
return nil
end
--- Returns the average Vec3 vector of the Units in the GROUP.
-- @param #GROUP self
-- @return DCS#Vec3 Current Vec3 of the GROUP or nil if cannot be found.
@@ -170,6 +170,27 @@ function IDENTIFIABLE:GetCoalition()
return nil
end
--- Returns true if identifiable is of RED coalition.
-- @param #IDENTIFIABLE self
-- @return #boolean If the identifiable is red.
function IDENTIFIABLE:IsRed()
return self:GetCoalition() == coalition.side.RED
end
--- Returns true if identifiable is of BLUE coalition.
-- @param #IDENTIFIABLE self
-- @return #boolean If the identifiable is blue.
function IDENTIFIABLE:IsBlue()
return self:GetCoalition() == coalition.side.BLUE
end
--- Returns true if identifiable is of NEUTRAL coalition.
-- @param #IDENTIFIABLE self
-- @return #boolean If the identifiable is neutral.
function IDENTIFIABLE:IsNeutral()
return self:GetCoalition() == coalition.side.NEUTRAL
end
--- Returns the name of the coalition of the Identifiable.
-- @param #IDENTIFIABLE self
-- @return #string The name of the coalition.
@@ -219,6 +219,25 @@ function POSITIONABLE:GetOrientationZ()
end
end
--- Returns the vectors of the orientation of the object:
-- X is the orientation parallel to the movement of the object, Z perpendicular and Y vertical orientation.
-- @param #POSITIONABLE self
-- @return Core.Vector#VECTOR X orientation, i.e. parallel to the direction of movement.
-- @return Core.Vector#VECTOR Y orientation, i.e. vertical.
-- @return Core.Vector#VECTOR Z orientation, i.e. perpendicular to the direction of movement.
function POSITIONABLE:GetOrientationVectors()
local position = self:GetPosition()
if position then
local vecx=VECTOR:NewFromVec(position.x)
local vecy=VECTOR:NewFromVec(position.y)
local vecz=VECTOR:NewFromVec(position.z)
return vecx, vecy, vecz
else
BASE:E( { "Cannot GetOrientation", Positionable = self, Alive = self:IsAlive() } )
return nil, nil, nil
end
end
--- Returns the @{DCS#Position3} position vectors indicating the point and direction vectors in 3D of the POSITIONABLE within the mission.
-- @param #POSITIONABLE self
-- @return DCS#Position The 3D position vectors of the POSITIONABLE.
@@ -286,6 +305,27 @@ function POSITIONABLE:GetVec2()
return nil
end
--- Returns the @{Core.Vector#VECTOR} indicating the 3D position of the object on the map.
-- @param #POSITIONABLE self
-- @return Core.Vector#VECTOR The 3D vector.
function POSITIONABLE:GetVector()
local DCSPositionable = self:GetDCSObject()
if DCSPositionable then
local Vec3 = DCSPositionable:getPoint() -- DCS#Vec3
local vector=VECTOR:NewFromVec(Vec3)
return vector
end
self:E( { "Cannot GetVec2", Positionable = self, Alive = self:IsAlive() } )
return nil
end
--- Returns a COORDINATE object indicating the point in 2D of the POSITIONABLE within the mission.
-- @param #POSITIONABLE self
-- @return Core.Point#COORDINATE The 3D point vector of the POSITIONABLE.
@@ -914,6 +954,24 @@ function POSITIONABLE:GetVelocityVec3()
return nil
end
--- Returns the velocity vector.
-- @param #POSITIONABLE self
-- @return Core.Vector#VECTOR The velocity vector or `nil` if the object does not exist.
function POSITIONABLE:GetVelocityVector()
local DCSPositionable = self:GetDCSObject()
if DCSPositionable and DCSPositionable:isExist() then
local vec3 = DCSPositionable:getVelocity()
local vector=VECTOR:NewFromVec(vec3)
return vector
end
BASE:E( { "Cannot GetVelocityVector", Positionable = self, Alive = self:IsAlive() } )
return nil
end
--- Get relative velocity with respect to another POSITIONABLE.
-- @param #POSITIONABLE self
-- @param #POSITIONABLE Positionable Other POSITIONABLE.
@@ -1865,6 +1923,7 @@ do -- Cargo
["HL_DSHK"] = 6*POSITIONABLE.DefaultInfantryWeight,
["CCKW_353"] = 16*POSITIONABLE.DefaultInfantryWeight, --GMC CCKW 2½-ton 6×6 truck, estimating 16 soldiers,
["MaxxPro_MRAP"] = 7*POSITIONABLE.DefaultInfantryWeight,
["Sd_Kfz_251"] = 10*POSITIONABLE.DefaultInfantryWeight,
}
}
@@ -64,6 +64,9 @@ function STATIC:Register( StaticName )
else
self:E(string.format("Static object %s does not exist!", tostring(self.StaticName)))
end
-- Cache position
self._vec3 = self:GetVec3()
return self
end
@@ -86,6 +89,30 @@ function STATIC:GetLife()
return nil
end
--- Get the position of the STATIC even if it is not alive.
-- @param #STATIC self
-- @return DCS#Vec2 The 2D point vector of the POSITIONABLE.
-- @return #nil The position was not cached.
function STATIC:GetVec2Cached()
local vec2 = self:GetVec2()
if not vec2 and self._vec3 then
vec2 = {x = self._vec3.x, y = self._vec3.z }
end
return vec2
end
--- Get the position of the STATIC even if it is not alive.
-- @param #STATIC self
-- @return DCS#Vec3 The 3D point vector of the POSITIONABLE.
-- @return #nil The position was not cached.
function STATIC:GetVec3Cached()
local vec3 = self:GetVec3()
if not vec3 and self._vec3 then
vec3 = self._vec3
end
return vec3
end
--- Finds a STATIC from the _DATABASE using a DCSStatic object.
-- @param #STATIC self
-- @param DCS#StaticObject DCSStatic An existing DCS Static object reference.
@@ -232,6 +259,8 @@ function STATIC:SpawnAt(Coordinate, Heading, Delay)
local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName)
SpawnStatic:SpawnFromPointVec2( Coordinate, Heading, self.StaticName )
-- Cache position
self._vec3 = self:GetVec3()
end
@@ -255,6 +284,8 @@ function STATIC:ReSpawn(CountryID, Delay)
local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName, CountryID)
SpawnStatic:Spawn(nil, self.StaticName)
-- Cache position
self._vec3 = self:GetVec3()
end
@@ -278,6 +309,8 @@ function STATIC:ReSpawnAt(Coordinate, Heading, Delay)
local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName, self:GetCountry())
SpawnStatic:SpawnFromCoordinate(Coordinate, Heading, self.StaticName)
-- Cache position
self._vec3 = self:GetVec3()
end