diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index a0f4fc877..adbebc43b 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -594,7 +594,7 @@ do -- COORDINATE --- Scan/find SCENERY objects within a certain radius around the coordinate using the world.searchObjects() DCS API function. -- @param #COORDINATE self -- @param #number radius (Optional) Scan radius in meters. Default 100 m. - -- @return table Set of scenery objects. + -- @return table Table of SCENERY objects. function COORDINATE:ScanScenery(radius) local _,_,_,_,_,scenerys=self:ScanObjects(radius, false, false, true) @@ -1519,7 +1519,7 @@ do -- COORDINATE -- @param #number Coalition (Optional) Coalition of the airbase. -- @return Wrapper.Airbase#AIRBASE Closest Airbase to the given coordinate. -- @return #number Distance to the closest airbase in meters. - function COORDINATE:GetClosestAirbase2(Category, Coalition) + function COORDINATE:GetClosestAirbase(Category, Coalition) -- Get all airbases of the map. local airbases=AIRBASE.GetAllAirbases(Coalition) @@ -1553,34 +1553,15 @@ do -- COORDINATE return closest,distmin end - --- Gets the nearest airbase with respect to the current coordinates. + --- [kept for downwards compatibility only] Gets the nearest airbase with respect to the current coordinates. -- @param #COORDINATE self -- @param #number Category (Optional) Category of the airbase. Enumerator of @{Wrapper.Airbase#AIRBASE.Category}. -- @param #number Coalition (Optional) Coalition of the airbase. -- @return Wrapper.Airbase#AIRBASE Closest Airbase to the given coordinate. -- @return #number Distance to the closest airbase in meters. - function COORDINATE:GetClosestAirbase(Category, Coalition) - - local a=self:GetVec3() - - local distmin=math.huge - local airbase=nil - for DCSairbaseID, DCSairbase in pairs(world.getAirbases(Coalition)) do - local b=DCSairbase:getPoint() - - local c=UTILS.VecSubstract(a,b) - local dist=UTILS.VecNorm(c) - - --env.info(string.format("Airbase %s dist=%d category=%d", DCSairbase:getName(), dist, DCSairbase:getCategory())) - - if dist x2 ) and Coordinate.x or x2 + y1 = ( Coordinate.y < y1 ) and Coordinate.y or y1 + y2 = ( Coordinate.y > y2 ) and Coordinate.y or y2 + z1 = ( Coordinate.y < z1 ) and Coordinate.z or z1 + z2 = ( Coordinate.y > z2 ) and Coordinate.z or z2 + + end + + Coordinate.x = ( x2 - x1 ) / 2 + x1 + Coordinate.y = ( y2 - y1 ) / 2 + y1 + Coordinate.z = ( z2 - z1 ) / 2 + z1 + + self:F( { Coordinate = Coordinate } ) + return Coordinate + + end + + --- + -- @param #SET_SCENERY self + -- @param Wrapper.Scenery#SCENERY MScenery + -- @return #SET_SCENERY self + function SET_SCENERY:IsIncludeObject( MScenery ) + self:F2( MScenery ) + return true + end +end diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 296dbb3ba..d19725683 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -631,8 +631,9 @@ ZONE_RADIUS = { -- @param #string ZoneName Name of the zone. -- @param DCS#Vec2 Vec2 The location of the zone. -- @param DCS#Distance Radius The radius of the zone. +-- @param DCS#Boolean DoNotRegisterZone Determins if the Zone should not be registered in the _Database Table. Default=false -- @return #ZONE_RADIUS self -function ZONE_RADIUS:New( ZoneName, Vec2, Radius ) +function ZONE_RADIUS:New( ZoneName, Vec2, Radius, DoNotRegisterZone ) -- Inherit ZONE_BASE. local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) -- #ZONE_RADIUS @@ -641,6 +642,10 @@ function ZONE_RADIUS:New( ZoneName, Vec2, Radius ) self.Radius = Radius self.Vec2 = Vec2 + if not DoNotRegisterZone then + _EVENTDISPATCHER:CreateEventNewZone(self) + end + --self.Coordinate=COORDINATE:NewFromVec2(Vec2) return self @@ -1538,7 +1543,7 @@ function ZONE:New( ZoneName ) end -- Create a new ZONE_RADIUS. - local self=BASE:Inherit( self, ZONE_RADIUS:New(ZoneName, {x=Zone.point.x, y=Zone.point.z}, Zone.radius)) + local self=BASE:Inherit( self, ZONE_RADIUS:New(ZoneName, {x=Zone.point.x, y=Zone.point.z}, Zone.radius, true)) self:F(ZoneName) -- Color of zone. @@ -1605,7 +1610,7 @@ function ZONE_UNIT:New( ZoneName, ZoneUNIT, Radius, Offset) self.relative_to_unit = Offset.relative_to_unit or false end - local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneUNIT:GetVec2(), Radius ) ) + local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneUNIT:GetVec2(), Radius, true ) ) self:F( { ZoneName, ZoneUNIT:GetVec2(), Radius } ) @@ -1721,7 +1726,7 @@ ZONE_GROUP = { -- @param DCS#Distance Radius The radius of the zone. -- @return #ZONE_GROUP self function ZONE_GROUP:New( ZoneName, ZoneGROUP, Radius ) - local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneGROUP:GetVec2(), Radius ) ) + local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneGROUP:GetVec2(), Radius, true ) ) self:F( { ZoneName, ZoneGROUP:GetVec2(), Radius } ) self._.ZoneGROUP = ZoneGROUP @@ -2321,7 +2326,7 @@ function ZONE_POLYGON_BASE:Boundary(Coalition, Color, Radius, Alpha, Segments, C for Segment = 0, Segments do local PointX = self._.Polygon[i].x + ( Segment * DeltaX / Segments ) local PointY = self._.Polygon[i].y + ( Segment * DeltaY / Segments ) - ZONE_RADIUS:New( "Zone", {x = PointX, y = PointY}, Radius ):DrawZone(Coalition, Color, 1, Color, Alpha, nil, true) + ZONE_RADIUS:New( "Zone", {x = PointX, y = PointY}, Radius, true ):DrawZone(Coalition, Color, 1, Color, Alpha, nil, false) end end j = i @@ -2948,7 +2953,7 @@ do -- ZONE_AIRBASE local Airbase = AIRBASE:FindByName( AirbaseName ) - local self = BASE:Inherit( self, ZONE_RADIUS:New( AirbaseName, Airbase:GetVec2(), Radius ) ) + local self = BASE:Inherit( self, ZONE_RADIUS:New( AirbaseName, Airbase:GetVec2(), Radius, true ) ) self._.ZoneAirbase = Airbase self._.ZoneVec2Cache = self._.ZoneAirbase:GetVec2() diff --git a/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua b/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua index e8abc02dd..78f499d30 100644 --- a/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua +++ b/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua @@ -68,9 +68,9 @@ do -- ZONE_CAPTURE_COALITION -- -- In order to use ZONE_CAPTURE_COALITION, you need to: -- - -- * Create a @{Zone} object from one of the ZONE_ classes. - -- Note that ZONE_POLYGON_ classes are not yet functional. - -- The only functional ZONE_ classses are those derived from a ZONE_RADIUS. + -- * Create a @{Zone} object from one of the ZONE_ classes. + -- The functional ZONE_ classses are those derived from a ZONE_RADIUS. + -- In order to use a ZONE_POLYGON, hand over the **GROUP name** of a late activated group forming a polygon with it's waypoints. -- * Set the state of the zone. Most of the time, Guarded would be the initial state. -- * Start the zone capturing **monitoring process**. -- This will check the presence of friendly and/or enemy units within the zone and will transition the state of the zone when the tactical situation changed. diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index c8fbb1524..0dae8cb06 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -422,6 +422,44 @@ function AIRWING:SetPayloadAmount(Payload, Navailable) return self end +--- Increase or decrease the amount of available payloads. Unlimited playloads first need to be set to a limited number with the `SetPayloadAmount` function. +-- @param #AIRWING self +-- @param #AIRWING.Payload Payload The payload table created by the `:NewPayload` function. +-- @param #number N Number of payloads to be added. Use negative number to decrease amount. Default 1. +-- @return #AIRWING self +function AIRWING:IncreasePayloadAmount(Payload, N) + + N=N or 1 + + if Payload and Payload.navail>=0 then + + -- Increase/decrease amount. + Payload.navail=Payload.navail+N + + -- Ensure playload does not drop below 0. + Payload.navail=math.max(Payload.navail, 0) + + end + + return self +end + +--- Get amount of payloads available for a given playload. +-- @param #AIRWING self +-- @param #AIRWING.Payload Payload The payload table created by the `:NewPayload` function. +-- @return #number Number of payloads available. Unlimited payloads will return -1. +function AIRWING:GetPayloadAmount(Payload) + return Payload.navail +end + +--- Get capabilities of a given playload. +-- @param #AIRWING self +-- @param #AIRWING.Payload Payload The payload data table. +-- @return #table Capabilities. +function AIRWING:GetPayloadCapabilities(Payload) + return Payload.capabilities +end + --- Add a mission capability to an existing payload. -- @param #AIRWING self -- @param #AIRWING.Payload Payload The payload table to which the capability should be added. diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index 30a84aa53..81c850308 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -497,7 +497,7 @@ do -- @field #AWACS AWACS = { ClassName = "AWACS", -- #string - version = "0.2.44", -- #string + version = "0.2.45", -- #string lid = "", -- #string coalition = coalition.side.BLUE, -- #number coalitiontxt = "blue", -- #string @@ -3515,7 +3515,7 @@ function AWACS:_CheckInAI(FlightGroup,Group,AuftragsNr) text = string.format("%s. %s. %s. %s",managedgroup.CallSign,self.callsigntxt,alpha,alphacheckbulls) self:__CheckedIn(1,managedgroup.GID) - local AW = FlightGroup:GetAirWing() + local AW = FlightGroup.legion if AW.HasOwnStation then self:__AssignAnchor(5,managedgroup.GID,AW.HasOwnStation,AW.StationName) else diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 35fd9794e..511fc7929 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -30,7 +30,7 @@ -- @module Ops.CSAR -- @image OPS_CSAR.jpg --- Date: June 2022 +-- Date: October 2022 ------------------------------------------------------------------------- --- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM @@ -270,7 +270,7 @@ CSAR.AircraftType["Bronco-OV-10A"] = 2 --- CSAR class version. -- @field #string version -CSAR.version="1.0.11" +CSAR.version="1.0.15" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -613,6 +613,19 @@ function CSAR:_DoubleEjection(_unitname) return false end +--- (User) Add a PLAYERTASK - FSM events will check success +-- @param #CSAR self +-- @param Ops.PlayerTask#PLAYERTASK PlayerTask +-- @return #CSAR self +function CSAR:AddPlayerTask(PlayerTask) + self:T(self.lid .. " AddPlayerTask") + if not self.PlayerTaskQueue then + self.PlayerTaskQueue = FIFO:New() + end + self.PlayerTaskQueue:Push(PlayerTask,PlayerTask.PlayerTaskNr) + return self +end + --- (Internal) Spawn a downed pilot -- @param #CSAR self -- @param #number country Country for template. @@ -1197,6 +1210,38 @@ function CSAR:_RemoveNameFromDownedPilots(name,force) return found end +--- [User] Set callsign options for TTS output. See @{Wrapper.Group#GROUP.GetCustomCallSign}() on how to set customized callsigns. +-- @param #CSAR self +-- @param #boolean ShortCallsign If true, only call out the major flight number +-- @param #boolean Keepnumber If true, keep the **customized callsign** in the #GROUP name for players as-is, no amendments or numbers. +-- @param #table CallsignTranslations (optional) Table to translate between DCS standard callsigns and bespoke ones. Does not apply if using customized +-- callsigns from playername or group name. +-- @return #CSAR self +function CSAR:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations) + if not ShortCallsign or ShortCallsign == false then + self.ShortCallsign = false + else + self.ShortCallsign = true + end + self.Keepnumber = Keepnumber or false + self.CallsignTranslations = CallsignTranslations + return self +end + +--- (Internal) Check if a name is in downed pilot table and remove it. +-- @param #CSAR self +-- @param #string UnitName +-- @return #string CallSign +function CSAR:_GetCustomCallSign(UnitName) + local callsign = Unitname + local unit = UNIT:FindByName(UnitName) + if unit and unit:IsAlive() then + local group = unit:GetGroup() + callsign = group:GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + end + return callsign +end + --- (Internal) Check state of wounded group. -- @param #CSAR self -- @param #string heliname heliname @@ -1253,9 +1298,9 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) local dist = UTILS.MetersToNM(self.autosmokedistance) disttext = string.format("%.0fnm",dist) end - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Finally, that is music in my ears!\nI'll pop a smoke when you are %s away.\nLand or hover by the smoke.", _heliName, _pilotName, disttext), self.messageTime,false,true) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Finally, that is music in my ears!\nI'll pop a smoke when you are %s away.\nLand or hover by the smoke.", self:_GetCustomCallSign(_heliName), _pilotName, disttext), self.messageTime,false,true) else - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Finally, that is music in my ears!\nRequest a flare or smoke if you need.", _heliName, _pilotName), self.messageTime,false,true) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Finally, that is music in my ears!\nRequest a flare or smoke if you need.", self:_GetCustomCallSign(_heliName), _pilotName), self.messageTime,false,true) end --mark as shown for THIS heli and THIS group self.heliVisibleMessage[_lookupKeyHeli] = true @@ -1319,7 +1364,7 @@ function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupNam _maxUnits = self.max_units end if _unitsInHelicopter + 1 > _maxUnits then - self:_DisplayMessageToSAR(_heliUnit, string.format("%s, %s. We\'re already crammed with %d guys! Sorry!", _pilotName, _heliName, _unitsInHelicopter, _unitsInHelicopter), self.messageTime,false,false,true) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s, %s. We\'re already crammed with %d guys! Sorry!", _pilotName, self:_GetCustomCallSign(_heliName), _unitsInHelicopter, _unitsInHelicopter), self.messageTime,false,false,true) return self end @@ -1337,13 +1382,29 @@ function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupNam _woundedGroup:Destroy(false) self:_RemoveNameFromDownedPilots(_woundedGroupName,true) - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s I\'m in! Get to the MASH ASAP! ", _heliName, _pilotName), self.messageTime,true,true) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s I\'m in! Get to the MASH ASAP! ", self:_GetCustomCallSign(_heliName), _pilotName), self.messageTime,true,true) + + self:_UpdateUnitCargoMass(_heliName) self:__Boarded(5,_heliName,_woundedGroupName,grouptable.desc) return self end +--- (Internal) Function to calculate and set Unit internal cargo mass +-- @param #CSAR self +-- @param #string _heliName Unit name +-- @return #CSAR self +function CSAR:_UpdateUnitCargoMass(_heliName) + self:T(self.lid .. " _UpdateUnitCargoMass") + local calculatedMass = self:_PilotsOnboard(_heliName)*80 + local Unit = UNIT:FindByName(_heliName) + if Unit then + Unit:SetUnitInternalCargo(calculatedMass) + end + return self +end + --- (Internal) Move group to destination. -- @param #CSAR self -- @param Wrapper.Group#GROUP _leader @@ -1358,7 +1419,6 @@ function CSAR:_OrderGroupToMoveToPoint(_leader, _destination) return self end - --- (internal) Function to check if the heli door(s) are open. Thanks to Shadowze. -- @param #CSAR self -- @param #string unit_name Name of unit. @@ -1392,9 +1452,9 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG if self.heliCloseMessage[_lookupKeyHeli] == nil then if self.autosmoke == true then - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land or hover at the smoke.", _heliName, _pilotName), self.messageTime,false,true) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land or hover at the smoke.", self:_GetCustomCallSign(_heliName), _pilotName), self.messageTime,false,true) else - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land in a safe place, I will go there ", _heliName, _pilotName), self.messageTime,false,true) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land in a safe place, I will go there ", self:_GetCustomCallSign(_heliName), _pilotName), self.messageTime,false,true) end self.heliCloseMessage[_lookupKeyHeli] = true end @@ -1447,7 +1507,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG end if _heliUnit:InAir() and _unitsInHelicopter + 1 <= _maxUnits then - -- TODO - make variable + -- DONE - make variable if _distance < self.rescuehoverdistance then --check height! @@ -1455,7 +1515,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG if leaderheight < 0 then leaderheight = 0 end local _height = _heliUnit:GetHeight() - leaderheight - -- TODO - make variable + -- DONE - make variable if _height <= self.rescuehoverheight then local _time = self.hoverStatus[_lookupKeyHeli] @@ -1561,9 +1621,12 @@ function CSAR:_RescuePilots(_heliUnit) self.inTransitGroups[_heliName] = nil - local _txt = string.format("%s: The %d pilot(s) have been taken to the\nmedical clinic. Good job!", _heliName, PilotsSaved) + local _txt = string.format("%s: The %d pilot(s) have been taken to the\nmedical clinic. Good job!", self:_GetCustomCallSign(_heliName), PilotsSaved) self:_DisplayMessageToSAR(_heliUnit, _txt, self.messageTime) + + self:_UpdateUnitCargoMass(_heliName) + -- trigger event self:__Rescued(-1,_heliUnit,_heliName, PilotsSaved) return self @@ -1597,7 +1660,7 @@ function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak, _overrid local _clear = _clear or nil local _time = _time or self.messageTime if _override or not self.suppressmessages then - local m = MESSAGE:New(_text,_time,"Info",_clear):ToGroup(group) + local m = MESSAGE:New(_text,_time,"CSAR",_clear):ToGroup(group) end -- integrate SRS if _speak and self.useSRS then @@ -1746,7 +1809,7 @@ function CSAR:_SignalFlare(_unitName) else _distance = string.format("%.1fkm",_closest.distance) end - local _msg = string.format("%s - Popping signal flare at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance) + local _msg = string.format("%s - Popping signal flare at your %s o\'clock. Distance %s", self:_GetCustomCallSign(_unitName), _clockDir, _distance) self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true) local _coord = _closest.pilot:GetCoordinate() @@ -1800,7 +1863,7 @@ function CSAR:_Reqsmoke( _unitName ) else _distance = string.format("%.1fkm",_closest.distance/1000) end - local _msg = string.format("%s - Popping smoke at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance) + local _msg = string.format("%s - Popping smoke at your %s o\'clock. Distance %s", self:_GetCustomCallSign(_unitName), _clockDir, _distance) self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true) local _coord = _closest.pilot:GetCoordinate() local color = self.smokecolor @@ -1851,7 +1914,7 @@ function CSAR:_GetClosestMASH(_heli) if self.allowFARPRescue then local position = _heli:GetCoordinate() - local afb,distance = position:GetClosestAirbase2(nil,self.coalition) + local afb,distance = position:GetClosestAirbase(nil,self.coalition) _shortestDistance = distance end @@ -2004,13 +2067,17 @@ function CSAR:_GetClockDirection(_heli, _group) local DirectionVec3 = _playerPosition:GetDirectionVec3( _targetpostions ) local Angle = _playerPosition:GetAngleDegrees( DirectionVec3 ) self:T(self.lid .. " _GetClockDirection"..tostring(Angle).." "..tostring(_heading)) - local clock = 12 - if _heading then - local Aspect = Angle - _heading - if Aspect == 0 then Aspect = 360 end - clock = math.abs(UTILS.Round((Aspect / 30),0)) - if clock == 0 then clock = 12 end - end + local hours = 0 + local clock = 12 + if _heading and Angle then + clock = 12 + --if angle == 0 then angle = 360 end + clock = _heading-Angle + hours = (clock/30)*-1 + clock = 12+hours + clock = UTILS.Round(clock,0) + if clock > 12 then clock = clock-12 end + end return clock end @@ -2282,6 +2349,29 @@ end function CSAR:onbeforeBoarded(From, Event, To, Heliname, Woundedgroupname) self:T({From, Event, To, Heliname, Woundedgroupname}) self:_ScheduledSARFlight(Heliname,Woundedgroupname) + local Unit = UNIT:FindByName(Heliname) + if Unit and Unit:IsPlayer() and self.PlayerTaskQueue then + local playername = Unit:GetPlayerName() + local dropcoord = Unit:GetCoordinate() or COORDINATE:New(0,0,0) + local dropvec2 = dropcoord:GetVec2() + self.PlayerTaskQueue:ForEach( + function (Task) + local task = Task -- Ops.PlayerTask#PLAYERTASK + local subtype = task:GetSubType() + -- right subtype? + if Event == subtype and not task:IsDone() then + local targetzone = task.Target:GetObject() -- Core.Zone#ZONE should be a zone in this case .... + if (targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE") and targetzone:IsVec2InZone(dropvec2)) + or (string.find(task.CSARPilotName,Woundedgroupname)) then + if task.Clients:HasUniqueID(playername) then + -- success + task:__Success(-1) + end + end + end + end + ) + end return self end @@ -2311,6 +2401,23 @@ function CSAR:onbeforeRescued(From, Event, To, HeliUnit, HeliName, PilotsSaved) self:T({From, Event, To, HeliName, HeliUnit}) self.rescues = self.rescues + 1 self.rescuedpilots = self.rescuedpilots + PilotsSaved + local Unit = HeliUnit or UNIT:FindByName(HeliName) + if Unit and Unit:IsPlayer() and self.PlayerTaskQueue then + local playername = Unit:GetPlayerName() + self.PlayerTaskQueue:ForEach( + function (Task) + local task = Task -- Ops.PlayerTask#PLAYERTASK + local subtype = task:GetSubType() + -- right subtype? + if Event == subtype and not task:IsDone() then + if task.Clients:HasUniqueID(playername) then + -- success + task:__Success(-1) + end + end + end + ) + end return self end diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 6ec93761c..6dea1271c 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -22,8 +22,7 @@ -- @module Ops.CTLD -- @image OPS_CTLD.jpg --- Date: Feb 2022 --- Last Update Sep 2022 +-- Last Update October 2022 do @@ -288,8 +287,8 @@ CTLD_ENGINEERING = { end -do - + +do ------------------------------------------------------ --- **CTLD_CARGO** class, extends Core.Base#BASE -- @type CTLD_CARGO @@ -308,9 +307,8 @@ do -- @field #string Subcategory Sub-category name. -- @extends Core.Base#BASE - --- --- @field CTLD_CARGO +-- @field #CTLD_CARGO CTLD_CARGO CTLD_CARGO = { ClassName = "CTLD_CARGO", ID = 0, @@ -343,7 +341,7 @@ CTLD_CARGO = { CRATE = "Crate", -- #string crate REPAIR = "Repair", -- #string repair ENGINEERS = "Engineers", -- #string engineers - STATIC = "Static", -- #string engineers + STATIC = "Static", -- #string statics } --- Function to create new CTLD_CARGO object. @@ -574,6 +572,10 @@ CTLD_CARGO = { end do +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +-- TODO CTLD +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + ------------------------------------------------------------------------- --- **CTLD** class, extends Core.Base#BASE, Core.Fsm#FSM -- @type CTLD @@ -824,6 +826,8 @@ do -- -- To award player with points, using the SCORING Class (SCORING: my_Scoring, CTLD: CTLD_Cargotransport) -- +-- my_scoring = SCORING:New("Combat Transport") +-- -- function CTLD_Cargotransport:OnAfterCratesDropped(From, Event, To, Group, Unit, Cargotable) -- local points = 10 -- if Unit then @@ -901,7 +905,7 @@ do -- -- my_ctld.useprefix = true -- this is true by default and MUST BE ON. -- --- ### 5.2 Integrate Hercules ground crew (F8 Menu) loadable objects (alternative method) +-- ### 5.2 Integrate Hercules ground crew (F8 Menu) loadable objects (alternative method, use either the above OR this method, NOT both!) -- -- Integrate to your CTLD instance like so, where `my_ctld` is a previously created CTLD instance: -- @@ -928,6 +932,8 @@ do -- The script works on the EVENTS.Shot trigger, which is used by the mod when you **drop cargo from the Hercules while flying**. Unloading on the ground does -- not achieve anything here. If you just want to unload on the ground, use the normal Moose CTLD (see 5.1). -- +-- DO NOT use the "splash damage" script together with this method! Your cargo will explode on the ground! +-- -- There are two ways of airdropping: -- -- 1) Very low and very slow (>5m and <10m AGL) - here you can drop stuff which has "Skid" at the end of the cargo name (loaded via F8 Ground Crew menu) @@ -1068,7 +1074,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.0.11" +CTLD.version="1.0.16" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1476,6 +1482,19 @@ function CTLD:SetTroopDropZoneRadius(Radius) return self end +--- (User) Add a PLAYERTASK - FSM events will check success +-- @param #CTLD self +-- @param Ops.PlayerTask#PLAYERTASK PlayerTask +-- @return #CTLD self +function CTLD:AddPlayerTask(PlayerTask) + self:T(self.lid .. " AddPlayerTask") + if not self.PlayerTaskQueue then + self.PlayerTaskQueue = FIFO:New() + end + self.PlayerTaskQueue:Push(PlayerTask,PlayerTask.PlayerTaskNr) + return self +end + --- (Internal) Event handler function -- @param #CTLD self -- @param Core.Event#EVENTDATA EventData @@ -3386,7 +3405,13 @@ end -- @return #CTLD self function CTLD:AddCTLDZone(Name, Type, Color, Active, HasBeacon, Shiplength, Shipwidth) self:T(self.lid .. " AddCTLDZone") - + + local zone = ZONE:FindByName(Name) + if not zone then + self:E(self.lid.."**** Zone does not exist: "..Name) + return self + end + local ctldzone = {} -- #CTLD.CargoZone ctldzone.active = Active or false ctldzone.color = Color or SMOKECOLOR.Red @@ -3632,9 +3657,10 @@ function CTLD:IsUnitInZone(Unit,Zonetype) local zoneret = nil local zonewret = nil local zonenameret = nil + local unitcoord = Unit:GetCoordinate() + local unitVec2 = unitcoord:GetVec2() for _,_cargozone in pairs(zonetable) do local czone = _cargozone -- #CTLD.CargoZone - local unitcoord = Unit:GetCoordinate() local zonename = czone.name local active = czone.active local color = czone.color @@ -3651,17 +3677,17 @@ function CTLD:IsUnitInZone(Unit,Zonetype) zone = ZONE:FindByName(zonename) self:T("Checking Zone: "..zonename) zonecoord = zone:GetCoordinate() - zoneradius = zone:GetRadius() + zoneradius = 1500 zonewidth = zoneradius elseif AIRBASE:FindByName(zonename) then zone = AIRBASE:FindByName(zonename):GetZone() self:T("Checking Zone: "..zonename) zonecoord = zone:GetCoordinate() - zoneradius = zone:GetRadius() + zoneradius = 2500 zonewidth = zoneradius end local distance = self:_GetDistance(zonecoord,unitcoord) - if distance <= zoneradius and active then + if zone:IsVec2InZone(unitVec2) and active then outcome = true end if maxdist > distance then @@ -4244,7 +4270,7 @@ end end ------------------------------------------------------------------- --- FSM functions +-- TODO FSM functions ------------------------------------------------------------------- --- (Internal) FSM Function onafterStart. @@ -4417,6 +4443,27 @@ end -- @return #CTLD self function CTLD:onbeforeTroopsDeployed(From, Event, To, Group, Unit, Troops) self:T({From, Event, To}) + if Unit and Unit:IsPlayer() and self.PlayerTaskQueue then + local playername = Unit:GetPlayerName() + local dropcoord = Troops:GetCoordinate() or COORDINATE:New(0,0,0) + local dropvec2 = dropcoord:GetVec2() + self.PlayerTaskQueue:ForEach( + function (Task) + local task = Task -- Ops.PlayerTask#PLAYERTASK + local subtype = task:GetSubType() + -- right subtype? + if Event == subtype and not task:IsDone() then + local targetzone = task.Target:GetObject() -- Core.Zone#ZONE should be a zone in this case .... + if targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE") and targetzone:IsVec2InZone(dropvec2) then + if task.Clients:HasUniqueID(playername) then + -- success + task:__Success(-1) + end + end + end + end + ) + end return self end @@ -4444,10 +4491,31 @@ end -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB build. -- @return #CTLD self function CTLD:onbeforeCratesBuild(From, Event, To, Group, Unit, Vehicle) - self:T({From, Event, To}) + self:I({From, Event, To}) + if Unit and Unit:IsPlayer() and self.PlayerTaskQueue then + local playername = Unit:GetPlayerName() + local dropcoord = Vehicle:GetCoordinate() or COORDINATE:New(0,0,0) + local dropvec2 = dropcoord:GetVec2() + self.PlayerTaskQueue:ForEach( + function (Task) + local task = Task -- Ops.PlayerTask#PLAYERTASK + local subtype = task:GetSubType() + -- right subtype? + if Event == subtype and not task:IsDone() then + local targetzone = task.Target:GetObject() -- Core.Zone#ZONE should be a zone in this case .... + if targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE") and targetzone:IsVec2InZone(dropvec2) then + if task.Clients:HasUniqueID(playername) then + -- success + task:__Success(-1) + end + end + end + end + ) + end return self end - + --- (Internal) FSM Function onbeforeTroopsRTB. -- @param #CTLD self -- @param #string From State. @@ -4802,7 +4870,9 @@ end -- end do do --- **Hercules Cargo AIR Drop Events** by Anubis Yinepu -- Moose CTLD OO refactoring by Applevangelist --- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +-- TODO CTLD_HERCULES +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ -- This script will only work for the Herculus mod by Anubis, and only for **Air Dropping** cargo from the Hercules. -- Use the standard Moose CTLD if you want to unload on the ground. -- Payloads carried by pylons 11, 12 and 13 need to be declared in the Herculus_Loadout.lua file @@ -4932,13 +5002,21 @@ CTLD_HERCULES.Types = { -- -- Expected template names are the ones in the rounded brackets. -- --- HINTS +-- ### HINTS -- -- The script works on the EVENTS.Shot trigger, which is used by the mod when you **drop cargo from the Hercules while flying**. Unloading on the ground does -- not achieve anything here. If you just want to unload on the ground, use the normal Moose CTLD. +-- **Do not use** the **splash damage** script together with this, your cargo will just explode when reaching the ground! +-- +-- ### Airdrops +-- -- There are two ways of airdropping: -- 1) Very low and very slow (>5m and <10m AGL) - here you can drop stuff which has "Skid" at the end of the cargo name (loaded via F8 Ground Crew menu) -- 2) Higher up and slow (>100m AGL) - here you can drop paratroopers and cargo which has "Air" at the end of the cargo name (loaded via F8 Ground Crew menu) +-- +-- ### General +-- +-- Use either this method to integrate the Hercules **or** the one from the "normal" CTLD. Never both! function CTLD_HERCULES:New(Coalition, Alias, CtldObject) -- Inherit everything from FSM class. local self=BASE:Inherit(self, FSM:New()) -- #CTLD_HERCULES diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 6c5d0bf2f..29271d3fd 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -210,7 +210,7 @@ FLIGHTGROUP.Players={} --- FLIGHTGROUP class version. -- @field #string version -FLIGHTGROUP.version="0.8.0" +FLIGHTGROUP.version="0.8.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -372,11 +372,34 @@ end --- Get airwing the flight group belongs to. -- @param #FLIGHTGROUP self --- @return Ops.AirWing#AIRWING The AIRWING object. -function FLIGHTGROUP:GetAirWing() +-- @return Ops.AirWing#AIRWING The AIRWING object (if any). +function FLIGHTGROUP:GetAirwing() return self.legion end +--- Get name of airwing the flight group belongs to. +-- @param #FLIGHTGROUP self +-- @return #string Name of the airwing or "None" if the flightgroup does not belong to any airwing. +function FLIGHTGROUP:GetAirwingName() + local name=self.legion and self.legion.alias or "None" + return name +end + +--- Get squadron the flight group belongs to. +-- @param #FLIGHTGROUP self +-- @return Ops.Squadron#SQUADRON The SQUADRON of this flightgroup or #nil if the flightgroup does not belong to any squadron. +function FLIGHTGROUP:GetSquadron() + return self.cohort +end + +--- Get squadron name the flight group belongs to. +-- @param #FLIGHTGROUP self +-- @return #string The squadron name or "None" if the flightgroup does not belon to any squadron. +function FLIGHTGROUP:GetSquadronName() + local name=self.cohort and self.cohort:GetName() or "None" + return name +end + --- Set if aircraft is VTOL capable. Unfortunately, there is no DCS way to determine this via scripting. -- @param #FLIGHTGROUP self -- @return #FLIGHTGROUP self @@ -836,6 +859,7 @@ end function FLIGHTGROUP:GetKills() return self.Nkills end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1935,7 +1959,7 @@ function FLIGHTGROUP:onafterArrived(From, Event, To) end --TODO: Check that current base is airwing base. - local airwing=self:GetAirWing() --airwing:GetAirbaseName()==self.currbase:GetName() + local airwing=self:GetAirwing() --airwing:GetAirbaseName()==self.currbase:GetName() -- Check what to do. if airwing and not (self:IsPickingup() or self:IsTransporting()) then diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 366d6a7d5..d2e0574e9 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -1854,7 +1854,7 @@ end --- Count total number of assets of the legion. -- @param #LEGION self --- @param #boolean InStock If true, only assets that are in the warehouse stock/inventory are counted. +-- @param #boolean InStock If `true`, only assets that are in the warehouse stock/inventory are counted. -- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types. -- @param #table Attributes (Optional) Count only assest that have a certain attribute(s), e.g. `WAREHOUSE.Attribute.AIR_BOMBER`. -- @return #number Amount of asset groups in stock. diff --git a/Moose Development/Moose/Ops/PlayerRecce.lua b/Moose Development/Moose/Ops/PlayerRecce.lua index fcef0bcb9..ef80816c8 100644 --- a/Moose Development/Moose/Ops/PlayerRecce.lua +++ b/Moose Development/Moose/Ops/PlayerRecce.lua @@ -1,10 +1,12 @@ ---- **Ops** - Allow a player in the Gazelle to detect, smoke, flare, lase and report ground units to others. +--- **Ops** - Allow a player in a helo like the Gazelle, KA-50 to recon and lase ground targets. -- -- ## Features: -- --- * Allow a player in the Gazelle to detect, smoke, flare, lase and report ground units to others. +-- * Allow a player in a helicopter to detect, smoke, flare, lase and report ground units to others. -- * Implements visual detection from the helo --- * Implements optical detection via the Vivianne system and lasing +-- * Implements optical detection via the Gazelle Vivianne system and lasing +-- * KA-50 BlackShark basic support +-- * Everyone else gets visual detection only -- * Upload target info to a PLAYERTASKCONTROLLER Instance -- -- === @@ -18,18 +20,18 @@ -- -- ### Authors: -- --- * Applevengelist (Design & Programming) +-- * Applevangelist (Design & Programming) -- -- === -- -- @module Ops.PlayerRecce --- @image @image Detection.JPG +-- @image Ops_PlayerRecce.png ------------------------------------------------------------------------------------------------------------------- -- PLAYERRECCE -- TODO: PLAYERRECCE -- DONE: No messages when no targets to flare or smoke --- TODO: Flare smoke group, not all targets +-- DONE: Smoke not all targets -- DONE: Messages to Attack Group, use client settings -- DONE: Lasing dist 8km -- DONE: Reference Point RP @@ -45,6 +47,9 @@ -- @field #string version -- @field #table ViewZone -- @field #table ViewZoneVisual +-- @field #table ViewZoneLaser +-- @field #table LaserFOV +-- @field #table LaserTarget -- @field Core.Set#SET_CLIENT PlayerSet -- @field #string Name -- @field #number Coalition @@ -72,6 +77,8 @@ -- @field Wrapper.Marker#MARKER RPMarker -- @field #number TForget -- @field Utilities.FiFo#FIFO TargetCache +-- @field #boolean smokeownposition +-- @field #table SmokeOwn -- @extends Core.Fsm#FSM --- @@ -82,9 +89,12 @@ -- -- # PLAYERRECCE -- --- * Allow a player in the Gazelle to detect, smoke, flare, lase and report ground units to others. +-- * Allow a player in a helicopter to detect, smoke, flare, lase and report ground units to others. -- * Implements visual detection from the helo --- * Implements optical detection via the Vivianne system and lasing +-- * Implements optical detection via the Gazelle Vivianne system and lasing +-- * KA-50 BlackShark basic support +-- * Everyone else gets visual detection only +-- * Upload target info to a PLAYERTASKCONTROLLER Instance -- -- If you have questions or suggestions, please visit the [MOOSE Discord](https://discord.gg/AeYAkHP) channel. -- @@ -94,11 +104,14 @@ PLAYERRECCE = { ClassName = "PLAYERRECCE", verbose = true, lid = nil, - version = "0.0.8", + version = "0.0.15", ViewZone = {}, ViewZoneVisual = {}, + ViewZoneLaser = {}, + LaserFOV = {}, + LaserTarget = {}, PlayerSet = nil, - debug = true, + debug = false, LaserSpots = {}, UnitLaserCodes = {}, LaserCodes = {}, @@ -117,6 +130,8 @@ PLAYERRECCE = { ReferencePoint = nil, TForget = 600, TargetCache = nil, + smokeownposition = true, + SmokeOwn = {}, } --- @@ -134,6 +149,7 @@ PLAYERRECCE.LaserRelativePos = { ["SA342Mistral"] = { x = 1.7, y = 1.2, z = 0 }, ["SA342Minigun"] = { x = 1.7, y = 1.2, z = 0 }, ["SA342L"] = { x = 1.7, y = 1.2, z = 0 }, + ["Ka-50"] = { x = 6.1, y = -0.85 , z = 0 } } --- @@ -144,6 +160,7 @@ PLAYERRECCE.MaxViewDistance = { ["SA342Mistral"] = 8000, ["SA342Minigun"] = 8000, ["SA342L"] = 8000, + ["Ka-50"] = 8000, } --- @@ -154,6 +171,7 @@ PLAYERRECCE.Cameraheight = { ["SA342Mistral"] = 2.85, ["SA342Minigun"] = 2.85, ["SA342L"] = 2.85, + ["Ka-50"] = 0.5, } --- @@ -164,6 +182,7 @@ PLAYERRECCE.CanLase = { ["SA342Mistral"] = true, ["SA342Minigun"] = false, -- no optics ["SA342L"] = true, + ["Ka-50"] = true, } --- @@ -188,7 +207,7 @@ PLAYERRECCE.FlareColor = { ["ownflare"] = FLARECOLOR.Green, } ---- Create and rund a new PlayerRecce instance. +--- Create and run a new PlayerRecce instance. -- @param #PLAYERRECCE self -- @param #string Name The name of this instance -- @param #number Coalition, e.g. coalition.side.BLUE @@ -224,10 +243,12 @@ function PLAYERRECCE:New(Name, Coalition, PlayerSet) self:AddTransition("*", "TargetDetected", "*") self:AddTransition("*", "TargetsSmoked", "*") self:AddTransition("*", "TargetsFlared", "*") + self:AddTransition("*", "Illumination", "*") self:AddTransition("*", "TargetLasing", "*") self:AddTransition("*", "TargetLOSLost", "*") self:AddTransition("*", "TargetReport", "*") self:AddTransition("*", "TargetReportSent", "*") + self:AddTransition("*", "Shack", "*") self:AddTransition("Running", "Stop", "Stopped") -- Player Events @@ -241,7 +262,7 @@ function PLAYERRECCE:New(Name, Coalition, PlayerSet) local starttime = math.random(5,10) self:__Status(-starttime) - self:I(self.lid..self.version.." Started.") + self:I(self.lid.." Started.") return self end @@ -265,14 +286,28 @@ function PLAYERRECCE:_EventHandler(EventData) self.ClientMenus[EventData.IniPlayerName] = nil self.LaserSpots[EventData.IniPlayerName] = nil self.OnStation[EventData.IniPlayerName] = false + self.LaserFOV[EventData.IniPlayerName] = nil + self.UnitLaserCodes[EventData.IniPlayerName] = nil + self.LaserTarget[EventData.IniPlayerName] = nil + self.AutoLase[EventData.IniPlayerName] = false + if self.ViewZone[EventData.IniPlayerName] then self.ViewZone[EventData.IniPlayerName]:UndrawZone() end + if self.ViewZoneLaser[EventData.IniPlayerName] then self.ViewZoneLaser[EventData.IniPlayerName]:UndrawZone() end + if self.ViewZoneVisual[EventData.IniPlayerName] then self.ViewZoneVisual[EventData.IniPlayerName]:UndrawZone() end end elseif EventData.id == EVENTS.PlayerEnterAircraft and EventData.IniCoalition == self.Coalition then - if EventData.IniPlayerName and EventData.IniGroup and self.UseSRS then + if EventData.IniPlayerName then self:T(self.lid.."Event for player: "..EventData.IniPlayerName) self.UnitLaserCodes[EventData.IniPlayerName] = 1688 self.ClientMenus[EventData.IniPlayerName] = nil self.LaserSpots[EventData.IniPlayerName] = nil self.OnStation[EventData.IniPlayerName] = false + self.LaserFOV[EventData.IniPlayerName] = nil + self.UnitLaserCodes[EventData.IniPlayerName] = nil + self.LaserTarget[EventData.IniPlayerName] = nil + self.AutoLase[EventData.IniPlayerName] = false + if self.ViewZone[EventData.IniPlayerName] then self.ViewZone[EventData.IniPlayerName]:UndrawZone() end + if self.ViewZoneLaser[EventData.IniPlayerName] then self.ViewZoneLaser[EventData.IniPlayerName]:UndrawZone() end + if self.ViewZoneVisual[EventData.IniPlayerName] then self.ViewZoneVisual[EventData.IniPlayerName]:UndrawZone() end self:_BuildMenus() end end @@ -303,10 +338,10 @@ function PLAYERRECCE:_GetClockDirection(unit, target) clock = UTILS.Round(clock,0) if clock > 12 then clock = clock-12 end end - if self.debug then - local text = string.format("Heading = %d, Angle = %d, Hours= %d, Clock = %d",_heading,Angle,hours,clock) - self:I(self.lid .. text) - end + --if self.debug then + --local text = string.format("Heading = %d, Angle = %d, Hours= %d, Clock = %d",_heading,Angle,hours,clock) + --self:I(self.lid .. text) + --end return clock end @@ -357,6 +392,29 @@ function PLAYERRECCE:SetAttackSet(AttackSet) return self end +---[Internal] Check Gazelle camera in on +-- @param #PLAYERRECCE self +-- @param Wrapper.Client#CLIENT client +-- @param #string playername +-- @return #boolen OnOff +function PLAYERRECCE:_CameraOn(client,playername) + local camera = true + local unit = client -- Wrapper.Unit#UNIT + if unit and unit:IsAlive() then + local typename = unit:GetTypeName() + if string.find(typename,"SA342") then + local dcsunit = Unit.getByName(client:GetName()) + local vivihorizontal = dcsunit:getDrawArgumentValue(215) or 0 -- (not in MiniGun) 1 to -1 -- zero is straight ahead, 1/-1 = 180 deg + if vivihorizontal < -0.7 or vivihorizontal > 0.7 then + camera = false + end + elseif typename == "Ka-50" then + camera = true + end + end + return camera +end + --- [Internal] Get the view parameters from a Gazelle camera -- @param #PLAYERRECCE self -- @param Wrapper.Unit#UNIT Gazelle @@ -389,7 +447,30 @@ function PLAYERRECCE:_GetGazelleVivianneSight(Gazelle) local heading = unit:GetHeading() local viviheading = (heading+horizontalview)%360 local maxview = self:_GetActualMaxLOSight(unit,viviheading, verticalview,vivioff) - return viviheading, verticalview, maxview, not vivioff + -- visual skew + local factor = 3.15 + self.GazelleViewFactors = { + [1]=1.18, + [2]=1.32, + [3]=1.46, + [4]=1.62, + [5]=1.77, + [6]=1.85, + [7]=2.05, + [8]=2.05, + [9]=2.3, + [10]=2.3, + [11]=2.27, + [12]=2.27, + [13]=2.43, + } + local lfac = UTILS.Round(maxview,-2) + if lfac <= 1300 then + factor = self.GazelleViewFactors[lfac/100] + maxview = math.ceil((maxview*factor)/100)*100 + end + if maxview > 8000 then maxview = 8000 end + return viviheading, verticalview,maxview, not vivioff end return 0,0,0,false end @@ -446,36 +527,48 @@ end -- @param #PLAYERRECCE self -- @param Wrapper.Unit#UNIT unit The unit which is looking -- @param #number vheading Heading where the unit or camera is looking --- @param #number vnod Nod down in degrees --- @param #number maxview Max line of sight, depending on height +-- @param #number minview Min line of sight - for lasing +-- @param #number maxview Max line of sight -- @param #number angle Angle left/right to be added to heading to form a triangle -- @param #boolean camon Camera is switched on --- @param #boolean draw Draw the zone on the F10 map +-- @param #boolean laser Zone is for lasing -- @return Core.Zone#ZONE_POLYGON ViewZone or nil if camera is off -function PLAYERRECCE:_GetViewZone(unit, vheading, vnod, maxview, angle, camon, draw) +function PLAYERRECCE:_GetViewZone(unit, vheading, minview, maxview, angle, camon, laser) self:T(self.lid.."_GetViewZone") local viewzone = nil if not camon then return nil end if unit and unit:IsAlive() then local unitname = unit:GetName() - if self.ViewZone[unitname] then - self.ViewZone[unitname]:UndrawZone() - end - --local vheading, vnod, maxview, vivon = self:GetGazelleVivianneSight(unit) - local startpos = unit:GetCoordinate() - local heading1 = (vheading+angle)%360 - local heading2 = (vheading-angle)%360 - local pos1 = startpos:Translate(maxview,heading1) - local pos2 = startpos:Translate(maxview,heading2) - local array = {} - table.insert(array,startpos:GetVec2()) - table.insert(array,pos1:GetVec2()) - table.insert(array,pos2:GetVec2()) - viewzone = ZONE_POLYGON:NewFromPointsArray(unitname,array) - if draw then - viewzone:DrawZone(-1,{0,0,1},nil,nil,nil,1) - self.ViewZone[unitname] = viewzone - end + if not laser then + -- Triangle + local startpos = unit:GetCoordinate() + local heading1 = (vheading+angle)%360 + local heading2 = (vheading-angle)%360 + local pos1 = startpos:Translate(maxview,heading1) + local pos2 = startpos:Translate(maxview,heading2) + local array = {} + table.insert(array,startpos:GetVec2()) + table.insert(array,pos1:GetVec2()) + table.insert(array,pos2:GetVec2()) + viewzone = ZONE_POLYGON:NewFromPointsArray(unitname,array) + else + -- Square + local startp = unit:GetCoordinate() + local heading1 = (vheading+90)%360 + local heading2 = (vheading-90)%360 + self:T({heading1,heading2}) + local startpos = startp:Translate(minview,vheading) + local pos1 = startpos:Translate(10,heading1) + local pos2 = startpos:Translate(10,heading2) + local pos3 = pos1:Translate(maxview,vheading) + local pos4 = pos2:Translate(maxview,vheading) + local array = {} + table.insert(array,pos1:GetVec2()) + table.insert(array,pos2:GetVec2()) + table.insert(array,pos4:GetVec2()) + table.insert(array,pos3:GetVec2()) + viewzone = ZONE_POLYGON:NewFromPointsArray(unitname,array) + end end return viewzone end @@ -508,7 +601,7 @@ function PLAYERRECCE:_CleanupTargetCache() self.TargetCache:ForEach( function(unit) local pull = false - if unit and unit:IsAlive() then + if unit and unit:IsAlive() and unit:GetLife() > 1 then if unit.PlayerRecceDetected and unit.PlayerRecceDetected.timestamp then local TNow = timer.getTime() if TNow-unit.PlayerRecceDetected.timestamp > self.TForget then @@ -538,26 +631,51 @@ end --@param #PLAYERRECCE self --@param Wrapper.Unit#UNIT unit The FACA unit --@param #boolean camera If true, use the unit's camera for targets in sight +--@param #laser laser Use laser zone --@return Core.Set#SET_UNIT Set of targets, can be empty! --@return #number count Count of targets -function PLAYERRECCE:_GetTargetSet(unit,camera) +function PLAYERRECCE:_GetTargetSet(unit,camera,laser) self:T(self.lid.."_GetTargetSet") local finaltargets = SET_UNIT:New() local finalcount = 0 - local heading,nod,maxview,angle = 0,30,8000,10 - local camon = true + local minview = 0 local typename = unit:GetTypeName() + local playername = unit:GetPlayerName() + local maxview = self.MaxViewDistance[typename] or 5000 + local heading,nod,maxview,angle = 0,30,8000,10 + local camon = false local name = unit:GetName() if string.find(typename,"SA342") and camera then heading,nod,maxview,camon = self:_GetGazelleVivianneSight(unit) angle=10 + -- Model nod and actual TV view don't compute + maxview = self.MaxViewDistance[typename] or 5000 + elseif typename == "Ka-50" and camera then + heading = unit:GetHeading() + nod,maxview,camon = 10,1000,true + angle = 10 + maxview = self.MaxViewDistance[typename] or 5000 else -- visual heading = unit:GetHeading() nod,maxview,camon = 10,1000,true angle = 45 end - local zone = self:_GetViewZone(unit,heading,nod,maxview,angle,camon) + if laser then + -- get min/max values + if not self.LaserFOV[playername] then + minview = 100 + maxview = 2000 + self.LaserFOV[playername] = { + min=100, + max=2000, + } + else + minview = self.LaserFOV[playername].min + maxview = self.LaserFOV[playername].max + end + end + local zone = self:_GetViewZone(unit,heading,minview,maxview,angle,camon,laser) if zone then local redcoalition = "red" if self.Coalition == coalition.side.RED then @@ -573,7 +691,7 @@ function PLAYERRECCE:_GetTargetSet(unit,camera) function(_unit) local _unit = _unit -- Wrapper.Unit#UNIT local _unitpos = _unit:GetCoordinate() - if startpos:IsLOS(_unitpos) then + if startpos:IsLOS(_unitpos) and _unit:IsAlive() and _unit:GetLife()>1 then self:T("Adding to final targets: ".._unit:GetName()) finaltargets:Add(_unit:GetName(),_unit) end @@ -588,19 +706,15 @@ end ---[Internal] --@param #PLAYERRECCE self --@param Core.Set#SET_UNIT targetset Set of targets, can be empty! ---@return Wrapper.Unit#UNIT Target +--@return Wrapper.Unit#UNIT Target or nil function PLAYERRECCE:_GetHVTTarget(targetset) self:T(self.lid.."_GetHVTTarget") - - -- get one target - -- local target = targetset:GetRandom() -- Wrapper.Unit#UNIT - -- sort units local unitsbythreat = {} local minthreat = self.minthreatlevel or 0 for _,_unit in pairs(targetset.Set) do local unit = _unit -- Wrapper.Unit#UNIT - if unit and unit:IsAlive() then + if unit and unit:IsAlive() and unit:GetLife() >1 then local threat = unit:GetThreatLevel() if threat >= minthreat then -- prefer radar units @@ -617,8 +731,12 @@ function PLAYERRECCE:_GetHVTTarget(targetset) local bNum = b[2] -- Coin value of b return aNum > bNum -- Return their comparisons, < for ascending, > for descending end) - - return unitsbythreat[1][1] + + if unitsbythreat[1] and unitsbythreat[1][1] then + return unitsbythreat[1][1] + else + return nil + end end --- [Internal] @@ -643,23 +761,32 @@ function PLAYERRECCE:_LaseTarget(client,targetset) else laser = self.LaserSpots[playername] end - if not laser:IsLasing() and target then + if self.LaserTarget[playername] then + -- still looking at target? + local target=self.LaserTarget[playername] -- Ops.Target#TARGET + local oldtarget = target:GetObject() --or laser.Target + self:T("Targetstate: "..target:GetState()) + if not oldtarget or targetset:IsNotInSet(oldtarget) or target:IsDead() or target:IsDestroyed() then + -- lost LOS or dead + laser:LaseOff() + if target:IsDead() or target:IsDestroyed() or target:GetLife() < 2 then + self:__Shack(-1,client,oldtarget) + self.LaserTarget[playername] = nil + else + self:__TargetLOSLost(-1,client,oldtarget) + self.LaserTarget[playername] = nil + end + end + elseif not laser:IsLasing() and target then local relativecam = self.LaserRelativePos[client:GetTypeName()] laser:SetRelativeStartPosition(relativecam) local lasercode = self.UnitLaserCodes[playername] or laser.LaserCode or 1688 local lasingtime = self.lasingtime or 60 local targettype = target:GetTypeName() - laser:LaseOn(target,lasercode,lasingtime) + laser:LaseOn(target,lasercode,lasingtime) + self.LaserTarget[playername] = TARGET:New(target) + self.LaserTarget[playername].TStatus = 9 self:__TargetLasing(-1,client,target,lasercode,lasingtime) - else - -- still looking at target? - local oldtarget=laser.Target - if targetset:IsNotInSet(oldtarget) then - -- lost LOS - local targettype = oldtarget:GetTypeName() - laser:LaseOff() - self:__TargetLOSLost(-1,client,oldtarget) - end end return self end @@ -702,6 +829,28 @@ function PLAYERRECCE:_SwitchOnStation(client,group,playername) return self end +--- [Internal] +-- @param #PLAYERRECCE self +-- @param Wrapper.Client#CLIENT client +-- @param Wrapper.Group#GROUP group +-- @param #string playername +-- @return #PLAYERRECCE self +function PLAYERRECCE:_SwitchSmoke(client,group,playername) + self:T(self.lid.."_SwitchLasing") + if not self.SmokeOwn[playername] then + self.SmokeOwn[playername] = true + MESSAGE:New("Smoke self is now ON",10,self.Name or "FACA"):ToClient(client) + else + self.SmokeOwn[playername] = false + MESSAGE:New("Smoke self is now OFF",10,self.Name or "FACA"):ToClient(client) + end + if self.ClientMenus[playername] then + self.ClientMenus[playername]:Remove() + self.ClientMenus[playername]=nil + end + return self +end + --- [Internal] -- @param #PLAYERRECCE self -- @param Wrapper.Client#CLIENT client @@ -724,6 +873,35 @@ function PLAYERRECCE:_SwitchLasing(client,group,playername) return self end +--- [Internal] +-- @param #PLAYERRECCE self +-- @param Wrapper.Client#CLIENT client +-- @param Wrapper.Group#GROUP group +-- @param #string playername +-- @param #number mindist +-- @param #number maxdist +-- @return #PLAYERRECCE self +function PLAYERRECCE:_SwitchLasingDist(client,group,playername,mindist,maxdist) + self:T(self.lid.."_SwitchLasingDist") + local mind = mindist or 100 + local maxd = maxdist or 2000 + if not self.LaserFOV[playername] then + self.LaserFOV[playername] = { + min=mind, + max=maxd, + } + else + self.LaserFOV[playername].min=mind + self.LaserFOV[playername].max=maxd + end + MESSAGE:New(string.format("Laser distance set to %d-%dm!",mindist,maxdist),10,"FACA"):ToClient(client) + if self.ClientMenus[playername] then + self.ClientMenus[playername]:Remove() + self.ClientMenus[playername]=nil + end + return self +end + --- [Internal] -- @param #PLAYERRECCE self -- @param Wrapper.Client#CLIENT client @@ -731,7 +909,7 @@ end -- @param #string playername -- @return #PLAYERRECCE self function PLAYERRECCE:_WIP(client,group,playername) - self:I(self.lid.."_WIP") + self:T(self.lid.."_WIP") return self end @@ -746,14 +924,17 @@ function PLAYERRECCE:_SmokeTargets(client,group,playername) local cameraset = self:_GetTargetSet(client,true) -- Core.Set#SET_UNIT local visualset = self:_GetTargetSet(client,false) -- Core.Set#SET_UNIT cameraset:AddSet(visualset) + if cameraset:CountAlive() > 0 then self:__TargetsSmoked(-1,client,playername,cameraset) end + local highsmoke = self.SmokeColor.highsmoke local medsmoke = self.SmokeColor.medsmoke local lowsmoke = self.SmokeColor.lowsmoke local lasersmoke = self.SmokeColor.lasersmoke local laser = self.LaserSpots[playername] -- Core.Spot#SPOT + -- laser targer gets extra smoke if laser and laser.Target and laser.Target:IsAlive() then laser.Target:GetCoordinate():Smoke(lasersmoke) @@ -761,23 +942,27 @@ function PLAYERRECCE:_SmokeTargets(client,group,playername) cameraset:Remove(laser.Target:GetName(),true) end end + -- smoke everything else - for _,_unit in pairs(cameraset.Set) do - local unit = _unit --Wrapper.Unit#UNIT - if unit then - local coord = unit:GetCoordinate() - local threat = unit:GetThreatLevel() - if coord then - local color = lowsmoke - if threat > 7 then - color = medsmoke - elseif threat > 2 then - color = lowsmoke - end - coord:Smoke(color) - end + local coordinate = cameraset:GetCoordinate() + local setthreat = cameraset:CalculateThreatLevelA2G() + + if coordinate then + local color = lowsmoke + if setthreat > 7 then + color = medsmoke + elseif setthreat > 2 then + color = lowsmoke end + coordinate:Smoke(color) end + + if self.SmokeOwn[playername] then + local cc = client:GetCoordinate() + local color = self.SmokeColor.ownsmoke + cc:Smoke(color) + end + return self end @@ -827,6 +1012,24 @@ function PLAYERRECCE:_FlareTargets(client,group,playername) return self end +--- [Internal] +-- @param #PLAYERRECCE self +-- @param Wrapper.Client#CLIENT client +-- @param Wrapper.Group#GROUP group +-- @param #string playername +-- @return #PLAYERRECCE self +function PLAYERRECCE:_IlluTargets(client,group,playername) + self:T(self.lid.."_IlluTargets") + local totalset, count = self:_GetKnownTargets(client) -- Core.Set#SET_UNIT + if count > 0 then + local coord = totalset:GetCoordinate() -- Core.Point#COORDINATE + coord.y = coord.y + 200 + coord:IlluminationBomb(nil,1) + self:__Illumination(1,client,playername,totalset) + end + return self +end + --- [Internal] -- @param #PLAYERRECCE self -- @param Wrapper.Client#CLIENT client @@ -835,12 +1038,13 @@ end -- @return #PLAYERRECCE self function PLAYERRECCE:_UploadTargets(client,group,playername) self:T(self.lid.."_UploadTargets") - local targetset, number = self:_GetTargetSet(client,true) - local vtargetset, vnumber = self:_GetTargetSet(client,false) - local totalset = SET_UNIT:New() - totalset:AddSet(targetset) - totalset:AddSet(vtargetset) - if totalset:CountAlive() > 0 then + --local targetset, number = self:_GetTargetSet(client,true) + --local vtargetset, vnumber = self:_GetTargetSet(client,false) + local totalset, count = self:_GetKnownTargets(client) + --local totalset = SET_UNIT:New() + -- totalset:AddSet(targetset) + --totalset:AddSet(vtargetset) + if count > 0 then self.Controller:AddTarget(totalset) self:__TargetReportSent(1,client,playername,totalset) end @@ -855,7 +1059,7 @@ end -- @return #PLAYERRECCE self function PLAYERRECCE:_ReportLaserTargets(client,group,playername) self:T(self.lid.."_ReportLaserTargets") - local targetset, number = self:_GetTargetSet(client,true) + local targetset, number = self:_GetTargetSet(client,true,true) if number > 0 and self.AutoLase[playername] then local Settings = ( client and _DATABASE:GetPlayerSettings( playername ) ) or _SETTINGS local target = self:_GetHVTTarget(targetset) -- the one we're lasing @@ -947,6 +1151,9 @@ function PLAYERRECCE:_BuildMenus() if not self.UnitLaserCodes[playername] then self:_SetClientLaserCode(nil,nil,playername,1688) end + if not self.SmokeOwn[playername] then + self.SmokeOwn[playername] = self.smokeownposition + end local group = client:GetGroup() if not self.ClientMenus[playername] then local canlase = self.CanLase[client:GetTypeName()] @@ -955,12 +1162,34 @@ function PLAYERRECCE:_BuildMenus() local text = string.format("Switch On-Station (%s)",txtonstation) local onstationmenu = MENU_GROUP_COMMAND:New(group,text,self.ClientMenus[playername],self._SwitchOnStation,self,client,group,playername) if self.OnStation[playername] then - local smokemenu = MENU_GROUP_COMMAND:New(group,"Smoke Targets",self.ClientMenus[playername],self._SmokeTargets,self,client,group,playername) - local smokemenu = MENU_GROUP_COMMAND:New(group,"Flare Targets",self.ClientMenus[playername],self._FlareTargets,self,client,group,playername) + local smoketopmenu = MENU_GROUP:New(group,"Visual Markers",self.ClientMenus[playername]) + local smokemenu = MENU_GROUP_COMMAND:New(group,"Smoke Targets",smoketopmenu,self._SmokeTargets,self,client,group,playername) + local flaremenu = MENU_GROUP_COMMAND:New(group,"Flare Targets",smoketopmenu,self._FlareTargets,self,client,group,playername) + local illumenu = MENU_GROUP_COMMAND:New(group,"Illuminate Area",smoketopmenu,self._IlluTargets,self,client,group,playername) + local ownsm = self.SmokeOwn[playername] and "ON" or "OFF" + local owntxt = string.format("Switch smoke self (%s)",ownsm) + local ownsmoke = MENU_GROUP_COMMAND:New(group,owntxt,smoketopmenu,self._SwitchSmoke,self,client,group,playername) if canlase then local txtonstation = self.AutoLase[playername] and "ON" or "OFF" local text = string.format("Switch Lasing (%s)",txtonstation) local lasemenu = MENU_GROUP_COMMAND:New(group,text,self.ClientMenus[playername],self._SwitchLasing,self,client,group,playername) + local lasedist = MENU_GROUP:New(group,"Set Laser Distance",self.ClientMenus[playername]) + local mindist = 100 + local maxdist = 2000 + if self.LaserFOV[playername] and self.LaserFOV[playername].max then + maxdist = self.LaserFOV[playername].max + end + local laselist={} + for i=2,8 do + local dist1 = (i*1000)-1000 + local dist2 = i*1000 + dist1 = dist1 == 1000 and 100 or dist1 + local text = string.format("%d-%dm",dist1,dist2) + if dist2 == maxdist then + text = text .. " (*)" + end + laselist[i] = MENU_GROUP_COMMAND:New(group,text,lasedist,self._SwitchLasingDist,self,client,group,playername,dist1,dist2) + end end local targetmenu = MENU_GROUP:New(group,"Target Report",self.ClientMenus[playername]) if canlase then @@ -1045,7 +1274,7 @@ function PLAYERRECCE:_CheckNewTargets(targetset,client,playername) table.insert(targetsbyclock[clock],obj) end ) - self:I("Known target Count: "..self.TargetCache:Count()) + self:T("Known target Count: "..self.TargetCache:Count()) if tempset:CountAlive() > 0 then self:TargetDetected(targetsbyclock,client,playername) end @@ -1117,6 +1346,24 @@ function PLAYERRECCE:SetMenuName(Name) return self end +--- [User] Enable smoking of own position +-- @param #PLAYERRECCE self +-- @return #PLAYERRECCE self +function PLAYERRECCE:EnableSmokeOwnPosition() + self:T(self.lid.."ENableSmokeOwnPosition") + self.smokeownposition = true + return self +end + +--- [User] Disable smoking of own position +-- @param #PLAYERRECCE self +-- @return #PLAYERRECCE +function PLAYERRECCE:DisableSmokeOwnPosition() + self:T(self.lid.."DisableSmokeOwnPosition") + self.smokeownposition = false + return self +end + --- [Internal] Get text for text-to-speech. -- Numbers are spaced out, e.g. "Heading 180" becomes "Heading 1 8 0 ". -- @param #PLAYERRECCE self @@ -1129,6 +1376,9 @@ function PLAYERRECCE:_GetTextForSpeech(text) -- get rid of leading or trailing spaces text=string.gsub(text,"^%s*","") text=string.gsub(text,"%s*$","") + text=string.gsub(text,"0","zero") + text=string.gsub(text,"9","niner") + text=string.gsub(text," "," ") return text end @@ -1162,29 +1412,46 @@ function PLAYERRECCE:onafterStatus(From, Event, To) function(Client) local client = Client -- Wrapper.Client#CLIENT local playername = client:GetPlayerName() + local cameraison = self:_CameraOn(client,playername) if client and client:IsAlive() and self.OnStation[playername] then - + --- + local targetset, targetcount, tzone = nil,0,nil + local laserset, lzone = nil,nil + local vistargetset, vistargetcount, viszone = nil,0,nil -- targets on camera - local targetset, targetcount, tzone = self:_GetTargetSet(client,true) - if targetset then - if self.ViewZone[playername] then - self.ViewZone[playername]:UndrawZone() - end - if self.debug and tzone then - self.ViewZone[playername]=tzone:DrawZone(self.Coalition,{0,0,1},nil,nil,nil,1) + if cameraison then + targetset, targetcount, tzone = self:_GetTargetSet(client,true) + if targetset then + if self.ViewZone[playername] then + self.ViewZone[playername]:UndrawZone() + end + if self.debug and tzone then + self.ViewZone[playername]=tzone:DrawZone(self.Coalition,{0,0,1},nil,nil,nil,1) + end end + self:T({targetcount=targetcount}) end - self:T({targetcount=targetcount}) -- lase targets on camera - if targetcount > 0 then - if self.CanLase[client:GetTypeName()] and self.AutoLase[playername] then - -- DONE move to lase at will - self:_LaseTarget(client,targetset) + if self.AutoLase[playername] and cameraison then + laserset, targetcount, lzone = self:_GetTargetSet(client,true,true) + if targetcount > 0 or self.LaserTarget[playername] then + if self.CanLase[client:GetTypeName()] then + -- DONE move to lase at will + self:_LaseTarget(client,laserset) + end end + if lzone then + if self.ViewZoneLaser[playername] then + self.ViewZoneLaser[playername]:UndrawZone() + end + if self.debug and tzone then + self.ViewZoneLaser[playername]=lzone:DrawZone(self.Coalition,{0,1,0},nil,nil,nil,1) + end + end + self:T({lasercount=targetcount}) end - -- visual targets - local vistargetset, vistargetcount, viszone = self:_GetTargetSet(client,false) + vistargetset, vistargetcount, viszone = self:_GetTargetSet(client,false) if vistargetset then if self.ViewZoneVisual[playername] then self.ViewZoneVisual[playername]:UndrawZone() @@ -1194,8 +1461,21 @@ function PLAYERRECCE:onafterStatus(From, Event, To) end end self:T({visualtargetcount=vistargetcount}) - targetset:AddSet(vistargetset) - self:_CheckNewTargets(targetset,client,playername) + if targetset then + vistargetset:AddSet(targetset) + end + if laserset then + vistargetset:AddSet(laserset) + end + if not cameraison and self.debug then + if self.ViewZoneLaser[playername] then + self.ViewZoneLaser[playername]:UndrawZone() + end + if self.ViewZone[playername] then + self.ViewZone[playername]:UndrawZone() + end + end + self:_CheckNewTargets(vistargetset,client,playername) end end ) @@ -1221,14 +1501,13 @@ function PLAYERRECCE:onafterRecceOnStation(From, Event, To, Client, Playername) local Settings = Client and _DATABASE:GetPlayerSettings(Playername) or _SETTINGS -- Core.Settings#SETTINGS coordtext = coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,Client,Settings) end - if self.debug then - local text = string.format("All stations, FACA %s on station\nat %s!",callsign, coordtext) - MESSAGE:New(text,15,self.Name or "FACA"):ToCoalition(self.Coalition) - end local text1 = "Party time!" local text2 = string.format("All stations, FACA %s on station\nat %s!",callsign, coordtext) local text2tts = string.format("All stations, FACA %s on station at %s!",callsign, coordtext) text2tts = self:_GetTextForSpeech(text2tts) + if self.debug then + self:I(text2.."\n"..text2tts) + end if self.UseSRS then local grp = Client:GetGroup() self.SRSQueue:NewTransmission(text1,nil,self.SRS,nil,2) @@ -1236,7 +1515,7 @@ function PLAYERRECCE:onafterRecceOnStation(From, Event, To, Client, Playername) MESSAGE:New(text2,10,self.Name or "FACA"):ToCoalition(self.Coalition) else MESSAGE:New(text1,10,self.Name or "FACA"):ToClient(Client) - MESSAGE:New(text2,10,self.Name or "FACA"):ToClient(Client) + MESSAGE:New(text2,10,self.Name or "FACA"):ToCoalition(self.Coalition) end return self end @@ -1262,7 +1541,7 @@ function PLAYERRECCE:onafterRecceOffStation(From, Event, To, Client, Playername) local texttts = string.format("All stations, FACA %s leaving station at %s, good bye!",callsign, coordtext) texttts = self:_GetTextForSpeech(texttts) if self.debug then - MESSAGE:New(text,15,self.Name or "FACA"):ToCoalition(self.Coalition) + self:I(text.."\n"..texttts) end local text1 = "Going home!" if self.UseSRS then @@ -1271,7 +1550,7 @@ function PLAYERRECCE:onafterRecceOffStation(From, Event, To, Client, Playername) self.SRSQueue:NewTransmission(texttts,nil,self.SRS,nil,2) MESSAGE:New(text,10,self.Name or "FACA"):ToCoalition(self.Coalition) else - MESSAGE:New(text,10,self.Name or "FACA"):ToClient(Client) + MESSAGE:New(text,10,self.Name or "FACA"):ToCoalition(self.Coalition) end return self end @@ -1307,9 +1586,18 @@ function PLAYERRECCE:onafterTargetDetected(From, Event, To, Targetsbyclock, Clie end if Settings:IsMetric() then targetdistance = UTILS.Round(targetdistance,-2) + if targetdistance >= 1000 then + targetdistance = UTILS.Round(targetdistance/1000,0) + dunits = "kilometer" + end else - targetdistance = UTILS.Round(UTILS.MetersToFeet(targetdistance),-2) - dunits = "feet" + if UTILS.MetersToNM(targetdistance) >=1 then + targetdistance = UTILS.Round(UTILS.MetersToNM(targetdistance),0) + dunits = "miles" + else + targetdistance = UTILS.Round(UTILS.MetersToFeet(targetdistance),-2) + dunits = "feet" + end end local text = string.format("Target! %s! %s o\'clock, %d %s!", ThreatTxt,i, targetdistance, dunits) local ttstext = string.format("Target! %s! %s oh clock, %d %s!", ThreatTxt, i, targetdistance, dunits) @@ -1334,9 +1622,18 @@ function PLAYERRECCE:onafterTargetDetected(From, Event, To, Targetsbyclock, Clie local targetdistance = GetNearest(targets) if Settings:IsMetric() then targetdistance = UTILS.Round(targetdistance,-2) + if targetdistance >= 1000 then + targetdistance = UTILS.Round(targetdistance/1000,0) + dunits = "kilometer" + end else - targetdistance = UTILS.Round(UTILS.MetersToFeet(targetdistance),-2) - dunits = "feet" + if UTILS.MetersToNM(targetdistance) >=1 then + targetdistance = UTILS.Round(UTILS.MetersToNM(targetdistance),0) + dunits = "miles" + else + targetdistance = UTILS.Round(UTILS.MetersToFeet(targetdistance),-2) + dunits = "feet" + end end local text = string.format(" %d targets! %s o\'clock, %d %s!", targetno, i, targetdistance, dunits) local ttstext = string.format("%d targets! %s oh clock, %d %s!", targetno, i, targetdistance, dunits) @@ -1351,6 +1648,45 @@ function PLAYERRECCE:onafterTargetDetected(From, Event, To, Targetsbyclock, Clie return self end +--- [Internal] Targets Illuminated +-- @param #PLAYERRECCE self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param Wrapper.Client#CLIENT Client +-- @param #string Playername +-- @param Core.Set#SET_UNIT TargetSet +-- @return #PLAYERRECCE self +function PLAYERRECCE:onafterIllumination(From, Event, To, Client, Playername, TargetSet) + self:T({From, Event, To}) + local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local coord = Client:GetCoordinate() + local coordtext = coord:ToStringBULLS(self.Coalition) + if self.AttackSet then + for _,_client in pairs(self.AttackSet.Set) do + local client = _client --Wrapper.Client#CLIENT + if client and client:IsAlive() then + local Settings = client and _DATABASE:GetPlayerSettings(client:GetPlayerName()) or _SETTINGS + local coordtext = coord:ToStringA2G(client,Settings) + if self.ReferencePoint then + coordtext = coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings) + end + local text = string.format("All stations, %s fired illumination\nat %s!",callsign, coordtext) + MESSAGE:New(text,15,self.Name or "FACA"):ToClient(client) + end + end + end + local text = "Sunshine!" + local ttstext = "Sunshine!" + if self.UseSRS then + local grp = Client:GetGroup() + self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,1,{grp},text,10) + else + MESSAGE:New(text,10,self.Name or "FACA"):ToClient(Client) + end + return self +end + --- [Internal] Targets Smoked -- @param #PLAYERRECCE self -- @param #string From @@ -1365,10 +1701,6 @@ function PLAYERRECCE:onafterTargetsSmoked(From, Event, To, Client, Playername, T local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) local coord = Client:GetCoordinate() local coordtext = coord:ToStringBULLS(self.Coalition) - if self.debug then - local text = string.format("All stations, %s smoked targets\nat %s!",callsign, coordtext) - MESSAGE:New(text,15,self.Name or "FACA"):ToCoalition(self.Coalition) - end if self.AttackSet then for _,_client in pairs(self.AttackSet.Set) do local client = _client --Wrapper.Client#CLIENT @@ -1408,10 +1740,6 @@ function PLAYERRECCE:onafterTargetsFlared(From, Event, To, Client, Playername, T local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) local coord = Client:GetCoordinate() local coordtext = coord:ToStringBULLS(self.Coalition) - if self.debug then - local text = string.format("All stations, %s flared\ntargets at %s!",callsign, coordtext) - MESSAGE:New(text,15,self.Name or "FACA"):ToCoalition(self.Coalition) - end if self.AttackSet then for _,_client in pairs(self.AttackSet.Set) do local client = _client --Wrapper.Client#CLIENT @@ -1457,9 +1785,19 @@ function PLAYERRECCE:onafterTargetLasing(From, Event, To, Client, Target, Laserc coordtext = coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,Client,Settings) end local targettype = Target:GetTypeName() - if self.debug then - local text = string.format("All stations, %s lasing %s\nat %s!\nCode %d, Duration %d seconds!",callsign, targettype, coordtext, Lasercode, Lasingtime) - MESSAGE:New(text,15,self.Name or "FACA"):ToCoalition(self.Coalition) + if self.AttackSet then + for _,_client in pairs(self.AttackSet.Set) do + local client = _client --Wrapper.Client#CLIENT + if client and client:IsAlive() then + local Settings = client and _DATABASE:GetPlayerSettings(client:GetPlayerName()) or _SETTINGS + if self.ReferencePoint then + coordtext = coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings) + end + local coordtext = coord:ToStringA2G(client,Settings) + local text = string.format("All stations, %s lasing %s\nat %s!\nCode %d, Duration %d seconds!",callsign, targettype, coordtext, Lasercode, Lasingtime) + MESSAGE:New(text,15,self.Name or "FACA"):ToClient(client) + end + end end local text = "Lasing!" local ttstext = "Laser on!" @@ -1472,6 +1810,50 @@ function PLAYERRECCE:onafterTargetLasing(From, Event, To, Client, Target, Laserc return self end +--- [Internal] Lased target destroyed +-- @param #PLAYERRECCE self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param Wrapper.Client#CLIENT Client +-- @param Wrapper.Unit#UNIT Target +-- @param #string Targettype +-- @return #PLAYERRECCE self +function PLAYERRECCE:onafterShack(From, Event, To, Client, Target, Targettype) + self:T({From, Event, To}) + local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local Settings = ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS + local coord = Client:GetCoordinate() + local coordtext = coord:ToStringBULLS(self.Coalition,Settings) + if self.ReferencePoint then + coordtext = coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,Client,Settings) + end + local targettype = "target" + if self.AttackSet then + for _,_client in pairs(self.AttackSet.Set) do + local client = _client --Wrapper.Client#CLIENT + if client and client:IsAlive() then + local Settings = client and _DATABASE:GetPlayerSettings(client:GetPlayerName()) or _SETTINGS + if self.ReferencePoint then + coordtext = coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings) + end + local coordtext = coord:ToStringA2G(client,Settings) + local text = string.format("All stations, %s good hit on %s\nat %s!",callsign, targettype, coordtext) + MESSAGE:New(text,15,self.Name or "FACA"):ToClient(client) + end + end + end + local text = "Shack!" + local ttstext = "Shack!" + if self.UseSRS then + local grp = Client:GetGroup() + self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,1) + else + MESSAGE:New(text,10,self.Name or "FACA"):ToClient(Client) + end + return self +end + --- [Internal] Laser lost LOS -- @param #PLAYERRECCE self -- @param #string From @@ -1489,10 +1871,20 @@ function PLAYERRECCE:onafterTargetLOSLost(From, Event, To, Client, Target) if self.ReferencePoint then coordtext = coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,Client,Settings) end - local targettype = Target:GetTypeName() - if self.debug then - local text = string.format("All stations, %s lost sight of %s\nat %s!",callsign, targettype, coordtext) - MESSAGE:New(text,15,self.Name or "FACA"):ToCoalition(self.Coalition) + local targettype = "target" --Target:GetTypeName() + if self.AttackSet then + for _,_client in pairs(self.AttackSet.Set) do + local client = _client --Wrapper.Client#CLIENT + if client and client:IsAlive() then + local Settings = client and _DATABASE:GetPlayerSettings(client:GetPlayerName()) or _SETTINGS + if self.ReferencePoint then + coordtext = coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings) + end + local coordtext = coord:ToStringA2G(client,Settings) + local text = string.format("All stations, %s lost sight of %s\nat %s!",callsign, targettype, coordtext) + MESSAGE:New(text,15,self.Name or "FACA"):ToClient(client) + end + end end local text = "Lost LOS!" local ttstext = "Lost L O S!" @@ -1553,7 +1945,6 @@ function PLAYERRECCE:onafterTargetReportSent(From, Event, To, Client, TargetSet) return self end - --- [Internal] Stop -- @param #PLAYERRECCE self -- @param #string From diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index 63edec3aa..df77e83ae 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -1,11 +1,11 @@ ---- **Ops** - PlayerTask (mission) for Players. +---- **Ops** - PlayerTask (mission) for Players. -- -- ## Main Features: -- -- * Simplifies defining and executing Player tasks -- * FSM events when a mission is added, done, successful or failed, replanned -- * Ready to use SRS and localization --- * Mission locations can be smoked, flared and marked on the map +-- * Mission locations can be smoked, flared, illuminated and marked on the map -- -- === -- @@ -21,7 +21,7 @@ -- === -- @module Ops.PlayerTask -- @image OPS_PlayerTask.jpg --- @date Last Update September 2022 +-- @date Last Update October 2022 do @@ -50,6 +50,13 @@ do -- @field Ops.PlayerTask#PLAYERTASKCONTROLLER TaskController -- @field #number timestamp -- @field #number lastsmoketime +-- @field #number coalition +-- @field #string Freetext +-- @field #string FreetextTTS +-- @field #string TaskSubType +-- @field #table NextTaskSuccess +-- @field #table NextTaskFailure +-- @field #string FinalState -- @extends Core.Fsm#FSM @@ -78,11 +85,17 @@ PLAYERTASK = { TaskController = nil, timestamp = 0, lastsmoketime = 0, + Freetext = nil, + FreetextTTS = nil, + TaskSubType = nil, + NextTaskSuccess = {}, + NextTaskFailure = {}, + FinalState = "none", } --- PLAYERTASK class version. -- @field #string version -PLAYERTASK.version="0.1.3" +PLAYERTASK.version="0.1.9" --- Generic task condition. -- @type PLAYERTASK.Condition @@ -95,6 +108,7 @@ PLAYERTASK.version="0.1.3" -- @param Ops.Target#TARGET Target Target for this task -- @param #boolean Repeat Repeat this task if true (default = false) -- @param #number Times Repeat on failure this many times if Repeat is true (default = 1) +-- @param #string TTSType TTS friendly task type name -- @return #PLAYERTASK self function PLAYERTASK:New(Type, Target, Repeat, Times, TTSType) @@ -252,6 +266,124 @@ function PLAYERTASK:_SetController(Controller) return self end +--- [User] Set a coalition side for this task +-- @param #PLAYERTASK self +-- @param #number Coalition Coaltion side to add, e.g. coalition.side.BLUE +-- @return #PLAYERTASK self +function PLAYERTASK:SetCoalition(Coalition) + self:T(self.lid.."SetCoalition") + self.coalition = Coalition or coalition.side.BLUE + return self +end + +--- [User] Get the coalition side for this task +-- @param #PLAYERTASK self +-- @return #number Coalition Coaltion side, e.g. coalition.side.BLUE, or nil if not set +function PLAYERTASK:GetCoalition() + self:T(self.lid.."GetCoalition") + return self.coalition +end + +--- [USER] Add a free text description to this task. +-- @param #PLAYERTASK self +-- @param #string Text +-- @return #PLAYERTASK self +function PLAYERTASK:AddFreetext(Text) + self:T(self.lid.."AddFreetext") + self.Freetext = Text + return self +end + +--- [USER] Query if a task has free text description. +-- @param #PLAYERTASK self +-- @return #PLAYERTASK self +function PLAYERTASK:HasFreetext() + self:T(self.lid.."HasFreetext") + return self.Freetext ~= nil and true or false +end + +--- [USER] Query if a task has free text TTS description. +-- @param #PLAYERTASK self +-- @return #PLAYERTASK self +function PLAYERTASK:HasFreetextTTS() + self:T(self.lid.."HasFreetextTTS") + return self.FreetextTTS ~= nil and true or false +end + +--- [USER] Set a task sub type description to this task. +-- @param #PLAYERTASK self +-- @param #string Type +-- @return #PLAYERTASK self +function PLAYERTASK:SetSubType(Type) + self:T(self.lid.."AddSubType") + self.TaskSubType = Type + return self +end + +--- [USER] Get task sub type description from this task. +-- @param #PLAYERTASK self +-- @return #string Type or nil +function PLAYERTASK:GetSubType() + self:T(self.lid.."GetSubType") + return self.TaskSubType +end + +--- [USER] Get the free text description from this task. +-- @param #PLAYERTASK self +-- @return #string Text +function PLAYERTASK:GetFreetext() + self:T(self.lid.."GetFreetext") + return self.Freetext or self.FreetextTTS or "No Details" +end + +--- [USER] Add a free text description for TTS to this task. +-- @param #PLAYERTASK self +-- @param #string TextTTS +-- @return #PLAYERTASK self +function PLAYERTASK:AddFreetextTTS(TextTTS) + self:T(self.lid.."AddFreetextTTS") + self.FreetextTTS = TextTTS + return self +end + +--- [USER] Get the free text TTS description from this task. +-- @param #PLAYERTASK self +-- @return #string Text +function PLAYERTASK:GetFreetextTTS() + self:T(self.lid.."GetFreetextTTS") + return self.FreetextTTS or self.Freetext or "No Details" +end + +--- [USER] Add a short free text description for the menu entry of this task. +-- @param #PLAYERTASK self +-- @param #string Text +-- @return #PLAYERTASK self +function PLAYERTASK:SetMenuName(Text) + self:T(self.lid.."SetMenuName") + self.Target.name = Text + return self +end + +--- [USER] Add a task to be assigned to same clients when task was a success. +-- @param #PLAYERTASK self +-- @param Ops.PlayerTask#PLAYERTASK Task +-- @return #PLAYERTASK self +function PLAYERTASK:AddNextTaskAfterSuccess(Task) + self:T(self.lid.."AddNextTaskAfterSuccess") + table.insert(self.NextTaskSuccess,Task) + return self +end + +--- [USER] Add a task to be assigned to same clients when task was a failure. +-- @param #PLAYERTASK self +-- @param Ops.PlayerTask#PLAYERTASK Task +-- @return #PLAYERTASK self +function PLAYERTASK:AddNextTaskAfterFailure(Task) + self:T(self.lid.."AddNextTaskAfterFailure") + table.insert(self.NextTaskFailure,Task) + return self +end + --- [User] Check if task is done -- @param #PLAYERTASK self -- @return #boolean done @@ -398,7 +530,7 @@ function PLAYERTASK:SmokeTarget(Color) if not self.lastsmoketime then self.lastsmoketime = 0 end local TDiff = timer.getAbsTime() - self.lastsmoketime if self.Target and TDiff > 299 then - local coordinate = self.Target:GetCoordinate() + local coordinate = self.Target:GetAverageCoordinate() if coordinate then coordinate:Smoke(color) self.lastsmoketime = timer.getAbsTime() @@ -415,7 +547,7 @@ function PLAYERTASK:FlareTarget(Color) self:T(self.lid.."SmokeTarget") local color = Color or FLARECOLOR.Red if self.Target then - local coordinate = self.Target:GetCoordinate() + local coordinate = self.Target:GetAverageCoordinate() if coordinate then coordinate:Flare(color,0) end @@ -423,6 +555,25 @@ function PLAYERTASK:FlareTarget(Color) return self end +--- [User] Illuminate Target Area +-- @param #PLAYERTASK self +-- @param #number Power Power of illumination bomb in Candela. Default 1000 cd. +-- @param #number Height Height above target used to release the bomb, default 150m. +-- @return #PLAYERTASK self +function PLAYERTASK:IlluminateTarget(Power,Height) + self:T(self.lid.."IlluminateTarget") + local Power = Power or 1000 + local Height = Height or 150 + if self.Target then + local coordinate = self.Target:GetAverageCoordinate() + if coordinate then + local bcoord = COORDINATE:NewFromVec2( coordinate:GetVec2(), Height ) + bcoord:IlluminationBomb(Power) + end + end + return self +end + -- success / failure function addion courtesy @FunkyFranky. --- [User] Add success condition. @@ -650,6 +801,7 @@ function PLAYERTASK:onafterCancel(From, Event, To) self.TaskController:__TaskCancelled(-1,self) end self.timestamp = timer.getAbsTime() + self.FinalState = "Cancel" self:__Done(-1) return self end @@ -669,6 +821,7 @@ function PLAYERTASK:onafterSuccess(From, Event, To) self.TargetMarker:Remove() end self.timestamp = timer.getAbsTime() + self.FinalState = "Success" self:__Done(-1) return self end @@ -693,9 +846,7 @@ function PLAYERTASK:onafterFailed(From, Event, To) if self.TargetMarker then self.TargetMarker:Remove() end - if self.TaskController then - self.TaskController:__TaskFailed(-1,self) - end + self.FinalState = "Failed" self:__Done(-1) end self.timestamp = timer.getAbsTime() @@ -715,6 +866,8 @@ do -- DONE Flash directions -- DONE less rebuilds menu, Task info menu available after join -- DONE Limit menu entries +-- DONE Integrated basic CTLD tasks +-- DONE Integrate basic CSAR tasks ------------------------------------------------------------------------------------------------------------------- --- PLAYERTASKCONTROLLER class. @@ -762,6 +915,9 @@ do -- @field #table PlayerInfoMenu -- @field #boolean noflaresmokemenu -- @field #boolean TransmitOnlyWithPlayers +-- @field #boolean buddylasing +-- @field Ops.PlayerRecce#PLAYERRECCE PlayerRecce +-- @field #number Coalition -- @extends Core.Fsm#FSM --- @@ -788,7 +944,7 @@ do -- -- ## 2 Task Types -- --- Targets can be of types GROUP, SET\_GROUP, UNIT, SET\_UNIT, STATIC, SET\_STATIC, AIRBASE, ZONE or COORDINATE. The system will auto-create tasks for players from these targets. +-- Targets can be of types GROUP, SET\_GROUP, UNIT, SET\_UNIT, STATIC, SET\_STATIC, SET\_SCENERY, AIRBASE, ZONE or COORDINATE. The system will auto-create tasks for players from these targets. -- Tasks are created as @{Ops.PlayerTask#PLAYERTASK} objects, which leverage @{Ops.Target#TARGET} for the management of the actual target. The system creates these task types -- from the target objects: -- @@ -808,6 +964,8 @@ do -- * ZONE and COORDINATE - Targets will be scanned for GROUND or STATIC enemy units and tasks created from these -- * Intercept - Any airborne targets, if the controller is of type "A2A" -- * Anti-Ship - Any ship targets, if the controller is of type "A2S" +-- * CTLD - Combat transport and logistics deployment +-- * CSAR - Combat search and rescue -- -- ## 3 Task repetition -- @@ -937,11 +1095,14 @@ do -- NONE = "None", -- POINTEROVERTARGET = "%s, %s, pointer in reach for task %03d, lasing!", -- POINTERTARGETREPORT = "\nPointer in reach: %s\nLasing: %s", +-- RECCETARGETREPORT = "\nRecce %s in reach: %s\nLasing: %s", -- POINTERTARGETLASINGTTS = ". Pointer in reach and lasing.", -- TARGET = "Target", -- FLASHON = "%s - Flashing directions is now ON!", -- FLASHOFF = "%s - Flashing directions is now OFF!", -- FLASHMENU = "Flash Directions Switch", +-- BRIEFING = "Briefing", +-- TARGETLOCATION ="Target location", -- }, -- -- e.g. @@ -1065,6 +1226,9 @@ PLAYERTASKCONTROLLER = { PlayerInfoMenu = {}, noflaresmokemenu = false, TransmitOnlyWithPlayers = true, + buddylasing = false, + PlayerRecce = nil, + Coalition = nil, } --- @@ -1080,8 +1244,10 @@ PLAYERTASKCONTROLLER.Type = { A2GS = "Air-To-Ground-Sea", } ---- Define a new AUFTRAG Type +--- Define new AUFTRAG Types AUFTRAG.Type.PRECISIONBOMBING = "Precision Bombing" +AUFTRAG.Type.CTLD = "Combat Transport" +AUFTRAG.Type.CSAR = "Combat Rescue" --- -- @type SeadAttributes @@ -1152,11 +1318,14 @@ PLAYERTASKCONTROLLER.Messages = { NONE = "None", POINTEROVERTARGET = "%s, %s, pointer in reach for task %03d, lasing!", POINTERTARGETREPORT = "\nPointer in reach: %s\nLasing: %s", + RECCETARGETREPORT = "\nRecce %s in reach: %s\nLasing: %s", POINTERTARGETLASINGTTS = ". Pointer in reach and lasing.", TARGET = "Target", FLASHON = "%s - Flashing directions is now ON!", FLASHOFF = "%s - Flashing directions is now OFF!", FLASHMENU = "Flash Directions Switch", + BRIEFING = "Briefing", + TARGETLOCATION ="Target location", }, DE = { TASKABORT = "Auftrag abgebrochen!", @@ -1213,19 +1382,22 @@ PLAYERTASKCONTROLLER.Messages = { NONE = "Keine", POINTEROVERTARGET = "%s, %s, Marker im Zielbereich für %03d, Laser an!", POINTERTARGETREPORT = "\nMarker im Zielbereich: %s\nLaser an: %s", + RECCETARGETREPORT = "\nSpäher % im Zielbereich: %s\nLasing: %s", POINTERTARGETLASINGTTS = ". Marker im Zielbereich, Laser is an.", TARGET = "Ziel", FLASHON = "%s - Richtungsangaben einblenden ist EIN!", FLASHOFF = "%s - Richtungsangaben einblenden ist AUS!", FLASHMENU = "Richtungsangaben Schalter", + BRIEFING = "Briefing", + TARGETLOCATION ="Zielkoordinate", }, } --- PLAYERTASK class version. -- @field #string version -PLAYERTASKCONTROLLER.version="0.1.38" +PLAYERTASKCONTROLLER.version="0.1.43" ---- Constructor +--- Create and run a new TASKCONTROLLER instance. -- @param #PLAYERTASKCONTROLLER self -- @param #string Name Name of this controller -- @param #number Coalition of this controller, e.g. coalition.side.BLUE @@ -1303,9 +1475,9 @@ function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter) self:AddTransition("*", "TaskRepeatOnFailed", "*") self:AddTransition("*", "Stop", "Stopped") - self:__Start(-1) + self:__Start(2) local starttime = math.random(5,10) - self:__Status(-starttime) + self:__Status(starttime) -- Player leaves self:HandleEvent(EVENTS.PlayerLeaveUnit, self._EventHandler) @@ -1454,12 +1626,13 @@ end -- @param #string text Original text. -- @return #string Spoken text. function PLAYERTASKCONTROLLER:_GetTextForSpeech(text) - + self:T(self.lid.."_GetTextForSpeech") -- Space out numbers. text=string.gsub(text,"%d","%1 ") -- get rid of leading or trailing spaces text=string.gsub(text,"^%s*","") text=string.gsub(text,"%s*$","") + text=string.gsub(text," "," ") return text end @@ -1546,6 +1719,27 @@ function PLAYERTASKCONTROLLER:EnablePrecisionBombing(FlightGroup,LaserCode) return self end + +--- [User] Allow precision laser-guided bombing on statics and "high-value" ground units (MBT etc) with player units lasing. +-- @param #PLAYERTASKCONTROLLER self +-- @param Ops.PlayerRecce#PLAYERRECCE Recce (Optional) The PLAYERRECCE object governing the lasing players. +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:EnableBuddyLasing(Recce) + self:T(self.lid.."EnableBuddyLasing") + self.buddylasing = true + self.PlayerRecce = Recce + return self +end + +--- [User] Allow precision laser-guided bombing on statics and "high-value" ground units (MBT etc) with player units lasing. +-- @param #PLAYERTASKCONTROLLER self +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:DisableBuddyLasing() + self:T(self.lid.."DisableBuddyLasing") + self.buddylasing = false + return self +end + --- [User] Allow addition of targets with user F10 map markers. -- @param #PLAYERTASKCONTROLLER self -- @param #string Tag (Optional) The tagname to use to identify commands, defaults to "TASK" @@ -1583,7 +1777,7 @@ end -- @return #string playername -- @return #string ttsplayername function PLAYERTASKCONTROLLER:_GetPlayerName(Client) - self:T(self.lid.."DisablePrecisionBombing") + self:T(self.lid.."_GetPlayerName") local playername = Client:GetPlayerName() local ttsplayername = nil if not self.customcallsigns[playername] then @@ -1717,7 +1911,7 @@ function PLAYERTASKCONTROLLER:_EventHandler(EventData) end playername = self:_GetTextForSpeech(playername) --local text = string.format("%s, %s, switch to %s for task assignment!",EventData.IniPlayerName,self.MenuName or self.Name,freqtext) - local text = string.format(switchtext,self.MenuName or self.Name,playername,freqtext) + local text = string.format(switchtext,playername,self.MenuName or self.Name,freqtext) self.SRSQueue:NewTransmission(text,nil,self.SRS,timer.getAbsTime()+60,2,{EventData.IniGroup},text,30,self.BCFrequency,self.BCModulation) end end @@ -1872,6 +2066,23 @@ function PLAYERTASKCONTROLLER:_CheckTaskQueue() self:T("*****Removing player " .. _id) self.TasksPerPlayer:PullByID(_id) end + -- Follow-up tasks? + local nexttasks = {} + if task.FinalState == "Success" then + nexttasks = task.NextTaskSuccess + elseif task.FinalState == "Failed" then + nexttasks = task.NextTaskFailure + end + local clientlist, count = task:GetClientObjects() + if count > 0 then + for _,_client in pairs(clientlist) do + local client = _client --Wrapper.Client#CLIENT + local group = client:GetGroup() + for _,task in pairs(nexttasks) do + self:_JoinTask(group,client,task,true) + end + end + end local TNow = timer.getAbsTime() if TNow - task.timestamp > 10 then local task = self.TaskQueue:PullByID(_id) -- Ops.PlayerTask#PLAYERTASK @@ -2225,7 +2436,7 @@ function PLAYERTASKCONTROLLER:_AddTask(Target) ttstype = self.gettext:GetEntry("BAITTS",self.locale) end -- see if we can do precision bombing - if (type == AUFTRAG.Type.BAI or type == AUFTRAG.Type.CAS) and self.precisionbombing then + if (type == AUFTRAG.Type.BAI or type == AUFTRAG.Type.CAS) and (self.precisionbombing or self.buddylasing) then -- threatlevel between 3 and 6 means, it's artillery, tank, modern tank or AAA if threat > 2 and threat < 7 then type = AUFTRAG.Type.PRECISIONBOMBING @@ -2317,16 +2528,53 @@ function PLAYERTASKCONTROLLER:_AddTask(Target) return self end +--- [User] Add a PLAYERTASK object to the list of (open) tasks +-- @param #PLAYERTASKCONTROLLER self +-- @param Ops.PlayerTask#PLAYERTASK PlayerTask +-- @return #PLAYERTASKCONTROLLER self +-- @usage +-- Example to create a PLAYERTASK of type CTLD and give Players 10 minutes to complete: +-- +-- local newtask = PLAYERTASK:New(AUFTRAG.Type.CTLD,ZONE:Find("Unloading"),false,0,"Combat Transport") +-- newtask.Time0 = timer.getAbsTime() -- inject a timestamp for T0 +-- newtask:AddFreetext("Transport crates to the drop zone and build a vehicle in the next 10 minutes!") +-- +-- -- add a condition for failure - fail after 10 minutes +-- newtask:AddConditionFailure( +-- function() +-- local Time = timer.getAbsTime() +-- if Time - newtask.Time0 > 600 then +-- return true +-- end +-- return false +-- end +-- ) +-- +-- taskmanager:AddPlayerTaskToQueue(PlayerTask) +function PLAYERTASKCONTROLLER:AddPlayerTaskToQueue(PlayerTask) + self:T(self.lid.."AddPlayerTaskToQueue") + if PlayerTask and PlayerTask.ClassName and PlayerTask.ClassName == "PLAYERTASK" then + PlayerTask:_SetController(self) + PlayerTask:SetCoalition(self.Coalition) + self.TaskQueue:Push(PlayerTask) + self:__TaskAdded(-1,PlayerTask) + else + self:E(self.lid.."***** NO valid PAYERTASK object sent!") + end + return self +end + --- [Internal] Join a player to a task -- @param #PLAYERTASKCONTROLLER self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Client#CLIENT Client -- @param Ops.PlayerTask#PLAYERTASK Task +-- @param #boolean Force Assign task even if client already has one -- @return #PLAYERTASKCONTROLLER self -function PLAYERTASKCONTROLLER:_JoinTask(Group, Client, Task) +function PLAYERTASKCONTROLLER:_JoinTask(Group, Client, Task, Force) self:T(self.lid.."_JoinTask") local playername, ttsplayername = self:_GetPlayerName(Client) - if self.TasksPerPlayer:HasUniqueID(playername) then + if self.TasksPerPlayer:HasUniqueID(playername) and not Force then -- Player already has a task if not self.NoScreenOutput then local text = self.gettext:GetEntry("HAVEACTIVETASK",self.locale) @@ -2350,7 +2598,7 @@ function PLAYERTASKCONTROLLER:_JoinTask(Group, Client, Task) --local m=MESSAGE:New(text,"10","Tasking"):ToAll() end if self.UseSRS then - self:I(self.lid..text) + self:T(self.lid..text) self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2) end self.TasksPerPlayer:Push(Task,playername) @@ -2424,8 +2672,10 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Group, Client, Task) self:T(self.lid.."_ActiveTaskInfo") local playername, ttsplayername = self:_GetPlayerName(Client) local text = "" + local textTTS = "" if self.TasksPerPlayer:HasUniqueID(playername) or Task then - -- TODO: Show multiple? + -- NODO: Show multiple? + -- Details local task = Task or self.TasksPerPlayer:ReadByID(playername) -- Ops.PlayerTask#PLAYERTASK local tname = self.gettext:GetEntry("TASKNAME",self.locale) local ttsname = self.gettext:GetEntry("TASKNAMETTS",self.locale) @@ -2438,6 +2688,7 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Group, Client, Task) else CoordText = Coordinate:ToStringA2A(Client) end + -- Threat Level local ThreatLevel = task.Target:GetThreatLevelMax() --local ThreatLevelText = "high" local ThreatLevelText = self.gettext:GetEntry("THREATHIGH",self.locale) @@ -2448,11 +2699,13 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Group, Client, Task) --ThreatLevelText = "low" ThreatLevelText = self.gettext:GetEntry("THREATLOW",self.locale) end + -- Targetno and Threat local targets = task.Target:CountTargets() or 0 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) + -- Prec bombing if task.Type == AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then if self.LasingDrone and self.LasingDrone.playertask then local yes = self.gettext:GetEntry("YES",self.locale) @@ -2464,12 +2717,57 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Group, Client, Task) text = text .. prectext end end + -- Buddylasing + if task.Type == AUFTRAG.Type.PRECISIONBOMBING and self.buddylasing then + if self.PlayerRecce then + local yes = self.gettext:GetEntry("YES",self.locale) + local no = self.gettext:GetEntry("NO",self.locale) + -- TODO make dist dependent on PlayerRecce Object + local reachdist = 8000 + local inreach = false + -- someone close enough? + local pset = self.PlayerRecce.PlayerSet:GetAliveSet() + for _,_player in pairs(pset) do + local player = _player -- Wrapper.Client#CLIENT + local pcoord = player:GetCoordinate() + if pcoord:Get2DDistance(Coordinate) <= reachdist then + inreach = true + local callsign = player:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local playername = player:GetPlayerName() + local islasing = no + if self.PlayerRecce.CanLase[player:GetTypeName()] and self.PlayerRecce.AutoLase[playername] then + -- TODO - maybe compare Spot target + islasing = yes + end + local inrtext = inreach == true and yes or no + local prectext = self.gettext:GetEntry("RECCETARGETREPORT",self.locale) + -- RECCETARGETREPORT = "\nSpäher % im Zielbereich: %s\nLasing: %s", + prectext = string.format(prectext,callsign,inrtext,islasing) + text = text .. prectext + end + end + end + -- Transport + elseif task.Type == AUFTRAG.Type.CTLD or task.Type == AUFTRAG.Type.CSAR then + -- 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.", + text = taskname + textTTS = taskname + local detail = task:GetFreetext() + local detailTTS = task:GetFreetextTTS() + local brieftxt = self.gettext:GetEntry("BRIEFING",self.locale) + local locatxt = self.gettext:GetEntry("TARGETLOCATION",self.locale) + text = text .. string.format("\n%s: %s\n%s %s",brieftxt,detail,locatxt,CoordText) + --text = text .. "\nBriefing: "..detail.."\nTarget location "..CoordText + --textTTS = textTTS .. "; Briefing: "..detailTTS.."\nTarget location "..CoordText + textTTS = textTTS .. string.format("; %s: %s; %s %s",brieftxt,detailTTS,locatxt,CoordText) + end + + -- Pilots local clienttxt = self.gettext:GetEntry("PILOTS",self.locale) if clientcount > 0 then for _,_name in pairs(clientlist) do if self.customcallsigns[_name] then - -- personalized flight name in player naming - --_name = string.match(_name,"| ([%a]+)") _name = self.customcallsigns[_name] end clienttxt = clienttxt .. _name .. ", " @@ -2479,7 +2777,14 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Group, Client, Task) local keine = self.gettext:GetEntry("NONE",self.locale) clienttxt = clienttxt .. keine end + + -- Task Report text = text .. clienttxt + if task:HasFreetext() and not ( task.Type == AUFTRAG.Type.CTLD or task.Type == AUFTRAG.Type.CSAR) then + local brieftxt = self.gettext:GetEntry("BRIEFING",self.locale) + text = text .. string.format("\n%s: ",brieftxt)..task:GetFreetext() + end + textTTS = textTTS .. clienttxt if self.UseSRS then if string.find(CoordText," BR, ") then CoordText = string.gsub(CoordText," BR, "," Bee, Arr, ") @@ -2492,6 +2797,15 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Group, Client, Task) local lasingtext = self.gettext:GetEntry("POINTERTARGETLASINGTTS",self.locale) ttstext = ttstext .. lasingtext end + elseif task.Type == AUFTRAG.Type.CTLD or task.Type == AUFTRAG.Type.CSAR then + ttstext = textTTS + if string.find(ttstext," BR, ") then + CoordText = string.gsub(ttstext," BR, "," Bee, Arr, ") + end + elseif task:HasFreetext() then + -- add tts freetext + local brieftxt = self.gettext:GetEntry("BRIEFING",self.locale) + ttstext = ttstext .. string.format("; %s: ",brieftxt)..task:GetFreetextTTS() end self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,2) end @@ -2758,10 +3072,12 @@ function PLAYERTASKCONTROLLER:_BuildMenus(Client,enforced,fromsuccess) local active = MENU_GROUP_DELAYED:New(group,menuactive,topmenu) local info = MENU_GROUP_COMMAND_DELAYED:New(group,menuinfo,active,self._ActiveTaskInfo,self,group,client) local mark = MENU_GROUP_COMMAND_DELAYED:New(group,menumark,active,self._MarkTask,self,group,client) - if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A or self.noflaresmokemenu then - -- no smoking/flaring here if A2A or designer has set to false - local smoke = MENU_GROUP_COMMAND_DELAYED:New(group,menusmoke,active,self._SmokeTask,self,group,client) - local flare = MENU_GROUP_COMMAND_DELAYED:New(group,menuflare,active,self._FlareTask,self,group,client) + if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A then + if self.noflaresmokemenu ~= true then + -- no smoking/flaring here if A2A or designer has set noflaresmokemenu to true + local smoke = MENU_GROUP_COMMAND_DELAYED:New(group,menusmoke,active,self._SmokeTask,self,group,client) + local flare = MENU_GROUP_COMMAND_DELAYED:New(group,menuflare,active,self._FlareTask,self,group,client) + end end local abort = MENU_GROUP_COMMAND_DELAYED:New(group,menuabort,active,self._AbortTask,self,group,client) if self.activehasinfomenu and self.taskinfomenu then diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index 8129059c4..09fb781fa 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -4,7 +4,7 @@ -- -- * Manages target, number alive, life points, damage etc. -- * Events when targets are damaged or destroyed --- * Various target objects: UNIT, GROUP, STATIC, AIRBASE, COORDINATE, SET_GROUP, SET_UNIT +-- * Various target objects: UNIT, GROUP, STATIC, AIRBASE, COORDINATE, SET_GROUP, SET_UNIT, SET_SCENERY -- -- === -- @@ -69,6 +69,7 @@ TARGET = { casualties = {}, threatlevel0 = 0, conditionStart = {}, + TStatus = 30, } @@ -146,7 +147,7 @@ _TARGETID=0 --- TARGET class version. -- @field #string version -TARGET.version="0.5.2" +TARGET.version="0.5.4" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -185,6 +186,7 @@ function TARGET:New(TargetObject) -- Defaults. self:SetPriority() self:SetImportance() + self.TStatus = 30 -- Log ID. self.lid=string.format("TARGET #%03d | ", _TARGETID) @@ -259,12 +261,13 @@ end -- * SET_UNIT -- * SET_STATIC -- * SET_OPSGROUP +-- * SET_SCENERY -- -- @param #TARGET self -- @param Wrapper.Positionable#POSITIONABLE Object The target GROUP, UNIT, STATIC, AIRBASE or COORDINATE. function TARGET:AddObject(Object) - if Object:IsInstanceOf("SET_GROUP") or Object:IsInstanceOf("SET_UNIT") or Object:IsInstanceOf("SET_STATIC") or Object:IsInstanceOf("SET_OPSGROUP") then + if Object:IsInstanceOf("SET_GROUP") or Object:IsInstanceOf("SET_UNIT") or Object:IsInstanceOf("SET_STATIC") or Object:IsInstanceOf("SET_SCENERY") or Object:IsInstanceOf("SET_OPSGROUP") then --- -- Sets @@ -570,7 +573,7 @@ function TARGET:onafterStatus(From, Event, To) -- Update status again in 30 sec. if self:IsAlive() then - self:__Status(-30) + self:__Status(-self.TStatus) end end @@ -828,8 +831,8 @@ function TARGET:_AddObject(Object) if static and static:IsAlive() then - target.Life0=1 - target.Life=1 + target.Life0=static:GetLife0() + target.Life=static:GetLife() target.N0=target.N0+1 table.insert(self.elements, target.Name) @@ -1131,8 +1134,9 @@ end --- Get target 3D position vector. -- @param #TARGET self -- @param #TARGET.Object Target Target object. +-- @param #boolean Average -- @return DCS#Vec3 Vector with x,y,z components. -function TARGET:GetTargetVec3(Target) +function TARGET:GetTargetVec3(Target, Average) if Target.Type==TARGET.ObjectType.GROUP then @@ -1140,6 +1144,9 @@ function TARGET:GetTargetVec3(Target) if object and object:IsAlive() then local vec3=object:GetVec3() + if Average then + vec3=object:GetAverageVec3() + end if vec3 then return vec3 @@ -1218,8 +1225,9 @@ end --- Get target coordinate. -- @param #TARGET self -- @param #TARGET.Object Target Target object. +-- @param #boolean Average -- @return Core.Point#COORDINATE Coordinate of the target. -function TARGET:GetTargetCoordinate(Target) +function TARGET:GetTargetCoordinate(Target, Average) if Target.Type==TARGET.ObjectType.COORDINATE then @@ -1229,7 +1237,7 @@ function TARGET:GetTargetCoordinate(Target) else -- Get updated position vector. - local vec3=self:GetTargetVec3(Target) + local vec3=self:GetTargetVec3(Target, Average) -- Update position. This saves us to create a new COORDINATE object each time. if vec3 then @@ -1362,6 +1370,26 @@ function TARGET:GetCoordinate() return nil end +--- Get average coordinate. +-- @param #TARGET self +-- @return Core.Point#COORDINATE Coordinate of the target. +function TARGET:GetAverageCoordinate() + + for _,_target in pairs(self.targets) do + local Target=_target --#TARGET.Object + + local coordinate=self:GetTargetCoordinate(Target,true) + + if coordinate then + return coordinate + end + + end + + self:E(self.lid..string.format("ERROR: Cannot get average coordinate of target %s", tostring(self.name))) + return nil +end + --- Get category. -- @param #TARGET self -- @return #string Target category. See `TARGET.Category.X`, where `X=AIRCRAFT, GROUND`. diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index e8e2e6fb9..906331fa4 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -1019,9 +1019,9 @@ function GROUP:GetVec2() end ---- Returns the current Vec3 vector of the first DCS Unit in the GROUP. +--- Returns the current Vec3 vector of the first Unit in the GROUP. -- @param #GROUP self --- @return DCS#Vec3 Current Vec3 of the first DCS Unit of the GROUP. +-- @return DCS#Vec3 Current Vec3 of the first Unit of the GROUP or nil if cannot be found. function GROUP:GetVec3() -- Get first unit. @@ -1036,6 +1036,37 @@ function GROUP:GetVec3() 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. +function GROUP:GetAverageVec3() + local units = self:GetUnits() or {} + -- Init. + local x=0 ; local y=0 ; local z=0 ; local n=0 + -- Loop over all units. + for _,unit in pairs(units) do + local vec3=nil --DCS#Vec3 + if unit and unit:IsAlive() then + vec3 = unit:GetVec3() + end + if vec3 then + -- Sum up posits. + x=x+vec3.x + y=y+vec3.y + z=z+vec3.z + -- Increase counter. + n=n+1 + end + end + + if n>0 then + -- Average. + local Vec3={x=x/n, y=y/n, z=z/n} --DCS#Vec3 + return Vec3 + end + return nil +end + --- Returns a POINT_VEC2 object indicating the point in 2D of the first UNIT of the GROUP within the mission. -- @param #GROUP self -- @return Core.Point#POINT_VEC2 The 2D point vector of the first DCS Unit of the GROUP. @@ -1056,6 +1087,21 @@ function GROUP:GetPointVec2() return nil end +--- Returns a COORDINATE object indicating the average position of the GROUP within the mission. +-- @param Wrapper.Group#GROUP self +-- @return Core.Point#COORDINATE The COORDINATE of the GROUP. +function GROUP:GetAverageCoordinate() + local vec3 = self:GetAverageVec3() + if vec3 then + local coord = COORDINATE:NewFromVec3(vec3) + local Heading = self:GetHeading() + coord.Heading = Heading + else + BASE:E( { "Cannot GetAverageCoordinate", Group = self, Alive = self:IsAlive() } ) + return nil + end +end + --- Returns a COORDINATE object indicating the point of the first UNIT of the GROUP within the mission. -- @param Wrapper.Group#GROUP self -- @return Core.Point#COORDINATE The COORDINATE of the GROUP. diff --git a/Moose Development/Moose/Wrapper/Scenery.lua b/Moose Development/Moose/Wrapper/Scenery.lua index 445154037..22b8b627a 100644 --- a/Moose Development/Moose/Wrapper/Scenery.lua +++ b/Moose Development/Moose/Wrapper/Scenery.lua @@ -4,7 +4,7 @@ -- -- ### Author: **FlightControl** -- --- ### Contributions: +-- ### Contributions: **Applevangelist** -- -- === -- @@ -14,6 +14,10 @@ --- @type SCENERY +-- @field #string ClassName +-- @field #string SceneryName +-- @field #DCS.Object SceneryObject +-- @field #number Life0 -- @extends Wrapper.Positionable#POSITIONABLE @@ -27,10 +31,9 @@ -- -- @field #SCENERY SCENERY = { - ClassName = "SCENERY", + ClassName = "SCENERY", } - --- Register scenery object as POSITIONABLE. --@param #SCENERY self --@param #string SceneryName Scenery name. @@ -40,17 +43,54 @@ function SCENERY:Register( SceneryName, SceneryObject ) local self = BASE:Inherit( self, POSITIONABLE:New( SceneryName ) ) self.SceneryName = SceneryName self.SceneryObject = SceneryObject + if self.SceneryObject then + self.Life0 = self.SceneryObject:getLife() + else + self.Life0 = 0 + end return self end ---- Register scenery object as POSITIONABLE. +--- Obtain DCS Object from the SCENERY Object. --@param #SCENERY self --@return #DCS.Object DCS scenery object. function SCENERY:GetDCSObject() return self.SceneryObject end ---- Register scenery object as POSITIONABLE. +--- Get current life points from the SCENERY Object. +--@param #SCENERY self +--@return #number life +function SCENERY:GetLife() + local life = 0 + if self.SceneryObject then + life = self.SceneryObject:getLife() + end + return life +end + +--- Get current initial life points from the SCENERY Object. +--@param #SCENERY self +--@return #number life +function SCENERY:GetLife0() + return self.Life0 or 0 +end + +--- Check if SCENERY Object is alive. +--@param #SCENERY self +--@return #number life +function SCENERY:IsAlive() + return self:GetLife() >= 1 and true or false +end + +--- Check if SCENERY Object is dead. +--@param #SCENERY self +--@return #number life +function SCENERY:IsDead() + return self:GetLife() < 1 and true or false +end + +--- Get the threat level of a SCENERY object. Always 0. --@param #SCENERY self --@return #number Threat level 0. --@return #string "Scenery". @@ -58,39 +98,73 @@ function SCENERY:GetThreatLevel() return 0, "Scenery" end ---- Find a SCENERY object by it's name/id. +--- Find a SCENERY object from its name or id. Since SCENERY isn't registered in the Moose database (just too many objects per map), we need to do a scan first +-- to find the correct object. --@param #SCENERY self ---@param #string name The name/id of the scenery object as taken from the ME. Ex. '595785449' ---@return #SCENERY Scenery Object or nil if not found. -function SCENERY:FindByName(name) - local findAirbase = function () - local airbases = AIRBASE.GetAllAirbases() - for index,airbase in pairs(airbases) do - local surftype = airbase:GetCoordinate():GetSurfaceType() - if surftype ~= land.SurfaceType.SHALLOW_WATER and surftype ~= land.SurfaceType.WATER then - return airbase:GetCoordinate() - end - end - return nil - end - - local sceneryScan = function (scancoord) - if scancoord ~= nil then - local _,_,sceneryfound,_,_,scenerylist = scancoord:ScanObjects(200, false, false, true) - if sceneryfound == true then - scenerylist[1].id_ = name - SCENERY.SceneryObject = SCENERY:Register(scenerylist[1].id_, scenerylist[1]) - return SCENERY.SceneryObject +--@param #string Name The name/id of the scenery object as taken from the ME. Ex. '595785449' +--@param Core.Point#COORDINATE Coordinate Where to find the scenery object +--@param #number Radius (optional) Search radius around coordinate, defaults to 100 +--@return #SCENERY Scenery Object or `nil` if it cannot be found +function SCENERY:FindByName(Name, Coordinate, Radius) + + local radius = Radius or 100 + local name = Name or "unknown" + local scenery = nil + + --- + -- @param Core.Point#COORDINATE coordinate + -- @param #number radius + -- @param #string name + local function SceneryScan(coordinate, radius, name) + if coordinate ~= nil then + local scenerylist = coordinate:ScanScenery(radius) + local rscenery = nil + for _,_scenery in pairs(scenerylist) do + local scenery = _scenery -- Wrapper.Scenery#SCENERY + if tostring(scenery.SceneryName) == tostring(name) then + rscenery = scenery + break + end end + return rscenery end return nil end - if SCENERY.SceneryObject then - SCENERY.SceneryObject.SceneryObject.id_ = name - SCENERY.SceneryObject.SceneryName = name - return SCENERY:Register(SCENERY.SceneryObject.SceneryObject.id_, SCENERY.SceneryObject.SceneryObject) - else - return sceneryScan(findAirbase()) + if Coordinate then + scenery = SceneryScan(Coordinate, radius, name) end + + return scenery +end + +--- Find a SCENERY object from its name or id. Since SCENERY isn't registered in the Moose database (just too many objects per map), we need to do a scan first +-- to find the correct object. +--@param #SCENERY self +--@param #string Name The name or id of the scenery object as taken from the ME. Ex. '595785449' +--@param Core.Zone#ZONE Zone Where to find the scenery object. Can be handed as zone name. +--@param #number Radius (optional) Search radius around coordinate, defaults to 100 +--@return #SCENERY Scenery Object or `nil` if it cannot be found +function SCENERY:FindByNameInZone(Name, Zone, Radius) + local radius = Radius or 100 + local name = Name or "unknown" + if type(Zone) == "string" then + Zone = ZONE:FindByName(Zone) + end + local coordinate = Zone:GetCoordinate() + return self:FindByName(Name,coordinate,Radius) +end + +--- Find a SCENERY object from its zone name. Since SCENERY isn't registered in the Moose database (just too many objects per map), we need to do a scan first +-- to find the correct object. +--@param #SCENERY self +--@param #string ZoneName The name of the scenery zone as created with a right-click on the map in the mission editor and select "assigned to...". Can be handed over as ZONE object. +--@return #SCENERY Scenery Object or `nil` if it cannot be found +function SCENERY:FindByZoneName( ZoneName ) + local zone = ZoneName + if type(ZoneName) == "string" then + zone = ZONE:FindByName(ZoneName) + end + local _id = zone:GetProperty('OBJECT ID') + return self:FindByName(_id, zone:GetCoordinate()) end diff --git a/Moose Development/Moose/Wrapper/Static.lua b/Moose Development/Moose/Wrapper/Static.lua index 08c9a0bce..fef0b098d 100644 --- a/Moose Development/Moose/Wrapper/Static.lua +++ b/Moose Development/Moose/Wrapper/Static.lua @@ -55,9 +55,33 @@ STATIC = { function STATIC:Register( StaticName ) local self = BASE:Inherit( self, POSITIONABLE:New( StaticName ) ) self.StaticName = StaticName + + local DCSStatic = StaticObject.getByName( self.StaticName ) + if DCSStatic then + local Life0 = DCSStatic:getLife() or 1 + self.Life0 = Life0 + end + return self end +--- Get initial life points +-- @param #STATIC self +-- @return #number lifepoints +function STATIC:GetLife0() + return self.Life0 or 1 +end + +--- Get current life points +-- @param #STATIC self +-- @return #number lifepoints or nil +function STATIC:GetLife() + local DCSStatic = StaticObject.getByName( self.StaticName ) + if DCSStatic then + return DCSStatic:getLife() or 1 + end + return nil +end --- Finds a STATIC from the _DATABASE using a DCSStatic object. -- @param #STATIC self