From 2157f8e8cd161577e33fa33829cb7927324b1d1a Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 26 Sep 2022 23:34:00 +0200 Subject: [PATCH 01/17] Airboss Take animation for determining the wire --- .gitignore | 1 + Moose Development/Moose/.vscode/settings.json | 12 ++++++-- Moose Development/Moose/Ops/Airboss.lua | 30 ++++++++++++++++--- Moose Development/Moose/Wrapper/Unit.lua | 18 +++++++++++ 4 files changed, 54 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index fe5b6b4f8..634be4279 100644 --- a/.gitignore +++ b/.gitignore @@ -228,3 +228,4 @@ MooseCodeWS.code-workspace .gitignore .gitignore /.gitignore +Moose Development/Moose/.vscode/settings.json diff --git a/Moose Development/Moose/.vscode/settings.json b/Moose Development/Moose/.vscode/settings.json index 13211d027..f25704250 100644 --- a/Moose Development/Moose/.vscode/settings.json +++ b/Moose Development/Moose/.vscode/settings.json @@ -1,7 +1,8 @@ { "Lua.workspace.preloadFileSize": 1000, "Lua.diagnostics.disable": [ - "undefined-doc-name" + "undefined-doc-name", + "need-check-nil" ], "Lua.diagnostics.globals": [ "BASE", @@ -9,9 +10,14 @@ "__Moose", "trigger", "coord", - "missionCommands" + "missionCommands", + "world" ], "Lua.completion.displayContext": 5, "Lua.runtime.version": "Lua 5.1", - "Lua.completion.callSnippet": "Both" + "Lua.completion.callSnippet": "Both", + "Lua.workspace.library": [ + "${3rd}/lfs/library" + ], + "Lua.workspace.checkThirdParty": false } \ No newline at end of file diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 1bd4c98da..85ec855b7 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -1809,7 +1809,7 @@ AIRBOSS.version = "1.2.1" function AIRBOSS:New( carriername, alias ) -- Inherit everthing from FSM class. - local self = BASE:Inherit( self, FSM:New() ) -- #AIRBOSS + self = BASE:Inherit( self, FSM:New() ) -- #AIRBOSS -- Debug. self:F2( { carriername = carriername, alias = alias } ) @@ -8222,7 +8222,7 @@ function AIRBOSS:OnEventLand( EventData ) local dist = coord:Get2DDistance( self:GetCoordinate() ) -- Get wire - local wire = self:_GetWire( coord, 0 ) + local wire = self:_GetWirePos( coord, 0 ) -- Aircraft type. local _type = EventData.IniUnit:GetTypeName() @@ -10058,12 +10058,34 @@ function AIRBOSS:_GetSternCoord() return self.sterncoord end +--- Get wire from +-- @param #AIRBOSS self +-- @param Core.Point#COORDINATE Lcoord Landing position. +-- @return #number Trapped wire (1-4) or 99 if no wire was trapped. +function AIRBOSS:_GetWire() + + local wireArgs={} + wireArgs[1]=141 + wireArgs[2]=142 + wireArgs[3]=143 + wireArgs[4]=144 + + for wire,drawArg in pairs(wireArgs) do + local value=self.carrier:GetDrawArgumentValue(drawArg) + if math.abs(value)>0.001 then + return wire + end + end + + return 99 +end + --- Get wire from landing position. -- @param #AIRBOSS self -- @param Core.Point#COORDINATE Lcoord Landing position. -- @param #number dc Distance correction. Shift the landing coord back if dc>0 and forward if dc<0. -- @return #number Trapped wire (1-4) or 99 if no wire was trapped. -function AIRBOSS:_GetWire( Lcoord, dc ) +function AIRBOSS:_GetWirePos( Lcoord, dc ) -- Final bearing (true). local FB = self:GetFinalBearing() @@ -10181,7 +10203,7 @@ function AIRBOSS:_Trapped( playerData ) end -- Get wire. - local wire = self:_GetWire( coord, dcorr ) + local wire = self:_GetWirePos( coord, dcorr ) -- Debug. local text = string.format( "Player %s _Trapped: v=%.1f km/h, s-dcorr=%.1f m ==> wire=%d (dcorr=%d)", playerData.name, v, s - dcorr, wire, dcorr ) diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 6195f03e5..06617bafb 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -963,6 +963,24 @@ function UNIT:GetDamageRelative() return 1 end +--- Returns the current value for an animation argument on the external model of the given object. +-- Each model animation has an id tied to with different values representing different states of the model. +-- Animation arguments can be figured out by opening the respective 3d model in the modelviewer. +-- @param #UNIT self +-- @param #number AnimationArgument Number corresponding to the animated part of the unit. +-- @return #number Value of the animation argument [-1, 1]. If draw argument value is invalid for the unit in question a value of 0 will be returned. +function UNIT:GetDrawArgumentValue(AnimationArgument) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local value = DCSUnit:getDrawArgumentValue(AnimationArgument or 0) + return value + end + + return 0 +end + --- Returns the category of the #UNIT from descriptor. Returns one of -- -- * Unit.Category.AIRPLANE From 819912cfd3e5a7ed58e904811f4b6ffbf929ca8c Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 28 Jan 2023 15:39:42 +0100 Subject: [PATCH 02/17] Database - Polygon drawings are adde as polygon zones to DB --- Moose Development/Moose/Core/Database.lua | 63 ++++++++++++++++++++++- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index afa1d6a64..2bd712f72 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -370,11 +370,70 @@ do -- Zones -- Add zone to DB. self:AddZone( ZoneName, Zone_Polygon ) end + end + + -- Drawings as zones + if env.mission.drawings and env.mission.drawings.layers then + + -- Loop over layers. + for layerID, layerData in pairs(env.mission.drawings.layers or {}) do + + -- Loop over objects in layers. + for objectID, objectData in pairs(layerData.objects or {}) do + + -- Check for polygon which has at least 4 points (we would need 3 but the origin seems to be there twice) + if objectData.polygonMode=="free" and objectData.points and #objectData.points>=4 then + + -- Name of the zone. + local ZoneName=objectData.name or "Unknown Drawing" + + -- Reference point. All other points need to be translated by this. + local vec2={x=objectData.mapX, y=objectData.mapY} + + -- Debug stuff. + --local vec3={x=objectData.mapX, y=0, z=objectData.mapY} + --local coord=COORDINATE:NewFromVec2(vec2):MarkToAll("MapX, MapY") + --trigger.action.markToAll(id, "mapXY", vec3) + -- Copy points array. + local points=UTILS.DeepCopy(objectData.points) + + -- Translate points. + for i,_point in pairs(points) do + local point=_point --DCS#Vec2 + points[i]=UTILS.Vec2Add(point, vec2) + end + + -- Remove last point. + table.remove(points, #points) + + -- Debug output + self:I(string.format("Register ZONE: %s (Polygon drawing with %d verticies)", ZoneName, #points)) + + -- Create new polygon zone. + local Zone=ZONE_POLYGON:NewFromPointsArray(ZoneName, points) + + -- Set color. + Zone:SetColor({1, 0, 0}, 0.15) + + -- Store in DB. + self.ZONENAMES[ZoneName] = ZoneName + + -- Add zone. + self:AddZone(ZoneName, Zone) + + end + + end + + end + + end + + end - - + end -- zone do -- Zone_Goal From 6adef473400b85ecf0ffa7f84e9da8a5b495c075 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 7 Feb 2023 22:47:55 +0100 Subject: [PATCH 03/17] PATHLINE Added new class PATHLINE --- Moose Development/Moose/Core/Database.lua | 75 +++++++++- Moose Development/Moose/Core/Pathline.lua | 163 ++++++++++++++++++++++ Moose Development/Moose/Modules.lua | 1 + Moose Setup/Moose.files | 1 + 4 files changed, 234 insertions(+), 6 deletions(-) create mode 100644 Moose Development/Moose/Core/Pathline.lua diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 2bd712f72..8cfad8cd2 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -88,6 +88,7 @@ DATABASE = { WAREHOUSES = {}, FLIGHTGROUPS = {}, FLIGHTCONTROLS = {}, + PATHLINES = {}, } local _DATABASECoalition = @@ -244,7 +245,7 @@ function DATABASE:FindAirbase( AirbaseName ) end -do -- Zones +do -- Zones and Pathlines --- Finds a @{Core.Zone} based on the zone name. -- @param #DATABASE self @@ -266,8 +267,7 @@ do -- Zones self.ZONES[ZoneName] = Zone end end - - + --- Deletes a @{Core.Zone} from the DATABASE based on the zone name. -- @param #DATABASE self -- @param #string ZoneName The name of the zone. @@ -277,6 +277,39 @@ do -- Zones end + --- Adds a @{Core.Pathline} based on its name in the DATABASE. + -- @param #DATABASE self + -- @param #string PathlineName The name of the pathline + -- @param Core.Pathline#PATHLINE Pathline The pathline. + function DATABASE:AddPathline( PathlineName, Pathline ) + + if not self.PATHLINES[PathlineName] then + self.PATHLINES[PathlineName]=Pathline + end + end + + --- Finds a @{Core.Pathline} by its name. + -- @param #DATABASE self + -- @param #string PathlineName The name of the Pathline. + -- @return Core.Pathline#PATHLINE The found PATHLINE. + function DATABASE:FindPathline( PathlineName ) + + local pathline = self.PATHLINES[PathlineName] + + return pathline + end + + + --- Deletes a @{Core.Pathline} from the DATABASE based on its name. + -- @param #DATABASE self + -- @param #string PathlineName The name of the PATHLINE. + function DATABASE:DeletePathline( PathlineName ) + + self.PATHLINES[PathlineName]=nil + + return self + end + --- Private method that registers new ZONE_BASE derived objects within the DATABASE Object. -- @param #DATABASE self -- @return #DATABASE self @@ -383,7 +416,7 @@ do -- Zones for objectID, objectData in pairs(layerData.objects or {}) do -- Check for polygon which has at least 4 points (we would need 3 but the origin seems to be there twice) - if objectData.polygonMode=="free" and objectData.points and #objectData.points>=4 then + if objectData.polygonMode and objectData.polygonMode=="free" and objectData.points and #objectData.points>=4 then -- Name of the zone. local ZoneName=objectData.name or "Unknown Drawing" @@ -409,7 +442,7 @@ do -- Zones table.remove(points, #points) -- Debug output - self:I(string.format("Register ZONE: %s (Polygon drawing with %d verticies)", ZoneName, #points)) + self:I(string.format("Register ZONE: %s (Polygon drawing with %d vertices)", ZoneName, #points)) -- Create new polygon zone. local Zone=ZONE_POLYGON:NewFromPointsArray(ZoneName, points) @@ -421,8 +454,38 @@ do -- Zones self.ZONENAMES[ZoneName] = ZoneName -- Add zone. - self:AddZone(ZoneName, Zone) + self:AddZone(ZoneName, Zone) + elseif objectData.lineMode and objectData.lineMode=="segments" and objectData.points and #objectData.points>=2 then + + -- Name of the zone. + local Name=objectData.name or "Unknown Line Drawing" + + -- Reference point. All other points need to be translated by this. + local vec2={x=objectData.mapX, y=objectData.mapY} + + -- Copy points array. + local points=UTILS.DeepCopy(objectData.points) + + -- Translate points. + for i,_point in pairs(points) do + local point=_point --DCS#Vec2 + points[i]=UTILS.Vec2Add(point, vec2) + end + + + -- Debug output + self:I(string.format("Register PATHLINE: %s (Line drawing with %d points)", Name, #points)) + + -- Create new polygon zone. + local Pathline=PATHLINE:NewFromVec2Array(Name, points) + + -- Set color. + --Zone:SetColor({1, 0, 0}, 0.15) + + -- Add zone. + self:AddPathline(Name,Pathline) + end end diff --git a/Moose Development/Moose/Core/Pathline.lua b/Moose Development/Moose/Core/Pathline.lua new file mode 100644 index 000000000..058933107 --- /dev/null +++ b/Moose Development/Moose/Core/Pathline.lua @@ -0,0 +1,163 @@ +--- **Core** - Path from A to B. +-- +-- **Main Features:** +-- +-- * Path from A to B +-- * Arbitrary number of segments +-- * Automatically from lines drawtool +-- +-- === +-- +-- ### Author: **funkyfranky** +-- +-- === +-- @module Core.Pathline +-- @image CORE_Pathline.png + + +--- PATHLINE class. +-- @type PATHLINE +-- @field #string ClassName Name of the class. +-- @field #string lid Class id string for output to DCS log file. +-- @field #string name Name of the path line. +-- @field #table points List of 3D points defining the path. +-- @extends Core.Base#BASE + +--- *When nothing goes right... Go left!* +-- +-- === +-- +-- # The PATHLINE Concept +-- +-- List of points defining a path from A to B. +-- +-- Line drawings created in the mission editor are automatically registered as pathlines and stored in the MOOSE database. +-- They can be accessed with the @{#PATHLINE.FindByName) function. +-- +-- +-- @field #PATHLINE +PATHLINE = { + ClassName = "PATHLINE", + lid = nil, + points = {}, +} + + +--- PATHLINE class version. +-- @field #string version +PATHLINE.version="0.0.1" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: A lot... + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new PATHLINE object. Points need to be added later. +-- @param #PATHLINE self +-- @param #string Name Name of the path. +-- @return #PATHLINE self +function PATHLINE:New(Name) + + -- Inherit everything from INTEL class. + local self=BASE:Inherit(self, BASE:New()) --#PATHLINE + + self.name=Name or "Unknown Path" + + self.lid=string.format("PATHLINE %s | ", Name) + + return self +end + +--- Create a new PATHLINE object from a given list of 2D points. +-- @param #PATHLINE self +-- @param #string Name Name of the pathline. +-- @param #table Vec2Array List of DCS#Vec2 points. +-- @return #PATHLINE self +function PATHLINE:NewFromVec2Array(Name, Vec2Array) + + local self=PATHLINE:New(Name) + + for i=1,#Vec2Array do + self:AddPointFromVec2(Vec2Array[i]) + end + + return self +end + +--- Create a new PATHLINE object from a given list of 3D points. +-- @param #PATHLINE self +-- @param #string Name Name of the pathline. +-- @param #table Vec3Array List of DCS#Vec3 points. +-- @return #PATHLINE self +function PATHLINE:NewFromVec3Array(Name, Vec3Array) + + local self=PATHLINE:New(Name) + + for i=1,#Vec3Array do + self:AddPointFromVec3(Vec3Array[i]) + end + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Find a pathline in the database. +-- @param #PATHLINE self +-- @param #string Name The name of the pathline. +-- @return #PATHLINE self +function PATHLINE:FindByName(Name) + local pathline = _DATABASE:FindPathline(Name) + return pathline +end + +--- Add a 2D point to the path. The third dimension is determined from the land height. +-- @param #PATHLINE self +-- @param DCS#Vec2 Vec2 The 2D vector (x,y) to add. +-- @return #PATHLINE self +function PATHLINE:AddPointFromVec2(Vec2) + + if Vec2 then + + local Vec3={x=Vec2.x, y=land.getHeight(Vec2), z=Vec2.y} + + self:AddPointFromVec3(Vec3) + + end + + return self +end + +--- Add a 3D point to the path. +-- @param #PATHLINE self +-- @param DCS#Vec3 Vec3 The §D vector (x,y) to add. +-- @return #PATHLINE self +function PATHLINE:AddPointFromVec3(Vec3) + + if Vec3 then + + table.insert(self.points, Vec3) + + end + + return self +end + +--- Get 3D points of pathline. +-- @param #PATHLINE self +-- @return List of DCS#Vec3 points. +function PATHLINE:GetPoints3D() + return self.points +end + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index c0ed4dd90..eda4bd2b2 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -33,6 +33,7 @@ __Moose.Include( 'Scripts/Moose/Core/Goal.lua' ) __Moose.Include( 'Scripts/Moose/Core/Spot.lua' ) __Moose.Include( 'Scripts/Moose/Core/MarkerOps_Base.lua' ) __Moose.Include( 'Scripts/Moose/Core/TextAndSound.lua' ) +__Moose.Include( 'Scripts/Moose/Core/Pathline.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Object.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Identifiable.lua' ) diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 9531ed658..dbc0c37e1 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -34,6 +34,7 @@ Core/MarkerOps_Base.lua Core/Astar.lua Core/Condition.lua Core/TextAndSound.lua +Core/Pathline.lua Wrapper/Object.lua Wrapper/Identifiable.lua From f02b7036a95d39cda901affed624d1768948c16e Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 8 Feb 2023 12:37:18 +0100 Subject: [PATCH 04/17] PATHLINE --- Moose Development/Moose/Core/Database.lua | 2 +- Moose Development/Moose/Core/Pathline.lua | 176 ++++++++++++++++++++-- Moose Development/Moose/DCS.lua | 28 ++-- 3 files changed, 186 insertions(+), 20 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 8cfad8cd2..a95504bb1 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -456,7 +456,7 @@ do -- Zones and Pathlines -- Add zone. self:AddZone(ZoneName, Zone) - elseif objectData.lineMode and objectData.lineMode=="segments" and objectData.points and #objectData.points>=2 then + elseif objectData.lineMode and (objectData.lineMode=="segments" or objectData.lineMode=="segment" or objectData.lineMode=="free") and objectData.points and #objectData.points>=2 then -- Name of the zone. local Name=objectData.name or "Unknown Line Drawing" diff --git a/Moose Development/Moose/Core/Pathline.lua b/Moose Development/Moose/Core/Pathline.lua index 058933107..5364bc505 100644 --- a/Moose Development/Moose/Core/Pathline.lua +++ b/Moose Development/Moose/Core/Pathline.lua @@ -42,6 +42,15 @@ PATHLINE = { points = {}, } +--- Point of line. +-- @type PATHLINE.Point +-- @field DCS#Vec3 vec3 3D position. +-- @field DCS#Vec2 vec2 2D position. +-- @field #number surfaceType Surface type. +-- @field #number landHeight Land height in meters. +-- @field #number depth Water depth in meters. +-- @field #number markerID Marker ID. + --- PATHLINE class version. -- @field #string version @@ -118,43 +127,192 @@ function PATHLINE:FindByName(Name) return pathline end ---- Add a 2D point to the path. The third dimension is determined from the land height. + + +--- Add a point to the path from a given 2D position. The third dimension is determined from the land height. -- @param #PATHLINE self -- @param DCS#Vec2 Vec2 The 2D vector (x,y) to add. -- @return #PATHLINE self function PATHLINE:AddPointFromVec2(Vec2) if Vec2 then - - local Vec3={x=Vec2.x, y=land.getHeight(Vec2), z=Vec2.y} - self:AddPointFromVec3(Vec3) + local point=self:_CreatePoint(Vec2) + + table.insert(self.points, point) end return self end ---- Add a 3D point to the path. +--- Add a point to the path from a given 3D position. -- @param #PATHLINE self --- @param DCS#Vec3 Vec3 The §D vector (x,y) to add. +-- @param DCS#Vec3 Vec3 The 3D vector (x,y) to add. -- @return #PATHLINE self function PATHLINE:AddPointFromVec3(Vec3) if Vec3 then + + local point=self:_CreatePoint(Vec3) - table.insert(self.points, Vec3) + table.insert(self.points, point) end return self end +--- Get name of pathline. +-- @param #PATHLINE self +-- @return #string Name of the pathline. +function PATHLINE:GetName() + return self.name +end + +--- Get number of points. +-- @param #PATHLINE self +-- @return #number Number of points. +function PATHLINE:GetNumberOfPoints() + local N=#self.points + return N +end + +--- Get points of pathline. Not that points are tables, that contain more information as just the 2D or 3D position but also the surface type etc. +-- @param #PATHLINE self +-- @return <#PATHLINE.Point> List of points. +function PATHLINE:GetPoints() + return self.points +end + --- Get 3D points of pathline. -- @param #PATHLINE self -- @return List of DCS#Vec3 points. -function PATHLINE:GetPoints3D() - return self.points +function PATHLINE:GetPoints3D() + + local vecs={} + + for _,_point in pairs(self.points) do + local point=_point --#PATHLINE.Point + table.insert(vecs, point.vec3) + end + + return vecs +end + +--- Get 2D points of pathline. +-- @param #PATHLINE self +-- @return List of DCS#Vec2 points. +function PATHLINE:GetPoints2D() + + local vecs={} + + for _,_point in pairs(self.points) do + local point=_point --#PATHLINE.Point + table.insert(vecs, point.vec2) + end + + return vecs +end + +--- Get COORDINATES of pathline. Note that COORDINATE objects are created when calling this function. That does involve deep copy calls and can have an impact on performance if done too often. +-- @param #PATHLINE self +-- @return List of COORDINATES points. +function PATHLINE:GetCoordinats() + + local vecs={} + + for _,_point in pairs(self.points) do + local point=_point --#PATHLINE.Point + local coord=COORDINATE:NewFromVec3(point.vec3) + end + + return vecs +end + +--- Get the n-th point. +-- @param #PATHLINE self +-- @param #number n The n-th point. +-- @return #number Number of points. +function PATHLINE:GetPoint3D(n) + + local N=self:GetNumberOfPoints() + + local vec3=nil + if n and n>=1 and n<=N then + + vec3=self.point[n] + else + self:E(self.lid..string.format("ERROR: No point in pathline for N=%s", tostring(n))) + end + + return vec3 +end + + + + +--- Mark points on F10 map. +-- @param #PATHLINE self +-- @param #boolean Switch If `true` or nil, set marks. If `false`, remove marks. +-- @return List of DCS#Vec3 points. +function PATHLINE:MarkPoints(Switch) + for i,_point in pairs(self.points) do + local point=_point --#PATHLINE.Point + if Switch==false then + + if point.markerID then + UTILS.RemoveMark(point.markerID, Delay) + end + + else + + if point.markerID then + UTILS.RemoveMark(point.markerID) + end + + point.markerID=UTILS.GetMarkID() + + local text=string.format("Pathline %s: Point #%d\nSurface Type=%d\nHeight=%.1f m\nDepth=%.1f m", self.name, i, point.surfaceType, point.landHeight, point.depth) + + trigger.action.markToAll(point.markerID, text, point.vec3, "") + + end + end +end + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Private functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Get 3D points of pathline. +-- @param #PATHLINE self +-- @param DCS#Vec3 Vec Position vector. Can also be a DCS#Vec2 in which case the altitude at landheight is taken. +-- @return #PATHLINE.Point +function PATHLINE:_CreatePoint(Vec) + + local point={} --#PATHLINE.Point + + if Vec.z then + -- Given vec is 3D + point.vec3=UTILS.DeepCopy(Vec) + point.vec2={x=Vec.x, y=Vec.z} + else + -- Given vec is 2D + point.vec2=UTILS.DeepCopy(Vec) + point.vec3={x=Vec.x, y=land.getHeight(Vec), z=Vec.y} + end + + -- Get surface type. + point.surfaceType=land.getSurfaceType(point.vec2) + + -- Get land height and depth. + point.landHeight, point.depth=land.getSurfaceHeightWithSeabed(point.vec2) + + point.markerID=nil + + return point end diff --git a/Moose Development/Moose/DCS.lua b/Moose Development/Moose/DCS.lua index 4db719cdb..602d9a608 100644 --- a/Moose Development/Moose/DCS.lua +++ b/Moose Development/Moose/DCS.lua @@ -193,21 +193,29 @@ do -- land --- [Type of surface enumerator](https://wiki.hoggitworld.com/view/DCS_singleton_land) -- @type land.SurfaceType - -- @field LAND - -- @field SHALLOW_WATER - -- @field WATER - -- @field ROAD - -- @field RUNWAY + -- @field LAND Land=1 + -- @field SHALLOW_WATER Shallow water=2 + -- @field WATER Water=3 + -- @field ROAD Road=4 + -- @field RUNWAY Runway=5 - --- Returns altitude MSL of the point. + --- Returns the distance from sea level (y-axis) of a given vec2 point. -- @function [parent=#land] getHeight - -- @param #Vec2 point point on the ground. - -- @return #Distance + -- @param #Vec2 point Point on the ground. + -- @return #number Height in meters. + + --- Returns the surface height and depth of a point. Useful for checking if the path is deep enough to support a given ship. + -- Both values are positive. When checked over water at sea level the first value is always zero. + -- When checked over water at altitude, for example the reservoir of the Inguri Dam, the first value is the corresponding altitude the water level is at. + -- @function [parent=#land] getSurfaceHeightWithSeabed + -- @param #Vec2 point Position where to check. + -- @return #number Height in meters. + -- @return #number Depth in meters. - --- returns surface type at the given point. + --- Returns surface type at the given point. -- @function [parent=#land] getSurfaceType -- @param #Vec2 point Point on the land. - -- @return #land.SurfaceType + -- @return #number Enumerator value from `land.SurfaceType` (LAND=1, SHALLOW_WATER=2, WATER=3, ROAD=4, RUNWAY=5) --- [DCS Singleton land](https://wiki.hoggitworld.com/view/DCS_singleton_land) land = {} --#land From e4c8ca34edfd0ac08a9f2d000324db16e49a0189 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 8 Feb 2023 16:11:47 +0100 Subject: [PATCH 05/17] Update Pathline.lua --- Moose Development/Moose/Core/Pathline.lua | 52 ++++++++++++++++++----- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/Moose Development/Moose/Core/Pathline.lua b/Moose Development/Moose/Core/Pathline.lua index 5364bc505..090da3931 100644 --- a/Moose Development/Moose/Core/Pathline.lua +++ b/Moose Development/Moose/Core/Pathline.lua @@ -3,7 +3,7 @@ -- **Main Features:** -- -- * Path from A to B --- * Arbitrary number of segments +-- * Arbitrary number of points -- * Automatically from lines drawtool -- -- === @@ -230,26 +230,56 @@ function PATHLINE:GetCoordinats() return vecs end ---- Get the n-th point. +--- Get the n-th point of the pathline. -- @param #PATHLINE self --- @param #number n The n-th point. --- @return #number Number of points. -function PATHLINE:GetPoint3D(n) +-- @param #number n The index of the point. Default is the first point. +-- @return #PATHLINE.Point Point. +function PATHLINE:GetPointFromIndex(n) local N=self:GetNumberOfPoints() + + n=n or 1 - local vec3=nil - if n and n>=1 and n<=N then - - vec3=self.point[n] + local point=nil --#PATHLINE.Point + + if n>=1 and n<=N then + point=self.point[n] else self:E(self.lid..string.format("ERROR: No point in pathline for N=%s", tostring(n))) end - - return vec3 + + return point end +--- Get the 3D position of the n-th point. +-- @param #PATHLINE self +-- @param #number n The n-th point. +-- @return DCS#VEC3 Position in 3D. +function PATHLINE:GetPoint3DFromIndex(n) + local point=self:GetPointFromIndex(n) + + if point then + return point.vec3 + end + + return nil +end + +--- Get the 2D position of the n-th point. +-- @param #PATHLINE self +-- @param #number n The n-th point. +-- @return DCS#VEC2 Position in 3D. +function PATHLINE:GetPoint2DFromIndex(n) + + local point=self:GetPointFromIndex(n) + + if point then + return point.vec2 + end + + return nil +end --- Mark points on F10 map. From ffc86bf046e361a5661cd4fa770fa3f0ba06c6c4 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 8 Feb 2023 16:28:04 +0100 Subject: [PATCH 06/17] Unwanted changes --- .gitignore | 1 - Moose Development/Moose/.vscode/settings.json | 9 ++------- Moose Development/Moose/Ops/Airboss.lua | 12 ++++++------ 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 634be4279..fe5b6b4f8 100644 --- a/.gitignore +++ b/.gitignore @@ -228,4 +228,3 @@ MooseCodeWS.code-workspace .gitignore .gitignore /.gitignore -Moose Development/Moose/.vscode/settings.json diff --git a/Moose Development/Moose/.vscode/settings.json b/Moose Development/Moose/.vscode/settings.json index f25704250..5d2eecfde 100644 --- a/Moose Development/Moose/.vscode/settings.json +++ b/Moose Development/Moose/.vscode/settings.json @@ -1,8 +1,7 @@ { "Lua.workspace.preloadFileSize": 1000, "Lua.diagnostics.disable": [ - "undefined-doc-name", - "need-check-nil" + "undefined-doc-name" ], "Lua.diagnostics.globals": [ "BASE", @@ -15,9 +14,5 @@ ], "Lua.completion.displayContext": 5, "Lua.runtime.version": "Lua 5.1", - "Lua.completion.callSnippet": "Both", - "Lua.workspace.library": [ - "${3rd}/lfs/library" - ], - "Lua.workspace.checkThirdParty": false + "Lua.completion.callSnippet": "Both" } \ No newline at end of file diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 9cebc5e58..338f2239c 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -1812,7 +1812,7 @@ AIRBOSS.version = "1.3.0" function AIRBOSS:New( carriername, alias ) -- Inherit everthing from FSM class. - self = BASE:Inherit( self, FSM:New() ) -- #AIRBOSS + local self = BASE:Inherit( self, FSM:New() ) -- #AIRBOSS -- Debug. self:F2( { carriername = carriername, alias = alias } ) @@ -8318,7 +8318,7 @@ function AIRBOSS:OnEventLand( EventData ) local dist = coord:Get2DDistance( self:GetCoordinate() ) -- Get wire - local wire = self:_GetWirePos( coord, 0 ) + local wire = self:_GetWire( coord, 0 ) -- Aircraft type. local _type = EventData.IniUnit:GetTypeName() @@ -10154,11 +10154,11 @@ function AIRBOSS:_GetSternCoord() return self.sterncoord end ---- Get wire from +--- Get wire from draw argument. -- @param #AIRBOSS self -- @param Core.Point#COORDINATE Lcoord Landing position. -- @return #number Trapped wire (1-4) or 99 if no wire was trapped. -function AIRBOSS:_GetWire() +function AIRBOSS:_GetWireFromDrawArg() local wireArgs={} wireArgs[1]=141 @@ -10181,7 +10181,7 @@ end -- @param Core.Point#COORDINATE Lcoord Landing position. -- @param #number dc Distance correction. Shift the landing coord back if dc>0 and forward if dc<0. -- @return #number Trapped wire (1-4) or 99 if no wire was trapped. -function AIRBOSS:_GetWirePos( Lcoord, dc ) +function AIRBOSS:_GetWire( Lcoord, dc ) -- Final bearing (true). local FB = self:GetFinalBearing() @@ -10299,7 +10299,7 @@ function AIRBOSS:_Trapped( playerData ) end -- Get wire. - local wire = self:_GetWirePos( coord, dcorr ) + local wire = self:_GetWire( coord, dcorr ) -- Debug. local text = string.format( "Player %s _Trapped: v=%.1f km/h, s-dcorr=%.1f m ==> wire=%d (dcorr=%d)", playerData.name, v, s - dcorr, wire, dcorr ) From 9f02232589a6e8ec4d1567e967c13a7651198682 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 8 Feb 2023 16:29:25 +0100 Subject: [PATCH 07/17] Update settings.json --- Moose Development/Moose/.vscode/settings.json | 1 - 1 file changed, 1 deletion(-) diff --git a/Moose Development/Moose/.vscode/settings.json b/Moose Development/Moose/.vscode/settings.json index 5d2eecfde..c40b79cc6 100644 --- a/Moose Development/Moose/.vscode/settings.json +++ b/Moose Development/Moose/.vscode/settings.json @@ -10,7 +10,6 @@ "trigger", "coord", "missionCommands", - "world" ], "Lua.completion.displayContext": 5, "Lua.runtime.version": "Lua 5.1", From 9862c32378c67473c8dc4833004517cf194ef7aa Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 8 Feb 2023 16:30:44 +0100 Subject: [PATCH 08/17] Update settings.json --- Moose Development/Moose/.vscode/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/.vscode/settings.json b/Moose Development/Moose/.vscode/settings.json index c40b79cc6..13211d027 100644 --- a/Moose Development/Moose/.vscode/settings.json +++ b/Moose Development/Moose/.vscode/settings.json @@ -9,7 +9,7 @@ "__Moose", "trigger", "coord", - "missionCommands", + "missionCommands" ], "Lua.completion.displayContext": 5, "Lua.runtime.version": "Lua 5.1", From 28c25816a656336942cd0b3101f060b83d6c79d8 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 9 Feb 2023 11:57:28 +0100 Subject: [PATCH 09/17] #AICSAR * Fixed issue with underlying OpsTransport * Added FSM Transition OnAfterPilotUnloaded() * Added options for SRS TTS output - no sound files * Added voice options for downed pilot and operator - these can have different voices --- Moose Development/Moose/Functional/AICSAR.lua | 206 ++++++++++++++++-- 1 file changed, 188 insertions(+), 18 deletions(-) diff --git a/Moose Development/Moose/Functional/AICSAR.lua b/Moose Development/Moose/Functional/AICSAR.lua index 5db982982..345d49d03 100644 --- a/Moose Development/Moose/Functional/AICSAR.lua +++ b/Moose Development/Moose/Functional/AICSAR.lua @@ -147,7 +147,7 @@ -- -- Switch on radio transmissions via **either** SRS **or** "normal" DCS radio e.g. like so: -- --- my_aicsar:SetSRSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone",270,radio.modulation.AM,5002) +-- my_aicsar:SetSRSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone",270,radio.modulation.AM,nil,5002) -- -- or -- @@ -161,7 +161,7 @@ -- @field #AICSAR AICSAR = { ClassName = "AICSAR", - version = "0.0.8", + version = "0.1.9", lid = "", coalition = coalition.side.BLUE, template = "", @@ -173,7 +173,7 @@ AICSAR = { pilotqueue = {}, pilotindex = 0, helos = {}, - verbose = true, + verbose = false, rescuezoneradius = 200, rescued = {}, autoonoff = true, @@ -194,6 +194,13 @@ AICSAR = { helonumber = 3, gettext = nil, locale ="en", -- default text language + SRSTTSRadio = false, + SRSGoogle = false, + SRSQ = nil, + SRSPilot = nil, + SRSPilotVoice = false, + SRSOperator = nil, + SRSOperatorVoice = false, } -- TODO Messages @@ -203,7 +210,7 @@ AICSAR.Messages = { EN = { INITIALOK = "Roger, Pilot, we hear you. Stay where you are, a helo is on the way!", INITIALNOTOK = "Sorry, Pilot. You're behind maximum operational distance! Good Luck!", - PILOTDOWN = "Pilot down at ", + PILOTDOWN = "Mayday, mayday, mayday! Pilot down at ", PILOTKIA = "Pilot KIA!", HELODOWN = "CSAR Helo Down!", PILOTRESCUED = "Pilot rescued!", @@ -212,7 +219,7 @@ AICSAR.Messages = { DE = { INITIALOK = "Copy, Pilot, wir hören Sie. Bleiben Sie, wo Sie sind!\nEin Hubschrauber sammelt Sie auf!", INITIALNOTOK = "Verstehe, Pilot. Sie sind zu weit weg von uns.\nViel Glück!", - PILOTDOWN = "Pilot abgestürzt: ", + PILOTDOWN = "Mayday, mayday, mayday! Pilot abgestürzt: ", PILOTKIA = "Pilot gefallen!", HELODOWN = "CSAR Hubschrauber verloren!", PILOTRESCUED = "Pilot gerettet!", @@ -309,6 +316,9 @@ function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone) -- Radio self.SRS = nil self.SRSRadio = false + self.SRSTTSRadio = false + self.SRSGoogle = false + self.SRSQ = nil self.SRSFrequency = 243 self.SRSPath = "\\" self.SRSModulation = radio.modulation.AM @@ -343,6 +353,7 @@ function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone) self:AddTransition("*", "Status", "*") -- CSAR status update. self:AddTransition("*", "PilotDown", "*") -- Pilot down self:AddTransition("*", "PilotPickedUp", "*") -- Pilot in helo + self:AddTransition("*", "PilotUnloaded", "*") -- Pilot Unloaded from helo self:AddTransition("*", "PilotRescued", "*") -- Pilot Rescued self:AddTransition("*", "PilotKIA", "*") -- Pilot dead self:AddTransition("*", "HeloDown", "*") -- Helo dead @@ -404,6 +415,15 @@ function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone) -- @param #string Event Event. -- @param #string To To state. + --- On after "PilotUnloaded" event. + -- @function [parent=#AICSAR] OnAfterPilotUnloaded + -- @param #AICSAR self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.FlightGroup#FLIGHTGROUP Helo + -- @param Ops.OpsGroup#OPSGROUP OpsGroup + --- On after "PilotKIA" event. -- @function [parent=#AICSAR] OnAfterPilotKIA -- @param #AICSAR self @@ -453,7 +473,7 @@ function AICSAR:InitLocalization() return self end ---- [User] Switch sound output on and use SRS +--- [User] Switch sound output on and use SRS output for sound files. -- @param #AICSAR self -- @param #boolean OnOff Switch on (true) or off (false). -- @param #string Path Path to your SRS Server Component, e.g. "E:\\\\Program Files\\\\DCS-SimpleRadio-Standalone" @@ -464,10 +484,12 @@ end -- @return #AICSAR self function AICSAR:SetSRSRadio(OnOff,Path,Frequency,Modulation,SoundPath,Port) self:T(self.lid .. "SetSRSRadio") - self:T(self.lid .. "SetSRSRadio to "..tostring(OnOff)) self.SRSRadio = OnOff and true + self.SRSTTSRadio = false self.SRSFrequency = Frequency or 243 self.SRSPath = Path or "c:\\" + self.SRS:SetLabel("ACSR") + self.SRS:SetCoalition(self.coalition) self.SRSModulation = Modulation or radio.modulation.AM local soundpath = os.getenv('TMP') .. "\\DCS\\Mission\\l10n\\DEFAULT" -- defaults to "l10n/DEFAULT/", i.e. add messages by "Sound to..." in the ME self.SRSSoundPath = SoundPath or soundpath @@ -479,6 +501,88 @@ function AICSAR:SetSRSRadio(OnOff,Path,Frequency,Modulation,SoundPath,Port) return self end +--- [User] Switch sound output on and use SRS-TTS output. The voice will be used across all outputs, unless you define an extra voice for downed pilots and/or the operator. +-- See `AICSAR:SetPilotTTSVoice()` and `AICSAR:SetOperatorTTSVoice()` +-- @param #AICSAR self +-- @param #boolean OnOff Switch on (true) or off (false). +-- @param #string Path Path to your SRS Server Component, e.g. "E:\\\\Program Files\\\\DCS-SimpleRadio-Standalone" +-- @param #number Frequency (Optional) Defaults to 243 (guard) +-- @param #number Modulation (Optional) Radio modulation. Defaults to radio.modulation.AM +-- @param #number Port (Optional) Port of the SRS, defaults to 5002. +-- @param #string Voice (Optional) The voice to be used. +-- @param #string Culture (Optional) The culture to be used, defaults to "en-GB" +-- @param #string Gender (Optional) The gender to be used, defaults to "male" +-- @param #string GoogleCredentials (Optional) Path to google credentials +-- @return #AICSAR self +function AICSAR:SetSRSTTSRadio(OnOff,Path,Frequency,Modulation,Port,Voice,Culture,Gender,GoogleCredentials) + self:T(self.lid .. "SetSRSTTSRadio") + self.SRSTTSRadio = OnOff and true + self.SRSRadio = false + self.SRSFrequency = Frequency or 243 + self.SRSPath = Path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" + self.SRSModulation = Modulation or radio.modulation.AM + self.SRSPort = Port or 5002 + if OnOff then + self.SRS = MSRS:New(Path,Frequency,Modulation,1) + self.SRS:SetPort(self.SRSPort) + self.SRS:SetCoalition(self.coalition) + self.SRS:SetLabel("ACSR") + self.SRS:SetVoice(Voice) + self.SRS:SetCulture(Culture) + self.SRS:SetGender(Gender) + if GoogleCredentials then + self.SRS:SetGoogle(GoogleCredentials) + self.SRSGoogle = true + end + self.SRSQ = MSRSQUEUE:New(self.alias) + end + return self +end + +--- [User] Set SRS TTS Voice of downed pilot. `AICSAR:SetSRSTTSRadio()` needs to be set first! +-- @param #AICSAR self +-- @param #string Voice The voice to be used, e.g. `MSRS.Voices.Google.Standard.en_US_Standard_J` for Google or `MSRS.Voices.Microsoft.David` for Microsoft. +-- Specific voices override culture and gender! +-- @param #string Culture (Optional) The culture to be used, defaults to "en-US" +-- @param #string Gender (Optional) The gender to be used, defaults to "male" +-- @return #AICSAR self +function AICSAR:SetPilotTTSVoice(Voice,Culture,Gender) + self:T(self.lid .. "SetPilotTTSVoice") + self.SRSPilotVoice = true + self.SRSPilot = MSRS:New(self.SRSPath,self.SRSFrequency,self.SRSModulation,1) + self.SRSPilot:SetCoalition(self.coalition) + self.SRSPilot:SetVoice(Voice) + self.SRSPilot:SetCulture(Culture or "en-US") + self.SRSPilot:SetGender(Gender or "male") + self.SRSPilot:SetLabel("PILOT") + if self.SRS.google then + self.SRSPilot:SetGoogle(self.SRS.google) + end + return self +end + +--- [User] Set SRS TTS Voice of the rescue operator. `AICSAR:SetSRSTTSRadio()` needs to be set first! +-- @param #AICSAR self +-- @param #string Voice The voice to be used, e.g. `MSRS.Voices.Google.Standard.en_US_Standard_J` for Google or `MSRS.Voices.Microsoft.David` for Microsoft. +-- Specific voices override culture and gender! +-- @param #string Culture (Optional) The culture to be used, defaults to "en-GB" +-- @param #string Gender (Optional) The gender to be used, defaults to "female" +-- @return #AICSAR self +function AICSAR:SetOperatorTTSVoice(Voice,Culture,Gender) + self:T(self.lid .. "SetOperatorTTSVoice") + self.SRSOperatorVoice = true + self.SRSOperator = MSRS:New(self.SRSPath,self.SRSFrequency,self.SRSModulation,1) + self.SRSOperator:SetCoalition(self.coalition) + self.SRSOperator:SetVoice(Voice) + self.SRSOperator:SetCulture(Culture or "en-GB") + self.SRSOperator:SetGender(Gender or "female") + self.SRSPilot:SetLabel("RESCUE") + if self.SRS.google then + self.SRSOperator:SetGoogle(self.SRS.google) + end + return self +end + --- [User] Switch sound output on and use normale (DCS) radio -- @param #AICSAR self -- @param #boolean OnOff Switch on (true) or off (false). @@ -545,13 +649,25 @@ function AICSAR:OnEventLandingAfterEjection(EventData) -- Mayday Message local Text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTDOWN",self.locale) local text = "" + local setting = {} + setting.MGRS_Accuracy = self.MGRS_Accuracy + local location = _LandingPos:ToStringMGRS(setting) + local msgtxt = Text..location.."!" + location = string.gsub(location,"MGRS ","") + location = string.gsub(location,"%s+","") + location = string.gsub(location,"([%a%d])","%1;") -- "0 5 1 " + location = string.gsub(location,"0","zero") + location = string.gsub(location,"9","niner") + location = "MGRS;"..location + if self.SRSGoogle then + location = string.format("%s",location) + end + text = Text .. location .. "!" + local ttstext = Text .. location .. "! Repeat! "..location if _coalition == self.coalition then if self.verbose then - local setting = {} - setting.MGRS_Accuracy = self.MGRS_Accuracy - local location = _LandingPos:ToStringMGRS(setting) - text = Text .. location .. "!" - MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition) + MESSAGE:New(msgtxt,15,"AICSAR"):ToCoalition(self.coalition) + -- MESSAGE:New(msgtxt,15,"AICSAR"):ToLog() end if self.SRSRadio then local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength) @@ -559,6 +675,12 @@ function AICSAR:OnEventLandingAfterEjection(EventData) self.SRS:PlaySoundFile(sound,2) elseif self.DCSRadio then self:DCSRadioBroadcast(Soundfile,Soundlength,text) + elseif self.SRSTTSRadio then + if self.SRSPilotVoice then + self.SRSQ:NewTransmission(ttstext,nil,self.SRSPilot,nil,1) + else + self.SRSQ:NewTransmission(ttstext,nil,self.SRS,nil,1) + end end end @@ -634,6 +756,10 @@ function AICSAR:_InitMission(Pilot,Index) self:__HeloDown(2,Helo,Index) end + local function AICHeloUnloaded(Helo,OpsGroup) + self:__PilotUnloaded(2,Helo,OpsGroup) + end + function helo:OnAfterLoadingDone(From,Event,To) AICPickedUp(helo,helo:GetCargoGroups(),Index) end @@ -642,6 +768,10 @@ function AICSAR:_InitMission(Pilot,Index) AICHeloDead(helo,Index) end + function helo:OnAfterUnloaded(From,Event,To,OpsGroupCargo) + AICHeloUnloaded(helo,OpsGroupCargo) + end + self.helos[Index] = helo return self @@ -652,7 +782,7 @@ end -- @param Wrapper.Group#GROUP Pilot The pilot to be rescued. -- @return #boolean outcome function AICSAR:_CheckInMashZone(Pilot) - self:T(self.lid .. "_CheckQueue") + self:T(self.lid .. "_CheckInMashZone") if Pilot:IsInZone(self.farpzone) then return true else @@ -696,8 +826,9 @@ end --- [Internal] Check pilot queue for next mission -- @param #AICSAR self +-- @param Ops.OpsGroup#OPSGROUP OpsGroup -- @return #AICSAR self -function AICSAR:_CheckQueue() +function AICSAR:_CheckQueue(OpsGroup) self:T(self.lid .. "_CheckQueue") for _index, _pilot in pairs(self.pilotqueue) do local classname = _pilot.ClassName and _pilot.ClassName or "NONE" @@ -709,8 +840,12 @@ function AICSAR:_CheckQueue() local flightgroup = self.helos[_index] -- Ops.FlightGroup#FLIGHTGROUP -- rescued? if self:_CheckInMashZone(_pilot) then - self:T("Pilot" .. _pilot.GroupName .. " rescued!") - _pilot:Destroy(false) + self:T("Pilot" .. _pilot.GroupName .. " rescued!") + if OpsGroup then + OpsGroup:Despawn(10) + else + _pilot:Destroy(true,10) + end self.pilotqueue[_index] = nil self.rescued[_index] = true self:__PilotRescued(2) @@ -767,7 +902,7 @@ end -- @return #AICSAR self function AICSAR:onafterStatus(From, Event, To) self:T({From, Event, To}) - self:_CheckQueue() + --self:_CheckQueue() self:_CheckHelos() self:__Status(30) return self @@ -814,6 +949,12 @@ function AICSAR:onafterPilotDown(From, Event, To, Coordinate, InReach) self.SRS:PlaySoundFile(sound,2) elseif self.DCSRadio then self:DCSRadioBroadcast(Soundfile,Soundlength,text) + elseif self.SRSTTSRadio then + if self.SRSOperatorVoice then + self.SRSQ:NewTransmission(text,nil,self.SRSOperator,nil,1) + else + self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1) + end end else local text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("INITIALNOTOK",self.locale) @@ -828,8 +969,15 @@ function AICSAR:onafterPilotDown(From, Event, To, Coordinate, InReach) self.SRS:PlaySoundFile(sound,2) elseif self.DCSRadio then self:DCSRadioBroadcast(Soundfile,Soundlength,text) + elseif self.SRSTTSRadio then + if self.SRSOperatorVoice then + self.SRSQ:NewTransmission(text,nil,self.SRSOperator,nil,1) + else + self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1) + end end end + self:_CheckQueue() return self end @@ -851,7 +999,9 @@ function AICSAR:onafterPilotKIA(From, Event, To) self.SRS:PlaySoundFile(sound,2) elseif self.DCSRadio then self:DCSRadioBroadcast(Soundfile,Soundlength,text) - end + elseif self.SRSTTSRadio then + self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1) + end return self end @@ -875,6 +1025,8 @@ function AICSAR:onafterHeloDown(From, Event, To, Helo, Index) self.SRS:PlaySoundFile(sound,2) elseif self.DCSRadio then self:DCSRadioBroadcast(Soundfile,Soundlength,text) + elseif self.SRSTTSRadio then + self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1) end local findex = 0 local fhname = Helo:GetName() @@ -927,10 +1079,26 @@ function AICSAR:onafterPilotRescued(From, Event, To) self.SRS:PlaySoundFile(sound,2) elseif self.DCSRadio then self:DCSRadioBroadcast(Soundfile,Soundlength,text) + elseif self.SRSTTSRadio then + self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1) end return self end +--- [Internal] onafterPilotUnloaded +-- @param #AICSAR self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param Ops.FlightGroup#FLIGHTGROUP Helo +-- @param Ops.OpsGroup#OPSGROUP OpsGroup +-- @return #AICSAR self +function AICSAR:onafterPilotUnloaded(From, Event, To, Helo, OpsGroup) + self:T({From, Event, To}) + self:_CheckQueue(OpsGroup) + return self +end + --- [Internal] onafterPilotPickedUp -- @param #AICSAR self -- @param #string From @@ -952,6 +1120,8 @@ function AICSAR:onafterPilotPickedUp(From, Event, To, Helo, CargoTable, Index) self.SRS:PlaySoundFile(sound,2) elseif self.DCSRadio then self:DCSRadioBroadcast(Soundfile,Soundlength,text) + elseif self.SRSTTSRadio then + self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1) end local findex = 0 local fhname = Helo:GetName() From 608033f38f3708d656ca2ea7a18434b7b0a06da9 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 9 Feb 2023 13:17:49 +0100 Subject: [PATCH 10/17] #AICSAR * Docu additions --- Moose Development/Moose/Functional/AICSAR.lua | 41 ++++++++++++++++--- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Functional/AICSAR.lua b/Moose Development/Moose/Functional/AICSAR.lua index 345d49d03..b1ee470ca 100644 --- a/Moose Development/Moose/Functional/AICSAR.lua +++ b/Moose Development/Moose/Functional/AICSAR.lua @@ -11,6 +11,7 @@ -- * Dedicated MASH zone -- * Some FSM functions to include in your mission scripts -- * Limit number of available helos +-- * SRS voice output via TTS or soundfiles -- -- === -- @@ -20,8 +21,8 @@ -- -- === -- --- ### Author: **applevangelist** --- Last Update April 2022 +-- ### Author: **Applevangelist** +-- Last Update February 2022 -- -- === -- @module Functional.AICSAR @@ -87,10 +88,11 @@ -- my_aicsar.rescuezoneradius -- landing zone around downed pilot. Defaults to 200m -- my_aicsar.autoonoff -- stop operations when human helicopter pilots are around. Defaults to true. -- my_aicsar.verbose -- text messages to own coalition about ongoing operations. Defaults to true. --- my_aicsarlimithelos -- limit available number of helos going on mission (defaults to true) +-- my_aicsar.limithelos -- limit available number of helos going on mission (defaults to true) -- my_aicsar.helonumber -- number of helos available (default: 3) +-- my_aicsar.verbose -- boolean, set to `true`for message output on-screen -- --- ## Radio options +-- ## Radio output options -- -- Radio messages, soundfile names and (for SRS) lengths are defined in three enumerators, so you can customize, localize messages and soundfiles to your liking: -- @@ -100,7 +102,7 @@ -- EN = { -- INITIALOK = "Roger, Pilot, we hear you. Stay where you are, a helo is on the way!", -- INITIALNOTOK = "Sorry, Pilot. You're behind maximum operational distance! Good Luck!", --- PILOTDOWN = "Pilot down at ", -- note that this will be appended with the position +-- PILOTDOWN = "Mayday, mayday, mayday! Pilot down at ", -- note that this will be appended with the position in MGRS -- PILOTKIA = "Pilot KIA!", -- HELODOWN = "CSAR Helo Down!", -- PILOTRESCUED = "Pilot rescued!", @@ -136,8 +138,31 @@ -- }, -- } -- +-- ## Radio output via SRS and Text-To-Speech (TTS) +-- +-- Radio output can be done via SRS and Text-To-Speech. No extra sound files required! +-- [Initially, Have a look at the guide on setting up SRS TTS for Moose](https://github.com/FlightControl-Master/MOOSE_GUIDES/blob/master/documents/Moose%20TTS%20Setup%20Guide.pdf). +-- The text from the `AICSAR.Messages` table above is converted on the fly to an .ogg-file, which is then played back via SRS on the selected frequency and mdulation. +-- Hint - the small black window popping up shortly is visible in Single-Player only. +-- +-- To set up AICSAR for SRS TTS output, add e.g. the following to your script: +-- +-- -- setup for google TTS, radio 243 AM, SRS server port 5002 with a google standard-quality voice (google cloud account required) +-- my_aicsar:SetSRSTTSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone",243,radio.modulation.AM,5002,MSRS.Voices.Google.Standard.en_US_Standard_D,"en-US","female","C:\\Program Files\\DCS-SimpleRadio-Standalone\\google.json") +-- +-- -- alternatively for MS Desktop TTS (voices need to be installed locally first!) +-- my_aicsar:SetSRSTTSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone",243,radio.modulation.AM,5002,MSRS.Voices.Microsoft.Hazel,"en-GB","female") +-- +-- -- define a different voice for the downed pilot(s) +-- my_aicsar:SetPilotTTSVoice(MSRS.Voices.Google.Standard.en_AU_Standard_D,"en-AU","male") +-- +-- -- define another voice for the operator +-- my_aicsar:SetOperatorTTSVoice(MSRS.Voices.Google.Standard.en_GB_Standard_A,"en-GB","female") +-- +-- ## Radio output via preproduced soundfiles +-- -- The easiest way to add a soundfile to your mission is to use the "Sound to..." trigger in the mission editor. This will effectively --- save your sound file inside of the .miz mission file. +-- save your sound file inside of the .miz mission file. [Example soundfiles are located on github](https://github.com/FlightControl-Master/MOOSE_SOUND/tree/master/AICSAR) -- -- To customize or localize your texts and sounds, you can take e.g. the following approach to add a German language version: -- @@ -1026,7 +1051,11 @@ function AICSAR:onafterHeloDown(From, Event, To, Helo, Index) elseif self.DCSRadio then self:DCSRadioBroadcast(Soundfile,Soundlength,text) elseif self.SRSTTSRadio then + if self.SRSOperatorVoice then + self.SRSQ:NewTransmission(text,nil,self.SRSOperator,nil,1) + else self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1) + end end local findex = 0 local fhname = Helo:GetName() From 2a3d69d0d5c28e66c76fd680d5d075e814b15786 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 9 Feb 2023 16:11:45 +0100 Subject: [PATCH 11/17] #DETECTION * Docu fixes --- Moose Development/Moose/Functional/Detection.lua | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Functional/Detection.lua b/Moose Development/Moose/Functional/Detection.lua index dfafcae01..e7f76ef0f 100644 --- a/Moose Development/Moose/Functional/Detection.lua +++ b/Moose Development/Moose/Functional/Detection.lua @@ -474,7 +474,7 @@ do -- DETECTION_BASE -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. - -- @param #table DetectedItem The DetectedItem. + -- @param #DetectedItem DetectedItem The DetectedItem data structure. self:AddTransition( "*", "Stop", "Stopped" ) @@ -2478,14 +2478,14 @@ do -- DETECTION_AREAS --- DETECTION_AREAS constructor. -- @param #DETECTION_AREAS self -- @param Core.Set#SET_GROUP DetectionSetGroup The @{Core.Set} of GROUPs in the Forward Air Controller role. - -- @param DCS#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. + -- @param #number DetectionZoneRange The range in meters within which targets are grouped upon the first detected target. Default 5000m. -- @return #DETECTION_AREAS function DETECTION_AREAS:New( DetectionSetGroup, DetectionZoneRange ) -- Inherits from DETECTION_BASE local self = BASE:Inherit( self, DETECTION_BASE:New( DetectionSetGroup ) ) - self.DetectionZoneRange = DetectionZoneRange + self.DetectionZoneRange = DetectionZoneRange or 5000 self._SmokeDetectedUnits = false self._FlareDetectedUnits = false @@ -2988,4 +2988,3 @@ do -- DETECTION_AREAS end end - From d6aa7ec17ca3957cc821c953b6956134b30ab205 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 10 Feb 2023 10:45:23 +0100 Subject: [PATCH 12/17] #Net * Initial Release --- Moose Development/Moose/Modules.lua | 1 + Moose Development/Moose/Wrapper/Net.lua | 298 ++++++++++++++++++++++++ Moose Setup/Moose.files | 1 + 3 files changed, 300 insertions(+) create mode 100644 Moose Development/Moose/Wrapper/Net.lua diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 46458f046..f2e5568c2 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -47,6 +47,7 @@ __Moose.Include( 'Scripts/Moose/Wrapper/Scenery.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Static.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Unit.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Weapon.lua' ) +__Moose.Include( 'Scripts/Moose/Wrapper/Net.lua' ) __Moose.Include( 'Scripts/Moose/Cargo/Cargo.lua' ) __Moose.Include( 'Scripts/Moose/Cargo/CargoUnit.lua' ) diff --git a/Moose Development/Moose/Wrapper/Net.lua b/Moose Development/Moose/Wrapper/Net.lua new file mode 100644 index 000000000..e822a176b --- /dev/null +++ b/Moose Development/Moose/Wrapper/Net.lua @@ -0,0 +1,298 @@ +--- **Wrapper** - DCS net functions. +-- +-- Encapsules **multiplayer** environment scripting functions from [net](https://wiki.hoggitworld.com/view/DCS_singleton_net) +-- +-- === +-- +-- ### Author: **applevangelist** +-- +-- === +-- +-- @module Wrapper.Net +-- @image Utils_Profiler.jpg + +do +--- The NET class +-- @type NET +-- @field #string ClassName +-- @field #string Version +-- @extends Core.Base#BASE + +--- Encapsules multiplayer environment scripting functions from [net](https://wiki.hoggitworld.com/view/DCS_singleton_net) +-- +-- @field #NET +NET = { + ClassName = "NET", + Version = "0.0.2" +} + +--- Instantiate a new NET object. +-- @param #NET self +-- @return #NET self +function NET:New() + -- Inherit base. + local self = BASE:Inherit(self, BASE:New()) -- #NET + return self +end + +--- Send chat message. +-- @param #NET self +-- @param #string Message Message to send +-- @param #boolean ToAll (Optional) +-- @return #NET self +function NET:SendChat(Message,ToAll) + if Message then + net.send_chat(Message, ToAll) + end + return self +end + +--- Find the PlayerID by name +-- @param #NET self +-- @param #string Name The player name whose ID to find +-- @return #number PlayerID or nil +function NET:GetPlayerIdByName(Name) + local playerList = self:GetPlayerList() + for i=1,#playerList do + local playerName = net.get_name(i) + if playerName == Name then + return playerList[i] + end + end + return nil +end + +--- Find the PlayerID from a CLIENT object. +-- @param #NET self +-- @param Wrapper.Client#CLIENT Client The client +-- @return #number PlayerID or nil +function NET:GetPlayerIDFromClient(Client) + local name = Client:GetPlayerName() + local id = self:GetPlayerIdByName(name) + return id +end + +--- Send chat message to a specific player. +-- @param #NET self +-- @param #string Message The text message +-- @param Wrapper.Client#CLIENT ToClient Client receiving the message +-- @param Wrapper.Client#CLIENT FromClient (Optional) Client sending the message +-- @return #NET self +function NET:SendChatToPlayer(Message, ToClient, FromClient) + local PlayerId = self:GetPlayerIDFromClient(ToClient) + local FromId = self:GetPlayerIDFromClient(FromClient) + if Message and PlayerId and FromId then + net.send_chat_to(Message, tonumber(PlayerId) , tonumber(FromId)) + elseif Message and PlayerId then + net.send_chat_to(Message, tonumber(PlayerId)) + end + return self +end + +--- Load a specific mission. +-- @param #NET self +-- @param #string Path and Mission +-- @return #boolean success +-- @usage +-- mynet:LoadMission(lfs.writeDir() .. 'Missions\\' .. 'MyTotallyAwesomeMission.miz') +function NET:LoadMission(Path) + local outcome = false + if Path then + outcome = net.load_mission(Path) + end + return outcome +end + +--- Load next mission. Returns false if at the end of list. +-- @param #NET self +-- @return #boolean success +function NET:LoadNextMission() + local outcome = false + outcome = net.load_next_mission() + return outcome +end + +--- Return a table of players currently connected to the server. +-- @param #NET self +-- @return #table PlayerList +function NET:GetPlayerList() + local plist = nil + plist = net.get_player_list() + return plist +end + +--- Returns the playerID of the local player. Always returns 1 for server. +-- @param #NET self +-- @return #number ID +function NET:GetMyPlayerID() + return net.get_my_player_id() +end + +--- Returns the playerID of the server. Currently always returns 1. +-- @param #NET self +-- @return #number ID +function NET:GetServerID() + return net.get_server_id() +end + +--- Return a table of attributes for a given client. If optional attribute is present, only that value is returned. +-- @param #NET self +-- @param Wrapper.Client#CLIENT Client The client. +-- @param #string Attribute (Optional) The attribute to obtain. List see below. +-- @return #table PlayerInfo or nil if it cannot be found +-- @usage +-- Table holds these attributes: +-- +-- 'id' : playerID +-- 'name' : player name +-- 'side' : 0 - spectators, 1 - red, 2 - blue +-- 'slot' : slotID of the player or +-- 'ping' : ping of the player in ms +-- 'ipaddr': IP address of the player, SERVER ONLY +-- 'ucid' : Unique Client Identifier, SERVER ONLY +-- +function NET:GetPlayerInfo(Client,Attribute) + local PlayerID = self:GetPlayerIDFromClient(Client) + if PlayerID then + return net.get_player_info(tonumber(PlayerID), Attribute) + else + return nil + end +end + +--- Kicks a player from the server. Can display a message to the user. +-- @param #NET self +-- @param Wrapper.Client#CLIENT Client The client +-- @param #string Message (Optional) The message to send. +-- @return #boolean success +function NET:Kick(Client,Message) + local PlayerID = self:GetPlayerIDFromClient(Client) + if PlayerID and tonumber(PlayerID) ~= 1 then + return net.kick(tonumber(PlayerID), Message) + else + return false + end +end + +--- Return a statistic for a given client. +-- @param #NET self +-- @param Wrapper.Client#CLIENT Client The client +-- @param #number StatisticID The statistic to obtain +-- @return #number Statistic or nil +-- @usage +-- StatisticIDs are: +-- +-- net.PS_PING (0) - ping (in ms) +-- net.PS_CRASH (1) - number of crashes +-- net.PS_CAR (2) - number of destroyed vehicles +-- net.PS_PLANE (3) - ... planes/helicopters +-- net.PS_SHIP (4) - ... ships +-- net.PS_SCORE (5) - total score +-- net.PS_LAND (6) - number of landings +-- net.PS_EJECT (7) - of ejects +-- +-- mynet:GetPlayerStatistic(Client,7) -- return number of ejects +function NET:GetPlayerStatistic(Client,StatisticID) + local PlayerID = self:GetPlayerIDFromClient(Client) + local stats = StatisticID or 0 + if stats > 7 or stats < 0 then stats = 0 end + if PlayerID then + return net.get_stat(tonumber(PlayerID),stats) + else + return nil + end +end + +--- Return the name of a given client. Same a CLIENT:GetPlayerName(). +-- @param #NET self +-- @param Wrapper.Client#CLIENT Client The client +-- @return #string Name or nil if not obtainable +function NET:GetName(Client) + local PlayerID = self:GetPlayerIDFromClient(Client) + if PlayerID then + return net.get_name(tonumber(PlayerID)) + else + return nil + end +end + +--- Returns the SideId and SlotId of a given client. +-- @param #NET self +-- @param Wrapper.Client#CLIENT Client The client +-- @return #number SideID i.e. 0 : spectators, 1 : Red, 2 : Blue +-- @return #number SlotID +function NET:GetSlot(Client) + local PlayerID = self:GetPlayerIDFromClient(Client) + if PlayerID then + local side,slot = net.get_slot(tonumber(PlayerID)) + return side,slot + else + return nil,nil + end +end + +--- Force the slot for a specific client. +-- @param #NET self +-- @param Wrapper.Client#CLIENT Client The client +-- @param #number SideID i.e. 0 : spectators, 1 : Red, 2 : Blue +-- @param #number SlotID Slot number +-- @return #boolean Success +function NET:ForceSlot(Client,SideID,SlotID) + local PlayerID = self:GetPlayerIDFromClient(Client) + if PlayerID then + return net.force_player_slot(tonumber(PlayerID), SideID, SlotID ) + else + return false + end +end + +--- Force a client back to spectators. +-- @param #NET self +-- @param Wrapper.Client#CLIENT Client The client +-- @return #boolean Succes +function NET:ReturnToSpectators(Client) + local outcome = self:ForceSlot(Client,0) + return outcome +end + +--- Converts a lua value to a JSON string. +-- @param #string Lua Anything lua +-- @return #table Json +function NET.Lua2Json(Lua) + return net.lua2json(Lua) +end + +--- Converts a JSON string to a lua value. +-- @param #string Json Anything JSON +-- @return #table Lua +function NET.Lua2Json(Json) + return net.json2lua(Json) +end + +--- Executes a lua string in a given lua environment in the game. +-- @param #NET self +-- @param #string State The state in which to execute - see below. +-- @param #string DoString The lua string to be executed. +-- @return #string Output +-- @usage +-- States are: +-- 'config': the state in which $INSTALL_DIR/Config/main.cfg is executed, as well as $WRITE_DIR/Config/autoexec.cfg - used for configuration settings +-- 'mission': holds current mission +-- 'export': runs $WRITE_DIR/Scripts/Export.lua and the relevant export API +function NET:DoStringIn(State,DoString) + return net.dostring_in(State,DoString) +end + +--- Write an "INFO" entry to the DCS log file, with the message Message. +-- @param #NET self +-- @param #string Message The message to be logged. +-- @return #NET self +function NET:Log(Message) + net.log(Message) + return self +end + +------------------------------------------------------------------------------- +-- End of NET +------------------------------------------------------------------------------- +end diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 6cd862e94..4b0666443 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -44,6 +44,7 @@ Wrapper/Airbase.lua Wrapper/Scenery.lua Wrapper/Marker.lua Wrapper/Weapon.lua +Wrapper/Net.lua Cargo/Cargo.lua Cargo/CargoUnit.lua From 3d5470eb222b7a5fd3245b977f7f33e387d37381 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 10 Feb 2023 11:00:14 +0100 Subject: [PATCH 13/17] #NET * Initial release --- Moose Development/Moose/Modules.lua | 1 + Moose Development/Moose/Wrapper/Net.lua | 298 ++++++++++++++++++++++++ Moose Setup/Moose.files | 1 + 3 files changed, 300 insertions(+) create mode 100644 Moose Development/Moose/Wrapper/Net.lua diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 2d8bee643..1540e2d0d 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -46,6 +46,7 @@ __Moose.Include( 'Scripts/Moose/Wrapper/Airbase.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Scenery.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Marker.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Weapon.lua' ) +__Moose.Include( 'Scripts/Moose/Wrapper/Net.lua' ) __Moose.Include( 'Scripts/Moose/Cargo/Cargo.lua' ) __Moose.Include( 'Scripts/Moose/Cargo/CargoUnit.lua' ) diff --git a/Moose Development/Moose/Wrapper/Net.lua b/Moose Development/Moose/Wrapper/Net.lua new file mode 100644 index 000000000..e822a176b --- /dev/null +++ b/Moose Development/Moose/Wrapper/Net.lua @@ -0,0 +1,298 @@ +--- **Wrapper** - DCS net functions. +-- +-- Encapsules **multiplayer** environment scripting functions from [net](https://wiki.hoggitworld.com/view/DCS_singleton_net) +-- +-- === +-- +-- ### Author: **applevangelist** +-- +-- === +-- +-- @module Wrapper.Net +-- @image Utils_Profiler.jpg + +do +--- The NET class +-- @type NET +-- @field #string ClassName +-- @field #string Version +-- @extends Core.Base#BASE + +--- Encapsules multiplayer environment scripting functions from [net](https://wiki.hoggitworld.com/view/DCS_singleton_net) +-- +-- @field #NET +NET = { + ClassName = "NET", + Version = "0.0.2" +} + +--- Instantiate a new NET object. +-- @param #NET self +-- @return #NET self +function NET:New() + -- Inherit base. + local self = BASE:Inherit(self, BASE:New()) -- #NET + return self +end + +--- Send chat message. +-- @param #NET self +-- @param #string Message Message to send +-- @param #boolean ToAll (Optional) +-- @return #NET self +function NET:SendChat(Message,ToAll) + if Message then + net.send_chat(Message, ToAll) + end + return self +end + +--- Find the PlayerID by name +-- @param #NET self +-- @param #string Name The player name whose ID to find +-- @return #number PlayerID or nil +function NET:GetPlayerIdByName(Name) + local playerList = self:GetPlayerList() + for i=1,#playerList do + local playerName = net.get_name(i) + if playerName == Name then + return playerList[i] + end + end + return nil +end + +--- Find the PlayerID from a CLIENT object. +-- @param #NET self +-- @param Wrapper.Client#CLIENT Client The client +-- @return #number PlayerID or nil +function NET:GetPlayerIDFromClient(Client) + local name = Client:GetPlayerName() + local id = self:GetPlayerIdByName(name) + return id +end + +--- Send chat message to a specific player. +-- @param #NET self +-- @param #string Message The text message +-- @param Wrapper.Client#CLIENT ToClient Client receiving the message +-- @param Wrapper.Client#CLIENT FromClient (Optional) Client sending the message +-- @return #NET self +function NET:SendChatToPlayer(Message, ToClient, FromClient) + local PlayerId = self:GetPlayerIDFromClient(ToClient) + local FromId = self:GetPlayerIDFromClient(FromClient) + if Message and PlayerId and FromId then + net.send_chat_to(Message, tonumber(PlayerId) , tonumber(FromId)) + elseif Message and PlayerId then + net.send_chat_to(Message, tonumber(PlayerId)) + end + return self +end + +--- Load a specific mission. +-- @param #NET self +-- @param #string Path and Mission +-- @return #boolean success +-- @usage +-- mynet:LoadMission(lfs.writeDir() .. 'Missions\\' .. 'MyTotallyAwesomeMission.miz') +function NET:LoadMission(Path) + local outcome = false + if Path then + outcome = net.load_mission(Path) + end + return outcome +end + +--- Load next mission. Returns false if at the end of list. +-- @param #NET self +-- @return #boolean success +function NET:LoadNextMission() + local outcome = false + outcome = net.load_next_mission() + return outcome +end + +--- Return a table of players currently connected to the server. +-- @param #NET self +-- @return #table PlayerList +function NET:GetPlayerList() + local plist = nil + plist = net.get_player_list() + return plist +end + +--- Returns the playerID of the local player. Always returns 1 for server. +-- @param #NET self +-- @return #number ID +function NET:GetMyPlayerID() + return net.get_my_player_id() +end + +--- Returns the playerID of the server. Currently always returns 1. +-- @param #NET self +-- @return #number ID +function NET:GetServerID() + return net.get_server_id() +end + +--- Return a table of attributes for a given client. If optional attribute is present, only that value is returned. +-- @param #NET self +-- @param Wrapper.Client#CLIENT Client The client. +-- @param #string Attribute (Optional) The attribute to obtain. List see below. +-- @return #table PlayerInfo or nil if it cannot be found +-- @usage +-- Table holds these attributes: +-- +-- 'id' : playerID +-- 'name' : player name +-- 'side' : 0 - spectators, 1 - red, 2 - blue +-- 'slot' : slotID of the player or +-- 'ping' : ping of the player in ms +-- 'ipaddr': IP address of the player, SERVER ONLY +-- 'ucid' : Unique Client Identifier, SERVER ONLY +-- +function NET:GetPlayerInfo(Client,Attribute) + local PlayerID = self:GetPlayerIDFromClient(Client) + if PlayerID then + return net.get_player_info(tonumber(PlayerID), Attribute) + else + return nil + end +end + +--- Kicks a player from the server. Can display a message to the user. +-- @param #NET self +-- @param Wrapper.Client#CLIENT Client The client +-- @param #string Message (Optional) The message to send. +-- @return #boolean success +function NET:Kick(Client,Message) + local PlayerID = self:GetPlayerIDFromClient(Client) + if PlayerID and tonumber(PlayerID) ~= 1 then + return net.kick(tonumber(PlayerID), Message) + else + return false + end +end + +--- Return a statistic for a given client. +-- @param #NET self +-- @param Wrapper.Client#CLIENT Client The client +-- @param #number StatisticID The statistic to obtain +-- @return #number Statistic or nil +-- @usage +-- StatisticIDs are: +-- +-- net.PS_PING (0) - ping (in ms) +-- net.PS_CRASH (1) - number of crashes +-- net.PS_CAR (2) - number of destroyed vehicles +-- net.PS_PLANE (3) - ... planes/helicopters +-- net.PS_SHIP (4) - ... ships +-- net.PS_SCORE (5) - total score +-- net.PS_LAND (6) - number of landings +-- net.PS_EJECT (7) - of ejects +-- +-- mynet:GetPlayerStatistic(Client,7) -- return number of ejects +function NET:GetPlayerStatistic(Client,StatisticID) + local PlayerID = self:GetPlayerIDFromClient(Client) + local stats = StatisticID or 0 + if stats > 7 or stats < 0 then stats = 0 end + if PlayerID then + return net.get_stat(tonumber(PlayerID),stats) + else + return nil + end +end + +--- Return the name of a given client. Same a CLIENT:GetPlayerName(). +-- @param #NET self +-- @param Wrapper.Client#CLIENT Client The client +-- @return #string Name or nil if not obtainable +function NET:GetName(Client) + local PlayerID = self:GetPlayerIDFromClient(Client) + if PlayerID then + return net.get_name(tonumber(PlayerID)) + else + return nil + end +end + +--- Returns the SideId and SlotId of a given client. +-- @param #NET self +-- @param Wrapper.Client#CLIENT Client The client +-- @return #number SideID i.e. 0 : spectators, 1 : Red, 2 : Blue +-- @return #number SlotID +function NET:GetSlot(Client) + local PlayerID = self:GetPlayerIDFromClient(Client) + if PlayerID then + local side,slot = net.get_slot(tonumber(PlayerID)) + return side,slot + else + return nil,nil + end +end + +--- Force the slot for a specific client. +-- @param #NET self +-- @param Wrapper.Client#CLIENT Client The client +-- @param #number SideID i.e. 0 : spectators, 1 : Red, 2 : Blue +-- @param #number SlotID Slot number +-- @return #boolean Success +function NET:ForceSlot(Client,SideID,SlotID) + local PlayerID = self:GetPlayerIDFromClient(Client) + if PlayerID then + return net.force_player_slot(tonumber(PlayerID), SideID, SlotID ) + else + return false + end +end + +--- Force a client back to spectators. +-- @param #NET self +-- @param Wrapper.Client#CLIENT Client The client +-- @return #boolean Succes +function NET:ReturnToSpectators(Client) + local outcome = self:ForceSlot(Client,0) + return outcome +end + +--- Converts a lua value to a JSON string. +-- @param #string Lua Anything lua +-- @return #table Json +function NET.Lua2Json(Lua) + return net.lua2json(Lua) +end + +--- Converts a JSON string to a lua value. +-- @param #string Json Anything JSON +-- @return #table Lua +function NET.Lua2Json(Json) + return net.json2lua(Json) +end + +--- Executes a lua string in a given lua environment in the game. +-- @param #NET self +-- @param #string State The state in which to execute - see below. +-- @param #string DoString The lua string to be executed. +-- @return #string Output +-- @usage +-- States are: +-- 'config': the state in which $INSTALL_DIR/Config/main.cfg is executed, as well as $WRITE_DIR/Config/autoexec.cfg - used for configuration settings +-- 'mission': holds current mission +-- 'export': runs $WRITE_DIR/Scripts/Export.lua and the relevant export API +function NET:DoStringIn(State,DoString) + return net.dostring_in(State,DoString) +end + +--- Write an "INFO" entry to the DCS log file, with the message Message. +-- @param #NET self +-- @param #string Message The message to be logged. +-- @return #NET self +function NET:Log(Message) + net.log(Message) + return self +end + +------------------------------------------------------------------------------- +-- End of NET +------------------------------------------------------------------------------- +end diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 4f079b028..607f2c9b0 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -47,6 +47,7 @@ Wrapper/Airbase.lua Wrapper/Scenery.lua Wrapper/Marker.lua Wrapper/Weapon.lua +Wrapper/Net.lua Cargo/Cargo.lua Cargo/CargoUnit.lua From 713a5b067f4521b583d48b01dde8907348ac0a34 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 10 Feb 2023 11:40:15 +0100 Subject: [PATCH 14/17] #PLAYERTASKCONTROLLER * Added `AddAgentSet()` --- Moose Development/Moose/Ops/PlayerTask.lua | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index bc8bd2bb8..94901b479 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -96,7 +96,7 @@ PLAYERTASK = { --- PLAYERTASK class version. -- @field #string version -PLAYERTASK.version="0.1.12" +PLAYERTASK.version="0.1.14" --- Generic task condition. -- @type PLAYERTASK.Condition @@ -3526,6 +3526,23 @@ function PLAYERTASKCONTROLLER:AddAgent(Recce) return self end +--- [User] Add agent SET_GROUP to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. +-- @param #PLAYERTASKCONTROLLER self +-- @param Core.Set#SET_GROUP RecceSet SET_GROUP of agents. +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:AddAgentSet(RecceSet) + self:T(self.lid.."AddAgentSet") + if self.Intel then + local Set = RecceSet:GetAliveSet() + for _,_Recce in pairs(Set) do + self.Intel:AddAgent(_Recce) + end + else + self:E(self.lid.."*****NO detection has been set up (yet)!") + end + return self +end + --- [User] Set up detection of STATIC objects. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. -- @param #PLAYERTASKCONTROLLER self -- @param #boolean OnOff Set to `true`for on and `false`for off. From d3c34ef04d34e23225098c4b62f6122b3ffd9b56 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Sat, 11 Feb 2023 22:11:21 +0100 Subject: [PATCH 15/17] Update MarkerOps_Base.lua (#1919) --- Moose Development/Moose/Core/MarkerOps_Base.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Core/MarkerOps_Base.lua b/Moose Development/Moose/Core/MarkerOps_Base.lua index 2cdb2afb6..b39250c8c 100644 --- a/Moose Development/Moose/Core/MarkerOps_Base.lua +++ b/Moose Development/Moose/Core/MarkerOps_Base.lua @@ -17,7 +17,7 @@ -- ### Author: **Applevangelist** -- -- Date: 5 May 2021 --- Last Update: Sep 2022 +-- Last Update: Feb 2023 -- -- === --- @@ -50,7 +50,7 @@ MARKEROPS_BASE = { ClassName = "MARKEROPS", Tag = "mytag", Keywords = {}, - version = "0.1.0", + version = "0.1.1", debug = false, Casesensitive = true, } @@ -124,7 +124,8 @@ function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive) -- @param #string Text The text on the marker -- @param #table Keywords Table of matching keywords found in the Event text -- @param Core.Point#COORDINATE Coord Coordinate of the marker. - + -- @param #number idx DCS Marker ID + --- On after "MarkDeleted" event. Triggered when a Marker is deleted from the F10 map. -- @function [parent=#MARKEROPS_BASE] OnAfterMarkDeleted -- @param #MARKEROPS_BASE self @@ -172,7 +173,7 @@ function MARKEROPS_BASE:OnEventMark(Event) if Eventtext~=nil then if self:_MatchTag(Eventtext) then local matchtable = self:_MatchKeywords(Eventtext) - self:MarkChanged(Eventtext,matchtable,coord) + self:MarkChanged(Eventtext,matchtable,coord,Event.idx) end end elseif Event.id==world.event.S_EVENT_MARK_REMOVED then From 960f261ddde52a1db89ea9b35f2dc4f424ba1c11 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Sat, 11 Feb 2023 22:17:24 +0100 Subject: [PATCH 16/17] Master merge (#1920) * #NET * Initial release * Update MarkerOps_Base.lua (#1919) --- Moose Development/Moose/Core/MarkerOps_Base.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Core/MarkerOps_Base.lua b/Moose Development/Moose/Core/MarkerOps_Base.lua index 2cdb2afb6..b39250c8c 100644 --- a/Moose Development/Moose/Core/MarkerOps_Base.lua +++ b/Moose Development/Moose/Core/MarkerOps_Base.lua @@ -17,7 +17,7 @@ -- ### Author: **Applevangelist** -- -- Date: 5 May 2021 --- Last Update: Sep 2022 +-- Last Update: Feb 2023 -- -- === --- @@ -50,7 +50,7 @@ MARKEROPS_BASE = { ClassName = "MARKEROPS", Tag = "mytag", Keywords = {}, - version = "0.1.0", + version = "0.1.1", debug = false, Casesensitive = true, } @@ -124,7 +124,8 @@ function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive) -- @param #string Text The text on the marker -- @param #table Keywords Table of matching keywords found in the Event text -- @param Core.Point#COORDINATE Coord Coordinate of the marker. - + -- @param #number idx DCS Marker ID + --- On after "MarkDeleted" event. Triggered when a Marker is deleted from the F10 map. -- @function [parent=#MARKEROPS_BASE] OnAfterMarkDeleted -- @param #MARKEROPS_BASE self @@ -172,7 +173,7 @@ function MARKEROPS_BASE:OnEventMark(Event) if Eventtext~=nil then if self:_MatchTag(Eventtext) then local matchtable = self:_MatchKeywords(Eventtext) - self:MarkChanged(Eventtext,matchtable,coord) + self:MarkChanged(Eventtext,matchtable,coord,Event.idx) end end elseif Event.id==world.event.S_EVENT_MARK_REMOVED then From 54fb9deb3ed8512cbb3f43266114dec9139dbd1a Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 12 Feb 2023 11:38:46 +0100 Subject: [PATCH 17/17] Polygon and line drawings --- Moose Development/Moose/Core/Database.lua | 56 +++++++++++++++++++++-- Moose Development/Moose/Core/Pathline.lua | 25 ++++++++-- 2 files changed, 74 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index a95504bb1..ce668fc57 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -416,10 +416,14 @@ do -- Zones and Pathlines for objectID, objectData in pairs(layerData.objects or {}) do -- Check for polygon which has at least 4 points (we would need 3 but the origin seems to be there twice) - if objectData.polygonMode and objectData.polygonMode=="free" and objectData.points and #objectData.points>=4 then + if objectData.polygonMode and (objectData.polygonMode=="free") and objectData.points and #objectData.points>=4 then + + --- + -- Drawing: Polygon free + --- -- Name of the zone. - local ZoneName=objectData.name or "Unknown Drawing" + local ZoneName=objectData.name or "Unknown free Polygon Drawing" -- Reference point. All other points need to be translated by this. local vec2={x=objectData.mapX, y=objectData.mapY} @@ -442,7 +446,7 @@ do -- Zones and Pathlines table.remove(points, #points) -- Debug output - self:I(string.format("Register ZONE: %s (Polygon drawing with %d vertices)", ZoneName, #points)) + self:I(string.format("Register ZONE: %s (Polygon (free) drawing with %d vertices)", ZoneName, #points)) -- Create new polygon zone. local Zone=ZONE_POLYGON:NewFromPointsArray(ZoneName, points) @@ -455,8 +459,53 @@ do -- Zones and Pathlines -- Add zone. self:AddZone(ZoneName, Zone) + + -- Check for polygon which has at least 4 points (we would need 3 but the origin seems to be there twice) + elseif objectData.polygonMode and objectData.polygonMode=="rect" then + + --- + -- Drawing: Polygon rect + --- + + -- Name of the zone. + local ZoneName=objectData.name or "Unknown rect Polygon Drawing" + -- Reference point (center of the rectangle). + local vec2={x=objectData.mapX, y=objectData.mapY} + + -- For a rectangular polygon drawing, we have the width (y) and height (x). + local w=objectData.width + local h=objectData.height + + -- Create points from center using with and height (width for y and height for x is a bit confusing, but this is how ED implemented it). + local points={} + points[1]={x=vec2.x-h/2, y=vec2.y+w/2} --Upper left + points[2]={x=vec2.x+h/2, y=vec2.y+w/2} --Upper right + points[3]={x=vec2.x+h/2, y=vec2.y-w/2} --Lower right + points[4]={x=vec2.x-h/2, y=vec2.y-w/2} --Lower left + + --local coord=COORDINATE:NewFromVec2(vec2):MarkToAll("MapX, MapY") + + -- Debug output + self:I(string.format("Register ZONE: %s (Polygon (rect) drawing with %d vertices)", ZoneName, #points)) + + -- Create new polygon zone. + local Zone=ZONE_POLYGON:NewFromPointsArray(ZoneName, points) + + -- Set color. + Zone:SetColor({1, 0, 0}, 0.15) + + -- Store in DB. + self.ZONENAMES[ZoneName] = ZoneName + + -- Add zone. + self:AddZone(ZoneName, Zone) + elseif objectData.lineMode and (objectData.lineMode=="segments" or objectData.lineMode=="segment" or objectData.lineMode=="free") and objectData.points and #objectData.points>=2 then + + --- + -- Drawing: Line (segments, segment or free) + --- -- Name of the zone. local Name=objectData.name or "Unknown Line Drawing" @@ -472,7 +521,6 @@ do -- Zones and Pathlines local point=_point --DCS#Vec2 points[i]=UTILS.Vec2Add(point, vec2) end - -- Debug output self:I(string.format("Register PATHLINE: %s (Line drawing with %d points)", Name, #points)) diff --git a/Moose Development/Moose/Core/Pathline.lua b/Moose Development/Moose/Core/Pathline.lua index 090da3931..a71da23a3 100644 --- a/Moose Development/Moose/Core/Pathline.lua +++ b/Moose Development/Moose/Core/Pathline.lua @@ -23,17 +23,36 @@ -- @field #table points List of 3D points defining the path. -- @extends Core.Base#BASE ---- *When nothing goes right... Go left!* +--- *The shortest distance between two points is a straight line.* -- Archimedes -- -- === -- -- # The PATHLINE Concept -- --- List of points defining a path from A to B. +-- List of points defining a path from A to B. The pathline can consist of multiple points. Each point holds the information of its position, the surface type, the land height +-- and the water depth (if over sea). -- -- Line drawings created in the mission editor are automatically registered as pathlines and stored in the MOOSE database. -- They can be accessed with the @{#PATHLINE.FindByName) function. -- +-- # Constructor +-- +-- The @{PATHLINE.New) function creates a new PATHLINE object. This does not hold any points. Points can be added with the @{#PATHLINE.AddPointFromVec2} and @{#PATHLINE.AddPointFromVec3} +-- +-- For a given table of 2D or 3D positions, a new PATHLINE object can be created with the @{#PATHLINE.NewFromVec2Array} or @{#PATHLINE.NewFromVec3Array}, respectively. +-- +-- # Line Drawings +-- +-- The most convenient way to create a pathline is the draw panel feature in the DCS mission editor. You can select "Line" and then "Segments", "Segment" or "Free" to draw your lines. +-- These line drawings are then automatically added to the MOOSE database as PATHLINE objects and can be retrieved with the @{#PATHLINE.FindByName) function, where the name is the one +-- 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 +-- water depth. +-- +-- To remove the marks, use @{#PATHLINE.MarkPoints}(`false`). -- -- @field #PATHLINE PATHLINE = { @@ -54,7 +73,7 @@ PATHLINE = { --- PATHLINE class version. -- @field #string version -PATHLINE.version="0.0.1" +PATHLINE.version="0.1.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list