From 2e66a854b1b849786d0490d80ece8d9d83dd8338 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 1 May 2021 00:55:43 +0200 Subject: [PATCH 001/111] Events and Templates --- Moose Development/Moose/Core/Event.lua | 74 +++++-- Moose Development/Moose/DCS.lua | 11 ++ Moose Development/Moose/Modules.lua | 1 + .../Moose/Utilities/Templates.lua | 183 ++++++++++++++++++ Moose Setup/Moose.files | 1 + 5 files changed, 255 insertions(+), 15 deletions(-) create mode 100644 Moose Development/Moose/Utilities/Templates.lua diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 07ddcfdf1..e243d2266 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -236,11 +236,11 @@ EVENTS = { RemoveUnit = world.event.S_EVENT_REMOVE_UNIT, PlayerEnterAircraft = world.event.S_EVENT_PLAYER_ENTER_AIRCRAFT, -- Added with DCS 2.5.6 - DetailedFailure = world.event.S_EVENT_DETAILED_FAILURE or -1, --We set this to -1 for backward compatibility to DCS 2.5.5 and earlier - Kill = world.event.S_EVENT_KILL or -1, - Score = world.event.S_EVENT_SCORE or -1, - UnitLost = world.event.S_EVENT_UNIT_LOST or -1, - LandingAfterEjection = world.event.S_EVENT_LANDING_AFTER_EJECTION or -1, + DetailedFailure = world.event.S_EVENT_DETAILED_FAILURE or -1, --We set this to -1 for backward compatibility to DCS 2.5.5 and earlier + Kill = world.event.S_EVENT_KILL or -1, + Score = world.event.S_EVENT_SCORE or -1, + UnitLost = world.event.S_EVENT_UNIT_LOST or -1, + LandingAfterEjection = 31, --world.event.S_EVENT_LANDING_AFTER_EJECTION or -1, -- Added with DCS 2.7.0 ParatrooperLanding = world.event.S_EVENT_PARATROOPER_LENDING or -1, DiscardChairAfterEjection = world.event.S_EVENT_DISCARD_CHAIR_AFTER_EJECTION or -1, @@ -524,7 +524,7 @@ local _EVENTMETA = { Event = "OnEventUnitLost", Text = "S_EVENT_UNIT_LOST" }, - [EVENTS.LandingAfterEjection] = { + [world.event.S_EVENT_LANDING_AFTER_EJECTION] = { Order = 1, Event = "OnEventLandingAfterEjection", Text = "S_EVENT_LANDING_AFTER_EJECTION" @@ -578,6 +578,10 @@ function EVENT:New() -- Add world event handler. self.EventHandler = world.addEventHandler(self) + for _,Event in pairs(self.Events) do + self:Init(Event,EventClass) + end + return self end @@ -588,7 +592,9 @@ end -- @param Core.Base#BASE EventClass The class object for which events are handled. -- @return #EVENT.Events function EVENT:Init( EventID, EventClass ) - self:F3( { _EVENTMETA[EventID].Text, EventClass } ) + self:I( { _EVENTMETA[EventID].Text, EventClass } ) + + env.info("FF EVENT.Init ID="..EventID) if not self.Events[EventID] then -- Create a WEAK table to ensure that the garbage collector is cleaning the event links when the object usage is cleaned. @@ -983,6 +989,8 @@ end -- @param #EVENTDATA Event Event data table. function EVENT:onEvent( Event ) + env.info("FF some event") + local ErrorHandler = function( errmsg ) env.info( "Error in SCHEDULER function:" .. errmsg ) @@ -1000,17 +1008,37 @@ function EVENT:onEvent( Event ) -- Check if this is a known event? if EventMeta then - if self and - self.Events and - self.Events[Event.id] and - self.MissionEnd == false and - ( Event.initiator ~= nil or ( Event.initiator == nil and Event.id ~= EVENTS.PlayerLeaveUnit ) ) then + env.info("FF some event 100 ID="..tostring(Event.id)) + + if Event.id==31 then + env.info("FF got event 31") + local initiator=Event.initiator~=nil + env.info(string.format("FF got event 31 initiator=%s", tostring(initiator))) + if self then + env.info(string.format("FF got event 31 self=true")) + end + if self.Events then + env.info(string.format("FF got event 31 self.Events=true")) + end + if self.Events[Event.id] then + env.info(string.format("FF got event 31 self.Events[Event.id]=true")) + else + env.info(string.format("FF NO event 31 self.Events[Event.id]=FALSE!")) + end + end + + --if self and self.Events and self.Events[Event.id] and self.MissionEnd==false and (Event.initiator~=nil or (Event.initiator==nil and Event.idss~=EVENTS.PlayerLeaveUnit)) then + if self and self.Events and self.MissionEnd==false and (Event.initiator~=nil or (Event.initiator==nil and Event.idss~=EVENTS.PlayerLeaveUnit)) then if Event.id and Event.id == EVENTS.MissionEnd then self.MissionEnd = true end - if Event.initiator then + env.info("FF some event 150") + + if Event.initiator then + + env.info("FF some event 200") Event.IniObjectCategory = Event.initiator:getCategory() @@ -1039,9 +1067,14 @@ function EVENT:onEvent( Event ) end if Event.IniObjectCategory == Object.Category.STATIC then + + env.info("FF some event 300") + if Event.id==31 then - --env.info("FF event 31") - -- Event.initiator is a Static object representing the pilot. But getName() error due to DCS bug. + + env.info("FF event 31") + + -- Event.initiator is a Static object representing the pilot. But getName() errors due to DCS bug. Event.IniDCSUnit = Event.initiator local ID=Event.initiator.id_ Event.IniDCSUnitName = string.format("Ejected Pilot ID %s", tostring(ID)) @@ -1049,6 +1082,17 @@ function EVENT:onEvent( Event ) Event.IniCoalition = 0 Event.IniCategory = 0 Event.IniTypeName = "Ejected Pilot" + + local static=Event.initiator + local vec3=static:getPoint() + + local template=TEMPLATE.GetGround(TEMPLATE.Ground.SoldierM4, "Ejected Pilot", country.id.USA, vec3) + local group=_DATABASE:Spawn(template) + Event.IniDCSGroup=group:GetDCSObject() + Event.IniCoalition=group:GetCoalition() + Event.IniCategory=group:GetCategory() + + else Event.IniDCSUnit = Event.initiator Event.IniDCSUnitName = Event.IniDCSUnit:getName() diff --git a/Moose Development/Moose/DCS.lua b/Moose Development/Moose/DCS.lua index 92052d5cf..01661a678 100644 --- a/Moose Development/Moose/DCS.lua +++ b/Moose Development/Moose/DCS.lua @@ -295,6 +295,17 @@ do -- country -- @field QATAR -- @field OMAN -- @field UNITED_ARAB_EMIRATES + -- @field SOUTH_AFRICA + -- @field CUBA + -- @field PORTUGAL + -- @field GDR + -- @field LEBANON + -- @field CJTF_BLUE + -- @field CJTF_RED + -- @field UN_PEACEKEEPERS + -- @field Argentinia + -- @field Cyprus + -- @field Slovenia country = {} --#country diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 62d483052..c4bb9eb87 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -2,6 +2,7 @@ __Moose.Include( 'Scripts/Moose/Utilities/Enums.lua' ) __Moose.Include( 'Scripts/Moose/Utilities/Routines.lua' ) __Moose.Include( 'Scripts/Moose/Utilities/Utils.lua' ) __Moose.Include( 'Scripts/Moose/Utilities/Profiler.lua' ) +__Moose.Include( 'Scripts/Moose/Utilities/Templates.lua' ) __Moose.Include( 'Scripts/Moose/Core/Base.lua' ) __Moose.Include( 'Scripts/Moose/Core/UserFlag.lua' ) diff --git a/Moose Development/Moose/Utilities/Templates.lua b/Moose Development/Moose/Utilities/Templates.lua new file mode 100644 index 000000000..51885c4a9 --- /dev/null +++ b/Moose Development/Moose/Utilities/Templates.lua @@ -0,0 +1,183 @@ +--- **Utils** Templates +-- +-- DCS unit templates +-- +-- @module Utilities.Templates +-- @image MOOSE.JPG + +--- TEMPLATE class. +-- @type TEMPLATE +-- @field #string ClassName Name of the class. + +--- *Templates* +-- +-- === +-- +-- ![Banner Image](..\Presentations\Utilities\PROFILER_Main.jpg) +-- +-- Get DCS templates from thin air. +-- +-- # Ground Units +-- +-- Ground units. +-- +-- # Naval Units +-- +-- Ships are not implemented yet. +-- +-- # Aircraft +-- +-- ## Airplanes +-- +-- Airplanes are not implemented yet. +-- +-- ## Helicopters +-- +-- Helicopters are not implemented yet. +-- +-- @field #TEMPLATE +TEMPLATE = { + ClassName = "TEMPLATE", + Ground = {}, + Naval = {}, + Airplane = {}, + Helicopter = {}, +} + +--- Pattern steps. +-- @type TEMPLATE.Ground +-- @param #string InfantryAK +TEMPLATE.Ground={ + InfantryAK="Infantry AK", + ParatrooperAKS74="Paratrooper AKS-74", + ParatrooperRPG16="Paratrooper RPG-16", + SoldierWWIIUS="soldier_wwii_us", + InfantryM248="Infantry M249", + SoldierM4="Soldier M4", +} + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Start/Stop Profiler +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Get template for ground units. +-- @param #string TypeName Type name of the unit(s) in the groups. See `TEMPLATE.Ground`. +-- @param #string GroupName Name of the spawned group. **Must be unique!** +-- @param #number CountryID Country ID. Default `country.id.USA`. Coalition is automatically determined by the one the country belongs to. +-- @param DCS#Vec3 Vec3 Position of the group. +-- @param #number Nunits Number of units. Default 1. +-- @param #number Radius Spawn radius for additonal units in meters. Default 50 m. +-- @return #table Template Template table. +function TEMPLATE.GetGround(TypeName, GroupName, CountryID, Vec3, Nunits, Radius) + + local template=UTILS.DeepCopy(TEMPLATE.GenericGround) + + template.name=GroupName or "Ground-1" + + -- These are additional entries required by the MOOSE _DATABASE:Spawn() function. + template.CountryID=country.id.USA + template.CoalitionID=coalition.getCountryCoalition(template.CountryID) + template.CategoryID=Unit.Category.GROUND_UNIT + + template.units[1].type=TypeName or "Infantry AK" + template.units[1].name=GroupName.."-1" + + if Vec3 then + TEMPLATE.SetPositionFromVec3(template, Vec3) + end + + return template +end + +TEMPLATE.GenericGround= +{ + ["visible"] = false, + ["tasks"] = {}, -- end of ["tasks"] + ["uncontrollable"] = false, + ["task"] = "Ground Nothing", + ["route"] = + { + ["spans"] = {}, -- end of ["spans"] + ["points"] = + { + [1] = + { + ["alt"] = 0, + ["type"] = "Turning Point", + ["ETA"] = 0, + ["alt_type"] = "BARO", + ["formation_template"] = "", + ["y"] = 0, + ["x"] = 0, + ["ETA_locked"] = true, + ["speed"] = 0, + ["action"] = "Off Road", + ["task"] = + { + ["id"] = "ComboTask", + ["params"] = + { + ["tasks"] = + { + }, -- end of ["tasks"] + }, -- end of ["params"] + }, -- end of ["task"] + ["speed_locked"] = true, + }, -- end of [1] + }, -- end of ["points"] + }, -- end of ["route"] + ["groupId"] = nil, + ["hidden"] = false, + ["units"] = + { + [1] = + { + ["transportable"] = + { + ["randomTransportable"] = false, + }, -- end of ["transportable"] + ["skill"] = "Average", + ["type"] = "Infantry AK", + ["unitId"] = nil, + ["y"] = 0, + ["x"] = 0, + ["name"] = "Infantry AK-47 Rus", + ["heading"] = 0, + ["playerCanDrive"] = false, + }, -- end of [1] + }, -- end of ["units"] + ["y"] = 0, + ["x"] = 0, + ["name"] = "Infantry AK-47 Rus", + ["start_time"] = 0, +} + +--- Set the position of the template. +-- @param #table Template The template to be modified. +-- @param DCS#Vec2 Vec2 2D Position vector with x and y components of the group. +function TEMPLATE.SetPositionFromVec2(Template, Vec2) + + Template.x=Vec2.x + Template.y=Vec2.y + + for _,unit in pairs(Template.units) do + unit.x=Vec2.x + unit.y=Vec2.y + end + + Template.route.points[1].x=Vec2.x + Template.route.points[1].y=Vec2.y + Template.route.points[1].alt=0 --TODO: Use land height. + +end + +--- Set the position of the template. +-- @param #table Template The template to be modified. +-- @param DCS#Vec3 Vec3 Position vector of the group. +function TEMPLATE.SetPositionFromVec3(Template, Vec3) + + local Vec2={x=Vec3.x, y=Vec3.z} + + TEMPLATE.SetPositionFromVec2(Template, Vec2) + +end \ No newline at end of file diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index f64bb2ff3..79c9b120e 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -3,6 +3,7 @@ Utilities/Routines.lua Utilities/Utils.lua Utilities/Enums.lua Utilities/Profiler.lua +Utilities/Templates.lua Core/Base.lua Core/UserFlag.lua From d1f3e3f4bbd654d6e4c990622a5e1e2b62afd9d2 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 2 May 2021 00:08:17 +0200 Subject: [PATCH 002/111] Update Templates.lua --- .../Moose/Utilities/Templates.lua | 220 +++++++++++++++--- 1 file changed, 194 insertions(+), 26 deletions(-) diff --git a/Moose Development/Moose/Utilities/Templates.lua b/Moose Development/Moose/Utilities/Templates.lua index 51885c4a9..df7c3ff0b 100644 --- a/Moose Development/Moose/Utilities/Templates.lua +++ b/Moose Development/Moose/Utilities/Templates.lua @@ -89,6 +89,42 @@ function TEMPLATE.GetGround(TypeName, GroupName, CountryID, Vec3, Nunits, Radius return template end + + +--- Set the position of the template. +-- @param #table Template The template to be modified. +-- @param DCS#Vec2 Vec2 2D Position vector with x and y components of the group. +function TEMPLATE.SetPositionFromVec2(Template, Vec2) + + Template.x=Vec2.x + Template.y=Vec2.y + + for _,unit in pairs(Template.units) do + unit.x=Vec2.x + unit.y=Vec2.y + end + + Template.route.points[1].x=Vec2.x + Template.route.points[1].y=Vec2.y + Template.route.points[1].alt=0 --TODO: Use land height. + +end + +--- Set the position of the template. +-- @param #table Template The template to be modified. +-- @param DCS#Vec3 Vec3 Position vector of the group. +function TEMPLATE.SetPositionFromVec3(Template, Vec3) + + local Vec2={x=Vec3.x, y=Vec3.z} + + TEMPLATE.SetPositionFromVec2(Template, Vec2) + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Generic Ground Template +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + TEMPLATE.GenericGround= { ["visible"] = false, @@ -152,32 +188,164 @@ TEMPLATE.GenericGround= ["start_time"] = 0, } ---- Set the position of the template. --- @param #table Template The template to be modified. --- @param DCS#Vec2 Vec2 2D Position vector with x and y components of the group. -function TEMPLATE.SetPositionFromVec2(Template, Vec2) +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Generic Ship Template +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - Template.x=Vec2.x - Template.y=Vec2.y - - for _,unit in pairs(Template.units) do - unit.x=Vec2.x - unit.y=Vec2.y - end - - Template.route.points[1].x=Vec2.x - Template.route.points[1].y=Vec2.y - Template.route.points[1].alt=0 --TODO: Use land height. - -end +TEMPLATE.GenericNaval= +{ + ["visible"] = false, + ["tasks"] = {}, -- end of ["tasks"] + ["uncontrollable"] = false, + ["route"] = + { + ["points"] = + { + [1] = + { + ["alt"] = 0, + ["type"] = "Turning Point", + ["ETA"] = 0, + ["alt_type"] = "BARO", + ["formation_template"] = "", + ["y"] = 0, + ["x"] = 0, + ["ETA_locked"] = true, + ["speed"] = 0, + ["action"] = "Turning Point", + ["task"] = + { + ["id"] = "ComboTask", + ["params"] = + { + ["tasks"] = + { + }, -- end of ["tasks"] + }, -- end of ["params"] + }, -- end of ["task"] + ["speed_locked"] = true, + }, -- end of [1] + }, -- end of ["points"] + }, -- end of ["route"] + ["groupId"] = nil, + ["hidden"] = false, + ["units"] = + { + [1] = + { + ["transportable"] = + { + ["randomTransportable"] = false, + }, -- end of ["transportable"] + ["skill"] = "Average", + ["type"] = "TICONDEROG", + ["unitId"] = nil, + ["y"] = 0, + ["x"] = 0, + ["name"] = "Naval-1-1", + ["heading"] = 0, + ["modulation"] = 0, + ["frequency"] = 127500000, + }, -- end of [1] + }, -- end of ["units"] + ["y"] = 0, + ["x"] = 0, + ["name"] = "Naval-1", + ["start_time"] = 0, +} ---- Set the position of the template. --- @param #table Template The template to be modified. --- @param DCS#Vec3 Vec3 Position vector of the group. -function TEMPLATE.SetPositionFromVec3(Template, Vec3) +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Generic Ship Template +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +TEMPLATE.GenericHelicopter= +{ + ["modulation"] = 0, + ["tasks"] = {}, -- end of ["tasks"] + ["radioSet"] = false, + ["task"] = "Nothing", + ["uncontrolled"] = false, + ["taskSelected"] = true, + ["route"] = + { + ["points"] = + { + [1] = + { + ["alt"] = 10, + ["action"] = "From Parking Area", + ["alt_type"] = "BARO", + ["speed"] = 41.666666666667, + ["task"] = + { + ["id"] = "ComboTask", + ["params"] = + { + ["tasks"] = + { + }, -- end of ["tasks"] + }, -- end of ["params"] + }, -- end of ["task"] + ["type"] = "TakeOffParking", + ["ETA"] = 0, + ["ETA_locked"] = true, + ["y"] = 618351.087765, + ["x"] = -356168.27327001, + ["formation_template"] = "", + ["airdromeId"] = 22, + ["speed_locked"] = true, + }, -- end of [1] + }, -- end of ["points"] + }, -- end of ["route"] + ["groupId"] = nil, + ["hidden"] = false, + ["units"] = + { + [1] = + { + ["alt"] = 10, + ["alt_type"] = "BARO", + ["livery_id"] = "USA X Black", + ["skill"] = "High", + ["parking"] = "4", + ["ropeLength"] = 15, + ["speed"] = 41.666666666667, + ["type"] = "AH-1W", + ["unitId"] = 8, + ["psi"] = 0, + ["parking_id"] = "10", + ["x"] = -356168.27327001, + ["name"] = "Rotary-1-1", + ["payload"] = + { + ["pylons"] = + { + }, -- end of ["pylons"] + ["fuel"] = "1250.0", + ["flare"] = 30, + ["chaff"] = 30, + ["gun"] = 100, + }, -- end of ["payload"] + ["y"] = 618351.087765, + ["heading"] = 0, + ["callsign"] = + { + [1] = 2, + [2] = 1, + [3] = 1, + ["name"] = "Springfield11", + }, -- end of ["callsign"] + ["onboard_num"] = "050", + }, -- end of [1] + }, -- end of ["units"] + ["y"] = 0, + ["x"] = 0, + ["name"] = "Rotary-1", + ["communication"] = true, + ["start_time"] = 0, + ["frequency"] = 127.5, +} +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - local Vec2={x=Vec3.x, y=Vec3.z} - - TEMPLATE.SetPositionFromVec2(Template, Vec2) - -end \ No newline at end of file From 20fe2ee5053ba72852e5f680c645f7b77fa1d138 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 10 May 2021 08:33:15 +0200 Subject: [PATCH 003/111] Update Event.lua --- Moose Development/Moose/Core/Event.lua | 30 +------------------------- 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 17baca821..cac2433e2 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -1008,38 +1008,14 @@ function EVENT:onEvent( Event ) -- Check if this is a known event? if EventMeta then - env.info("FF some event 100 ID="..tostring(Event.id)) - - if Event.id==31 then - env.info("FF got event 31") - local initiator=Event.initiator~=nil - env.info(string.format("FF got event 31 initiator=%s", tostring(initiator))) - if self then - env.info(string.format("FF got event 31 self=true")) - end - if self.Events then - env.info(string.format("FF got event 31 self.Events=true")) - end - if self.Events[Event.id] then - env.info(string.format("FF got event 31 self.Events[Event.id]=true")) - else - env.info(string.format("FF NO event 31 self.Events[Event.id]=FALSE!")) - end - end - - --if self and self.Events and self.Events[Event.id] and self.MissionEnd==false and (Event.initiator~=nil or (Event.initiator==nil and Event.idss~=EVENTS.PlayerLeaveUnit)) then - if self and self.Events and self.MissionEnd==false and (Event.initiator~=nil or (Event.initiator==nil and Event.idss~=EVENTS.PlayerLeaveUnit)) then + if self and self.Events and self.Events[Event.id] and self.MissionEnd==false and (Event.initiator~=nil or (Event.initiator==nil and Event.idss~=EVENTS.PlayerLeaveUnit)) then if Event.id and Event.id == EVENTS.MissionEnd then self.MissionEnd = true end - env.info("FF some event 150") - if Event.initiator then - env.info("FF some event 200") - Event.IniObjectCategory = Event.initiator:getCategory() if Event.IniObjectCategory == Object.Category.UNIT then @@ -1068,12 +1044,8 @@ function EVENT:onEvent( Event ) if Event.IniObjectCategory == Object.Category.STATIC then - env.info("FF some event 300") - if Event.id==31 then - env.info("FF event 31") - -- Event.initiator is a Static object representing the pilot. But getName() errors due to DCS bug. Event.IniDCSUnit = Event.initiator local ID=Event.initiator.id_ From 59e4f487264fc402ce80b113f91492913d933f74 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 10 May 2021 17:47:42 +0200 Subject: [PATCH 004/111] COORDINATES and ZONES - Quad zones defined in the ME are now registered as ZONE_POLYGON_BASE - Added draw functions to COORDINATE (line, circle, arrow, rectangle, text) --- Moose Development/Moose/Core/Database.lua | 69 +++++- Moose Development/Moose/Core/Event.lua | 6 +- Moose Development/Moose/Core/Fsm.lua | 2 +- Moose Development/Moose/Core/Point.lua | 208 +++++++++++++++-- Moose Development/Moose/Core/Zone.lua | 239 +++++++++++++++++++- Moose Development/Moose/Utilities/Utils.lua | 11 + 6 files changed, 504 insertions(+), 31 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index eafe78140..0d6d2bb35 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -298,24 +298,81 @@ do -- Zones -- @return #DATABASE self function DATABASE:_RegisterZones() - for ZoneID, ZoneData in pairs( env.mission.triggers.zones ) do + for ZoneID, ZoneData in pairs(env.mission.triggers.zones) do local ZoneName = ZoneData.name + + -- Color + local color=ZoneData.color or {1, 0, 0, 0.15} + + -- Create new Zone + local Zone=nil --Core.Zone#ZONE_BASE + + if ZoneData.type==0 then + + --- + -- Circular zone + --- + + self:I(string.format("Register ZONE: %s (Circular)", ZoneName)) + + Zone=ZONE:New(ZoneName) + + else - self:I( { "Register ZONE:", Name = ZoneName } ) - local Zone = ZONE:New( ZoneName ) - self.ZONENAMES[ZoneName] = ZoneName - self:AddZone( ZoneName, Zone ) + --- + -- Quad-point zone + --- + + self:I(string.format("Register ZONE: %s (Polygon, Quad)", ZoneName)) + + Zone=ZONE_POLYGON_BASE:New(ZoneName, ZoneData.verticies) + + for i,vec2 in pairs(ZoneData.verticies) do + local coord=COORDINATE:NewFromVec2(vec2) + coord:MarkToAll(string.format("%s Point %d", ZoneName, i)) + end + + end + + if Zone then + + -- Debug output. + --self:I({"Register ZONE: %s (", Name = ZoneName}) + + Zone.Color=color + + + -- Store in DB. + self.ZONENAMES[ZoneName] = ZoneName + + -- Add zone. + self:AddZone(ZoneName, Zone) + + end + end + -- Polygon zones defined by late activated groups. for ZoneGroupName, ZoneGroup in pairs( self.GROUPS ) do if ZoneGroupName:match("#ZONE_POLYGON") then + local ZoneName1 = ZoneGroupName:match("(.*)#ZONE_POLYGON") local ZoneName2 = ZoneGroupName:match(".*#ZONE_POLYGON(.*)") local ZoneName = ZoneName1 .. ( ZoneName2 or "" ) - self:I( { "Register ZONE_POLYGON:", Name = ZoneName } ) + -- Debug output + self:I(string.format("Register ZONE: %s (Polygon)", ZoneName)) + + -- Create a new polygon zone. local Zone_Polygon = ZONE_POLYGON:New( ZoneName, ZoneGroup ) + + -- Set color. + Zone_Polygon:SetColor({1, 0, 0}, 0.15) + + -- Store name in DB. self.ZONENAMES[ZoneName] = ZoneName + + -- Add zone to DB. self:AddZone( ZoneName, Zone_Polygon ) end end diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index cac2433e2..85384ac02 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -592,9 +592,7 @@ end -- @param Core.Base#BASE EventClass The class object for which events are handled. -- @return #EVENT.Events function EVENT:Init( EventID, EventClass ) - self:I( { _EVENTMETA[EventID].Text, EventClass } ) - - env.info("FF EVENT.Init ID="..EventID) + self:F( { _EVENTMETA[EventID].Text, EventClass } ) if not self.Events[EventID] then -- Create a WEAK table to ensure that the garbage collector is cleaning the event links when the object usage is cleaned. @@ -989,8 +987,6 @@ end -- @param #EVENTDATA Event Event data table. function EVENT:onEvent( Event ) - env.info("FF some event") - local ErrorHandler = function( errmsg ) env.info( "Error in SCHEDULER function:" .. errmsg ) diff --git a/Moose Development/Moose/Core/Fsm.lua b/Moose Development/Moose/Core/Fsm.lua index 80dfcb0c5..644a04d63 100644 --- a/Moose Development/Moose/Core/Fsm.lua +++ b/Moose Development/Moose/Core/Fsm.lua @@ -540,7 +540,7 @@ do -- FSM --- Returns a table with the scores defined. -- @param #FSM self - -- @param #table Scores. + -- @return #table Scores. function FSM:GetScores() return self._Scores or {} end diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 26aaa9dd7..59465fdd2 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -164,6 +164,7 @@ do -- COORDINATE -- -- * @{#COORDINATE.WaypointAir}(): Build an air route point. -- * @{#COORDINATE.WaypointGround}(): Build a ground route point. + -- * @{#COORDINATE.WaypointNaval}(): Build a naval route point. -- -- Route points can be used in the Route methods of the @{Wrapper.Group#GROUP} class. -- @@ -183,10 +184,18 @@ do -- COORDINATE -- -- ## 9) Coordinate text generation -- - -- -- * @{#COORDINATE.ToStringBR}(): Generates a Bearing & Range text in the format of DDD for DI where DDD is degrees and DI is distance. -- * @{#COORDINATE.ToStringLL}(): Generates a Latutude & Longutude text. -- + -- ## 10) Drawings on F10 map + -- + -- * @{#COORDINATE.CircleToAll}(): Draw a circle on the F10 map. + -- * @{#COORDINATE.LineToAll}(): Draw a line on the F10 map. + -- * @{#COORDINATE.RectToAll}(): Draw a rectangle on the F10 map. + -- * @{#COORDINATE.QuadToAll}(): Draw a shape with four points on the F10 map. + -- * @{#COORDINATE.TextToAll}(): Write some text on the F10 map. + -- * @{#COORDINATE.ArrowToAll}(): Draw an arrow on the F10 map. + -- -- @field #COORDINATE COORDINATE = { ClassName = "COORDINATE", @@ -1984,13 +1993,13 @@ do -- COORDINATE -- @param #COORDINATE self -- @param #COORDINATE Endpoint COORDIANTE to where the line is drawn. -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. - -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). -- @param #number Alpha Transparency [0,1]. Default 1. + -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. -- @param #string Text (Optional) Text displayed when mark is added. Default none. - -- @return #number The resulting Mark ID which is a number. - function COORDINATE:LineToAll(Endpoint, Coalition, LineType, Color, Alpha, ReadOnly, Text) + -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. + function COORDINATE:LineToAll(Endpoint, Coalition, Color, Alpha, LineType, ReadOnly, Text) local MarkID = UTILS.GetMarkID() if ReadOnly==nil then ReadOnly=false @@ -2007,18 +2016,17 @@ do -- COORDINATE --- Circle to all. -- Creates a circle on the map with a given radius, color, fill color, and outline. -- @param #COORDINATE self - -- @param #COORDINATE Center COORDIANTE of the center of the circle. -- @param #numberr Radius Radius in meters. Default 1000 m. -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. - -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). -- @param #number Alpha Transparency [0,1]. Default 1. - -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red (default). - -- @param #number FillAlpha Transparency [0,1]. Default 0.5. + -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. + -- @param #number FillAlpha Transparency [0,1]. Default 0.15. + -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. -- @param #string Text (Optional) Text displayed when mark is added. Default none. - -- @return #number The resulting Mark ID which is a number. - function COORDINATE:CircleToAll(Radius, Coalition, LineType, Color, Alpha, FillColor, FillAlpha, ReadOnly, Text) + -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. + function COORDINATE:CircleToAll(Radius, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text) local MarkID = UTILS.GetMarkID() if ReadOnly==nil then ReadOnly=false @@ -2029,14 +2037,188 @@ do -- COORDINATE Color=Color or {1,0,0} Color[4]=Alpha or 1.0 LineType=LineType or 1 - FillColor=FillColor or {1,0,0} - FillColor[4]=FillAlpha or 0.5 + FillColor=FillColor or Color + FillColor[4]=FillAlpha or 0.15 trigger.action.circleToAll(Coalition, MarkID, vec3, Radius, Color, FillColor, LineType, ReadOnly, Text or "") return MarkID end end -- Markings - + + --- Rectangle to all. Creates a rectangle on the map from the COORDINATE in one corner to the end COORDINATE in the opposite corner. + -- Creates a line on the F10 map from one point to another. + -- @param #COORDINATE self + -- @param #COORDINATE Endpoint COORDIANTE in the opposite corner. + -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. + -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). + -- @param #number Alpha Transparency [0,1]. Default 1. + -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. + -- @param #number FillAlpha Transparency [0,1]. Default 0.15. + -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. + -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. + -- @param #string Text (Optional) Text displayed when mark is added. Default none. + -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. + function COORDINATE:RectToAll(Endpoint, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text) + local MarkID = UTILS.GetMarkID() + if ReadOnly==nil then + ReadOnly=false + end + local vec3=Endpoint:GetVec3() + Coalition=Coalition or -1 + Color=Color or {1,0,0} + Color[4]=Alpha or 1.0 + LineType=LineType or 1 + FillColor=FillColor or Color + FillColor[4]=FillAlpha or 0.15 + trigger.action.rectToAll(Coalition, MarkID, self:GetVec3(), vec3, Color, FillColor, LineType, ReadOnly, Text or "") + return MarkID + end + + --- Creates a shape defined by 4 points on the F10 map. The first point is the current COORDINATE. The remaining three points need to be specified. + -- @param #COORDINATE self + -- @param #COORDINATE Coord2 Second COORDIANTE of the quad shape. + -- @param #COORDINATE Coord3 Third COORDIANTE of the quad shape. + -- @param #COORDINATE Coord4 Fourth COORDIANTE of the quad shape. + -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. + -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). + -- @param #number Alpha Transparency [0,1]. Default 1. + -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. + -- @param #number FillAlpha Transparency [0,1]. Default 0.15. + -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. + -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. + -- @param #string Text (Optional) Text displayed when mark is added. Default none. + -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. + function COORDINATE:QuadToAll(Coord2, Coord3, Coord4, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text) + local MarkID = UTILS.GetMarkID() + if ReadOnly==nil then + ReadOnly=false + end + local point1=self:GetVec3() + local point2=Coord2:GetVec3() + local point3=Coord3:GetVec3() + local point4=Coord4:GetVec3() + Coalition=Coalition or -1 + Color=Color or {1,0,0} + Color[4]=Alpha or 1.0 + LineType=LineType or 1 + FillColor=FillColor or Color + FillColor[4]=FillAlpha or 0.15 + trigger.action.quadToAll(Coalition, MarkID, self:GetVec3(), point2, point3, point4, Color, FillColor, LineType, ReadOnly, Text or "") + return MarkID + end + + --- Creates a free form shape on the F10 map. The first point is the current COORDINATE. The remaining points need to be specified. + -- **NOTE**: A free form polygon must have **at least three points** in total and currently only **up to 10 points** in total are supported. + -- @param #COORDINATE self + -- @param #table Coordinates Table of coordinates of the remaining points of the shape. + -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. + -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). + -- @param #number Alpha Transparency [0,1]. Default 1. + -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. + -- @param #number FillAlpha Transparency [0,1]. Default 0.15. + -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. + -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. + -- @param #string Text (Optional) Text displayed when mark is added. Default none. + -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. + function COORDINATE:MarkupToAllFreeForm(Coordinates, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text) + local MarkID = UTILS.GetMarkID() + if ReadOnly==nil then + ReadOnly=false + end + Coalition=Coalition or -1 + Color=Color or {1,0,0} + Color[4]=Alpha or 1.0 + LineType=LineType or 1 + FillColor=FillColor or Color + FillColor[4]=FillAlpha or 0.15 + + local vecs={} + table.insert(vecs, self:GetVec3()) + for _,coord in ipairs(Coordinates) do + table.insert(vecs, coord:GetVec3()) + end + + if #vecs<3 then + self:E("ERROR: A free form polygon needs at least three points!") + elseif #vecs==3 then + trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], Color, FillColor, LineType, ReadOnly, Text or "") + elseif #vecs==4 then + trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], Color, FillColor, LineType, ReadOnly, Text or "") + elseif #vecs==5 then + trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], Color, FillColor, LineType, ReadOnly, Text or "") + elseif #vecs==6 then + trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], Color, FillColor, LineType, ReadOnly, Text or "") + elseif #vecs==7 then + trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], Color, FillColor, LineType, ReadOnly, Text or "") + elseif #vecs==8 then + trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], vecs[8], Color, FillColor, LineType, ReadOnly, Text or "") + elseif #vecs==9 then + trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], vecs[8], vecs[9], Color, FillColor, LineType, ReadOnly, Text or "") + elseif #vecs==10 then + trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], vecs[8], vecs[9], vecs[10], Color, FillColor, LineType, ReadOnly, Text or "") + else + self:E("ERROR: Currently a free form polygon can only have 10 points in total!") + -- Unfortunately, unpack(vecs) does not work! So no idea how to generalize this :( + trigger.action.markupToAll(7, Coalition, MarkID, unpack(vecs), Color, FillColor, LineType, ReadOnly, Text or "") + end + + return MarkID + end + + --- Text to all. Creates a text imposed on the map at the COORDINATE. Text scales with the map. + -- @param #COORDINATE self + -- @param #string Text Text displayed on the F10 map. + -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. + -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). + -- @param #number Alpha Transparency [0,1]. Default 1. + -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. + -- @param #number FillAlpha Transparency [0,1]. Default 0.15. + -- @param #number FontSize Font size. + -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. + -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. + function COORDINATE:TextToAll(Text, Coalition, Color, Alpha, FillColor, FillAlpha, FontSize, ReadOnly) + local MarkID = UTILS.GetMarkID() + if ReadOnly==nil then + ReadOnly=false + end + Coalition=Coalition or -1 + Color=Color or {1,0,0} + Color[4]=Alpha or 1.0 + FillColor=FillColor or Color + FillColor[4]=FillAlpha or 0.15 + FontSize=FontSize or 12 + trigger.action.textToAll(Coalition, MarkID, self:GetVec3(), Color, FillColor, FontSize, ReadOnly, Text or "Hello World") + return MarkID + end + + --- Arrow to all. Creates an arrow from the COORDINATE to the endpoint COORDINATE on the F10 map. There is no control over other dimensions of the arrow. + -- @param #COORDINATE self + -- @param #COORDINATE Endpoint COORDINATE where the tip of the arrow is pointing at. + -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. + -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). + -- @param #number Alpha Transparency [0,1]. Default 1. + -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. + -- @param #number FillAlpha Transparency [0,1]. Default 0.15. + -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. + -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. + -- @param #string Text (Optional) Text displayed when mark is added. Default none. + -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. + function COORDINATE:ArrowToAll(Endpoint, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text) + local MarkID = UTILS.GetMarkID() + if ReadOnly==nil then + ReadOnly=false + end + local vec3=Endpoint:GetVec3() + Coalition=Coalition or -1 + Color=Color or {1,0,0} + Color[4]=Alpha or 1.0 + LineType=LineType or 1 + FillColor=FillColor or Color + FillColor[4]=FillAlpha or 0.15 + --trigger.action.textToAll(Coalition, MarkID, self:GetVec3(), Color, FillColor, FontSize, ReadOnly, Text or "Hello World") + trigger.action.arrowToAll(Coalition, MarkID, vec3, self:GetVec3(), Color, FillColor, LineType, ReadOnly, Text or "") + return MarkID + end --- Returns if a Coordinate has Line of Sight (LOS) with the ToCoordinate. -- @param #COORDINATE self diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 8ef630eee..f14619959 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -56,6 +56,8 @@ --- @type ZONE_BASE -- @field #string ZoneName Name of the zone. -- @field #number ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability. +-- @field #number DrawID Unique ID of the drawn zone on the F10 map. +-- @field #table Color Table with four entries, e.g. {1, 0, 0, 0.15}. First three are RGB color code. Fourth is the transparency Alpha value. -- @extends Core.Fsm#FSM @@ -104,7 +106,9 @@ ZONE_BASE = { ClassName = "ZONE_BASE", ZoneName = "", ZoneProbability = 1, - } + DrawID=nil, + Color={} +} --- The ZONE_BASE.BoundingSquare @@ -324,6 +328,76 @@ function ZONE_BASE:BoundZone() end + +--- Set color of zone. +-- @param #ZONE_BASE self +-- @param #table RGBcolor RGB color table. Default `{1, 0, 0}`. +-- @param #number Alpha Transparacy between 0 and 1. Default 0.15. +-- @return #ZONE_BASE self +function ZONE_BASE:SetColor(RGBcolor, Alpha) + + RGBcolor=RGBcolor or {1, 0, 0} + Alpha=Alpha or 0.15 + + self.Color={} + self.Color[1]=RGBcolor[1] + self.Color[2]=RGBcolor[2] + self.Color[3]=RGBcolor[3] + self.Color[4]=Alpha + + return self +end + +--- Get color table of the zone. +-- @param #ZONE_BASE self +-- @return #table Table with four entries, e.g. {1, 0, 0, 0.15}. First three are RGB color code. Fourth is the transparency Alpha value. +function ZONE_BASE:GetColor() + return self.Color +end + +--- Get RGB color of zone. +-- @param #ZONE_BASE self +-- @return #table Table with three entries, e.g. {1, 0, 0}, which is the RGB color code. +function ZONE_BASE:GetColorRGB() + local rgb={} + rgb[1]=self.Color[1] + rgb[2]=self.Color[2] + rgb[3]=self.Color[3] + return rgb +end + +--- Get transperency Alpha value of zone. +-- @param #ZONE_BASE self +-- @return #number Alpha value. +function ZONE_BASE:GetColorAlpha() + local alpha=self.Color[4] + return alpha +end + +--- Remove the drawing of the zone from the F10 map. +-- @param #ZONE_BASE self +-- @param #number Delay (Optional) Delay before the drawing is removed. +-- @return #ZONE_BASE self +function ZONE_BASE:UndrawZone(Delay) + if Delay and Delay>0 then + self:ScheduleOnce(Delay, ZONE_BASE.UndrawZone, self) + else + if self.DrawID then + UTILS.RemoveMark(self.DrawID) + end + end + return self +end + +--- Get ID of the zone object drawn on the F10 map. +-- The ID can be used to remove the drawn object from the F10 map view via `UTILS.RemoveMark(MarkID)`. +-- @param #ZONE_BASE self +-- @return #number Unique ID of the +function ZONE_BASE:GetDrawID() + return self.DrawID +end + + --- Smokes the zone boundaries in a color. -- @param #ZONE_BASE self -- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. @@ -469,6 +543,32 @@ function ZONE_RADIUS:MarkZone(Points) end +--- Draw the zone circle on the F10 map. +-- @param #ZONE_RADIUS self +-- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. +-- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red. +-- @param #number Alpha Transparency [0,1]. Default 1. +-- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. +-- @param #number FillAlpha Transparency [0,1]. Default 0.15. +-- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. +-- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. +-- @return #ZONE_RADIUS self +function ZONE_RADIUS:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) + + local coordinate=self:GetCoordinate() + + local Radius=self:GetRadius() + + Color=Color or self:GetColorRGB() + Alpha=Alpha or 1 + FillColor=FillColor or Color + FillAlpha=FillAlpha or self:GetColorAlpha() + + self.DrawID=coordinate:CircleToAll(Radius, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) + + return self +end + --- Bounds the zone with tires. -- @param #ZONE_RADIUS self -- @param #number Points (optional) The amount of points in the circle. Default 360. @@ -1116,22 +1216,37 @@ ZONE = { } ---- Constructor of ZONE, taking the zone name. +--- Constructor of ZONE taking the zone name. -- @param #ZONE self -- @param #string ZoneName The name of the zone as defined within the mission editor. --- @return #ZONE +-- @return #ZONE self function ZONE:New( ZoneName ) + -- First try to find the zone in the DB. + local zone=_DATABASE:FindZone(ZoneName) + + if zone then + --env.info("FF found zone in DB") + return zone + end + + -- Get zone from DCS trigger function. local Zone = trigger.misc.getZone( ZoneName ) + -- Error! if not Zone then error( "Zone " .. ZoneName .. " does not exist." ) return nil end - local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, { x = Zone.point.x, y = Zone.point.z }, Zone.radius ) ) - self:F( ZoneName ) + -- Create a new ZONE_RADIUS. + local self=BASE:Inherit( self, ZONE_RADIUS:New(ZoneName, {x=Zone.point.x, y=Zone.point.z}, Zone.radius)) + self:F(ZoneName) + + -- Color of zone. + self.Color={1, 0, 0, 0.15} + -- DCS zone. self.Zone = Zone return self @@ -1425,7 +1540,7 @@ function ZONE_POLYGON_BASE:New( ZoneName, PointsArray ) end --- Returns the center location of the polygon. --- @param #ZONE_GROUP self +-- @param #ZONE_POLYGON_BASE self -- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Group} location. function ZONE_POLYGON_BASE:GetVec2() self:F( self.ZoneName ) @@ -1435,6 +1550,78 @@ function ZONE_POLYGON_BASE:GetVec2() return { x = ( Bounds.x2 + Bounds.x1 ) / 2, y = ( Bounds.y2 + Bounds.y1 ) / 2 } end +--- Get a vertex of the polygon. +-- @param #ZONE_POLYGON_BASE self +-- @param #number Index Index of the vertex. Default 1. +-- @return DCS#Vec2 Vertex of the polygon. +function ZONE_POLYGON_BASE:GetVertexVec2(Index) + return self._.Polygon[Index or 1] +end + +--- Get a vertex of the polygon. +-- @param #ZONE_POLYGON_BASE self +-- @param #number Index Index of the vertex. Default 1. +-- @return DCS#Vec3 Vertex of the polygon. +function ZONE_POLYGON_BASE:GetVertexVec3(Index) + local vec2=self:GetVertexVec2(Index) + if vec2 then + local vec3={x=vec2.x, y=land.getHeight(vec2), z=vec2.y} + return vec3 + end + return nil +end + +--- Get a vertex of the polygon. +-- @param #ZONE_POLYGON_BASE self +-- @param #number Index Index of the vertex. Default 1. +-- @return Core.Point#COORDINATE Vertex of the polygon. +function ZONE_POLYGON_BASE:GetVertexCoordinate(Index) + local vec2=self:GetVertexVec2(Index) + if vec2 then + local coord=COORDINATE:NewFromVec2(vec2) + return coord + end + return nil +end + + +--- Get a list of verticies of the polygon. +-- @param #ZONE_POLYGON_BASE self +-- @return List of DCS#Vec2 verticies defining the edges of the polygon. +function ZONE_POLYGON_BASE:GetVerticiesVec2() + return self._.Polygon +end + +--- Get a list of verticies of the polygon. +-- @param #ZONE_POLYGON_BASE self +-- @return #table List of DCS#Vec3 verticies defining the edges of the polygon. +function ZONE_POLYGON_BASE:GetVerticiesVec3() + + local coords={} + + for i,vec2 in ipairs(self._.Polygon) do + local vec3={x=vec2.x, y=land.getHeight(vec2), z=vec2.y} + table.insert(coords, vec3) + end + + return coords +end + +--- Get a list of verticies of the polygon. +-- @param #ZONE_POLYGON_BASE self +-- @return #table List of COORDINATES verticies defining the edges of the polygon. +function ZONE_POLYGON_BASE:GetVerticiesCoordinates() + + local coords={} + + for i,vec2 in ipairs(self._.Polygon) do + local coord=COORDINATE:NewFromVec2(vec2) + table.insert(coords, coord) + end + + return coords +end + --- Flush polygon coordinates as a table in DCS.log. -- @param #ZONE_POLYGON_BASE self -- @return #ZONE_POLYGON_BASE self @@ -1494,6 +1681,46 @@ function ZONE_POLYGON_BASE:BoundZone( UnBound ) end +--- Draw the zone on the F10 map. **NOTE** Currently, only polygons with **exactly four points** are supported! +-- @param #ZONE_POLYGON_BASE self +-- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. +-- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red. +-- @param #number Alpha Transparency [0,1]. Default 1. +-- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. +-- @param #number FillAlpha Transparency [0,1]. Default 0.15. +-- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. +-- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. +-- @return #ZONE_POLYGON_BASE self +function ZONE_POLYGON_BASE:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) + + local coordinate=COORDINATE:NewFromVec2(self._.Polygon[1]) + + Color=Color or self:GetColorRGB() + Alpha=Alpha or 1 + FillColor=FillColor or Color + FillAlpha=FillAlpha or self:GetColorAlpha() + + + if #self._.Polygon==4 then + + local Coord2=COORDINATE:NewFromVec2(self._.Polygon[2]) + local Coord3=COORDINATE:NewFromVec2(self._.Polygon[3]) + local Coord4=COORDINATE:NewFromVec2(self._.Polygon[4]) + + self.DrawID=coordinate:QuadToAll(Coord2, Coord3, Coord4, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) + + else + + local Coordinates=self:GetVerticiesCoordinates() + table.remove(Coordinates, 1) + + self.DrawID=coordinate:MarkupToAllFreeForm(Coordinates, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) + + end + + + return self +end --- Smokes the zone boundaries in a color. -- @param #ZONE_POLYGON_BASE self diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 693550719..db7d189d2 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -652,6 +652,17 @@ function UTILS.GetMarkID() end +--- Remove an object (marker, circle, arrow, text, quad, ...) on the F10 map. +-- @param #number MarkID Unique ID of the object. +-- @param #number Delay (Optional) Delay in seconds before the mark is removed. +function UTILS.RemoveMark(MarkID, Delay) + if Delay and Delay>0 then + TIMER:New(UTILS.RemoveMark, MarkID):Start(Delay) + else + trigger.action.removeMark(MarkID) + end +end + -- Test if a Vec2 is in a radius of another Vec2 function UTILS.IsInRadius( InVec2, Vec2, Radius ) From 7cd29501a9990f84762f203ce72a241097572856 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 11 May 2021 00:21:50 +0200 Subject: [PATCH 005/111] Templates update --- Moose Development/Moose/Core/Event.lua | 55 ++- Moose Development/Moose/Core/Point.lua | 14 +- Moose Development/Moose/Core/Zone.lua | 12 +- .../Moose/Utilities/Templates.lua | 365 +++++++++++++++--- 4 files changed, 358 insertions(+), 88 deletions(-) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 85384ac02..e06b6c979 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -240,7 +240,7 @@ EVENTS = { Kill = world.event.S_EVENT_KILL or -1, Score = world.event.S_EVENT_SCORE or -1, UnitLost = world.event.S_EVENT_UNIT_LOST or -1, - LandingAfterEjection = 31, --world.event.S_EVENT_LANDING_AFTER_EJECTION or -1, + LandingAfterEjection = world.event.S_EVENT_LANDING_AFTER_EJECTION or -1, -- Added with DCS 2.7.0 ParatrooperLanding = world.event.S_EVENT_PARATROOPER_LENDING or -1, DiscardChairAfterEjection = world.event.S_EVENT_DISCARD_CHAIR_AFTER_EJECTION or -1, @@ -524,7 +524,7 @@ local _EVENTMETA = { Event = "OnEventUnitLost", Text = "S_EVENT_UNIT_LOST" }, - [world.event.S_EVENT_LANDING_AFTER_EJECTION] = { + [EVENTS.LandingAfterEjection] = { Order = 1, Event = "OnEventLandingAfterEjection", Text = "S_EVENT_LANDING_AFTER_EJECTION" @@ -592,7 +592,7 @@ end -- @param Core.Base#BASE EventClass The class object for which events are handled. -- @return #EVENT.Events function EVENT:Init( EventID, EventClass ) - self:F( { _EVENTMETA[EventID].Text, EventClass } ) + self:F3( { _EVENTMETA[EventID].Text, EventClass } ) if not self.Events[EventID] then -- Create a WEAK table to ensure that the garbage collector is cleaning the event links when the object usage is cleaned. @@ -1124,34 +1124,33 @@ function EVENT:onEvent( Event ) end if Event.TgtObjectCategory == Object.Category.STATIC then - BASE:T({StaticTgtEvent = Event.id}) -- get base data - Event.TgtDCSUnit = Event.target - if Event.target:isExist() and Event.id ~= 33 then -- leave out ejected seat object - Event.TgtDCSUnitName = Event.TgtDCSUnit:getName() + Event.TgtDCSUnit = Event.target + if Event.target:isExist() and Event.id ~= 33 then -- leave out ejected seat object + Event.TgtDCSUnitName = Event.TgtDCSUnit:getName() + Event.TgtUnitName = Event.TgtDCSUnitName + Event.TgtUnit = STATIC:FindByName( Event.TgtDCSUnitName, false ) + Event.TgtCoalition = Event.TgtDCSUnit:getCoalition() + Event.TgtCategory = Event.TgtDCSUnit:getDesc().category + Event.TgtTypeName = Event.TgtDCSUnit:getTypeName() + else + Event.TgtDCSUnitName = string.format("No target object for Event ID %s", tostring(Event.id)) + Event.TgtUnitName = Event.TgtDCSUnitName + Event.TgtUnit = nil + Event.TgtCoalition = 0 + Event.TgtCategory = 0 + if Event.id == 6 then + Event.TgtTypeName = "Ejected Pilot" + Event.TgtDCSUnitName = string.format("Ejected Pilot ID %s", tostring(Event.IniDCSUnitName)) + Event.TgtUnitName = Event.TgtDCSUnitName + elseif Event.id == 33 then + Event.TgtTypeName = "Ejection Seat" + Event.TgtDCSUnitName = string.format("Ejection Seat ID %s", tostring(Event.IniDCSUnitName)) Event.TgtUnitName = Event.TgtDCSUnitName - Event.TgtUnit = STATIC:FindByName( Event.TgtDCSUnitName, false ) - Event.TgtCoalition = Event.TgtDCSUnit:getCoalition() - Event.TgtCategory = Event.TgtDCSUnit:getDesc().category - Event.TgtTypeName = Event.TgtDCSUnit:getTypeName() else - Event.TgtDCSUnitName = string.format("No target object for Event ID %s", tostring(Event.id)) - Event.TgtUnitName = Event.TgtDCSUnitName - Event.TgtUnit = nil - Event.TgtCoalition = 0 - Event.TgtCategory = 0 - if Event.id == 6 then - Event.TgtTypeName = "Ejected Pilot" - Event.TgtDCSUnitName = string.format("Ejected Pilot ID %s", tostring(Event.IniDCSUnitName)) - Event.TgtUnitName = Event.TgtDCSUnitName - elseif Event.id == 33 then - Event.TgtTypeName = "Ejection Seat" - Event.TgtDCSUnitName = string.format("Ejection Seat ID %s", tostring(Event.IniDCSUnitName)) - Event.TgtUnitName = Event.TgtDCSUnitName - else - Event.TgtTypeName = "Static" - end - end + Event.TgtTypeName = "Static" + end + end end if Event.TgtObjectCategory == Object.Category.SCENERY then diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 59465fdd2..1df5bdc7b 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -634,9 +634,9 @@ do -- COORDINATE --- Return a random Coordinate within an Outer Radius and optionally NOT within an Inner Radius of the COORDINATE. -- @param #COORDINATE self - -- @param DCS#Distance OuterRadius - -- @param DCS#Distance InnerRadius - -- @return #COORDINATE + -- @param DCS#Distance OuterRadius Outer radius in meters. + -- @param DCS#Distance InnerRadius Inner radius in meters. + -- @return #COORDINATE self function COORDINATE:GetRandomCoordinateInRadius( OuterRadius, InnerRadius ) self:F2( { OuterRadius, InnerRadius } ) @@ -2172,8 +2172,8 @@ do -- COORDINATE -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). -- @param #number Alpha Transparency [0,1]. Default 1. -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. - -- @param #number FillAlpha Transparency [0,1]. Default 0.15. - -- @param #number FontSize Font size. + -- @param #number FillAlpha Transparency [0,1]. Default 0.3. + -- @param #number FontSize Font size. Default 14. -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. function COORDINATE:TextToAll(Text, Coalition, Color, Alpha, FillColor, FillAlpha, FontSize, ReadOnly) @@ -2185,8 +2185,8 @@ do -- COORDINATE Color=Color or {1,0,0} Color[4]=Alpha or 1.0 FillColor=FillColor or Color - FillColor[4]=FillAlpha or 0.15 - FontSize=FontSize or 12 + FillColor[4]=FillAlpha or 0.3 + FontSize=FontSize or 14 trigger.action.textToAll(Coalition, MarkID, self:GetVec3(), Color, FillColor, FontSize, ReadOnly, Text or "Hello World") return MarkID end diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index f14619959..035d0e2a4 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -17,6 +17,7 @@ -- * Get zone properties. -- * Get zone bounding box. -- * Set/get zone name. +-- * Draw zones (circular and polygon) on the F10 map. -- -- -- There are essentially two core functions that zones accomodate: @@ -495,6 +496,10 @@ end -- * @{#ZONE_RADIUS.GetRandomPointVec2}(): Gets a @{Core.Point#POINT_VEC2} object representing a random 2D point in the zone. -- * @{#ZONE_RADIUS.GetRandomPointVec3}(): Gets a @{Core.Point#POINT_VEC3} object representing a random 3D point in the zone. Note that the height of the point is at landheight. -- +-- ## Draw zone +-- +-- * @{#ZONE_RADIUS.DrawZone}(): Draws the zone on the F10 map. +-- -- @field #ZONE_RADIUS ZONE_RADIUS = { ClassName="ZONE_RADIUS", @@ -1506,6 +1511,11 @@ end -- * @{#ZONE_POLYGON_BASE.GetRandomVec2}(): Gets a random 2D point in the zone. -- * @{#ZONE_POLYGON_BASE.GetRandomPointVec2}(): Return a @{Core.Point#POINT_VEC2} object representing a random 2D point within the zone. -- * @{#ZONE_POLYGON_BASE.GetRandomPointVec3}(): Return a @{Core.Point#POINT_VEC3} object representing a random 3D point at landheight within the zone. +-- +-- ## Draw zone +-- +-- * @{#ZONE_POLYGON_BASE.DrawZone}(): Draws the zone on the F10 map. +-- -- -- @field #ZONE_POLYGON_BASE ZONE_POLYGON_BASE = { @@ -1938,7 +1948,7 @@ end -- This is especially handy if you want to quickly setup a SET_ZONE... -- So when you would declare `local SetZone = SET_ZONE:New():FilterPrefixes( "Defense" ):FilterStart()`, -- then SetZone would contain the ZONE_POLYGON object `DefenseZone` as part of the zone collection, --- without much scripting overhead!!! +-- without much scripting overhead! -- -- @field #ZONE_POLYGON ZONE_POLYGON = { diff --git a/Moose Development/Moose/Utilities/Templates.lua b/Moose Development/Moose/Utilities/Templates.lua index df7c3ff0b..182925689 100644 --- a/Moose Development/Moose/Utilities/Templates.lua +++ b/Moose Development/Moose/Utilities/Templates.lua @@ -44,10 +44,10 @@ TEMPLATE = { Helicopter = {}, } ---- Pattern steps. --- @type TEMPLATE.Ground +--- Ground unit type names. +-- @type TEMPLATE.TypeGround -- @param #string InfantryAK -TEMPLATE.Ground={ +TEMPLATE.TypeGround={ InfantryAK="Infantry AK", ParatrooperAKS74="Paratrooper AKS-74", ParatrooperRPG16="Paratrooper RPG-16", @@ -56,40 +56,227 @@ TEMPLATE.Ground={ SoldierM4="Soldier M4", } +--- Naval unit type names. +-- @type TEMPLATE.TypeNaval +-- @param #string Ticonderoga +TEMPLATE.TypeNaval={ + Ticonderoga="TICONDEROG", +} + +--- Rotary wing unit type names. +-- @type TEMPLATE.TypeAirplane +-- @param #string A10C +TEMPLATE.TypeAirplane={ + A10C="A-10C", +} + +--- Rotary wing unit type names. +-- @type TEMPLATE.TypeHelicopter +-- @param #string AH1W +TEMPLATE.TypeHelicopter={ + AH1W="AH-1W", +} + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Start/Stop Profiler +-- Ground Template ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Get template for ground units. -- @param #string TypeName Type name of the unit(s) in the groups. See `TEMPLATE.Ground`. -- @param #string GroupName Name of the spawned group. **Must be unique!** -- @param #number CountryID Country ID. Default `country.id.USA`. Coalition is automatically determined by the one the country belongs to. --- @param DCS#Vec3 Vec3 Position of the group. +-- @param DCS#Vec3 Vec3 Position of the group and the first unit. -- @param #number Nunits Number of units. Default 1. -- @param #number Radius Spawn radius for additonal units in meters. Default 50 m. -- @return #table Template Template table. function TEMPLATE.GetGround(TypeName, GroupName, CountryID, Vec3, Nunits, Radius) + -- Defaults. + TypeName=TypeName or TEMPLATE.TypeGround.SoldierM4 + GroupName=GroupName or "Ground-1" + CountryID=CountryID or country.id.USA + Vec3=Vec3 or {x=0, y=0, z=0} + Nunits=Nunits or 1 + Radius=Radius or 50 + + + -- Get generic template. local template=UTILS.DeepCopy(TEMPLATE.GenericGround) - - template.name=GroupName or "Ground-1" + + -- Set group name. + template.name=GroupName -- These are additional entries required by the MOOSE _DATABASE:Spawn() function. - template.CountryID=country.id.USA + template.CountryID=CountryID template.CoalitionID=coalition.getCountryCoalition(template.CountryID) template.CategoryID=Unit.Category.GROUND_UNIT - template.units[1].type=TypeName or "Infantry AK" - template.units[1].name=GroupName.."-1" + -- Set first unit. + template.units[1].type=TypeName + template.units[1].name=GroupName.."-1" if Vec3 then TEMPLATE.SetPositionFromVec3(template, Vec3) end + + TEMPLATE.SetUnits(template, Nunits, COORDINATE:NewFromVec3(Vec3), Radius) + + return template +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Naval Template +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Get template for ground units. +-- @param #string TypeName Type name of the unit(s) in the groups. See `TEMPLATE.Ground`. +-- @param #string GroupName Name of the spawned group. **Must be unique!** +-- @param #number CountryID Country ID. Default `country.id.USA`. Coalition is automatically determined by the one the country belongs to. +-- @param DCS#Vec3 Vec3 Position of the group and the first unit. +-- @param #number Nunits Number of units. Default 1. +-- @param #number Radius Spawn radius for additonal units in meters. Default 500 m. +-- @return #table Template Template table. +function TEMPLATE.GetNaval(TypeName, GroupName, CountryID, Vec3, Nunits, Radius) + + -- Defaults. + TypeName=TypeName or TEMPLATE.TypeNaval.Ticonderoga + GroupName=GroupName or "Naval-1" + CountryID=CountryID or country.id.USA + Vec3=Vec3 or {x=0, y=0, z=0} + Nunits=Nunits or 1 + Radius=Radius or 500 + + + -- Get generic template. + local template=UTILS.DeepCopy(TEMPLATE.GenericNaval) + + -- Set group name. + template.name=GroupName + + -- These are additional entries required by the MOOSE _DATABASE:Spawn() function. + template.CountryID=CountryID + template.CoalitionID=coalition.getCountryCoalition(template.CountryID) + template.CategoryID=Unit.Category.SHIP + + -- Set first unit. + template.units[1].type=TypeName + template.units[1].name=GroupName.."-1" + + if Vec3 then + TEMPLATE.SetPositionFromVec3(template, Vec3) + end + + TEMPLATE.SetUnits(template, Nunits, COORDINATE:NewFromVec3(Vec3), Radius) + + return template +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Aircraft Template +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Get template for fixed wing units. +-- @param #string TypeName Type name of the unit(s) in the groups. See `TEMPLATE.Ground`. +-- @param #string GroupName Name of the spawned group. **Must be unique!** +-- @param #number CountryID Country ID. Default `country.id.USA`. Coalition is automatically determined by the one the country belongs to. +-- @param DCS#Vec3 Vec3 Position of the group and the first unit. +-- @param #number Nunits Number of units. Default 1. +-- @param #number Radius Spawn radius for additonal units in meters. Default 500 m. +-- @return #table Template Template table. +function TEMPLATE.GetAirplane(TypeName, GroupName, CountryID, Vec3, Nunits, Radius) + + -- Defaults. + TypeName=TypeName or TEMPLATE.TypeAirplane.A10C + GroupName=GroupName or "Airplane-1" + CountryID=CountryID or country.id.USA + Vec3=Vec3 or {x=0, y=1000, z=0} + Nunits=Nunits or 1 + Radius=Radius or 100 + + local template=TEMPLATE._GetAircraft(true, TypeName, GroupName, CountryID, Vec3, Nunits, Radius) + + return template +end + +--- Get template for fixed wing units. +-- @param #string TypeName Type name of the unit(s) in the groups. See `TEMPLATE.Ground`. +-- @param #string GroupName Name of the spawned group. **Must be unique!** +-- @param #number CountryID Country ID. Default `country.id.USA`. Coalition is automatically determined by the one the country belongs to. +-- @param DCS#Vec3 Vec3 Position of the group and the first unit. +-- @param #number Nunits Number of units. Default 1. +-- @param #number Radius Spawn radius for additonal units in meters. Default 500 m. +-- @return #table Template Template table. +function TEMPLATE.GetHelicopter(TypeName, GroupName, CountryID, Vec3, Nunits, Radius) + + -- Defaults. + TypeName=TypeName or TEMPLATE.TypeHelicopter.AH1W + GroupName=GroupName or "Helicopter-1" + CountryID=CountryID or country.id.USA + Vec3=Vec3 or {x=0, y=500, z=0} + Nunits=Nunits or 1 + Radius=Radius or 100 + + -- Limit unis to 4. + Nunits=math.min(Nunits, 4) + + local template=TEMPLATE._GetAircraft(false, TypeName, GroupName, CountryID, Vec3, Nunits, Radius) return template end +--- Get template for aircraft units. +-- @param #boolean Airplane If true, this is a fixed wing. Else, rotary wing. +-- @param #string TypeName Type name of the unit(s) in the groups. See `TEMPLATE.Ground`. +-- @param #string GroupName Name of the spawned group. **Must be unique!** +-- @param #number CountryID Country ID. Default `country.id.USA`. Coalition is automatically determined by the one the country belongs to. +-- @param DCS#Vec3 Vec3 Position of the group and the first unit. +-- @param #number Nunits Number of units. Default 1. +-- @param #number Radius Spawn radius for additonal units in meters. Default 500 m. +-- @return #table Template Template table. +function TEMPLATE._GetAircraft(Airplane, TypeName, GroupName, CountryID, Vec3, Nunits, Radius) + + -- Defaults. + TypeName=TypeName + GroupName=GroupName or "Aircraft-1" + CountryID=CountryID or country.id.USA + Vec3=Vec3 or {x=0, y=0, z=0} + Nunits=Nunits or 1 + Radius=Radius or 100 + + -- Get generic template. + local template=UTILS.DeepCopy(TEMPLATE.GenericAircraft) + + -- Set group name. + template.name=GroupName + + -- These are additional entries required by the MOOSE _DATABASE:Spawn() function. + template.CountryID=CountryID + template.CoalitionID=coalition.getCountryCoalition(template.CountryID) + if Airplane then + template.CategoryID=Unit.Category.AIRPLANE + else + template.CategoryID=Unit.Category.HELICOPTER + end + + -- Set first unit. + template.units[1].type=TypeName + template.units[1].name=GroupName.."-1" + + -- Set position. + if Vec3 then + TEMPLATE.SetPositionFromVec3(template, Vec3) + end + + -- Set number of units. + TEMPLATE.SetUnits(template, Nunits, COORDINATE:NewFromVec3(Vec3), Radius) + + return template +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Misc Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Set the position of the template. -- @param #table Template The template to be modified. @@ -121,6 +308,84 @@ function TEMPLATE.SetPositionFromVec3(Template, Vec3) end +--- Set the position of the template. +-- @param #table Template The template to be modified. +-- @param #number N Total number of units in the group. +-- @param Core.Point#COORDINATE Coordinate Position of the first unit. +-- @param #number Radius Radius in meters to randomly place the additional units. +function TEMPLATE.SetUnits(Template, N, Coordinate, Radius) + + local units=Template.units + + local unit1=units[1] + + local Vec3=Coordinate:GetVec3() + + unit1.x=Vec3.x + unit1.y=Vec3.z + unit1.alt=Vec3.y + + for i=2,N do + units[i]=UTILS.DeepCopy(unit1) + end + + for i=1,N do + local unit=units[i] + unit.name=string.format("%s-%d", Template.name, i) + if i>1 then + local vec2=Coordinate:GetRandomCoordinateInRadius(Radius, 5):GetVec2() + unit.x=vec2.x + unit.y=vec2.y + unit.alt=unit1.alt + end + end + +end + +--- Set the position of the template. +-- @param #table Template The template to be modified. +-- @param Wrapper.Airbase#AIRBASE AirBase The airbase where the aircraft are spawned. +-- @param #table ParkingSpots List of parking spot IDs. Every unit needs one! +-- @param #boolean EngineOn If true, aircraft are spawned hot. +function TEMPLATE.SetAirbase(Template, AirBase, ParkingSpots, EngineOn) + + -- Airbase ID. + local AirbaseID=AirBase:GetID() + + -- Spawn point. + local point=Template.route.points[1] + + -- Set ID. + if AirBase:IsAirdrome() then + point.airdromeId=AirbaseID + else + point.helipadId=AirbaseID + point.linkUnit=AirbaseID + end + + if EngineOn then + point.action=COORDINATE.WaypointAction.FromParkingAreaHot + point.type=COORDINATE.WaypointType.TakeOffParkingHot + else + point.action=COORDINATE.WaypointAction.FromParkingArea + point.type=COORDINATE.WaypointType.TakeOffParking + end + + for i,unit in ipairs(Template.units) do + unit.parking_id=ParkingSpots[i] + end + +end + +--- Add a waypoint. +-- @param #table Template The template to be modified. +-- @param #table Waypoint Waypoint table. +function TEMPLATE.AddWaypoint(Template, Waypoint) + + table.insert(Template.route.points, Waypoint) + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Generic Ground Template ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -255,79 +520,82 @@ TEMPLATE.GenericNaval= } ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Generic Ship Template +-- Generic Aircraft Template ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -TEMPLATE.GenericHelicopter= +TEMPLATE.GenericAircraft= { - ["modulation"] = 0, - ["tasks"] = {}, -- end of ["tasks"] - ["radioSet"] = false, - ["task"] = "Nothing", + ["groupId"] = nil, + ["name"] = "Rotary-1", ["uncontrolled"] = false, - ["taskSelected"] = true, + ["hidden"] = false, + ["task"] = "Nothing", + ["y"] = 0, + ["x"] = 0, + ["start_time"] = 0, + ["communication"] = true, + ["radioSet"] = false, + ["frequency"] = 127.5, + ["modulation"] = 0, + ["taskSelected"] = true, + ["tasks"] = {}, -- end of ["tasks"] ["route"] = { ["points"] = { [1] = { - ["alt"] = 10, - ["action"] = "From Parking Area", - ["alt_type"] = "BARO", - ["speed"] = 41.666666666667, + ["y"] = 0, + ["x"] = 0, + ["alt"] = 1000, + ["alt_type"] = "BARO", + ["action"] = "Turning Point", + ["type"] = "Turning Point", + ["airdromeId"] = nil, ["task"] = { ["id"] = "ComboTask", ["params"] = { - ["tasks"] = - { - }, -- end of ["tasks"] + ["tasks"] = {}, -- end of ["tasks"] }, -- end of ["params"] }, -- end of ["task"] - ["type"] = "TakeOffParking", ["ETA"] = 0, ["ETA_locked"] = true, - ["y"] = 618351.087765, - ["x"] = -356168.27327001, + ["speed"] = 100, + ["speed_locked"] = true, ["formation_template"] = "", - ["airdromeId"] = 22, - ["speed_locked"] = true, }, -- end of [1] }, -- end of ["points"] }, -- end of ["route"] - ["groupId"] = nil, - ["hidden"] = false, ["units"] = { [1] = { - ["alt"] = 10, - ["alt_type"] = "BARO", + ["name"] = "Rotary-1-1", + ["unitId"] = nil, + ["type"] = "AH-1W", + ["onboard_num"] = "050", ["livery_id"] = "USA X Black", ["skill"] = "High", - ["parking"] = "4", ["ropeLength"] = 15, - ["speed"] = 41.666666666667, - ["type"] = "AH-1W", - ["unitId"] = 8, + ["speed"] = 0, + ["x"] = 0, + ["y"] = 0, + ["alt"] = 10, + ["alt_type"] = "BARO", + ["heading"] = 0, ["psi"] = 0, - ["parking_id"] = "10", - ["x"] = -356168.27327001, - ["name"] = "Rotary-1-1", + ["parking"] = nil, + ["parking_id"] = nil, ["payload"] = { - ["pylons"] = - { - }, -- end of ["pylons"] + ["pylons"] = {}, -- end of ["pylons"] ["fuel"] = "1250.0", ["flare"] = 30, ["chaff"] = 30, ["gun"] = 100, }, -- end of ["payload"] - ["y"] = 618351.087765, - ["heading"] = 0, ["callsign"] = { [1] = 2, @@ -335,15 +603,8 @@ TEMPLATE.GenericHelicopter= [3] = 1, ["name"] = "Springfield11", }, -- end of ["callsign"] - ["onboard_num"] = "050", }, -- end of [1] }, -- end of ["units"] - ["y"] = 0, - ["x"] = 0, - ["name"] = "Rotary-1", - ["communication"] = true, - ["start_time"] = 0, - ["frequency"] = 127.5, } ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 057e231a9d57133d422eec2d3b23e49ba9200e00 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 11 May 2021 11:41:26 +0200 Subject: [PATCH 006/111] Emission --- Moose Development/Moose/Wrapper/Group.lua | 6 ++-- Moose Development/Moose/Wrapper/Unit.lua | 40 +++-------------------- 2 files changed, 8 insertions(+), 38 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 1a2f00fa8..263b3a0ec 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -2551,9 +2551,10 @@ do -- Players end ---- GROUND - Switch on/off radar emissions +--- GROUND - Switch on/off radar emissions for the group. -- @param #GROUP self --- @param #boolean switch +-- @param #boolean switch If true, emission is enabled. If false, emission is disabled. +-- @return #GROUP self function GROUP:EnableEmission(switch) self:F2( self.GroupName ) local switch = switch or false @@ -2566,6 +2567,7 @@ function GROUP:EnableEmission(switch) end + return self end --do -- Smoke diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 65199d97d..eb6d472d8 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -761,40 +761,6 @@ function UNIT:GetFuel() return nil end ---- Sets the passed group or unit objects radar emitters on or off. Can be used on sam sites for example to shut down the radar without setting AI off or changing the alarm state. --- @param #UNIT self --- @param #boolean Switch If `true` or `nil`, emission is enabled. If `false`, emission is turned off. --- @return #UNIT self -function UNIT:SetEmission(Switch) - - if Switch==nil then - Switch=true - end - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - DCSUnit:enableEmission(Switch) - end - - return self -end - ---- Sets the passed group or unit objects radar emitters ON. Can be used on sam sites for example to shut down the radar without setting AI off or changing the alarm state. --- @param #UNIT self --- @return #UNIT self -function UNIT:EnableEmission() - self:SetEmission(true) - return self -end - ---- Sets the passed group or unit objects radar emitters OFF. Can be used on sam sites for example to shut down the radar without setting AI off or changing the alarm state. --- @param #UNIT self --- @return #UNIT self -function UNIT:DisableEmission() - self:SetEmission(false) - return self -end --- Returns a list of one @{Wrapper.Unit}. -- @param #UNIT self @@ -1429,9 +1395,10 @@ function UNIT:GetTemplateFuel() return nil end ---- GROUND - Switch on/off radar emissions. +--- GROUND - Switch on/off radar emissions of a unit. -- @param #UNIT self --- @param #boolean switch +-- @param #boolean switch If true, emission is enabled. If false, emission is disabled. +-- @return #UNIT self function UNIT:EnableEmission(switch) self:F2( self.UnitName ) @@ -1445,4 +1412,5 @@ function UNIT:EnableEmission(switch) end + return self end From b68f271fb726cf989bf6ad29f74ed5098de0ab91 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 11 May 2021 11:51:25 +0200 Subject: [PATCH 007/111] Update Event.lua --- Moose Development/Moose/Core/Event.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index e06b6c979..1b3952ac1 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -578,10 +578,6 @@ function EVENT:New() -- Add world event handler. self.EventHandler = world.addEventHandler(self) - for _,Event in pairs(self.Events) do - self:Init(Event,EventClass) - end - return self end From a893d46cb9684f9e286459a57f77880ed7104794 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 11 May 2021 11:54:27 +0200 Subject: [PATCH 008/111] Update Event.lua --- Moose Development/Moose/Core/Event.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 1b3952ac1..02dab0afc 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -1000,7 +1000,7 @@ function EVENT:onEvent( Event ) -- Check if this is a known event? if EventMeta then - if self and self.Events and self.Events[Event.id] and self.MissionEnd==false and (Event.initiator~=nil or (Event.initiator==nil and Event.idss~=EVENTS.PlayerLeaveUnit)) then + if self and self.Events and self.Events[Event.id] and self.MissionEnd==false and (Event.initiator~=nil or (Event.initiator==nil and Event.id~=EVENTS.PlayerLeaveUnit)) then if Event.id and Event.id == EVENTS.MissionEnd then self.MissionEnd = true From 1ae41319fa76bb001f8ff7d8218e5b60cb32d521 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 12 May 2021 09:18:31 +0200 Subject: [PATCH 009/111] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 976a47f96..b622326a8 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ This repository contains the source lua code of the MOOSE framework. ### [MOOSE_INCLUDE](https://github.com/FlightControl-Master/MOOSE_INCLUDE) - For use and generated -This repository contains the Moose.lua file to be included within your missions. +This repository contains the Moose.lua file to be included within your missions. Note that the Moose\_.lua is technically the same as Moose.lua, but without any commentary or unnecessary whitespace in it. You only need to load **one** of those at the beginning of your mission. ### [MOOSE_DOCS](https://github.com/FlightControl-Master/MOOSE_DOCS) - Not for use From e49020b905c69e92b7437579fa8241248839fd13 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 12 May 2021 09:19:07 +0200 Subject: [PATCH 010/111] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 976a47f96..b622326a8 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ This repository contains the source lua code of the MOOSE framework. ### [MOOSE_INCLUDE](https://github.com/FlightControl-Master/MOOSE_INCLUDE) - For use and generated -This repository contains the Moose.lua file to be included within your missions. +This repository contains the Moose.lua file to be included within your missions. Note that the Moose\_.lua is technically the same as Moose.lua, but without any commentary or unnecessary whitespace in it. You only need to load **one** of those at the beginning of your mission. ### [MOOSE_DOCS](https://github.com/FlightControl-Master/MOOSE_DOCS) - Not for use From 0410ae6877a6ba3eff978128742f87194219ddba Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 13 May 2021 23:30:34 +0200 Subject: [PATCH 011/111] Mariana Islands --- Moose Development/Moose/Utilities/Utils.lua | 6 ++++++ Moose Development/Moose/Wrapper/Airbase.lua | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index db7d189d2..3fd6e9482 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -50,6 +50,7 @@ BIGSMOKEPRESET = { -- @field #string PersianGulf Persian Gulf map. -- @field #string TheChannel The Channel map. -- @field #string Syria Syria map. +-- @field #string MarianaIslands Mariana Islands map. DCSMAP = { Caucasus="Caucasus", NTTR="Nevada", @@ -57,6 +58,7 @@ DCSMAP = { PersianGulf="PersianGulf", TheChannel="TheChannel", Syria="Syria", + MarianaIslands="MarianaIslands" } @@ -1182,6 +1184,8 @@ function UTILS.GetMagneticDeclination(map) declination=-10 elseif map==DCSMAP.Syria then declination=5 + elseif map==DCSMAP.MarianaIslands then + declination=-2 else declination=0 end @@ -1310,6 +1314,8 @@ function UTILS.GMTToLocalTimeDifference() return 2 -- This map currently needs +2 elseif theatre==DCSMAP.Syria then return 3 -- Damascus is UTC+3 hours + elseif theatre==DCSMAP.MarianaIslands then + return 2 -- Guam is UTC+10 hours but is +2 for now. else BASE:E(string.format("ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0", tostring(theatre))) return 0 diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 453e4857b..220e3a254 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -416,6 +416,25 @@ AIRBASE.Syria={ } +--- Airbases of the Mariana Islands map. +-- +-- * AIRBASE.MarianaIslands.Rota_International_Airport +-- * AIRBASE.MarianaIslands.Andersen +-- * AIRBASE.MarianaIslands.Northwest_Field +-- * AIRBASE.MarianaIslands.Antonio_B_Won_Pat_International_Airport +-- * AIRBASE.MarianaIslands.Saipan_International_Airport +-- * AIRBASE.MarianaIslands.Tinian_International_Airport +-- @field MarianaIslands +AIRBASE.MarianaIslands={ + ["Rota_International_Airport"]="Rota International Airport", + ["Andersen"]="Andersen", + ["Northwest_Field"]="Northwest_Field", + ["Antonio_B_Won_Pat_International_Airport"]="Antonio B. Won Pat International Airport", + ["Saipan_International_Airport"]="Saipan International Airport", + ["Tinian_International_Airport"]="Tinian International Airport", +} + + --- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy". -- @type AIRBASE.ParkingSpot -- @field Core.Point#COORDINATE Coordinate Coordinate of the parking spot. From 41b01a508d06dbf5a4ab11a260de77e7c778f246 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 14 May 2021 21:57:19 +0200 Subject: [PATCH 012/111] Removed debug coordinate marks for quad zones --- Moose Development/Moose/Core/Database.lua | 13 +++++-------- Moose Development/Moose/Core/Point.lua | 17 +++++++++++------ Moose Development/Moose/Core/Zone.lua | 2 +- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 0d6d2bb35..b30329ecf 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -327,20 +327,17 @@ do -- Zones Zone=ZONE_POLYGON_BASE:New(ZoneName, ZoneData.verticies) - for i,vec2 in pairs(ZoneData.verticies) do - local coord=COORDINATE:NewFromVec2(vec2) - coord:MarkToAll(string.format("%s Point %d", ZoneName, i)) - end + --for i,vec2 in pairs(ZoneData.verticies) do + -- local coord=COORDINATE:NewFromVec2(vec2) + -- coord:MarkToAll(string.format("%s Point %d", ZoneName, i)) + --end end if Zone then - -- Debug output. - --self:I({"Register ZONE: %s (", Name = ZoneName}) - + -- Store color of zone. Zone.Color=color - -- Store in DB. self.ZONENAMES[ZoneName] = ZoneName diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 8c30032c0..f79d54593 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -2121,23 +2121,28 @@ do -- COORDINATE -- @param #string Text (Optional) Text displayed when mark is added. Default none. -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. function COORDINATE:MarkupToAllFreeForm(Coordinates, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text) + local MarkID = UTILS.GetMarkID() if ReadOnly==nil then ReadOnly=false end + Coalition=Coalition or -1 + Color=Color or {1,0,0} Color[4]=Alpha or 1.0 + LineType=LineType or 1 - FillColor=FillColor or Color + + FillColor=FillColor or UTILS.DeepCopy(Color) FillColor[4]=FillAlpha or 0.15 local vecs={} - table.insert(vecs, self:GetVec3()) - for _,coord in ipairs(Coordinates) do - table.insert(vecs, coord:GetVec3()) + vecs[1]=self:GetVec3() + for i,coord in ipairs(Coordinates) do + vecs[i+1]=coord:GetVec3() end - + if #vecs<3 then self:E("ERROR: A free form polygon needs at least three points!") elseif #vecs==3 then @@ -2147,7 +2152,7 @@ do -- COORDINATE elseif #vecs==5 then trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], Color, FillColor, LineType, ReadOnly, Text or "") elseif #vecs==6 then - trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], Color, FillColor, LineType, ReadOnly, Text or "") + trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], Color, FillColor, LineType, Text or "") elseif #vecs==7 then trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], Color, FillColor, LineType, ReadOnly, Text or "") elseif #vecs==8 then diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 035d0e2a4..8ad723486 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1723,7 +1723,7 @@ function ZONE_POLYGON_BASE:DrawZone(Coalition, Color, Alpha, FillColor, FillAlph local Coordinates=self:GetVerticiesCoordinates() table.remove(Coordinates, 1) - + self.DrawID=coordinate:MarkupToAllFreeForm(Coordinates, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) end From 47d814e409996fbbbe4d5f3719c452cfd206f8f2 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Fri, 21 May 2021 12:35:32 +0200 Subject: [PATCH 013/111] CONTROLLABLE:PatrolRouteRandom fix for Subs (#1536) * CONTROLLABLE:PatrolRouteRandom fix for Subs fixes issue #1535 * Update Controllable.lua * Update Controllable.lua --- .../Moose/Wrapper/Controllable.lua | 165 +++++++++++------- 1 file changed, 105 insertions(+), 60 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index e2d3edca6..58e1a747e 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -175,11 +175,8 @@ -- * @{#CONTROLLABLE.OptionKeepWeaponsOnThreat} -- -- ## 5.5) Air-2-Air missile attack range: --- * @{#CONTROLLABLE.OptionAAAttackRange}(): Defines the usage of A2A missiles against possible targets. --- --- ## 5.6) GROUND units attack range: --- * @{#CONTROLLABLE.OptionEngageRange}(): Engage range limit in percent (a number between 0 and 100). Default 100. Defines the range at which a GROUND unit/group (e.g. a SAM site) is allowed to use its weapons automatically. --- +-- * @{#CONTROLLABLE.OptionAAAttackRange}(): Defines the usage of A2A missiles against possible targets . +-- -- @field #CONTROLLABLE CONTROLLABLE = { ClassName = "CONTROLLABLE", @@ -507,7 +504,7 @@ function CONTROLLABLE:TaskCombo( DCSTasks ) tasks = DCSTasks } } - + return DCSTaskCombo end @@ -805,12 +802,12 @@ end -- @return #CONTROLLABLE self function CONTROLLABLE:CommandSetFrequency(Frequency, Modulation, Delay) - local CommandSetFrequency = { - id = 'SetFrequency', - params = { - frequency = Frequency*1000000, - modulation = Modulation or radio.modulation.AM, - } + local CommandSetFrequency = { + id = 'SetFrequency', + params = { + frequency = Frequency*1000000, + modulation = Modulation or radio.modulation.AM, + } } if Delay and Delay>0 then @@ -884,7 +881,7 @@ function CONTROLLABLE:TaskAttackGroup( AttackGroup, WeaponType, WeaponExpend, At groupId = AttackGroup:GetID(), weaponType = WeaponType or 1073741822, expend = WeaponExpend or "Auto", - attackQtyLimit = AttackQty and true or false, + attackQtyLimit = AttackQty and true or false, attackQty = AttackQty or 1, directionEnabled = Direction and true or false, direction = Direction and math.rad(Direction) or 0, @@ -924,7 +921,7 @@ function CONTROLLABLE:TaskAttackUnit(AttackUnit, GroupAttack, WeaponExpend, Atta weaponType = WeaponType or 1073741822, } } - + return DCSTask end @@ -1088,7 +1085,7 @@ function CONTROLLABLE:TaskEmbarking(Coordinate, GroupSetForEmbarking, Duration, -- Distribution --local distribution={} --distribution[id]=gids - + local groupID=self and self:GetID() local DCSTask = { @@ -1317,7 +1314,7 @@ function CONTROLLABLE:TaskLandAtVec2(Vec2, Duration) duration = Duration, }, } - + return DCSTask end @@ -1432,7 +1429,7 @@ end -- @param #number AmmoCount (optional) Quantity of ammunition to expand (omit to fire until ammunition is depleted). -- @param #number WeaponType (optional) Enum for weapon type ID. This value is only required if you want the group firing to use a specific weapon, for instance using the task on a ship to force it to fire guided missiles at targets within cannon range. See http://wiki.hoggit.us/view/DCS_enum_weapon_flag -- @param #number Altitude (Optional) Altitude in meters. --- @param #number ASL Altitude is above mean sea level. Default is above ground level. +-- @param #number ASL Altitude is above mean sea level. Default is above ground level. -- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType, Altitude, ASL ) @@ -1454,7 +1451,7 @@ function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType, Alti DCSTask.params.expendQty = AmmoCount DCSTask.params.expendQtyEnabled = true end - + if Altitude then DCSTask.params.altitude=Altitude end @@ -1462,7 +1459,7 @@ function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType, Alti if WeaponType then DCSTask.params.weaponType=WeaponType end - + --self:I(DCSTask) return DCSTask @@ -1482,6 +1479,7 @@ end --- (AIR + GROUND) The task makes the controllable/unit a FAC and orders the FAC to control the target (enemy ground controllable) destruction. -- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. -- If the task is assigned to the controllable lead unit will be a FAC. +-- It's important to note that depending on the type of unit that is being assigned the task (AIR or GROUND), you must choose the correct type of callsign enumerator. For airborne controllables use CALLSIGN.Aircraft and for ground based use CALLSIGN.JTAC enumerators. -- @param #CONTROLLABLE self -- @param Wrapper.Group#GROUP AttackGroup Target GROUP object. -- @param #number WeaponType Bitmask of weapon types, which are allowed to use. @@ -1489,7 +1487,7 @@ end -- @param #boolean Datalink (Optional) Allows to use datalink to send the target information to attack aircraft. Enabled by default. -- @param #number Frequency Frequency in MHz used to communicate with the FAC. Default 133 MHz. -- @param #number Modulation Modulation of radio for communication. Default 0=AM. --- @param #number CallsignName Callsign enumerator name of the FAC. +-- @param #number CallsignName Callsign enumerator name of the FAC. (CALLSIGN.Aircraft.{name} for airborne controllables, CALLSIGN.JTACS.{name} for ground units) -- @param #number CallsignNumber Callsign number, e.g. Axeman-**1**. -- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskFAC_AttackGroup( AttackGroup, WeaponType, Designation, Datalink, Frequency, Modulation, CallsignName, CallsignNumber ) @@ -1811,7 +1809,7 @@ function CONTROLLABLE:TaskFunction( FunctionString, ... ) -- DCS task. local DCSTask = self:TaskWrappedAction(self:CommandDoScript(table.concat( DCSScript ))) - + return DCSTask end @@ -1853,8 +1851,27 @@ do -- Patrol methods -- Calculate the new Route. local FromCoord = PatrolGroup:GetCoordinate() - local From = FromCoord:WaypointGround( 120 ) - + + -- test for submarine + local depth = 0 + local IsSub = false + if PatrolGroup:IsShip() then + local navalvec3 = FromCoord:GetVec3() + if navalvec3.y < 0 then + depth = navalvec3.y + IsSub = true + end + end + + + local Waypoint = Waypoints[1] + local Speed = Waypoint.speed or (20 / 3.6) + local From = FromCoord:WaypointGround( Speed ) + + if IsSub then + From = FromCoord:WaypointNaval( Speed, Waypoint.alt ) + end + table.insert( Waypoints, 1, From ) local TaskRoute = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolRoute" ) @@ -1894,7 +1911,16 @@ do -- Patrol methods if ToWaypoint then FromWaypoint = ToWaypoint end - + -- test for submarine + local depth = 0 + local IsSub = false + if PatrolGroup:IsShip() then + local navalvec3 = FromCoord:GetVec3() + if navalvec3.y < 0 then + depth = navalvec3.y + IsSub = true + end + end -- Loop until a waypoint has been found that is not the same as the current waypoint. -- Otherwise the object zon't move or drive in circles and the algorithm would not do exactly -- what it is supposed to do, which is making groups drive around. @@ -1909,9 +1935,13 @@ do -- Patrol methods local ToCoord = COORDINATE:NewFromVec2( { x = Waypoint.x, y = Waypoint.y } ) -- Create a "ground route point", which is a "point" structure that can be given as a parameter to a Task local Route = {} - Route[#Route+1] = FromCoord:WaypointGround( Speed, Formation ) - Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation ) - + if IsSub then + Route[#Route+1] = FromCoord:WaypointNaval( Speed, depth ) + Route[#Route+1] = ToCoord:WaypointNaval( Speed, Waypoint.alt ) + else + Route[#Route+1] = FromCoord:WaypointGround( Speed, Formation ) + Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation ) + end local TaskRouteToZone = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolRouteRandom", Speed, Formation, ToWaypoint ) @@ -1952,9 +1982,20 @@ do -- Patrol methods self:F( { PatrolGroup = PatrolGroup:GetName() } ) if PatrolGroup:IsGround() or PatrolGroup:IsShip() then - + -- Calculate the new Route. local FromCoord = PatrolGroup:GetCoordinate() + + -- test for submarine + local depth = 0 + local IsSub = false + if PatrolGroup:IsShip() then + local navalvec3 = FromCoord:GetVec3() + if navalvec3.y < 0 then + depth = navalvec3.y + IsSub = true + end + end -- Select a random Zone and get the Coordinate of the new Zone. local RandomZone = ZoneList[ math.random( 1, #ZoneList ) ] -- Core.Zone#ZONE @@ -1962,9 +2003,13 @@ do -- Patrol methods -- Create a "ground route point", which is a "point" structure that can be given as a parameter to a Task local Route = {} - Route[#Route+1] = FromCoord:WaypointGround( Speed, Formation ) - Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation ) - + if IsSub then + Route[#Route+1] = FromCoord:WaypointNaval( Speed, depth ) + Route[#Route+1] = ToCoord:WaypointNaval( Speed, depth ) + else + Route[#Route+1] = FromCoord:WaypointGround( Speed, Formation ) + Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation ) + end local TaskRouteToZone = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolZones", ZoneList, Speed, Formation, DelayMin, DelayMax ) @@ -1988,7 +2033,7 @@ function CONTROLLABLE:TaskRoute( Points ) id = 'Mission', params = { airborne = self:IsAir(), - route = {points = Points}, + route = {points = Points}, }, } @@ -2914,9 +2959,9 @@ end function CONTROLLABLE:OptionROE(ROEvalue) local DCSControllable = self:GetDCSObject() - + if DCSControllable then - + local Controller = self:_GetController() if self:IsAir() then @@ -3480,13 +3525,13 @@ end -- @return #CONTROLLABLE self function CONTROLLABLE:OptionProhibitAfterburner(Prohibit) self:F2( { self.ControllableName } ) - + if Prohibit==nil then Prohibit=true end if self:IsAir() then - self:SetOption(AI.Option.Air.id.PROHIBIT_AB, Prohibit) + self:SetOption(AI.Option.Air.id.PROHIBIT_AB, Prohibit) end return self @@ -3497,9 +3542,9 @@ end -- @return #CONTROLLABLE self function CONTROLLABLE:OptionECM_Never() self:F2( { self.ControllableName } ) - + if self:IsAir() then - self:SetOption(AI.Option.Air.id.ECM_USING, 0) + self:SetOption(AI.Option.Air.id.ECM_USING, 0) end return self @@ -3510,9 +3555,9 @@ end -- @return #CONTROLLABLE self function CONTROLLABLE:OptionECM_OnlyLockByRadar() self:F2( { self.ControllableName } ) - + if self:IsAir() then - self:SetOption(AI.Option.Air.id.ECM_USING, 1) + self:SetOption(AI.Option.Air.id.ECM_USING, 1) end return self @@ -3524,9 +3569,9 @@ end -- @return #CONTROLLABLE self function CONTROLLABLE:OptionECM_DetectedLockByRadar() self:F2( { self.ControllableName } ) - + if self:IsAir() then - self:SetOption(AI.Option.Air.id.ECM_USING, 2) + self:SetOption(AI.Option.Air.id.ECM_USING, 2) end return self @@ -3537,9 +3582,9 @@ end -- @return #CONTROLLABLE self function CONTROLLABLE:OptionECM_AlwaysOn() self:F2( { self.ControllableName } ) - + if self:IsAir() then - self:SetOption(AI.Option.Air.id.ECM_USING, 3) + self:SetOption(AI.Option.Air.id.ECM_USING, 3) end return self @@ -3681,22 +3726,22 @@ end --- Sets Controllable Option for A2A attack range for AIR FIGHTER units. -- @param #CONTROLLABLE self --- @param #number range Defines the range +-- @param #number range Defines the range -- @return #CONTROLLABLE self -- @usage Range can be one of MAX_RANGE = 0, NEZ_RANGE = 1, HALF_WAY_RMAX_NEZ = 2, TARGET_THREAT_EST = 3, RANDOM_RANGE = 4. Defaults to 3. See: https://wiki.hoggitworld.com/view/DCS_option_missileAttack function CONTROLLABLE:OptionAAAttackRange(range) - self:F2( { self.ControllableName } ) + self:F2( { self.ControllableName } ) -- defaults to 3 local range = range or 3 if range < 0 or range > 4 then range = 3 - end + end local DCSControllable = self:GetDCSObject() if DCSControllable then local Controller = self:_GetController() - if Controller then + if Controller then if self:IsAir() then - self:SetOption(AI.Option.Air.val.MISSILE_ATTACK, range) + self:SetOption(AI.Option.Air.val.MISSILE_ATTACK, range) end end return self @@ -3709,7 +3754,7 @@ end -- @param #number EngageRange Engage range limit in percent (a number between 0 and 100). Default 100. -- @return #CONTROLLABLE self function CONTROLLABLE:OptionEngageRange(EngageRange) - self:F2( { self.ControllableName } ) + self:F2( { self.ControllableName } ) -- Set default if not specified. EngageRange=EngageRange or 100 if EngageRange < 0 or EngageRange > 100 then @@ -3718,9 +3763,9 @@ function CONTROLLABLE:OptionEngageRange(EngageRange) local DCSControllable = self:GetDCSObject() if DCSControllable then local Controller = self:_GetController() - if Controller then + if Controller then if self:IsGround() then - self:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION, EngageRange) + self:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION, EngageRange) end end return self @@ -3736,9 +3781,9 @@ end -- @param #boolean shortcut If true and onroad is set, take a shorter route - if available - off road, default false -- @return #CONTROLLABLE self function CONTROLLABLE:RelocateGroundRandomInRadius(speed, radius, onroad, shortcut) - self:F2( { self.ControllableName } ) + self:F2( { self.ControllableName } ) - local _coord = self:GetCoordinate() + local _coord = self:GetCoordinate() local _radius = radius or 500 local _speed = speed or 20 local _tocoord = _coord:GetRandomCoordinateInRadius(_radius,100) @@ -3746,7 +3791,7 @@ function CONTROLLABLE:RelocateGroundRandomInRadius(speed, radius, onroad, shortc local _grptsk = {} local _candoroad = false local _shortcut = shortcut or false - + -- create a DCS Task an push it on the group -- TaskGroundOnRoad(ToCoordinate,Speed,OffRoadFormation,Shortcut,FromCoordinate,WaypointFunction,WaypointFunctionArguments) if onroad then @@ -3756,23 +3801,23 @@ function CONTROLLABLE:RelocateGroundRandomInRadius(speed, radius, onroad, shortc self:TaskRouteToVec2(_tocoord:GetVec2(),_speed,"Off Road") end - return self + return self end --- Defines how long a GROUND unit/group will move to avoid an ongoing attack. -- @param #CONTROLLABLE self --- @param #number Seconds Any positive number: AI will disperse, but only for the specified time before continuing their route. 0: AI will not disperse. +-- @param #number Seconds Any positive number: AI will disperse, but only for the specified time before continuing their route. 0: AI will not disperse. -- @return #CONTROLLABLE self function CONTROLLABLE:OptionDisperseOnAttack(Seconds) - self:F2( { self.ControllableName } ) + self:F2( { self.ControllableName } ) -- Set default if not specified. local seconds = Seconds or 0 local DCSControllable = self:GetDCSObject() if DCSControllable then local Controller = self:_GetController() - if Controller then + if Controller then if self:IsGround() then - self:SetOption(AI.Option.GROUND.id.DISPERSE_ON_ATTACK, seconds) + self:SetOption(AI.Option.GROUND.id.DISPERSE_ON_ATTACK, seconds) end end return self From d39074bf3e7d9bf56e044102fce6e54ccb0c44ec Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 22 May 2021 00:35:00 +0200 Subject: [PATCH 014/111] Sound --- Moose Development/Moose/AddOns/SRS.lua | 151 +++++++++++++++++++++ Moose Development/Moose/Core/SoundFile.lua | 110 +++++++++++++++ 2 files changed, 261 insertions(+) create mode 100644 Moose Development/Moose/AddOns/SRS.lua create mode 100644 Moose Development/Moose/Core/SoundFile.lua diff --git a/Moose Development/Moose/AddOns/SRS.lua b/Moose Development/Moose/AddOns/SRS.lua new file mode 100644 index 000000000..681b3dc2a --- /dev/null +++ b/Moose Development/Moose/AddOns/SRS.lua @@ -0,0 +1,151 @@ +--- **AddOn** - Simple Radio +-- +-- === +-- +-- **Main Features:** +-- +-- * SRS integration +-- +-- === +-- +-- ## Youtube Videos: +-- +-- * None +-- +-- === +-- +-- ## Missions: Example missions can be found [here](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20ATIS) +-- +-- === +-- +-- ## Sound files: Check out the pinned messages in the Moose discord #ops-atis channel. +-- +-- === +-- +-- Automatic terminal information service, or ATIS, is a continuous broadcast of recorded aeronautical information in busier terminal areas, *i.e.* airports and their immediate surroundings. +-- ATIS broadcasts contain essential information, such as current weather information, active runways, and any other information required by the pilots. +-- +-- === +-- +-- ### Author: **funkyfranky** +-- @module Addons.SRS +-- @image Addons_SRS.png + + +--- MSRS class. +-- @type MSRS +-- @field #string ClassName Name of the class. +-- @field #string lid Class id string for output to DCS log file. +-- @extends Core.Fsm#FSM + +--- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde +-- +-- === +-- +-- ![Banner Image](..\Presentations\ATIS\ATIS_Main.png) +-- +-- # The MSRS Concept +-- +-- Automatic terminal information service, or ATIS, is a continuous broadcast of recorded aeronautical information in busier terminal areas, *i.e.* airports and their immediate surroundings. +-- ATIS broadcasts contain essential information, such as current weather information, active runways, and any other information required by the pilots. +-- +-- @field #MSRS +MSRS = { + ClassName = "MSRS", + lid = nil, +} + +--- ATIS class version. +-- @field #string version +MSRS.version="0.9.1" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: A lot. + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new ATIS class object for a specific aircraft carrier unit. +-- @param #MSRS self +-- @param #string airbasename Name of the airbase. +-- @param #number frequency Radio frequency in MHz. Default 143.00 MHz. +-- @param #number modulation Radio modulation: 0=AM, 1=FM. Default 0=AM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators +-- @return #MSRS self +function MSRS:New(airbasename, frequency, modulation) + + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, FSM:New()) -- #ATIS + + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Set sound files folder within miz file. +-- @param #MSRS self +-- @param #string path Path for sound files. Default "ATIS Soundfiles/". Mind the slash "/" at the end! +-- @return #MSRS self +function MSRS:SetSoundfilesPath(path) + self.soundpath=tostring(path or "ATIS Soundfiles/") + self:I(self.lid..string.format("Setting sound files path to %s", self.soundpath)) + return self +end + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Start & Status +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Start ATIS FSM. +-- @param #MSRS self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function MSRS:onafterStart(From, Event, To) + +end + +--- Update status. +-- @param #MSRS self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function MSRS:onafterStatus(From, Event, To) + + -- Get FSM state. + local fsmstate=self:GetState() + + self:__Status(-60) +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- FSM Events +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Check if radio queue is empty. If so, start broadcasting the message again. +-- @param #MRSRS self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function MSRS:onafterCheckQueue(From, Event, To) + + -- Check back in 5 seconds. + self:__CheckQueue(-5) +end + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Misc Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Core/SoundFile.lua b/Moose Development/Moose/Core/SoundFile.lua new file mode 100644 index 000000000..85e7745ac --- /dev/null +++ b/Moose Development/Moose/Core/SoundFile.lua @@ -0,0 +1,110 @@ +--- **Core** - Sound file +-- +-- === +-- +-- ## Features: +-- +-- * Add a sound file to the +-- +-- === +-- +-- ### Author: **funkyfranky** +-- +-- === +-- +-- @module Core.UserFlag +-- @image Core_Userflag.JPG +-- + +do -- Sound File + + --- @type SOUNDFILE + -- @field #string ClassName Name of the class + -- @field #string Name Name of the flag. + -- @extends Core.Base#BASE + + + --- Management of DCS User Flags. + -- + -- # 1. USERFLAG constructor + -- + -- * @{#USERFLAG.New}(): Creates a new USERFLAG object. + -- + -- @field #SOUNDFILE + SOUNDFILE={ + ClassName = "SOUNDFILE", + Name = nil, + } + + --- Constructor. + -- @param #SOUNDFILE self + -- @param #string UserFlagName The name of the userflag, which is a free text string. + -- @return #SOUNDFILE + function SOUNDFILE:New( UserFlagName ) --R2.3 + + local self=BASE:Inherit(self, BASE:New()) -- #SOUNDFILE + + self.UserFlagName = UserFlagName + + return self + end + + --- Get the userflag name. + -- @param #USERFLAG self + -- @return #string Name of the user flag. + function USERFLAG:GetName() + return self.UserFlagName + end + + --- Set the userflag to a given Number. + -- @param #USERFLAG self + -- @param #number Number The number value to be checked if it is the same as the userflag. + -- @param #number Delay Delay in seconds, before the flag is set. + -- @return #USERFLAG The userflag instance. + -- @usage + -- local BlueVictory = USERFLAG:New( "VictoryBlue" ) + -- BlueVictory:Set( 100 ) -- Set the UserFlag VictoryBlue to 100. + -- + function USERFLAG:Set( Number, Delay ) --R2.3 + + if Delay and Delay>0 then + self:ScheduleOnce(Delay, USERFLAG.Set, self, Number) + else + --env.info(string.format("Setting flag \"%s\" to %d at T=%.1f", self.UserFlagName, Number, timer.getTime())) + trigger.action.setUserFlag( self.UserFlagName, Number ) + end + + return self + end + + + --- Get the userflag Number. + -- @param #USERFLAG self + -- @return #number Number The number value to be checked if it is the same as the userflag. + -- @usage + -- local BlueVictory = USERFLAG:New( "VictoryBlue" ) + -- local BlueVictoryValue = BlueVictory:Get() -- Get the UserFlag VictoryBlue value. + -- + function USERFLAG:Get() --R2.3 + + return trigger.misc.getUserFlag( self.UserFlagName ) + end + + + + --- Check if the userflag has a value of Number. + -- @param #USERFLAG self + -- @param #number Number The number value to be checked if it is the same as the userflag. + -- @return #boolean true if the Number is the value of the userflag. + -- @usage + -- local BlueVictory = USERFLAG:New( "VictoryBlue" ) + -- if BlueVictory:Is( 1 ) then + -- return "Blue has won" + -- end + function USERFLAG:Is( Number ) --R2.3 + + return trigger.misc.getUserFlag( self.UserFlagName ) == Number + + end + +end \ No newline at end of file From 7a3fb9585110359225766c57e4f995ab39c8a8b6 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 22 May 2021 23:26:18 +0200 Subject: [PATCH 015/111] Sound Update 1 - Moved all related files to new Moose folder "Sound/" --- Moose Development/Moose/Core/SoundFile.lua | 110 -------------- Moose Development/Moose/Modules.lua | 11 +- .../Moose/{Core => Sound}/Radio.lua | 0 .../Moose/{Core => Sound}/RadioQueue.lua | 33 +++-- .../Moose/{Core => Sound}/RadioSpeech.lua | 0 .../Moose/{AddOns => Sound}/SRS.lua | 0 Moose Development/Moose/Sound/SoundFile.lua | 138 ++++++++++++++++++ .../Moose/{Core => Sound}/UserSound.lua | 0 Moose Development/Moose/Utilities/Utils.lua | 20 ++- Moose Setup/Moose.files | 11 +- 10 files changed, 195 insertions(+), 128 deletions(-) delete mode 100644 Moose Development/Moose/Core/SoundFile.lua rename Moose Development/Moose/{Core => Sound}/Radio.lua (100%) rename Moose Development/Moose/{Core => Sound}/RadioQueue.lua (93%) rename Moose Development/Moose/{Core => Sound}/RadioSpeech.lua (100%) rename Moose Development/Moose/{AddOns => Sound}/SRS.lua (100%) create mode 100644 Moose Development/Moose/Sound/SoundFile.lua rename Moose Development/Moose/{Core => Sound}/UserSound.lua (100%) diff --git a/Moose Development/Moose/Core/SoundFile.lua b/Moose Development/Moose/Core/SoundFile.lua deleted file mode 100644 index 85e7745ac..000000000 --- a/Moose Development/Moose/Core/SoundFile.lua +++ /dev/null @@ -1,110 +0,0 @@ ---- **Core** - Sound file --- --- === --- --- ## Features: --- --- * Add a sound file to the --- --- === --- --- ### Author: **funkyfranky** --- --- === --- --- @module Core.UserFlag --- @image Core_Userflag.JPG --- - -do -- Sound File - - --- @type SOUNDFILE - -- @field #string ClassName Name of the class - -- @field #string Name Name of the flag. - -- @extends Core.Base#BASE - - - --- Management of DCS User Flags. - -- - -- # 1. USERFLAG constructor - -- - -- * @{#USERFLAG.New}(): Creates a new USERFLAG object. - -- - -- @field #SOUNDFILE - SOUNDFILE={ - ClassName = "SOUNDFILE", - Name = nil, - } - - --- Constructor. - -- @param #SOUNDFILE self - -- @param #string UserFlagName The name of the userflag, which is a free text string. - -- @return #SOUNDFILE - function SOUNDFILE:New( UserFlagName ) --R2.3 - - local self=BASE:Inherit(self, BASE:New()) -- #SOUNDFILE - - self.UserFlagName = UserFlagName - - return self - end - - --- Get the userflag name. - -- @param #USERFLAG self - -- @return #string Name of the user flag. - function USERFLAG:GetName() - return self.UserFlagName - end - - --- Set the userflag to a given Number. - -- @param #USERFLAG self - -- @param #number Number The number value to be checked if it is the same as the userflag. - -- @param #number Delay Delay in seconds, before the flag is set. - -- @return #USERFLAG The userflag instance. - -- @usage - -- local BlueVictory = USERFLAG:New( "VictoryBlue" ) - -- BlueVictory:Set( 100 ) -- Set the UserFlag VictoryBlue to 100. - -- - function USERFLAG:Set( Number, Delay ) --R2.3 - - if Delay and Delay>0 then - self:ScheduleOnce(Delay, USERFLAG.Set, self, Number) - else - --env.info(string.format("Setting flag \"%s\" to %d at T=%.1f", self.UserFlagName, Number, timer.getTime())) - trigger.action.setUserFlag( self.UserFlagName, Number ) - end - - return self - end - - - --- Get the userflag Number. - -- @param #USERFLAG self - -- @return #number Number The number value to be checked if it is the same as the userflag. - -- @usage - -- local BlueVictory = USERFLAG:New( "VictoryBlue" ) - -- local BlueVictoryValue = BlueVictory:Get() -- Get the UserFlag VictoryBlue value. - -- - function USERFLAG:Get() --R2.3 - - return trigger.misc.getUserFlag( self.UserFlagName ) - end - - - - --- Check if the userflag has a value of Number. - -- @param #USERFLAG self - -- @param #number Number The number value to be checked if it is the same as the userflag. - -- @return #boolean true if the Number is the value of the userflag. - -- @usage - -- local BlueVictory = USERFLAG:New( "VictoryBlue" ) - -- if BlueVictory:Is( 1 ) then - -- return "Blue has won" - -- end - function USERFLAG:Is( Number ) --R2.3 - - return trigger.misc.getUserFlag( self.UserFlagName ) == Number - - end - -end \ No newline at end of file diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index c4bb9eb87..06f53d723 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -6,7 +6,6 @@ __Moose.Include( 'Scripts/Moose/Utilities/Templates.lua' ) __Moose.Include( 'Scripts/Moose/Core/Base.lua' ) __Moose.Include( 'Scripts/Moose/Core/UserFlag.lua' ) -__Moose.Include( 'Scripts/Moose/Core/UserSound.lua' ) __Moose.Include( 'Scripts/Moose/Core/Report.lua' ) __Moose.Include( 'Scripts/Moose/Core/Scheduler.lua' ) __Moose.Include( 'Scripts/Moose/Core/ScheduleDispatcher.lua' ) @@ -21,9 +20,6 @@ __Moose.Include( 'Scripts/Moose/Core/Point.lua' ) __Moose.Include( 'Scripts/Moose/Core/Velocity.lua' ) __Moose.Include( 'Scripts/Moose/Core/Message.lua' ) __Moose.Include( 'Scripts/Moose/Core/Fsm.lua' ) -__Moose.Include( 'Scripts/Moose/Core/Radio.lua' ) -__Moose.Include( 'Scripts/Moose/Core/RadioQueue.lua' ) -__Moose.Include( 'Scripts/Moose/Core/RadioSpeech.lua' ) __Moose.Include( 'Scripts/Moose/Core/Spawn.lua' ) __Moose.Include( 'Scripts/Moose/Core/SpawnStatic.lua' ) __Moose.Include( 'Scripts/Moose/Core/Timer.lua' ) @@ -113,6 +109,13 @@ __Moose.Include( 'Scripts/Moose/Actions/Act_Route.lua' ) __Moose.Include( 'Scripts/Moose/Actions/Act_Account.lua' ) __Moose.Include( 'Scripts/Moose/Actions/Act_Assist.lua' ) +__Moose.Include( 'Scripts/Moose/Sound/UserSound.lua' ) +__Moose.Include( 'Scripts/Moose/Sound/SoundFile.lua' ) +__Moose.Include( 'Scripts/Moose/Sound/Radio.lua' ) +__Moose.Include( 'Scripts/Moose/Sound/RadioQueue.lua' ) +__Moose.Include( 'Scripts/Moose/Sound/RadioSpeech.lua' ) +__Moose.Include( 'Scripts/Moose/Sound/SRS.lua' ) + __Moose.Include( 'Scripts/Moose/Tasking/CommandCenter.lua' ) __Moose.Include( 'Scripts/Moose/Tasking/Mission.lua' ) __Moose.Include( 'Scripts/Moose/Tasking/Task.lua' ) diff --git a/Moose Development/Moose/Core/Radio.lua b/Moose Development/Moose/Sound/Radio.lua similarity index 100% rename from Moose Development/Moose/Core/Radio.lua rename to Moose Development/Moose/Sound/Radio.lua diff --git a/Moose Development/Moose/Core/RadioQueue.lua b/Moose Development/Moose/Sound/RadioQueue.lua similarity index 93% rename from Moose Development/Moose/Core/RadioQueue.lua rename to Moose Development/Moose/Sound/RadioQueue.lua index a63677a98..3e6afcd51 100644 --- a/Moose Development/Moose/Core/RadioQueue.lua +++ b/Moose Development/Moose/Sound/RadioQueue.lua @@ -1,4 +1,4 @@ ---- **Core** - Queues Radio Transmissions. +--- **Sound** - Queues Radio Transmissions. -- -- === -- @@ -10,8 +10,8 @@ -- -- ### Authors: funkyfranky -- --- @module Core.RadioQueue --- @image Core_Radio.JPG +-- @module Sound.RadioQueue +-- @image Sound_Radio.JPG --- Manages radio transmissions. -- @@ -125,9 +125,9 @@ function RADIOQUEUE:Start(delay, dt) -- Start Scheduler. if self.schedonce then - self:_CheckRadioQueueDelayed(delay) + self:_CheckRadioQueueDelayed(self.delay) else - self.RQid=self.scheduler:Schedule(nil, RADIOQUEUE._CheckRadioQueue, {self}, delay, dt) + self.RQid=self.scheduler:Schedule(nil, RADIOQUEUE._CheckRadioQueue, {self}, self.delay, self.dt) end return self @@ -202,7 +202,7 @@ end --- Add a transmission to the radio queue. -- @param #RADIOQUEUE self -- @param #RADIOQUEUE.Transmission transmission The transmission data table. --- @return #RADIOQUEUE self The RADIOQUEUE object. +-- @return #RADIOQUEUE self function RADIOQUEUE:AddTransmission(transmission) self:F({transmission=transmission}) @@ -221,7 +221,7 @@ function RADIOQUEUE:AddTransmission(transmission) return self end ---- Add a transmission to the radio queue. +--- Create a new transmission and add it to the radio queue. -- @param #RADIOQUEUE self -- @param #string filename Name of the sound file. Usually an ogg or wav file type. -- @param #number duration Duration in seconds the file lasts. @@ -233,6 +233,8 @@ end -- @return #RADIOQUEUE self The RADIOQUEUE object. function RADIOQUEUE:NewTransmission(filename, duration, path, tstart, interval, subtitle, subduration) + env.info("FF new transmission.") + -- Sanity checks. if not filename then self:E(self.lid.."ERROR: No filename specified.") @@ -272,6 +274,19 @@ function RADIOQUEUE:NewTransmission(filename, duration, path, tstart, interval, return self end +--- Create a new transmission and add it to the radio queue. +-- @param #RADIOQUEUE self +-- @param Sound.SoundFile#SOUNDFILE soundfile Sound file object to be added. +-- @param #number tstart Start time (abs) seconds. Default now. +-- @param #number interval Interval in seconds after the last transmission finished. +-- @return #RADIOQUEUE self +function RADIOQUEUE:AddSoundfile(soundfile, tstart, interval) + env.info(string.format("FF add soundfile: name=%s%s", soundfile:GetPath(), soundfile:GetFileName())) + self:NewTransmission(soundfile:GetFileName(), soundfile.duration, soundfile:GetPath(), tstart, interval, soundfile.subtitle, soundfile.subduration) + + return self +end + --- Convert a number (as string) into a radio transmission. -- E.g. for board number or headings. -- @param #RADIOQUEUE self @@ -292,7 +307,7 @@ function RADIOQUEUE:Number2Transmission(number, delay, interval) end -- Split string into characters. - local numbers=_split(number) + local numbers=UTILS.GetCharacters(number) --l_split(number) local wait=0 for i=1,#numbers do @@ -547,7 +562,7 @@ function RADIOQUEUE:_GetRadioSender() return nil end ---- Get unit from which we want to transmit a radio message. This has to be an aircraft for subtitles to work. +--- Get unit from which we want to transmit a radio message. This has to be an aircraft or ground unit for subtitles to work. -- @param #RADIOQUEUE self -- @return DCS#Vec3 Vector 3D. function RADIOQUEUE:_GetRadioSenderCoord() diff --git a/Moose Development/Moose/Core/RadioSpeech.lua b/Moose Development/Moose/Sound/RadioSpeech.lua similarity index 100% rename from Moose Development/Moose/Core/RadioSpeech.lua rename to Moose Development/Moose/Sound/RadioSpeech.lua diff --git a/Moose Development/Moose/AddOns/SRS.lua b/Moose Development/Moose/Sound/SRS.lua similarity index 100% rename from Moose Development/Moose/AddOns/SRS.lua rename to Moose Development/Moose/Sound/SRS.lua diff --git a/Moose Development/Moose/Sound/SoundFile.lua b/Moose Development/Moose/Sound/SoundFile.lua new file mode 100644 index 000000000..f1a759cbb --- /dev/null +++ b/Moose Development/Moose/Sound/SoundFile.lua @@ -0,0 +1,138 @@ +--- **Sound** - Sound file management. +-- +-- === +-- +-- ## Features: +-- +-- * Add a sound file to the +-- +-- === +-- +-- ### Author: **funkyfranky** +-- +-- === +-- +-- @module Sound.Soundfile +-- @image Sound_Soundfile.png +-- + +do -- Sound File + + --- @type SOUNDFILE + -- @field #string ClassName Name of the class + -- @field #string filename Name of the flag. + -- @field #string path Directory path, where the sound file is located. + -- @field #string duration Duration of the sound file in seconds. + -- @field #string subtitle Subtitle of the transmission. + -- @field #number subduration Duration in seconds how long the subtitle is displayed. + -- @field #boolean insideMiz If true (default), the sound file is located inside the mission .miz file. + -- @extends Core.Base#BASE + + + --- Sound files used by other classes. + -- + -- # 1. USERFLAG constructor + -- + -- * @{#USERFLAG.New}(): Creates a new USERFLAG object. + -- + -- @field #SOUNDFILE + SOUNDFILE={ + ClassName = "SOUNDFILE", + filename = nil, + path = "l10n/DEFAULT", + duration = 3, + subtitle = nil, + subduration = 0, + insideMiz = true, + } + + --- Constructor to create a new SOUNDFILE object. + -- @param #SOUNDFILE self + -- @param #string filename The name of the sound file, e.g. "Hello World.ogg". + -- @param #string Path The path of the directory, where the sound file is located. Default is "l10n/DEFAULT/" within the miz file. + -- @param #number Duration Duration in seconds, how long it takes to play the sound file. Default is 3 seconds. + -- @return #SOUNDFILE self + function SOUNDFILE:New(filename, Path, Duration) + + -- Inherit BASE. + local self=BASE:Inherit(self, BASE:New()) -- #SOUNDFILE + + -- Set file name. + self.filename=filename or "Hallo World.ogg" + + -- Set path + self.path=Path or "l10n/DEFAULT/" + + self.duration=Duration or 3 + + -- Debug info: + self:I(string.format("New SOUNDFILE: file name=%s, path=%s", self.filename, self.path)) + + return self + end + + --- Set path, where the . + -- @param #SOUNDFILE self + -- @param #string Path Path to the directory, where the sound file is located. + -- @return self + function SOUNDFILE:SetPath(Path) + + self.path=Path or "l10n/DEFAULT/" + + while self.path:sub(-1)=="/" or self.path:sub(-1)=="\\" do + self.path=self.path:sub(1,-1) + end + + return self + end + + + --- Get the sound file name. + -- @param #SOUNDFILE self + -- @return #string Name of the soud file. This does *not* include its path. + function SOUNDFILE:GetFileName() + return self.filename + end + + --- Get path of the directory, where the sound file is located. + -- @param #SOUNDFILE self + -- @return #string Path. + function SOUNDFILE:GetPath() + local path=self.path or "l10n/DEFAULT" + path=path.."/" + return path + end + + --- Get the complete sound file name inlcuding its path. + -- @param #SOUNDFILE self + -- @return #string Name of the sound file. + function SOUNDFILE:GetName() + local filename=self:GetFileName() + local path=self:GetPath() + local name=string.format("%s/%s", path, filename) + return name + end + + + --- Set the userflag to a given Number. + -- @param #SOUNDFILE self + -- @param #number Number The number value to be checked if it is the same as the userflag. + -- @param #number Delay Delay in seconds, before the flag is set. + -- @return #SOUNDFILE self + -- @usage + -- local BlueVictory = USERFLAG:New( "VictoryBlue" ) + -- BlueVictory:Set( 100 ) -- Set the UserFlag VictoryBlue to 100. + -- + function SOUNDFILE:Set( Number, Delay ) --R2.3 + + if Delay and Delay>0 then + self:ScheduleOnce(Delay, USERFLAG.Set, self, Number) + else + --env.info(string.format("Setting flag \"%s\" to %d at T=%.1f", self.UserFlagName, Number, timer.getTime())) + trigger.action.setUserFlag( self.UserFlagName, Number ) + end + + return self + end + +end \ No newline at end of file diff --git a/Moose Development/Moose/Core/UserSound.lua b/Moose Development/Moose/Sound/UserSound.lua similarity index 100% rename from Moose Development/Moose/Core/UserSound.lua rename to Moose Development/Moose/Sound/UserSound.lua diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 3fd6e9482..af7642406 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -682,7 +682,10 @@ function UTILS.IsInSphere( InVec3, Vec3, Radius ) return InSphere end --- Beaufort scale: returns Beaufort number and wind description as a function of wind speed in m/s. +--- Beaufort scale: returns Beaufort number and wind description as a function of wind speed in m/s. +-- @param #number speed Wind speed in m/s. +-- @return #number Beaufort number. +-- @return #string Beauford wind description. function UTILS.BeaufortScale(speed) local bn=nil local bd=nil @@ -742,6 +745,21 @@ function UTILS.Split(str, sep) return result end +--- Get a table of all characters in a string. +-- @param #string str Sting. +-- @return #table Individual characters. +function UTILS.GetCharacters(str) + + local chars={} + + for i=1,#str do + local c=str:sub(i,i) + table.insert(chars, c) + end + + return chars +end + --- Convert time in seconds to hours, minutes and seconds. -- @param #number seconds Time in seconds, e.g. from timer.getAbsTime() function. -- @param #boolean short (Optional) If true, use short output, i.e. (HH:)MM:SS without day. diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 79c9b120e..939bf7770 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -7,7 +7,6 @@ Utilities/Templates.lua Core/Base.lua Core/UserFlag.lua -Core/UserSound.lua Core/Report.lua Core/Scheduler.lua Core/ScheduleDispatcher.lua @@ -22,9 +21,6 @@ Core/Point.lua Core/Velocity.lua Core/Message.lua Core/Fsm.lua -Core/Radio.lua -Core/RadioQueue.lua -Core/RadioSpeech.lua Core/Spawn.lua Core/SpawnStatic.lua Core/Timer.lua @@ -114,6 +110,13 @@ Actions/Act_Route.lua Actions/Act_Account.lua Actions/Act_Assist.lua +Sound/UserSound.lua +Sound/SoundFile.lua +Sound/Radio.lua +Sound/RadioQueue.lua +Sound/RadioSpeech.lua +Sound/SRS.lua + Tasking/CommandCenter.lua Tasking/Mission.lua Tasking/Task.lua From e55bb21401520d1fc4bc684ea3250fe00f025103 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 23 May 2021 23:32:26 +0200 Subject: [PATCH 016/111] Sound 2 --- Moose Development/Moose/Sound/SRS.lua | 106 ++++++++++---------- Moose Development/Moose/Sound/SoundFile.lua | 2 +- 2 files changed, 56 insertions(+), 52 deletions(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 681b3dc2a..1cad9d407 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -1,10 +1,11 @@ ---- **AddOn** - Simple Radio +--- **Sound** - Simple Radio Standalone Integration -- -- === -- -- **Main Features:** -- --- * SRS integration +-- * Play sound files via SRS +-- * Play text-to-speach via SRS -- -- === -- @@ -31,12 +32,11 @@ -- @module Addons.SRS -- @image Addons_SRS.png - --- MSRS class. -- @type MSRS -- @field #string ClassName Name of the class. -- @field #string lid Class id string for output to DCS log file. --- @extends Core.Fsm#FSM +-- @extends Core.Base#BASE --- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde -- @@ -46,8 +46,11 @@ -- -- # The MSRS Concept -- --- Automatic terminal information service, or ATIS, is a continuous broadcast of recorded aeronautical information in busier terminal areas, *i.e.* airports and their immediate surroundings. --- ATIS broadcasts contain essential information, such as current weather information, active runways, and any other information required by the pilots. +-- This class allows to broadcast sound files or text via Simple Radio Standalone (SRS). +-- +-- # Prerequisites +-- +-- This script needs SRS version >= 0.9.6. -- -- @field #MSRS MSRS = { @@ -57,7 +60,7 @@ MSRS = { --- ATIS class version. -- @field #string version -MSRS.version="0.9.1" +MSRS.version="0.0.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -69,16 +72,22 @@ MSRS.version="0.9.1" -- Constructor ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Create a new ATIS class object for a specific aircraft carrier unit. +--- Create a new MSRS object. -- @param #MSRS self --- @param #string airbasename Name of the airbase. --- @param #number frequency Radio frequency in MHz. Default 143.00 MHz. --- @param #number modulation Radio modulation: 0=AM, 1=FM. Default 0=AM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators +-- @param #string PathToSRS Path to the directory, where SRS is located. +-- @param #number Frequency Radio frequency in MHz. Default 143.00 MHz. +-- @param #number Modulation Radio modulation: 0=AM (default), 1=FM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators. -- @return #MSRS self -function MSRS:New(airbasename, frequency, modulation) +function MSRS:New(PathToSRS, Frequency, Modulation) -- Inherit everything from FSM class. - local self=BASE:Inherit(self, FSM:New()) -- #ATIS + local self=BASE:Inherit(self, FSM:New()) -- #MSRS + + self.path=self:SetPath(PathToSRS) + + self.frequency=Frequency or 143 + + self.modulation=Modulation or radio.modulation.AM return self @@ -88,56 +97,51 @@ end -- User Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Set sound files folder within miz file. + +--- Set path, where the sound file is located. -- @param #MSRS self --- @param #string path Path for sound files. Default "ATIS Soundfiles/". Mind the slash "/" at the end! +-- @param #string Path Path to the directory, where the sound file is located. -- @return #MSRS self -function MSRS:SetSoundfilesPath(path) - self.soundpath=tostring(path or "ATIS Soundfiles/") - self:I(self.lid..string.format("Setting sound files path to %s", self.soundpath)) +function MSRS:SetPath(Path) + + if Path==nil then + return nil + end + + self.path=Path + + -- Remove (back)slashes. + while self.path:sub(-1)=="/" or self.path:sub(-1)=="\\" do + self.path=self.path:sub(1,-1) + end + return self end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Start & Status -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Start ATIS FSM. +--- Get path to SRS directory. -- @param #MSRS self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function MSRS:onafterStart(From, Event, To) +-- @return #string +function MSRS:GetPath() + return self.path +end + +--- Set path, where the sound file is located. +-- @param #MSRS self +-- @param #string Path Path to the directory, where the sound file is located. +-- @return #MSRS self +function MSRS:PlaySoundfile(Soundfile) + + end ---- Update status. +--- Set path, where the sound file is located. -- @param #MSRS self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function MSRS:onafterStatus(From, Event, To) +-- @param #string Message Text message. +-- @return #MSRS self +function MSRS:PlayText(Message) - -- Get FSM state. - local fsmstate=self:GetState() - self:__Status(-60) -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- FSM Events -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Check if radio queue is empty. If so, start broadcasting the message again. --- @param #MRSRS self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function MSRS:onafterCheckQueue(From, Event, To) - - -- Check back in 5 seconds. - self:__CheckQueue(-5) end diff --git a/Moose Development/Moose/Sound/SoundFile.lua b/Moose Development/Moose/Sound/SoundFile.lua index f1a759cbb..564fe1306 100644 --- a/Moose Development/Moose/Sound/SoundFile.lua +++ b/Moose Development/Moose/Sound/SoundFile.lua @@ -71,7 +71,7 @@ do -- Sound File return self end - --- Set path, where the . + --- Set path, where the sound file is located. -- @param #SOUNDFILE self -- @param #string Path Path to the directory, where the sound file is located. -- @return self From 85fef96d0038f3573c4baba3ba4489ca02e5f777 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 24 May 2021 10:00:16 +0200 Subject: [PATCH 017/111] ZONE_POLYGON_BASE:Boundary added (#1537) * ZONE_POLYGON_BASE:Boundary added ZONE_POLYGON_BASE:Boundary added * Update Zone.lua Change Radius default value --- Moose Development/Moose/Core/Zone.lua | 749 +++++++++++++++----------- 1 file changed, 446 insertions(+), 303 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 8ad723486..65ac3cb9d 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1,9 +1,9 @@ --- **Core** - Define zones within your mission of various forms, with various capabilities. --- +-- -- === --- +-- -- ## Features: --- +-- -- * Create radius zones. -- * Create trigger zones. -- * Create polygon zones. @@ -18,24 +18,24 @@ -- * Get zone bounding box. -- * Set/get zone name. -- * Draw zones (circular and polygon) on the F10 map. --- --- +-- +-- -- There are essentially two core functions that zones accomodate: --- +-- -- * Test if an object is within the zone boundaries. -- * Provide the zone behaviour. Some zones are static, while others are moveable. --- +-- -- The object classes are using the zone classes to test the zone boundaries, which can take various forms: --- +-- -- * Test if completely within the zone. -- * Test if partly within the zone (for @{Wrapper.Group#GROUP} objects). -- * Test if not in the zone. -- * Distance to the nearest intersecting point of the zone. -- * Distance to the center of the zone. -- * ... --- +-- -- Each of these ZONE classes have a zone name, and specific parameters defining the zone type: --- +-- -- * @{#ZONE_BASE}: The ZONE_BASE class defining the base for all other zone classes. -- * @{#ZONE_RADIUS}: The ZONE_RADIUS class defined by a zone name, a location and a radius. -- * @{#ZONE}: The ZONE class, defined by the zone name as defined within the Mission Editor. @@ -43,15 +43,15 @@ -- * @{#ZONE_GROUP}: The ZONE_GROUP class defines by a zone around a @{Wrapper.Group#GROUP} with a radius. -- * @{#ZONE_POLYGON}: The ZONE_POLYGON class defines by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon. -- --- === --- --- ### Author: **FlightControl** --- ### Contributions: --- -- === --- +-- +-- ### Author: **FlightControl** +-- ### Contributions: +-- +-- === +-- -- @module Core.Zone --- @image Core_Zones.JPG +-- @image Core_Zones.JPG --- @type ZONE_BASE @@ -63,28 +63,28 @@ --- This class is an abstract BASE class for derived classes, and is not meant to be instantiated. --- +-- -- ## Each zone has a name: --- +-- -- * @{#ZONE_BASE.GetName}(): Returns the name of the zone. -- * @{#ZONE_BASE.SetName}(): Sets the name of the zone. --- --- +-- +-- -- ## Each zone implements two polymorphic functions defined in @{Core.Zone#ZONE_BASE}: --- +-- -- * @{#ZONE_BASE.IsVec2InZone}(): Returns if a 2D vector is within the zone. -- * @{#ZONE_BASE.IsVec3InZone}(): Returns if a 3D vector is within the zone. -- * @{#ZONE_BASE.IsPointVec2InZone}(): Returns if a 2D point vector is within the zone. -- * @{#ZONE_BASE.IsPointVec3InZone}(): Returns if a 3D point vector is within the zone. --- +-- -- ## A zone has a probability factor that can be set to randomize a selection between zones: --- +-- -- * @{#ZONE_BASE.SetZoneProbability}(): Set the randomization probability of a zone to be selected, taking a value between 0 and 1 ( 0 = 0%, 1 = 100% ) -- * @{#ZONE_BASE.GetZoneProbability}(): Get the randomization probability of a zone to be selected, passing a value between 0 and 1 ( 0 = 0%, 1 = 100% ) -- * @{#ZONE_BASE.GetZoneMaybe}(): Get the zone taking into account the randomization probability. nil is returned if this zone is not a candidate. --- +-- -- ## A zone manages vectors: --- +-- -- * @{#ZONE_BASE.GetVec2}(): Returns the 2D vector coordinate of the zone. -- * @{#ZONE_BASE.GetVec3}(): Returns the 3D vector coordinate of the zone. -- * @{#ZONE_BASE.GetPointVec2}(): Returns the 2D point vector coordinate of the zone. @@ -92,16 +92,16 @@ -- * @{#ZONE_BASE.GetRandomVec2}(): Define a random 2D vector within the zone. -- * @{#ZONE_BASE.GetRandomPointVec2}(): Define a random 2D point vector within the zone. -- * @{#ZONE_BASE.GetRandomPointVec3}(): Define a random 3D point vector within the zone. --- +-- -- ## A zone has a bounding square: --- +-- -- * @{#ZONE_BASE.GetBoundingSquare}(): Get the outer most bounding square of the zone. --- --- ## A zone can be marked: --- +-- +-- ## A zone can be marked: +-- -- * @{#ZONE_BASE.SmokeZone}(): Smokes the zone boundaries in a color. -- * @{#ZONE_BASE.FlareZone}(): Flares the zone boundaries in a color. --- +-- -- @field #ZONE_BASE ZONE_BASE = { ClassName = "ZONE_BASE", @@ -129,7 +129,7 @@ function ZONE_BASE:New( ZoneName ) self:F( ZoneName ) self.ZoneName = ZoneName - + return self end @@ -206,7 +206,7 @@ end -- @param #ZONE_BASE self -- @return #nil. function ZONE_BASE:GetVec2() - return nil + return nil end --- Returns a @{Core.Point#POINT_VEC2} of the zone. @@ -215,30 +215,14 @@ end -- @return Core.Point#POINT_VEC2 The PointVec2 of the zone. function ZONE_BASE:GetPointVec2() self:F2( self.ZoneName ) - + local Vec2 = self:GetVec2() local PointVec2 = POINT_VEC2:NewFromVec2( Vec2 ) self:T2( { PointVec2 } ) - - return PointVec2 -end - ---- Returns a @{Core.Point#COORDINATE} of the zone. --- @param #ZONE_BASE self --- @return Core.Point#COORDINATE The Coordinate of the zone. -function ZONE_BASE:GetCoordinate() - self:F2( self.ZoneName ) - - local Vec2 = self:GetVec2() - - local Coordinate = COORDINATE:NewFromVec2( Vec2 ) - - self:T2( { Coordinate } ) - - return Coordinate + return PointVec2 end @@ -248,16 +232,16 @@ end -- @return DCS#Vec3 The Vec3 of the zone. function ZONE_BASE:GetVec3( Height ) self:F2( self.ZoneName ) - + Height = Height or 0 - + local Vec2 = self:GetVec2() local Vec3 = { x = Vec2.x, y = Height and Height or land.getHeight( self:GetVec2() ), z = Vec2.y } self:T2( { Vec3 } ) - - return Vec3 + + return Vec3 end --- Returns a @{Core.Point#POINT_VEC3} of the zone. @@ -266,14 +250,14 @@ end -- @return Core.Point#POINT_VEC3 The PointVec3 of the zone. function ZONE_BASE:GetPointVec3( Height ) self:F2( self.ZoneName ) - + local Vec3 = self:GetVec3( Height ) local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) self:T2( { PointVec3 } ) - - return PointVec3 + + return PointVec3 end --- Returns a @{Core.Point#COORDINATE} of the zone. @@ -281,15 +265,27 @@ end -- @param DCS#Distance Height The height to add to the land height where the center of the zone is located. -- @return Core.Point#COORDINATE The Coordinate of the zone. function ZONE_BASE:GetCoordinate( Height ) --R2.1 - self:F2( self.ZoneName ) - + self:F2(self.ZoneName) + local Vec3 = self:GetVec3( Height ) - local PointVec3 = COORDINATE:NewFromVec3( Vec3 ) + if self.Coordinate then - self:T2( { PointVec3 } ) - - return PointVec3 + -- Update coordinates. + self.Coordinate.x=Vec3.x + self.Coordinate.y=Vec3.y + self.Coordinate.z=Vec3.z + + --env.info("FF GetCoordinate NEW for ZONE_BASE "..tostring(self.ZoneName)) + else + + -- Create a new coordinate object. + self.Coordinate=COORDINATE:NewFromVec3(Vec3) + + --env.info("FF GetCoordinate NEW for ZONE_BASE "..tostring(self.ZoneName)) + end + + return self.Coordinate end @@ -339,7 +335,7 @@ function ZONE_BASE:SetColor(RGBcolor, Alpha) RGBcolor=RGBcolor or {1, 0, 0} Alpha=Alpha or 0.15 - + self.Color={} self.Color[1]=RGBcolor[1] self.Color[2]=RGBcolor[2] @@ -375,7 +371,7 @@ function ZONE_BASE:GetColorAlpha() return alpha end ---- Remove the drawing of the zone from the F10 map. +--- Remove the drawing of the zone from the F10 map. -- @param #ZONE_BASE self -- @param #number Delay (Optional) Delay before the drawing is removed. -- @return #ZONE_BASE self @@ -391,7 +387,7 @@ function ZONE_BASE:UndrawZone(Delay) end --- Get ID of the zone object drawn on the F10 map. --- The ID can be used to remove the drawn object from the F10 map view via `UTILS.RemoveMark(MarkID)`. +-- The ID can be used to remove the drawn object from the F10 map view via `UTILS.RemoveMark(MarkID)`. -- @param #ZONE_BASE self -- @return #number Unique ID of the function ZONE_BASE:GetDrawID() @@ -404,7 +400,7 @@ end -- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. function ZONE_BASE:SmokeZone( SmokeColor ) self:F2( SmokeColor ) - + end --- Set the randomization probability of a zone to be selected. @@ -413,7 +409,7 @@ end -- @return #ZONE_BASE self function ZONE_BASE:SetZoneProbability( ZoneProbability ) self:F( { self:GetName(), ZoneProbability = ZoneProbability } ) - + self.ZoneProbability = ZoneProbability or 1 return self end @@ -422,8 +418,8 @@ end -- @param #ZONE_BASE self -- @return #number A value between 0 and 1. 0 = 0% and 1 = 100% probability. function ZONE_BASE:GetZoneProbability() - self:F2() - + self:F2() + return self.ZoneProbability end @@ -432,15 +428,15 @@ end -- @return #ZONE_BASE The zone is selected taking into account the randomization probability factor. -- @return #nil The zone is not selected taking into account the randomization probability factor. -- @usage --- +-- -- local ZoneArray = { ZONE:New( "Zone1" ), ZONE:New( "Zone2" ) } --- +-- -- -- We set a zone probability of 70% to the first zone and 30% to the second zone. -- ZoneArray[1]:SetZoneProbability( 0.5 ) -- ZoneArray[2]:SetZoneProbability( 0.5 ) --- +-- -- local ZoneSelected = nil --- +-- -- while ZoneSelected == nil do -- for _, Zone in pairs( ZoneArray ) do -- ZoneSelected = Zone:GetZoneMaybe() @@ -449,12 +445,12 @@ end -- end -- end -- end --- +-- -- -- The result should be that Zone1 would be more probable selected than Zone2. --- +-- function ZONE_BASE:GetZoneMaybe() self:F2() - + local Randomization = math.random() if Randomization <= self.ZoneProbability then return self @@ -472,34 +468,34 @@ end --- The ZONE_RADIUS class defined by a zone name, a location and a radius. -- This class implements the inherited functions from Core.Zone#ZONE_BASE taking into account the own zone format and properties. --- +-- -- ## ZONE_RADIUS constructor --- +-- -- * @{#ZONE_RADIUS.New}(): Constructor. --- +-- -- ## Manage the radius of the zone --- +-- -- * @{#ZONE_RADIUS.SetRadius}(): Sets the radius of the zone. -- * @{#ZONE_RADIUS.GetRadius}(): Returns the radius of the zone. --- +-- -- ## Manage the location of the zone --- +-- -- * @{#ZONE_RADIUS.SetVec2}(): Sets the @{DCS#Vec2} of the zone. -- * @{#ZONE_RADIUS.GetVec2}(): Returns the @{DCS#Vec2} of the zone. -- * @{#ZONE_RADIUS.GetVec3}(): Returns the @{DCS#Vec3} of the zone, taking an additional height parameter. --- +-- -- ## Zone point randomization --- +-- -- Various functions exist to find random points within the zone. --- +-- -- * @{#ZONE_RADIUS.GetRandomVec2}(): Gets a random 2D point in the zone. -- * @{#ZONE_RADIUS.GetRandomPointVec2}(): Gets a @{Core.Point#POINT_VEC2} object representing a random 2D point in the zone. -- * @{#ZONE_RADIUS.GetRandomPointVec3}(): Gets a @{Core.Point#POINT_VEC3} object representing a random 3D point in the zone. Note that the height of the point is at landheight. --- +-- -- ## Draw zone --- +-- -- * @{#ZONE_RADIUS.DrawZone}(): Draws the zone on the F10 map. --- +-- -- @field #ZONE_RADIUS ZONE_RADIUS = { ClassName="ZONE_RADIUS", @@ -512,15 +508,54 @@ ZONE_RADIUS = { -- @param DCS#Distance Radius The radius of the zone. -- @return #ZONE_RADIUS self function ZONE_RADIUS:New( ZoneName, Vec2, Radius ) + + -- Inherit ZONE_BASE. local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) -- #ZONE_RADIUS self:F( { ZoneName, Vec2, Radius } ) self.Radius = Radius self.Vec2 = Vec2 - + + --self.Coordinate=COORDINATE:NewFromVec2(Vec2) + return self end +--- Update zone from a 2D vector. +-- @param #ZONE_RADIUS self +-- @param DCS#Vec2 Vec2 The location of the zone. +-- @param DCS#Distance Radius The radius of the zone. +-- @return #ZONE_RADIUS self +function ZONE_RADIUS:UpdateFromVec2(Vec2, Radius) + + -- New center of the zone. + self.Vec2=Vec2 + + if Radius then + self.Radius=Radius + end + + return self +end + +--- Update zone from a 2D vector. +-- @param #ZONE_RADIUS self +-- @param DCS#Vec3 Vec3 The location of the zone. +-- @param DCS#Distance Radius The radius of the zone. +-- @return #ZONE_RADIUS self +function ZONE_RADIUS:UpdateFromVec3(Vec3, Radius) + + -- New center of the zone. + self.Vec2.x=Vec3.x + self.Vec2.y=Vec3.z + + if Radius then + self.Radius=Radius + end + + return self +end + --- Mark the zone with markers on the F10 map. -- @param #ZONE_RADIUS self -- @param #number Points (Optional) The amount of points in the circle. Default 360. @@ -534,21 +569,21 @@ function ZONE_RADIUS:MarkZone(Points) local Angle local RadialBase = math.pi*2 - + for Angle = 0, 360, (360 / Points ) do - + local Radial = Angle * RadialBase / 360 - + Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() - + COORDINATE:NewFromVec2(Point):MarkToAll(self:GetName()) end - + end ---- Draw the zone circle on the F10 map. +--- Draw the zone circle on the F10 map. -- @param #ZONE_RADIUS self -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red. @@ -561,16 +596,16 @@ end function ZONE_RADIUS:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) local coordinate=self:GetCoordinate() - + local Radius=self:GetRadius() - + Color=Color or self:GetColorRGB() Alpha=Alpha or 1 FillColor=FillColor or Color FillAlpha=FillAlpha or self:GetColorAlpha() self.DrawID=coordinate:CircleToAll(Radius, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) - + return self end @@ -589,17 +624,17 @@ function ZONE_RADIUS:BoundZone( Points, CountryID, UnBound ) local Angle local RadialBase = math.pi*2 - + -- for Angle = 0, 360, (360 / Points ) do local Radial = Angle * RadialBase / 360 Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() - + local CountryName = _DATABASE.COUNTRY_NAME[CountryID] - + local Tire = { - ["country"] = CountryName, + ["country"] = CountryName, ["category"] = "Fortifications", ["canCargo"] = false, ["shape_name"] = "H-tyre_B_WF", @@ -633,7 +668,7 @@ function ZONE_RADIUS:SmokeZone( SmokeColor, Points, AddHeight, AngleOffset ) local Point = {} local Vec2 = self:GetVec2() - + AddHeight = AddHeight or 0 AngleOffset = AngleOffset or 0 @@ -641,7 +676,7 @@ function ZONE_RADIUS:SmokeZone( SmokeColor, Points, AddHeight, AngleOffset ) local Angle local RadialBase = math.pi*2 - + for Angle = 0, 360, 360 / Points do local Radial = ( Angle + AngleOffset ) * RadialBase / 360 Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() @@ -665,14 +700,14 @@ function ZONE_RADIUS:FlareZone( FlareColor, Points, Azimuth, AddHeight ) local Point = {} local Vec2 = self:GetVec2() - + AddHeight = AddHeight or 0 - + Points = Points and Points or 360 local Angle local RadialBase = math.pi*2 - + for Angle = 0, 360, 360 / Points do local Radial = Angle * RadialBase / 360 Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() @@ -714,8 +749,8 @@ function ZONE_RADIUS:GetVec2() self:F2( self.ZoneName ) self:T2( { self.Vec2 } ) - - return self.Vec2 + + return self.Vec2 end --- Sets the @{DCS#Vec2} of the zone. @@ -724,12 +759,12 @@ end -- @return DCS#Vec2 The new location of the zone. function ZONE_RADIUS:SetVec2( Vec2 ) self:F2( self.ZoneName ) - + self.Vec2 = Vec2 self:T2( { self.Vec2 } ) - - return self.Vec2 + + return self.Vec2 end --- Returns the @{DCS#Vec3} of the ZONE_RADIUS. @@ -745,8 +780,8 @@ function ZONE_RADIUS:GetVec3( Height ) local Vec3 = { x = Vec2.x, y = land.getHeight( self:GetVec2() ) + Height, z = Vec2.y } self:T2( { Vec3 } ) - - return Vec3 + + return Vec3 end @@ -755,7 +790,7 @@ end --- Scan the zone for the presence of units of the given ObjectCategories. -- Note that after a zone has been scanned, the zone can be evaluated by: --- +-- -- * @{ZONE_RADIUS.IsAllInZoneOfCoalition}(): Scan the presence of units in the zone of a coalition. -- * @{ZONE_RADIUS.IsAllInZoneOfOtherCoalition}(): Scan the presence of units in the zone of an other coalition. -- * @{ZONE_RADIUS.IsSomeInZoneOfCoalition}(): Scan if there is some presence of units in the zone of the given coalition. @@ -777,7 +812,7 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) local ZoneCoord = self:GetCoordinate() local ZoneRadius = self:GetRadius() - + self:F({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()}) local SphereSearch = { @@ -790,18 +825,18 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) local function EvaluateZone( ZoneObject ) --if ZoneObject:isExist() then --FF: isExist always returns false for SCENERY objects since DCS 2.2 and still in DCS 2.5 - if ZoneObject then - + if ZoneObject then + local ObjectCategory = ZoneObject:getCategory() - + --local name=ZoneObject:getName() --env.info(string.format("Zone object %s", tostring(name))) --self:E(ZoneObject) - + if ( ObjectCategory == Object.Category.UNIT and ZoneObject:isExist() and ZoneObject:isActive() ) or (ObjectCategory == Object.Category.STATIC and ZoneObject:isExist()) then - + local CoalitionDCSUnit = ZoneObject:getCoalition() - + local Include = false if not UnitCategories then -- Anythink found is included. @@ -809,29 +844,29 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) else -- Check if found object is in specified categories. local CategoryDCSUnit = ZoneObject:getDesc().category - + for UnitCategoryID, UnitCategory in pairs( UnitCategories ) do if UnitCategory == CategoryDCSUnit then Include = true break end end - + end - + if Include then - + local CoalitionDCSUnit = ZoneObject:getCoalition() - + -- This coalition is inside the zone. self.ScanData.Coalitions[CoalitionDCSUnit] = true - + self.ScanData.Units[ZoneObject] = ZoneObject - + self:F2( { Name = ZoneObject:getName(), Coalition = CoalitionDCSUnit } ) end end - + if ObjectCategory == Object.Category.SCENERY then local SceneryType = ZoneObject:getTypeName() local SceneryName = ZoneObject:getName() @@ -839,15 +874,15 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) self.ScanData.Scenery[SceneryType][SceneryName] = SCENERY:Register( SceneryName, ZoneObject ) self:F2( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } ) end - + end - + return true end -- Search objects. world.searchObjects( ObjectCategories, SphereSearch, EvaluateZone ) - + end --- Count the number of different coalitions inside the zone. @@ -886,6 +921,32 @@ function ZONE_RADIUS:GetScannedSetUnit() return SetUnit end +--- Get a set of scanned units. +-- @param #ZONE_RADIUS self +-- @return Core.Set#SET_GROUP Set of groups. +function ZONE_RADIUS:GetScannedSetGroup() + + self.ScanSetGroup=self.ScanSetGroup or SET_GROUP:New() --Core.Set#SET_GROUP + + self.ScanSetGroup.Set={} + + if self.ScanData then + for ObjectID, UnitObject in pairs( self.ScanData.Units ) do + local UnitObject = UnitObject -- DCS#Unit + if UnitObject:isExist() then + + local FoundUnit=UNIT:FindByName(UnitObject:getName()) + if FoundUnit then + local group=FoundUnit:GetGroup() + self.ScanSetGroup:AddGroup(group) + end + end + end + end + + return self.ScanSetGroup +end + --- Count the number of different coalitions inside the zone. -- @param #ZONE_RADIUS self @@ -893,11 +954,11 @@ end function ZONE_RADIUS:CountScannedCoalitions() local Count = 0 - + for CoalitionID, Coalition in pairs( self.ScanData.Coalitions ) do Count = Count + 1 end - + return Count end @@ -925,16 +986,16 @@ function ZONE_RADIUS:GetScannedCoalition( Coalition ) else local Count = 0 local ReturnCoalition = nil - + for CoalitionID, Coalition in pairs( self.ScanData.Coalitions ) do Count = Count + 1 ReturnCoalition = CoalitionID end - + if Count ~= 1 then ReturnCoalition = nil end - + return ReturnCoalition end end @@ -1045,7 +1106,7 @@ function ZONE_RADIUS:SearchZone( EvaluateFunction, ObjectCategories ) local ZoneCoord = self:GetCoordinate() local ZoneRadius = self:GetRadius() - + self:F({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()}) local SphereSearch = { @@ -1057,8 +1118,8 @@ function ZONE_RADIUS:SearchZone( EvaluateFunction, ObjectCategories ) } local function EvaluateZone( ZoneDCSUnit ) - - + + local ZoneUnit = UNIT:Find( ZoneDCSUnit ) return EvaluateFunction( ZoneUnit ) @@ -1074,15 +1135,15 @@ end -- @return #boolean true if the location is within the zone. function ZONE_RADIUS:IsVec2InZone( Vec2 ) self:F2( Vec2 ) - + local ZoneVec2 = self:GetVec2() - + if ZoneVec2 then if (( Vec2.x - ZoneVec2.x )^2 + ( Vec2.y - ZoneVec2.y ) ^2 ) ^ 0.5 <= self:GetRadius() then return true end end - + return false end @@ -1114,9 +1175,9 @@ function ZONE_RADIUS:GetRandomVec2( inner, outer ) local angle = math.random() * math.pi * 2; Point.x = Vec2.x + math.cos( angle ) * math.random(_inner, _outer); Point.y = Vec2.y + math.sin( angle ) * math.random(_inner, _outer); - + self:T( { Point } ) - + return Point end @@ -1131,7 +1192,7 @@ function ZONE_RADIUS:GetRandomPointVec2( inner, outer ) local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2( inner, outer ) ) self:T3( { PointVec2 } ) - + return PointVec2 end @@ -1146,7 +1207,7 @@ function ZONE_RADIUS:GetRandomVec3( inner, outer ) local Vec2 = self:GetRandomVec2( inner, outer ) self:T3( { x = Vec2.x, y = self.y, z = Vec2.y } ) - + return { x = Vec2.x, y = self.y, z = Vec2.y } end @@ -1162,7 +1223,7 @@ function ZONE_RADIUS:GetRandomPointVec3( inner, outer ) local PointVec3 = POINT_VEC3:NewFromVec2( self:GetRandomVec2( inner, outer ) ) self:T3( { PointVec3 } ) - + return PointVec3 end @@ -1178,7 +1239,7 @@ function ZONE_RADIUS:GetRandomCoordinate( inner, outer ) local Coordinate = COORDINATE:NewFromVec2( self:GetRandomVec2(inner, outer) ) self:T3( { Coordinate = Coordinate } ) - + return Coordinate end @@ -1190,32 +1251,32 @@ end --- The ZONE class, defined by the zone name as defined within the Mission Editor. -- This class implements the inherited functions from @{#ZONE_RADIUS} taking into account the own zone format and properties. --- +-- -- ## ZONE constructor --- +-- -- * @{#ZONE.New}(): Constructor. This will search for a trigger zone with the name given, and will return for you a ZONE object. --- +-- -- ## Declare a ZONE directly in the DCS mission editor! --- +-- -- You can declare a ZONE using the DCS mission editor by adding a trigger zone in the mission editor. --- +-- -- Then during mission startup, when loading Moose.lua, this trigger zone will be detected as a ZONE declaration. -- Within the background, a ZONE object will be created within the @{Core.Database}. -- The ZONE name will be the trigger zone name. --- +-- -- So, you can search yourself for the ZONE object by using the @{#ZONE.FindByName}() method. -- In this example, `local TriggerZone = ZONE:FindByName( "DefenseZone" )` would return the ZONE object --- that was created at mission startup, and reference it into the `TriggerZone` local object. --- +-- that was created at mission startup, and reference it into the `TriggerZone` local object. +-- -- Refer to mission `ZON-110` for a demonstration. --- +-- -- This is especially handy if you want to quickly setup a SET_ZONE... -- So when you would declare `local SetZone = SET_ZONE:New():FilterPrefixes( "Defense" ):FilterStart()`, -- then SetZone would contain the ZONE object `DefenseZone` as part of the zone collection, --- without much scripting overhead!!! --- --- --- @field #ZONE +-- without much scripting overhead!!! +-- +-- +-- @field #ZONE ZONE = { ClassName="ZONE", } @@ -1229,7 +1290,7 @@ function ZONE:New( ZoneName ) -- First try to find the zone in the DB. local zone=_DATABASE:FindZone(ZoneName) - + if zone then --env.info("FF found zone in DB") return zone @@ -1237,7 +1298,7 @@ function ZONE:New( ZoneName ) -- Get zone from DCS trigger function. local Zone = trigger.misc.getZone( ZoneName ) - + -- Error! if not Zone then error( "Zone " .. ZoneName .. " does not exist." ) @@ -1247,13 +1308,13 @@ function ZONE:New( ZoneName ) -- Create a new ZONE_RADIUS. local self=BASE:Inherit( self, ZONE_RADIUS:New(ZoneName, {x=Zone.point.x, y=Zone.point.z}, Zone.radius)) self:F(ZoneName) - + -- Color of zone. self.Color={1, 0, 0, 0.15} -- DCS zone. self.Zone = Zone - + return self end @@ -1262,7 +1323,7 @@ end -- @param #string ZoneName The name of the zone. -- @return #ZONE_BASE self function ZONE:FindByName( ZoneName ) - + local ZoneFound = _DATABASE:FindZone( ZoneName ) return ZoneFound end @@ -1275,15 +1336,15 @@ end --- # ZONE_UNIT class, extends @{Zone#ZONE_RADIUS} --- +-- -- The ZONE_UNIT class defined by a zone attached to a @{Wrapper.Unit#UNIT} with a radius and optional offsets. -- This class implements the inherited functions from @{#ZONE_RADIUS} taking into account the own zone format and properties. --- +-- -- @field #ZONE_UNIT ZONE_UNIT = { ClassName="ZONE_UNIT", } - + --- Constructor to create a ZONE_UNIT instance, taking the zone name, a zone unit and a radius and optional offsets in X and Y directions. -- @param #ZONE_UNIT self -- @param #string ZoneName Name of the zone. @@ -1298,30 +1359,30 @@ ZONE_UNIT = { -- dx, dy OR rho, theta may be used, not both. -- @return #ZONE_UNIT self function ZONE_UNIT:New( ZoneName, ZoneUNIT, Radius, Offset) - + if Offset then - -- check if the inputs was reasonable, either (dx, dy) or (rho, theta) can be given, else raise an exception. + -- check if the inputs was reasonable, either (dx, dy) or (rho, theta) can be given, else raise an exception. if (Offset.dx or Offset.dy) and (Offset.rho or Offset.theta) then - error("Cannot use (dx, dy) with (rho, theta)") + error("Cannot use (dx, dy) with (rho, theta)") end - + self.dy = Offset.dy or 0.0 self.dx = Offset.dx or 0.0 self.rho = Offset.rho or 0.0 self.theta = (Offset.theta or 0.0) * math.pi / 180.0 self.relative_to_unit = Offset.relative_to_unit or false end - + local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneUNIT:GetVec2(), Radius ) ) self:F( { ZoneName, ZoneUNIT:GetVec2(), Radius } ) self.ZoneUNIT = ZoneUNIT self.LastVec2 = ZoneUNIT:GetVec2() - + -- Zone objects are added to the _DATABASE and SET_ZONE objects. _EVENTDISPATCHER:CreateEventNewZone( self ) - + return self end @@ -1331,32 +1392,32 @@ end -- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Unit#UNIT}location and the offset, if any. function ZONE_UNIT:GetVec2() self:F2( self.ZoneName ) - + local ZoneVec2 = self.ZoneUNIT:GetVec2() if ZoneVec2 then - + local heading if self.relative_to_unit then heading = ( self.ZoneUNIT:GetHeading() or 0.0 ) * math.pi / 180.0 else heading = 0.0 end - + -- update the zone position with the offsets. if (self.dx or self.dy) then - + -- use heading to rotate offset relative to unit using rotation matrix in 2D. -- see: https://en.wikipedia.org/wiki/Rotation_matrix - ZoneVec2.x = ZoneVec2.x + self.dx * math.cos( -heading ) + self.dy * math.sin( -heading ) - ZoneVec2.y = ZoneVec2.y - self.dx * math.sin( -heading ) + self.dy * math.cos( -heading ) + ZoneVec2.x = ZoneVec2.x + self.dx * math.cos( -heading ) + self.dy * math.sin( -heading ) + ZoneVec2.y = ZoneVec2.y - self.dx * math.sin( -heading ) + self.dy * math.cos( -heading ) end - + -- if using the polar coordinates - if (self.rho or self.theta) then + if (self.rho or self.theta) then ZoneVec2.x = ZoneVec2.x + self.rho * math.cos( self.theta + heading ) ZoneVec2.y = ZoneVec2.y + self.rho * math.sin( self.theta + heading ) end - + self.LastVec2 = ZoneVec2 return ZoneVec2 else @@ -1365,7 +1426,7 @@ function ZONE_UNIT:GetVec2() self:T2( { ZoneVec2 } ) - return nil + return nil end --- Returns a random location within the zone. @@ -1377,7 +1438,7 @@ function ZONE_UNIT:GetRandomVec2() local RandomVec2 = {} --local Vec2 = self.ZoneUNIT:GetVec2() -- FF: This does not take care of the new offset feature! local Vec2 = self:GetVec2() - + if not Vec2 then Vec2 = self.LastVec2 end @@ -1385,9 +1446,9 @@ function ZONE_UNIT:GetRandomVec2() local angle = math.random() * math.pi*2; RandomVec2.x = Vec2.x + math.cos( angle ) * math.random() * self:GetRadius(); RandomVec2.y = Vec2.y + math.sin( angle ) * math.random() * self:GetRadius(); - + self:T( { RandomVec2 } ) - + return RandomVec2 end @@ -1397,16 +1458,16 @@ end -- @return DCS#Vec3 The point of the zone. function ZONE_UNIT:GetVec3( Height ) self:F2( self.ZoneName ) - + Height = Height or 0 - + local Vec2 = self:GetVec2() local Vec3 = { x = Vec2.x, y = land.getHeight( self:GetVec2() ) + Height, z = Vec2.y } self:T2( { Vec3 } ) - - return Vec3 + + return Vec3 end --- @type ZONE_GROUP @@ -1415,12 +1476,12 @@ end --- The ZONE_GROUP class defines by a zone around a @{Wrapper.Group#GROUP} with a radius. The current leader of the group defines the center of the zone. -- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. --- +-- -- @field #ZONE_GROUP ZONE_GROUP = { ClassName="ZONE_GROUP", } - + --- Constructor to create a ZONE_GROUP instance, taking the zone name, a zone @{Wrapper.Group#GROUP} and a radius. -- @param #ZONE_GROUP self -- @param #string ZoneName Name of the zone. @@ -1436,7 +1497,7 @@ function ZONE_GROUP:New( ZoneName, ZoneGROUP, Radius ) -- Zone objects are added to the _DATABASE and SET_ZONE objects. _EVENTDISPATCHER:CreateEventNewZone( self ) - + return self end @@ -1446,9 +1507,9 @@ end -- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Group} location. function ZONE_GROUP:GetVec2() self:F( self.ZoneName ) - + local ZoneVec2 = nil - + if self._.ZoneGROUP:IsAlive() then ZoneVec2 = self._.ZoneGROUP:GetVec2() self._.ZoneVec2Cache = ZoneVec2 @@ -1457,7 +1518,7 @@ function ZONE_GROUP:GetVec2() end self:T( { ZoneVec2 } ) - + return ZoneVec2 end @@ -1473,9 +1534,9 @@ function ZONE_GROUP:GetRandomVec2() local angle = math.random() * math.pi*2; Point.x = Vec2.x + math.cos( angle ) * math.random() * self:GetRadius(); Point.y = Vec2.y + math.sin( angle ) * math.random() * self:GetRadius(); - + self:T( { Point } ) - + return Point end @@ -1490,7 +1551,7 @@ function ZONE_GROUP:GetRandomPointVec2( inner, outer ) local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2() ) self:T3( { PointVec2 } ) - + return PointVec2 end @@ -1503,47 +1564,90 @@ end --- The ZONE_POLYGON_BASE class defined by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon. -- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. -- This class is an abstract BASE class for derived classes, and is not meant to be instantiated. --- +-- -- ## Zone point randomization --- +-- -- Various functions exist to find random points within the zone. --- +-- -- * @{#ZONE_POLYGON_BASE.GetRandomVec2}(): Gets a random 2D point in the zone. -- * @{#ZONE_POLYGON_BASE.GetRandomPointVec2}(): Return a @{Core.Point#POINT_VEC2} object representing a random 2D point within the zone. -- * @{#ZONE_POLYGON_BASE.GetRandomPointVec3}(): Return a @{Core.Point#POINT_VEC3} object representing a random 3D point at landheight within the zone. -- -- ## Draw zone --- --- * @{#ZONE_POLYGON_BASE.DrawZone}(): Draws the zone on the F10 map. -- --- +-- * @{#ZONE_POLYGON_BASE.DrawZone}(): Draws the zone on the F10 map. +-- * @{#ZONE_POLYGON_BASE.Boundary}(): Draw a frontier on the F10 map with small filled circles. +-- +-- -- @field #ZONE_POLYGON_BASE ZONE_POLYGON_BASE = { ClassName="ZONE_POLYGON_BASE", } ---- A points array. +--- A 2D points array. -- @type ZONE_POLYGON_BASE.ListVec2 --- @list +-- @list Table of 2D vectors. + +--- A 3D points array. +-- @type ZONE_POLYGON_BASE.ListVec3 +-- @list Table of 3D vectors. --- Constructor to create a ZONE_POLYGON_BASE instance, taking the zone name and an array of @{DCS#Vec2}, forming a polygon. -- The @{Wrapper.Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected. -- @param #ZONE_POLYGON_BASE self -- @param #string ZoneName Name of the zone. --- @param #ZONE_POLYGON_BASE.ListVec2 PointsArray An array of @{DCS#Vec2}, forming a polygon.. +-- @param #ZONE_POLYGON_BASE.ListVec2 PointsArray An array of @{DCS#Vec2}, forming a polygon. -- @return #ZONE_POLYGON_BASE self function ZONE_POLYGON_BASE:New( ZoneName, PointsArray ) + + -- Inherit ZONE_BASE. local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) self:F( { ZoneName, PointsArray } ) - local i = 0 - + if PointsArray then + + self._.Polygon = {} + + for i = 1, #PointsArray do + self._.Polygon[i] = {} + self._.Polygon[i].x = PointsArray[i].x + self._.Polygon[i].y = PointsArray[i].y + end + + end + + return self +end + +--- Update polygon points with an array of @{DCS#Vec2}. +-- @param #ZONE_POLYGON_BASE self +-- @param #ZONE_POLYGON_BASE.ListVec2 Vec2Array An array of @{DCS#Vec2}, forming a polygon. +-- @return #ZONE_POLYGON_BASE self +function ZONE_POLYGON_BASE:UpdateFromVec2(Vec2Array) + self._.Polygon = {} - - for i = 1, #PointsArray do + + for i=1,#Vec2Array do self._.Polygon[i] = {} - self._.Polygon[i].x = PointsArray[i].x - self._.Polygon[i].y = PointsArray[i].y + self._.Polygon[i].x=Vec2Array[i].x + self._.Polygon[i].y=Vec2Array[i].y + end + + return self +end + +--- Update polygon points with an array of @{DCS#Vec3}. +-- @param #ZONE_POLYGON_BASE self +-- @param #ZONE_POLYGON_BASE.ListVec3 Vec2Array An array of @{DCS#Vec3}, forming a polygon. +-- @return #ZONE_POLYGON_BASE self +function ZONE_POLYGON_BASE:UpdateFromVec3(Vec3Array) + + self._.Polygon = {} + + for i=1,#Vec3Array do + self._.Polygon[i] = {} + self._.Polygon[i].x=Vec3Array[i].x + self._.Polygon[i].y=Vec3Array[i].z end return self @@ -1556,13 +1660,13 @@ function ZONE_POLYGON_BASE:GetVec2() self:F( self.ZoneName ) local Bounds = self:GetBoundingSquare() - - return { x = ( Bounds.x2 + Bounds.x1 ) / 2, y = ( Bounds.y2 + Bounds.y1 ) / 2 } + + return { x = ( Bounds.x2 + Bounds.x1 ) / 2, y = ( Bounds.y2 + Bounds.y1 ) / 2 } end --- Get a vertex of the polygon. -- @param #ZONE_POLYGON_BASE self --- @param #number Index Index of the vertex. Default 1. +-- @param #number Index Index of the vertex. Default 1. -- @return DCS#Vec2 Vertex of the polygon. function ZONE_POLYGON_BASE:GetVertexVec2(Index) return self._.Polygon[Index or 1] @@ -1570,7 +1674,7 @@ end --- Get a vertex of the polygon. -- @param #ZONE_POLYGON_BASE self --- @param #number Index Index of the vertex. Default 1. +-- @param #number Index Index of the vertex. Default 1. -- @return DCS#Vec3 Vertex of the polygon. function ZONE_POLYGON_BASE:GetVertexVec3(Index) local vec2=self:GetVertexVec2(Index) @@ -1583,7 +1687,7 @@ end --- Get a vertex of the polygon. -- @param #ZONE_POLYGON_BASE self --- @param #number Index Index of the vertex. Default 1. +-- @param #number Index Index of the vertex. Default 1. -- @return Core.Point#COORDINATE Vertex of the polygon. function ZONE_POLYGON_BASE:GetVertexCoordinate(Index) local vec2=self:GetVertexVec2(Index) @@ -1613,7 +1717,7 @@ function ZONE_POLYGON_BASE:GetVerticiesVec3() local vec3={x=vec2.x, y=land.getHeight(vec2), z=vec2.y} table.insert(coords, vec3) end - + return coords end @@ -1628,7 +1732,7 @@ function ZONE_POLYGON_BASE:GetVerticiesCoordinates() local coord=COORDINATE:NewFromVec2(vec2) table.insert(coords, coord) end - + return coords end @@ -1649,24 +1753,24 @@ end -- @return #ZONE_POLYGON_BASE self function ZONE_POLYGON_BASE:BoundZone( UnBound ) - local i - local j + local i + local j local Segments = 10 - + i = 1 j = #self._.Polygon - + while i <= #self._.Polygon do self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) - + local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y - + for Segment = 0, Segments do -- We divide each line in 5 segments and smoke a point on the line. local PointX = self._.Polygon[i].x + ( Segment * DeltaX / Segments ) local PointY = self._.Polygon[i].y + ( Segment * DeltaY / Segments ) local Tire = { - ["country"] = "USA", + ["country"] = "USA", ["category"] = "Fortifications", ["canCargo"] = false, ["shape_name"] = "H-tyre_B_WF", @@ -1676,12 +1780,12 @@ function ZONE_POLYGON_BASE:BoundZone( UnBound ) ["name"] = string.format( "%s-Tire #%0d", self:GetName(), ((i - 1) * Segments) + Segment ), ["heading"] = 0, } -- end of ["group"] - + local Group = coalition.addStaticObject( country.id.USA, Tire ) if UnBound and UnBound == true then Group:destroy() end - + end j = i i = i + 1 @@ -1712,23 +1816,23 @@ function ZONE_POLYGON_BASE:DrawZone(Coalition, Color, Alpha, FillColor, FillAlph if #self._.Polygon==4 then - + local Coord2=COORDINATE:NewFromVec2(self._.Polygon[2]) local Coord3=COORDINATE:NewFromVec2(self._.Polygon[3]) local Coord4=COORDINATE:NewFromVec2(self._.Polygon[4]) - + self.DrawID=coordinate:QuadToAll(Coord2, Coord3, Coord4, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) - + else - + local Coordinates=self:GetVerticiesCoordinates() table.remove(Coordinates, 1) self.DrawID=coordinate:MarkupToAllFreeForm(Coordinates, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) - + end - - + + return self end @@ -1741,16 +1845,16 @@ function ZONE_POLYGON_BASE:SmokeZone( SmokeColor, Segments ) self:F2( SmokeColor ) Segments=Segments or 10 - + local i=1 local j=#self._.Polygon - + while i <= #self._.Polygon do self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) - + local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y - + for Segment = 0, Segments do -- We divide each line in 5 segments and smoke a point on the line. local PointX = self._.Polygon[i].x + ( Segment * DeltaX / Segments ) local PointY = self._.Polygon[i].y + ( Segment * DeltaY / Segments ) @@ -1775,18 +1879,18 @@ function ZONE_POLYGON_BASE:FlareZone( FlareColor, Segments, Azimuth, AddHeight ) self:F2(FlareColor) Segments=Segments or 10 - + AddHeight = AddHeight or 0 - + local i=1 local j=#self._.Polygon - + while i <= #self._.Polygon do self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) - + local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y - + for Segment = 0, Segments do -- We divide each line in 5 segments and smoke a point on the line. local PointX = self._.Polygon[i].x + ( Segment * DeltaX / Segments ) local PointY = self._.Polygon[i].y + ( Segment * DeltaY / Segments ) @@ -1810,17 +1914,17 @@ end function ZONE_POLYGON_BASE:IsVec2InZone( Vec2 ) self:F2( Vec2 ) - local Next - local Prev + local Next + local Prev local InPolygon = false - + Next = 1 Prev = #self._.Polygon - + while Next <= #self._.Polygon do self:T( { Next, Prev, self._.Polygon[Next], self._.Polygon[Prev] } ) if ( ( ( self._.Polygon[Next].y > Vec2.y ) ~= ( self._.Polygon[Prev].y > Vec2.y ) ) and - ( Vec2.x < ( self._.Polygon[Prev].x - self._.Polygon[Next].x ) * ( Vec2.y - self._.Polygon[Next].y ) / ( self._.Polygon[Prev].y - self._.Polygon[Next].y ) + self._.Polygon[Next].x ) + ( Vec2.x < ( self._.Polygon[Prev].x - self._.Polygon[Next].x ) * ( Vec2.y - self._.Polygon[Next].y ) / ( self._.Polygon[Prev].y - self._.Polygon[Next].y ) + self._.Polygon[Next].x ) ) then InPolygon = not InPolygon end @@ -1843,9 +1947,9 @@ function ZONE_POLYGON_BASE:GetRandomVec2() local Vec2Found = false local Vec2 local BS = self:GetBoundingSquare() - + self:T2( BS ) - + while Vec2Found == false do Vec2 = { x = math.random( BS.x1, BS.x2 ), y = math.random( BS.y1, BS.y2 ) } self:T2( Vec2 ) @@ -1853,7 +1957,7 @@ function ZONE_POLYGON_BASE:GetRandomVec2() Vec2Found = true end end - + self:T2( Vec2 ) return Vec2 @@ -1866,7 +1970,7 @@ function ZONE_POLYGON_BASE:GetRandomPointVec2() self:F2() local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2() ) - + self:T2( PointVec2 ) return PointVec2 @@ -1879,7 +1983,7 @@ function ZONE_POLYGON_BASE:GetRandomPointVec3() self:F2() local PointVec3 = POINT_VEC3:NewFromVec2( self:GetRandomVec2() ) - + self:T2( PointVec3 ) return PointVec3 @@ -1893,7 +1997,7 @@ function ZONE_POLYGON_BASE:GetRandomCoordinate() self:F2() local Coordinate = COORDINATE:NewFromVec2( self:GetRandomVec2() ) - + self:T2( Coordinate ) return Coordinate @@ -1909,19 +2013,58 @@ function ZONE_POLYGON_BASE:GetBoundingSquare() local y1 = self._.Polygon[1].y local x2 = self._.Polygon[1].x local y2 = self._.Polygon[1].y - + for i = 2, #self._.Polygon do self:T2( { self._.Polygon[i], x1, y1, x2, y2 } ) x1 = ( x1 > self._.Polygon[i].x ) and self._.Polygon[i].x or x1 x2 = ( x2 < self._.Polygon[i].x ) and self._.Polygon[i].x or x2 y1 = ( y1 > self._.Polygon[i].y ) and self._.Polygon[i].y or y1 y2 = ( y2 < self._.Polygon[i].y ) and self._.Polygon[i].y or y2 - + end return { x1 = x1, y1 = y1, x2 = x2, y2 = y2 } end +--- Draw a frontier on the F10 map with small filled circles. +-- @param #ZONE_POLYGON_BASE self +-- @param #number Coalition (Optional) Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1= All. +-- @param #table Color (Optional) RGB color table {r, g, b}, e.g. {1, 0, 0} for red. Default {1, 1, 1}= White. +-- @param #number Radius (Optional) Radius of the circles in meters. Default 1000. +-- @param #number Alpha (Optional) Alpha transparency [0,1]. Default 1. +-- @param #number Segments (Optional) Number of segments within boundary line. Default 10. +-- @param #boolean Closed (Optional) Link the last point with the first one to obtain a closed boundary. Default false +-- @return #ZONE_POLYGON_BASE self +function ZONE_POLYGON_BASE:Boundary(Coalition, Color, Radius, Alpha, Segments, Closed) + Coalition = Coalition or -1 + Color = Color or {1, 1, 1} + Radius = Radius or 1000 + Alpha = Alpha or 1 + Segments = Segments or 10 + Closed = Closed or false + local i = 1 + local j = #self._.Polygon + if (Closed) then + Limit = #self._.Polygon + 1 + else + Limit = #self._.Polygon + end + while i <= #self._.Polygon do + self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) + if j ~= Limit then + local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x + local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y + 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) + end + end + j = i + i = i + 1 + end + return self +end --- @type ZONE_POLYGON -- @extends #ZONE_POLYGON_BASE @@ -1929,27 +2072,27 @@ end --- The ZONE_POLYGON class defined by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon. -- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. --- +-- -- ## Declare a ZONE_POLYGON directly in the DCS mission editor! --- +-- -- You can declare a ZONE_POLYGON using the DCS mission editor by adding the ~ZONE_POLYGON tag in the group name. --- +-- -- So, imagine you have a group declared in the mission editor, with group name `DefenseZone~ZONE_POLYGON`. -- Then during mission startup, when loading Moose.lua, this group will be detected as a ZONE_POLYGON declaration. -- Within the background, a ZONE_POLYGON object will be created within the @{Core.Database} using the properties of the group. -- The ZONE_POLYGON name will be the group name without the ~ZONE_POLYGON tag. --- +-- -- So, you can search yourself for the ZONE_POLYGON by using the @{#ZONE_POLYGON.FindByName}() method. -- In this example, `local PolygonZone = ZONE_POLYGON:FindByName( "DefenseZone" )` would return the ZONE_POLYGON object -- that was created at mission startup, and reference it into the `PolygonZone` local object. --- +-- -- Mission `ZON-510` shows a demonstration of this feature or method. --- +-- -- This is especially handy if you want to quickly setup a SET_ZONE... -- So when you would declare `local SetZone = SET_ZONE:New():FilterPrefixes( "Defense" ):FilterStart()`, -- then SetZone would contain the ZONE_POLYGON object `DefenseZone` as part of the zone collection, -- without much scripting overhead! --- +-- -- @field #ZONE_POLYGON ZONE_POLYGON = { ClassName="ZONE_POLYGON", @@ -2001,7 +2144,7 @@ end -- @param #string ZoneName The name of the polygon zone. -- @return #ZONE_POLYGON self function ZONE_POLYGON:FindByName( ZoneName ) - + local ZoneFound = _DATABASE:FindZone( ZoneName ) return ZoneFound end @@ -2010,85 +2153,85 @@ do -- ZONE_AIRBASE --- @type ZONE_AIRBASE -- @extends #ZONE_RADIUS - - + + --- The ZONE_AIRBASE class defines by a zone around a @{Wrapper.Airbase#AIRBASE} with a radius. -- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. - -- + -- -- @field #ZONE_AIRBASE ZONE_AIRBASE = { ClassName="ZONE_AIRBASE", } - - - + + + --- Constructor to create a ZONE_AIRBASE instance, taking the zone name, a zone @{Wrapper.Airbase#AIRBASE} and a radius. -- @param #ZONE_AIRBASE self -- @param #string AirbaseName Name of the airbase. -- @param DCS#Distance Radius (Optional)The radius of the zone in meters. Default 4000 meters. -- @return #ZONE_AIRBASE self function ZONE_AIRBASE:New( AirbaseName, Radius ) - + Radius=Radius or 4000 - + local Airbase = AIRBASE:FindByName( AirbaseName ) - + local self = BASE:Inherit( self, ZONE_RADIUS:New( AirbaseName, Airbase:GetVec2(), Radius ) ) - + self._.ZoneAirbase = Airbase self._.ZoneVec2Cache = self._.ZoneAirbase:GetVec2() - + -- Zone objects are added to the _DATABASE and SET_ZONE objects. _EVENTDISPATCHER:CreateEventNewZone( self ) - + return self end - + --- Get the airbase as part of the ZONE_AIRBASE object. -- @param #ZONE_AIRBASE self -- @return Wrapper.Airbase#AIRBASE The airbase. function ZONE_AIRBASE:GetAirbase() return self._.ZoneAirbase - end - + end + --- Returns the current location of the @{Wrapper.Group}. -- @param #ZONE_AIRBASE self -- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Group} location. function ZONE_AIRBASE:GetVec2() self:F( self.ZoneName ) - + local ZoneVec2 = nil - + if self._.ZoneAirbase:IsAlive() then ZoneVec2 = self._.ZoneAirbase:GetVec2() self._.ZoneVec2Cache = ZoneVec2 else ZoneVec2 = self._.ZoneVec2Cache end - + self:T( { ZoneVec2 } ) - + return ZoneVec2 end - + --- Returns a random location within the zone of the @{Wrapper.Group}. -- @param #ZONE_AIRBASE self -- @return DCS#Vec2 The random location of the zone based on the @{Wrapper.Group} location. function ZONE_AIRBASE:GetRandomVec2() self:F( self.ZoneName ) - + local Point = {} local Vec2 = self._.ZoneAirbase:GetVec2() - + local angle = math.random() * math.pi*2; Point.x = Vec2.x + math.cos( angle ) * math.random() * self:GetRadius(); Point.y = Vec2.y + math.sin( angle ) * math.random() * self:GetRadius(); - + self:T( { Point } ) - + return Point end - + --- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. -- @param #ZONE_AIRBASE self -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. @@ -2096,11 +2239,11 @@ do -- ZONE_AIRBASE -- @return Core.Point#POINT_VEC2 The @{Core.Point#POINT_VEC2} object reflecting the random 3D location within the zone. function ZONE_AIRBASE:GetRandomPointVec2( inner, outer ) self:F( self.ZoneName, inner, outer ) - + local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2() ) - + self:T3( { PointVec2 } ) - + return PointVec2 end From 75849fbfb55a21cce177dea0d87d22951e00b010 Mon Sep 17 00:00:00 2001 From: Shadowze <30597073+shadowze@users.noreply.github.com> Date: Tue, 25 May 2021 22:30:16 +0100 Subject: [PATCH 018/111] Update Positionable.lua function added to work out if a unit is a submarine --- .../Moose/Wrapper/Positionable.lua | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index 1a12d7206..ac4e49ad9 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -684,6 +684,27 @@ function POSITIONABLE:IsShip() end +--- Returns if the unit is a submarine. +-- @param #POSITIONABLE self +-- @return #boolean Submarines attributes result. +function POSITIONABLE:IsSubmarine() + self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitDescriptor = DCSUnit:getDesc() + if UnitDescriptor.attributes["Submarines"] == true then + return true + else + return false + end + end + + return nil +end + + --- Returns true if the POSITIONABLE is in the air. -- Polymorphic, is overridden in GROUP and UNIT. -- @param Wrapper.Positionable#POSITIONABLE self @@ -817,8 +838,7 @@ end -- @return #number The velocity in knots. function POSITIONABLE:GetVelocityKNOTS() self:F2( self.PositionableName ) - local velmps=self:GetVelocityMPS() - return UTILS.MpsToKnots(velmps) + return UTILS.MpsToKnots(self:GetVelocityMPS()) end --- Returns the Angle of Attack of a positionable. From 6b747e924b8df7d9812decb2d008d9e05053fa98 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 25 May 2021 23:32:54 +0200 Subject: [PATCH 019/111] Sound update 3 --- Moose Development/Moose/Sound/SRS.lua | 41 ++++++++++++++++----- Moose Development/Moose/Sound/SoundFile.lua | 2 + 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 1cad9d407..f5e2e5470 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -9,17 +9,15 @@ -- -- === -- --- ## Youtube Videos: --- --- * None +-- ## Youtube Videos: None yet -- -- === -- --- ## Missions: Example missions can be found [here](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20ATIS) +-- ## Missions: None yet -- -- === -- --- ## Sound files: Check out the pinned messages in the Moose discord #ops-atis channel. +-- ## Sound files: None yet. -- -- === -- @@ -110,11 +108,18 @@ function MSRS:SetPath(Path) self.path=Path - -- Remove (back)slashes. - while self.path:sub(-1)=="/" or self.path:sub(-1)=="\\" do - self.path=self.path:sub(1,-1) + -- Remove (back)slashes. + local nmax=1000 + local n=1 + while (self.path:sub(-1)=="/" or self.path:sub(-1)==[[\]]) and n<=nmax do + env.info(string.format("FF SRS path=%s (before)", self.path)) + self.path=self.path:sub(1,#self.path-1) + env.info(string.format("FF SRS path=%s (after)", self.path)) + n=n+1 end + env.info(string.format("FF SRS path=%s (final)", self.path)) + return self end @@ -127,11 +132,27 @@ end --- Set path, where the sound file is located. -- @param #MSRS self --- @param #string Path Path to the directory, where the sound file is located. +-- @param Sound.SoundFile#SOUNDFILE Soundfile Sound file to play. +-- @param #number Delay Delay in seconds, before the sound file is played. -- @return #MSRS self -function MSRS:PlaySoundfile(Soundfile) +function MSRS:PlaySoundfile(Soundfile, Delay) + if Delay and Delay>0 then + self:ScheduleOnce(Delay, MSRS.PlaySoundfile, Soundfile, 0) + else + + local exe=self:GetPath().."/".."DCS-SR-ExternalAudio.exe" + local soundfile=Soundfile:GetName() + + env.info(string.format("FF PlaySoundfile soundfile=%s", soundfile)) + + local command=string.format("%s --file %s --freqs %d --modulations %d --coalition %d", exe, soundfile, self.frequency, 0) + + env.info(string.format("FF PlaySoundfile command=%s", command)) + + end + -- TODO: execute! end diff --git a/Moose Development/Moose/Sound/SoundFile.lua b/Moose Development/Moose/Sound/SoundFile.lua index 564fe1306..12e63e665 100644 --- a/Moose Development/Moose/Sound/SoundFile.lua +++ b/Moose Development/Moose/Sound/SoundFile.lua @@ -60,6 +60,8 @@ do -- Sound File -- Set file name. self.filename=filename or "Hallo World.ogg" + --TODO: check that sound file is .ogg or .mp3 + -- Set path self.path=Path or "l10n/DEFAULT/" From 6e37300d9b6cb7039cd307e65f4f2fa409de98c3 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 26 May 2021 08:44:30 +0200 Subject: [PATCH 020/111] Update Controllable.lua Added function `IsSubmarine()`removed type from `OptionDisperseOnAttack()` --- .../Moose/Wrapper/Controllable.lua | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 58e1a747e..7f2f1f795 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -3817,10 +3817,30 @@ function CONTROLLABLE:OptionDisperseOnAttack(Seconds) local Controller = self:_GetController() if Controller then if self:IsGround() then - self:SetOption(AI.Option.GROUND.id.DISPERSE_ON_ATTACK, seconds) + self:SetOption(AI.Option.Ground.id.DISPERSE_ON_ATTACK, seconds) end end return self end return nil end + +--- Returns if the unit is a submarine. +-- @param #POSITIONABLE self +-- @return #boolean Submarines attributes result. +function POSITIONABLE:IsSubmarine() + self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitDescriptor = DCSUnit:getDesc() + if UnitDescriptor.attributes["Submarines"] == true then + return true + else + return false + end + end + + return nil +end From dab486ec99391b6364fda3f0bcd3ceb118b3cc48 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 26 May 2021 09:16:26 +0200 Subject: [PATCH 021/111] Update Controllable.lua Typo in `OptionDisperseOnAttack()` --- Moose Development/Moose/Wrapper/Controllable.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 58e1a747e..5100f51a2 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -1832,7 +1832,7 @@ end do -- Patrol methods - --- (GROUND) Patrol iteratively using the waypoints the for the (parent) group. + --- (GROUND) Patrol iteratively using the waypoints of the (parent) group. -- @param #CONTROLLABLE self -- @return #CONTROLLABLE function CONTROLLABLE:PatrolRoute() @@ -3817,7 +3817,7 @@ function CONTROLLABLE:OptionDisperseOnAttack(Seconds) local Controller = self:_GetController() if Controller then if self:IsGround() then - self:SetOption(AI.Option.GROUND.id.DISPERSE_ON_ATTACK, seconds) + self:SetOption(AI.Option.Ground.id.DISPERSE_ON_ATTACK, seconds) end end return self From cb14961dcd88726816ee4b826a1b5e6db692e418 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 26 May 2021 15:54:20 +0200 Subject: [PATCH 022/111] Update Zone.lua (#1541) added function `ZONE_POLYGON_BASE:Boundary()` --- Moose Development/Moose/Core/Zone.lua | 40 +++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 70d087b4b..65ac3cb9d 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1576,6 +1576,7 @@ end -- ## Draw zone -- -- * @{#ZONE_POLYGON_BASE.DrawZone}(): Draws the zone on the F10 map. +-- * @{#ZONE_POLYGON_BASE.Boundary}(): Draw a frontier on the F10 map with small filled circles. -- -- -- @field #ZONE_POLYGON_BASE @@ -2025,6 +2026,45 @@ function ZONE_POLYGON_BASE:GetBoundingSquare() return { x1 = x1, y1 = y1, x2 = x2, y2 = y2 } end +--- Draw a frontier on the F10 map with small filled circles. +-- @param #ZONE_POLYGON_BASE self +-- @param #number Coalition (Optional) Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1= All. +-- @param #table Color (Optional) RGB color table {r, g, b}, e.g. {1, 0, 0} for red. Default {1, 1, 1}= White. +-- @param #number Radius (Optional) Radius of the circles in meters. Default 1000. +-- @param #number Alpha (Optional) Alpha transparency [0,1]. Default 1. +-- @param #number Segments (Optional) Number of segments within boundary line. Default 10. +-- @param #boolean Closed (Optional) Link the last point with the first one to obtain a closed boundary. Default false +-- @return #ZONE_POLYGON_BASE self +function ZONE_POLYGON_BASE:Boundary(Coalition, Color, Radius, Alpha, Segments, Closed) + Coalition = Coalition or -1 + Color = Color or {1, 1, 1} + Radius = Radius or 1000 + Alpha = Alpha or 1 + Segments = Segments or 10 + Closed = Closed or false + local i = 1 + local j = #self._.Polygon + if (Closed) then + Limit = #self._.Polygon + 1 + else + Limit = #self._.Polygon + end + while i <= #self._.Polygon do + self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) + if j ~= Limit then + local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x + local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y + 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) + end + end + j = i + i = i + 1 + end + return self +end --- @type ZONE_POLYGON -- @extends #ZONE_POLYGON_BASE From 3129c8c8ea0ad537bf7045d486c6c19f5a439855 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 27 May 2021 23:19:40 +0200 Subject: [PATCH 023/111] Sound update 4 --- Moose Development/Moose/Sound/SRS.lua | 137 +++++++++++++++----- Moose Development/Moose/Sound/SoundFile.lua | 42 ++---- 2 files changed, 120 insertions(+), 59 deletions(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index f5e2e5470..35ad41c96 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -34,6 +34,14 @@ -- @type MSRS -- @field #string ClassName Name of the class. -- @field #string lid Class id string for output to DCS log file. +-- @field #table frequencies Frequencies used in the transmissions. +-- @field #table modulations Modulations used in the transmissions. +-- @field #number coalition Coalition of the transmission. +-- @field #number port Port. Default 5002. +-- @field #string name Name. Default "DCS-STTS". +-- @field #number volume Volume between 0 (min) and 1 (max). Default 1. +-- @field #string culture Culture. Default "en-GB". +-- @field #string path Path to the SRS exe. -- @extends Core.Base#BASE --- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde @@ -52,11 +60,24 @@ -- -- @field #MSRS MSRS = { - ClassName = "MSRS", - lid = nil, + ClassName = "MSRS", + lid = nil, + frequencies = {}, + modulations = {}, + coalition = 0, + speed = 1, + port = 5002, + name = "DCS-STTS", + volume = 1, + culture = "en-GB", + gender = "female", + voice = nil, + latitude = nil, + longitude = nil, + altitude = nil, } ---- ATIS class version. +--- MSRS class version. -- @field #string version MSRS.version="0.0.1" @@ -78,15 +99,16 @@ MSRS.version="0.0.1" -- @return #MSRS self function MSRS:New(PathToSRS, Frequency, Modulation) - -- Inherit everything from FSM class. - local self=BASE:Inherit(self, FSM:New()) -- #MSRS - - self.path=self:SetPath(PathToSRS) - - self.frequency=Frequency or 143 - - self.modulation=Modulation or radio.modulation.AM + -- Defaults. + Frequency =Frequency or 143 + Modulation= Modulation or radio.modulation.AM + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, BASE:New()) -- #MSRS + + self:SetPath(PathToSRS) + self:SetFrequencies(Frequency) + self:SetModulations(Modulation) return self end @@ -96,7 +118,7 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Set path, where the sound file is located. +--- Set path to SRS install directory. More precisely, path to where the DCS- -- @param #MSRS self -- @param #string Path Path to the directory, where the sound file is located. -- @return #MSRS self @@ -106,31 +128,62 @@ function MSRS:SetPath(Path) return nil end + -- Set path. self.path=Path -- Remove (back)slashes. - local nmax=1000 - local n=1 + local n=1 ; local nmax=1000 while (self.path:sub(-1)=="/" or self.path:sub(-1)==[[\]]) and n<=nmax do - env.info(string.format("FF SRS path=%s (before)", self.path)) self.path=self.path:sub(1,#self.path-1) - env.info(string.format("FF SRS path=%s (after)", self.path)) n=n+1 end - env.info(string.format("FF SRS path=%s (final)", self.path)) + self:I(string.format("SRS path=%s", self:GetPath())) return self end --- Get path to SRS directory. -- @param #MSRS self --- @return #string +-- @return #string Path to the directory. function MSRS:GetPath() return self.path end ---- Set path, where the sound file is located. +--- Set frequencies. +-- @param #MSRS self +-- @param #table Frequencies Frequencies in MHz. Can also be given as a #number if only one frequency should be used. +-- @return #MSRS self +function MSRS:SetFrequencies(Frequencies) + + -- Ensure table. + if type(Frequencies)~="table" then + Frequencies={Frequencies} + end + + self.frequencies=Frequencies + + return self +end + + +--- Set modulations. +-- @param #MSRS self +-- @param #table Modulations Modulations. Can also be given as a #number if only one modulation should be used. +-- @return #MSRS self +function MSRS:SetModulations(Modulations) + + -- Ensure table. + if type(Modulations)~="table" then + Modulations={Modulations} + end + + self.modulations=Modulations + + return self +end + +--- Play sound file (ogg or mp3) via SRS. -- @param #MSRS self -- @param Sound.SoundFile#SOUNDFILE Soundfile Sound file to play. -- @param #number Delay Delay in seconds, before the sound file is played. @@ -138,31 +191,57 @@ end function MSRS:PlaySoundfile(Soundfile, Delay) if Delay and Delay>0 then - self:ScheduleOnce(Delay, MSRS.PlaySoundfile, Soundfile, 0) + self:ScheduleOnce(Delay, MSRS.PlaySoundfile, self, Soundfile, 0) else local exe=self:GetPath().."/".."DCS-SR-ExternalAudio.exe" local soundfile=Soundfile:GetName() + local freq=table.concat(self.frequencies, " ") + local modu=table.concat(self.modulations, " ") + local coal=self.coalition + local port=self.port - env.info(string.format("FF PlaySoundfile soundfile=%s", soundfile)) - - local command=string.format("%s --file %s --freqs %d --modulations %d --coalition %d", exe, soundfile, self.frequency, 0) + local command=string.format("%s --file %s --freqs %s --modulations %s --coalition %d --port %d -h", exe, soundfile, freq, modu, coal, port) env.info(string.format("FF PlaySoundfile command=%s", command)) - - end - - -- TODO: execute! + -- Execute SRS command. + os.execute(command) + + end + + return self end ---- Set path, where the sound file is located. +--- Play text message via STTS. -- @param #MSRS self -- @param #string Message Text message. +-- @param #number Delay Delay in seconds, before the message is played. -- @return #MSRS self -function MSRS:PlayText(Message) +function MSRS:PlayText(Message, Delay) + if Delay and Delay>0 then + self:ScheduleOnce(Delay, MSRS.PlayText, self, Message, 0) + else + local text=string.format("\"%s\"", Message) + local exe=self:GetPath().."/".."DCS-SR-ExternalAudio.exe" + local freq=table.concat(self.frequencies, " ") + local modu=table.concat(self.modulations, " ") + local coal=self.coalition + local port=self.port + local gender="male" + + local command=string.format("%s -h --text=%s --freqs=%s --modulations=%s --coalition=%d --port=%d --gender=%s", exe, text, freq, modu, coal, port, gender) + + env.info(string.format("FF Text command=%s", command)) + + -- Execute SRS command. + os.execute(command) + + end + + return self end diff --git a/Moose Development/Moose/Sound/SoundFile.lua b/Moose Development/Moose/Sound/SoundFile.lua index 12e63e665..4b65bffc1 100644 --- a/Moose Development/Moose/Sound/SoundFile.lua +++ b/Moose Development/Moose/Sound/SoundFile.lua @@ -48,17 +48,17 @@ do -- Sound File --- Constructor to create a new SOUNDFILE object. -- @param #SOUNDFILE self - -- @param #string filename The name of the sound file, e.g. "Hello World.ogg". + -- @param #string FileName The name of the sound file, e.g. "Hello World.ogg". -- @param #string Path The path of the directory, where the sound file is located. Default is "l10n/DEFAULT/" within the miz file. -- @param #number Duration Duration in seconds, how long it takes to play the sound file. Default is 3 seconds. -- @return #SOUNDFILE self - function SOUNDFILE:New(filename, Path, Duration) + function SOUNDFILE:New(FileName, Path, Duration) -- Inherit BASE. local self=BASE:Inherit(self, BASE:New()) -- #SOUNDFILE -- Set file name. - self.filename=filename or "Hallo World.ogg" + self.filename=FileName or "Hallo World.ogg" --TODO: check that sound file is .ogg or .mp3 @@ -76,15 +76,19 @@ do -- Sound File --- Set path, where the sound file is located. -- @param #SOUNDFILE self -- @param #string Path Path to the directory, where the sound file is located. - -- @return self + -- @return #SOUNDFILE self function SOUNDFILE:SetPath(Path) self.path=Path or "l10n/DEFAULT/" - - while self.path:sub(-1)=="/" or self.path:sub(-1)=="\\" do - self.path=self.path:sub(1,-1) + + -- Remove (back)slashes. + local nmax=1000 + local n=1 + while (self.path:sub(-1)=="/" or self.path:sub(-1)==[[\]]) and n<=nmax do + self.path=self.path:sub(1,#self.path-1) + n=n+1 end - + return self end @@ -115,26 +119,4 @@ do -- Sound File return name end - - --- Set the userflag to a given Number. - -- @param #SOUNDFILE self - -- @param #number Number The number value to be checked if it is the same as the userflag. - -- @param #number Delay Delay in seconds, before the flag is set. - -- @return #SOUNDFILE self - -- @usage - -- local BlueVictory = USERFLAG:New( "VictoryBlue" ) - -- BlueVictory:Set( 100 ) -- Set the UserFlag VictoryBlue to 100. - -- - function SOUNDFILE:Set( Number, Delay ) --R2.3 - - if Delay and Delay>0 then - self:ScheduleOnce(Delay, USERFLAG.Set, self, Number) - else - --env.info(string.format("Setting flag \"%s\" to %d at T=%.1f", self.UserFlagName, Number, timer.getTime())) - trigger.action.setUserFlag( self.UserFlagName, Number ) - end - - return self - end - end \ No newline at end of file From 4827b73bb14c256dd29323c89d7e9acb71b6b75d Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 28 May 2021 23:00:43 +0200 Subject: [PATCH 024/111] Sound update --- Moose Development/Moose/Ops/ATIS.lua | 10 +- Moose Development/Moose/Sound/RadioQueue.lua | 52 +++-- Moose Development/Moose/Sound/SRS.lua | 181 ++++++++++++++--- Moose Development/Moose/Sound/SoundFile.lua | 199 +++++++++++++++++-- 4 files changed, 373 insertions(+), 69 deletions(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 91333138d..96353d11d 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -59,7 +59,7 @@ -- @field #number frequency Radio frequency in MHz. -- @field #number modulation Radio modulation 0=AM or 1=FM. -- @field #number power Radio power in Watts. Default 100 W. --- @field Core.RadioQueue#RADIOQUEUE radioqueue Radio queue for broadcasing messages. +-- @field Sound.RadioQueue#RADIOQUEUE radioqueue Radio queue for broadcasing messages. -- @field #string soundpath Path to sound files. -- @field #string relayunitname Name of the radio relay unit. -- @field #table towerfrequency Table with tower frequencies. @@ -2102,6 +2102,14 @@ end -- @param #string Text Report text. function ATIS:onafterReport(From, Event, To, Text) self:T(self.lid..string.format("Report:\n%s", Text)) + + -- Remove line breaks + local text=string.gsub(Text, "[\r\n]", "") + env.info("FF: "..text) + + local msrs=MSRS:New("D:\\DCS\\_SRS\\", 305, Modulation) + msrs:PlayText(text) + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Sound/RadioQueue.lua b/Moose Development/Moose/Sound/RadioQueue.lua index 3e6afcd51..1f5e9cd07 100644 --- a/Moose Development/Moose/Sound/RadioQueue.lua +++ b/Moose Development/Moose/Sound/RadioQueue.lua @@ -35,6 +35,7 @@ -- @field #table numbers Table of number transmission parameters. -- @field #boolean checking Scheduler is checking the radio queue. -- @field #boolean schedonce Call ScheduleOnce instead of normal scheduler. +-- @field Sound.SRS#MSRS msrs Moose SRS class. -- @extends Core.Base#BASE RADIOQUEUE = { ClassName = "RADIOQUEUE", @@ -69,6 +70,8 @@ RADIOQUEUE = { -- @field #boolean isplaying If true, transmission is currently playing. -- @field #number Tplay Mission time (abs) in seconds when the transmission should be played. -- @field #number interval Interval in seconds before next transmission. +-- @field Sound.SoundFile#SOUNDFILE soundfile Sound file object to play via SRS. +-- @field Sound.SoundFile#SOUNDTEXT soundtext Sound TTS object to play via SRS. --- Create a new RADIOQUEUE object for a given radio frequency/modulation. @@ -170,6 +173,15 @@ function RADIOQUEUE:SetRadioPower(power) return self end +--- Set SRS. +-- @param #RADIOQUEUE self +-- @param #string PathToSRS Path to SRS. +-- @return #RADIOQUEUE self The RADIOQUEUE object. +function RADIOQUEUE:SetSRS(PathToSRS) + self.msrs=MSRS:New(PathToSRS, self.frequency/1000000, self.modulation) + return self +end + --- Set parameters of a digit. -- @param #RADIOQUEUE self -- @param #number digit The digit 0-9. @@ -230,7 +242,7 @@ end -- @param #number interval Interval in seconds after the last transmission finished. -- @param #string subtitle Subtitle of the transmission. -- @param #number subduration Duration [sec] of the subtitle being displayed. Default 5 sec. --- @return #RADIOQUEUE self The RADIOQUEUE object. +-- @return #RADIOQUEUE.Transmission Radio transmission table. function RADIOQUEUE:NewTransmission(filename, duration, path, tstart, interval, subtitle, subduration) env.info("FF new transmission.") @@ -271,7 +283,7 @@ function RADIOQUEUE:NewTransmission(filename, duration, path, tstart, interval, -- Add transmission to queue. self:AddTransmission(transmission) - return self + return transmission end --- Create a new transmission and add it to the radio queue. @@ -280,10 +292,10 @@ end -- @param #number tstart Start time (abs) seconds. Default now. -- @param #number interval Interval in seconds after the last transmission finished. -- @return #RADIOQUEUE self -function RADIOQUEUE:AddSoundfile(soundfile, tstart, interval) +function RADIOQUEUE:AddSoundFile(soundfile, tstart, interval) env.info(string.format("FF add soundfile: name=%s%s", soundfile:GetPath(), soundfile:GetFileName())) - self:NewTransmission(soundfile:GetFileName(), soundfile.duration, soundfile:GetPath(), tstart, interval, soundfile.subtitle, soundfile.subduration) - + local transmission=self:NewTransmission(soundfile:GetFileName(), soundfile.duration, soundfile:GetPath(), tstart, interval, soundfile.subtitle, soundfile.subduration) + transmission.soundfile=soundfile return self end @@ -295,19 +307,9 @@ end -- @param #number interval Interval between the next call. -- @return #number Duration of the call in seconds. function RADIOQUEUE:Number2Transmission(number, delay, interval) - - --- Split string into characters. - local function _split(str) - local chars={} - for i=1,#str do - local c=str:sub(i,i) - table.insert(chars, c) - end - return chars - end -- Split string into characters. - local numbers=UTILS.GetCharacters(number) --l_split(number) + local numbers=UTILS.GetCharacters(number) local wait=0 for i=1,#numbers do @@ -340,6 +342,11 @@ end -- @param #RADIOQUEUE.Transmission transmission The transmission. function RADIOQUEUE:Broadcast(transmission) + if (transmission.soundfile or transmission.soundtext) and self.msrs then + self:_BroadcastSRS(transmission) + return + end + -- Get unit sending the transmission. local sender=self:_GetRadioSender() @@ -431,6 +438,19 @@ function RADIOQUEUE:Broadcast(transmission) end end +--- Broadcast radio message. +-- @param #RADIOQUEUE self +-- @param #RADIOQUEUE.Transmission transmission The transmission. +function RADIOQUEUE:_BroadcastSRS(transmission) + + if transmission.soundfile then + self.msrs:PlaySoundFile(transmission.soundfile) + elseif transmission.soundtext then + self.msrs:PlaySoundText(transmission.soundtext) + end + +end + --- Start checking the radio queue. -- @param #RADIOQUEUE self -- @param #number delay Delay in seconds before checking. diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 35ad41c96..c4fc31c15 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -1,4 +1,4 @@ ---- **Sound** - Simple Radio Standalone Integration +--- **Sound** - Simple Radio Standalone (SRS) Integration. -- -- === -- @@ -41,7 +41,10 @@ -- @field #string name Name. Default "DCS-STTS". -- @field #number volume Volume between 0 (min) and 1 (max). Default 1. -- @field #string culture Culture. Default "en-GB". --- @field #string path Path to the SRS exe. +-- @field #string gender Gender. Default "female". +-- @field #string voice Specifc voce. +-- @field Core.Point#COORDINATE coordinate Coordinate from where the transmission is send. +-- @field #string path Path to the SRS exe. This includes the final slash "/". -- @extends Core.Base#BASE --- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde @@ -62,16 +65,17 @@ MSRS = { ClassName = "MSRS", lid = nil, + port = 5002, + name = "MSRS", frequencies = {}, modulations = {}, coalition = 0, - speed = 1, - port = 5002, - name = "DCS-STTS", - volume = 1, - culture = "en-GB", gender = "female", + culture = "en-GB", voice = nil, + volume = 1, + speed = 1, + coordinate = nil, latitude = nil, longitude = nil, altitude = nil, @@ -79,7 +83,7 @@ MSRS = { --- MSRS class version. -- @field #string version -MSRS.version="0.0.1" +MSRS.version="0.0.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -94,8 +98,8 @@ MSRS.version="0.0.1" --- Create a new MSRS object. -- @param #MSRS self -- @param #string PathToSRS Path to the directory, where SRS is located. --- @param #number Frequency Radio frequency in MHz. Default 143.00 MHz. --- @param #number Modulation Radio modulation: 0=AM (default), 1=FM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators. +-- @param #number Frequency Radio frequency in MHz. Default 143.00 MHz. Can also be given as a #table of multiple frequencies. +-- @param #number Modulation Radio modulation: 0=AM (default), 1=FM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators. Can also be given as a #table of multiple modulations. -- @return #MSRS self function MSRS:New(PathToSRS, Frequency, Modulation) @@ -107,9 +111,11 @@ function MSRS:New(PathToSRS, Frequency, Modulation) local self=BASE:Inherit(self, BASE:New()) -- #MSRS self:SetPath(PathToSRS) + self:SetPort() self:SetFrequencies(Frequency) self:SetModulations(Modulation) - + self:SetGender() + return self end @@ -117,7 +123,6 @@ end -- User Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - --- Set path to SRS install directory. More precisely, path to where the DCS- -- @param #MSRS self -- @param #string Path Path to the directory, where the sound file is located. @@ -125,6 +130,7 @@ end function MSRS:SetPath(Path) if Path==nil then + self:E("ERROR: No path to SRS directory specified!") return nil end @@ -138,6 +144,8 @@ function MSRS:SetPath(Path) n=n+1 end + self.path=self.path.."/" + self:I(string.format("SRS path=%s", self:GetPath())) return self @@ -145,11 +153,26 @@ end --- Get path to SRS directory. -- @param #MSRS self --- @return #string Path to the directory. +-- @return #string Path to the directory. This includes the final slash "/". function MSRS:GetPath() return self.path end +--- Set port. +-- @param #MSRS self +-- @param #number Port Port. Default 5002. +-- @return #MSRS self +function MSRS:SetPort(Port) + self.port=Port or 5002 +end + +--- Get port. +-- @param #MSRS self +-- @return #number Port. +function MSRS:GetPort() + return self.port +end + --- Set frequencies. -- @param #MSRS self -- @param #table Frequencies Frequencies in MHz. Can also be given as a #number if only one frequency should be used. @@ -166,6 +189,13 @@ function MSRS:SetFrequencies(Frequencies) return self end +--- Get frequencies. +-- @param #MSRS self +-- @param #table Frequencies in MHz. +function MSRS:GetFrequencies() + return self.frequencies +end + --- Set modulations. -- @param #MSRS self @@ -183,25 +213,73 @@ function MSRS:SetModulations(Modulations) return self end +--- Get modulations. +-- @param #MSRS self +-- @param #table Modulations. +function MSRS:GetModulations() + return self.modulations +end + +--- Set gender. +-- @param #MSRS self +-- @param #string Gender Gender: "male" or "female" (default). +-- @return #MSRS self +function MSRS:SetGender(Gender) + + Gender=Gender or "female" + + Gender=Gender:lower() + + self.gender=Gender + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Transmission Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + --- Play sound file (ogg or mp3) via SRS. -- @param #MSRS self -- @param Sound.SoundFile#SOUNDFILE Soundfile Sound file to play. -- @param #number Delay Delay in seconds, before the sound file is played. -- @return #MSRS self -function MSRS:PlaySoundfile(Soundfile, Delay) +function MSRS:PlaySoundFile(Soundfile, Delay) if Delay and Delay>0 then - self:ScheduleOnce(Delay, MSRS.PlaySoundfile, self, Soundfile, 0) + self:ScheduleOnce(Delay, MSRS.PlaySoundFile, self, Soundfile, 0) else - local exe=self:GetPath().."/".."DCS-SR-ExternalAudio.exe" local soundfile=Soundfile:GetName() - local freq=table.concat(self.frequencies, " ") - local modu=table.concat(self.modulations, " ") - local coal=self.coalition - local port=self.port + + local command=self:_GetCommand() - local command=string.format("%s --file %s --freqs %s --modulations %s --coalition %d --port %d -h", exe, soundfile, freq, modu, coal, port) + command=command.." --file="..tostring(soundfile) + + env.info(string.format("FF PlaySoundfile command=%s", command)) + + -- Execute SRS command. + os.execute(command) + + end + + return self +end + +--- Play a SOUNDTEXT text-to-speech object. +-- @param #MSRS self +-- @param Sound.SoundFile#SOUNDTEXT SoundText Sound text. +-- @param #number Delay Delay in seconds, before the sound file is played. +-- @return #MSRS self +function MSRS:PlaySoundText(SoundText, Delay) + + if Delay and Delay>0 then + self:ScheduleOnce(Delay, MSRS.PlaySoundText, self, SoundText, 0) + else + + local command=self:_GetCommand(nil, nil, nil, SoundText.gender, SoundText.voice, SoundText.culture, SoundText.volume, SoundText.speed) + + command=command..string.format(" --text=\"%s\"", tostring(SoundText.text)) env.info(string.format("FF PlaySoundfile command=%s", command)) @@ -215,29 +293,26 @@ end --- Play text message via STTS. -- @param #MSRS self --- @param #string Message Text message. +-- @param #string Text Text message. -- @param #number Delay Delay in seconds, before the message is played. -- @return #MSRS self -function MSRS:PlayText(Message, Delay) +function MSRS:PlayText(Text, Delay) if Delay and Delay>0 then - self:ScheduleOnce(Delay, MSRS.PlayText, self, Message, 0) + self:ScheduleOnce(Delay, MSRS.PlayText, self, Text, 0) else - local text=string.format("\"%s\"", Message) - local exe=self:GetPath().."/".."DCS-SR-ExternalAudio.exe" - local freq=table.concat(self.frequencies, " ") - local modu=table.concat(self.modulations, " ") - local coal=self.coalition - local port=self.port - local gender="male" + local text=string.format("\"%s\"", Text) - local command=string.format("%s -h --text=%s --freqs=%s --modulations=%s --coalition=%d --port=%d --gender=%s", exe, text, freq, modu, coal, port, gender) + local command=self:_GetCommand() + + command=command..string.format(" --text=\"%s\"", tostring(Text)) env.info(string.format("FF Text command=%s", command)) -- Execute SRS command. - os.execute(command) + local x=os.execute(command) + env.info(x) end @@ -249,6 +324,46 @@ end -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Get SRS command to play sound using the `DCS-SR-ExternalAudio.exe`. +-- @param #MSRS self +-- @param #table freqs Frequencies in MHz. +-- @param #table modus Modulations. +-- @param #number coal Coalition. +-- @param #string gender Gender. +-- @param #string voice Voice. +-- @param #string culture Culture. +-- @param #number volume Volume. +-- @param #number speed Speed. +-- @param #number port Port. +-- @return #string Command. +function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, speed, port) + + + local exe=self:GetPath().."DCS-SR-ExternalAudio.exe" + freqs=table.concat(freqs or self.frequencies, ",") + modus=table.concat(modus or self.modulations, ",") + coal=coal or self.coalition + gender=gender or self.gender + voice=voice or self.voice + culture=culture or self.culture + volume=volume or self.volume + speed=speed or self.speed + port=port or self.port + + local command=string.format("%s --freqs=%s --modulations=%s --coalition=%d --port=%d --gender=%s --volume=%.2f --speed=%d", exe, freqs, modus, coal, port, gender, volume, speed) + + if voice then + command=command..string.format(" --voice=\"%s\"", tostring(voice)) + end + + if culture then + command=command.." --culture="..tostring(culture) + end + + env.info("FF command="..command) + + return command +end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Sound/SoundFile.lua b/Moose Development/Moose/Sound/SoundFile.lua index 4b65bffc1..884eeab6a 100644 --- a/Moose Development/Moose/Sound/SoundFile.lua +++ b/Moose Development/Moose/Sound/SoundFile.lua @@ -1,4 +1,4 @@ ---- **Sound** - Sound file management. +--- **Sound** - Sound output classes. -- -- === -- @@ -16,12 +16,46 @@ -- @image Sound_Soundfile.png -- +do -- Sound Base + + --- @type SOUNDBASE + -- @field #string ClassName Name of the class + -- @extends Core.Base#BASE + + + --- Sound files used by other classes. + -- + -- # 1. USERFLAG constructor + -- + -- * @{#USERFLAG.New}(): Creates a new USERFLAG object. + -- + -- @field #SOUNDBASE + SOUNDBASE={ + ClassName = "SOUNDBASE", + } + + --- Constructor to create a new SOUNDBASE object. + -- @param #SOUNDBASE self + -- @return #SOUNDBASE self + function SOUNDBASE:New() + + -- Inherit BASE. + local self=BASE:Inherit(self, BASE:New()) -- #SOUNDFILE + + + + return self + end + +end + + do -- Sound File --- @type SOUNDFILE -- @field #string ClassName Name of the class -- @field #string filename Name of the flag. - -- @field #string path Directory path, where the sound file is located. + -- @field #string path Directory path, where the sound file is located. This includes the final slash "/". -- @field #string duration Duration of the sound file in seconds. -- @field #string subtitle Subtitle of the transmission. -- @field #number subduration Duration in seconds how long the subtitle is displayed. @@ -39,7 +73,7 @@ do -- Sound File SOUNDFILE={ ClassName = "SOUNDFILE", filename = nil, - path = "l10n/DEFAULT", + path = "l10n/DEFAULT/", duration = 3, subtitle = nil, subduration = 0, @@ -58,12 +92,10 @@ do -- Sound File local self=BASE:Inherit(self, BASE:New()) -- #SOUNDFILE -- Set file name. - self.filename=FileName or "Hallo World.ogg" - - --TODO: check that sound file is .ogg or .mp3 + self:SetFileName(FileName) -- Set path - self.path=Path or "l10n/DEFAULT/" + self:SetPath(Path) self.duration=Duration or 3 @@ -79,19 +111,39 @@ do -- Sound File -- @return #SOUNDFILE self function SOUNDFILE:SetPath(Path) + -- Init path. self.path=Path or "l10n/DEFAULT/" -- Remove (back)slashes. - local nmax=1000 - local n=1 + local nmax=1000 ; local n=1 while (self.path:sub(-1)=="/" or self.path:sub(-1)==[[\]]) and n<=nmax do self.path=self.path:sub(1,#self.path-1) n=n+1 end + + -- Append slash. + self.path=self.path.."/" return self end - + + --- Get path of the directory, where the sound file is located. + -- @param #SOUNDFILE self + -- @return #string Path. + function SOUNDFILE:GetPath() + local path=self.path or "l10n/DEFAULT/" + return path + end + + --- Set sound file name. This must be a .ogg or .mp3 file! + -- @param #SOUNDFILE self + -- @param #string FileName Name of the file. + -- @return #SOUNDFILE self + function SOUNDFILE:SetFileName(FileName) + --TODO: check that sound file is really .ogg or .mp3 + self.filename=FileName or "HelloWorld.mp3" + return self + end --- Get the sound file name. -- @param #SOUNDFILE self @@ -99,24 +151,133 @@ do -- Sound File function SOUNDFILE:GetFileName() return self.filename end - - --- Get path of the directory, where the sound file is located. + + + --- Set duration how long it takes to play the sound file. -- @param #SOUNDFILE self - -- @return #string Path. - function SOUNDFILE:GetPath() - local path=self.path or "l10n/DEFAULT" - path=path.."/" - return path + -- @param #string Duration Duration in seconds. Default 3 seconds. + -- @return #SOUNDFILE self + function SOUNDFILE:SetDuration(Duration) + self.duration=Duration or 3 + return self + end + + --- Get duration how long the sound file takes to play. + -- @param #SOUNDFILE self + -- @return #number Duration in seconds. + function SOUNDFILE:GetDuration() + return self.duration or 3 end --- Get the complete sound file name inlcuding its path. -- @param #SOUNDFILE self -- @return #string Name of the sound file. function SOUNDFILE:GetName() - local filename=self:GetFileName() local path=self:GetPath() - local name=string.format("%s/%s", path, filename) + local filename=self:GetFileName() + local name=string.format("%s%s", path, filename) return name end +end + +do -- Text-To-Speech + + --- @type SOUNDTEXT + -- @field #string ClassName Name of the class + -- @field #string text Text to speak. + -- @field #number duration Duration in seconds. + -- @field #string gender Gender. + -- @field #string voice Voice. + -- @field #string culture Culture. + -- @extends Core.Base#BASE + + + --- Sound files used by other classes. + -- + -- # 1. USERFLAG constructor + -- + -- * @{#USERFLAG.New}(): Creates a new USERFLAG object. + -- + -- @field #SOUNDTEXT + SOUNDTEXT={ + ClassName = "SOUNDTEXT", + } + + --- Constructor to create a new SOUNDTEXT object. + -- @param #SOUNDTEXT self + -- @param #string Text The text to speak. + -- @param #number Duration Duration in seconds, how long it takes to play the text. Default is 3 seconds. + -- @return #SOUNDTEXT self + function SOUNDTEXT:New(Text, Duration) + + -- Inherit BASE. + local self=BASE:Inherit(self, BASE:New()) -- #SOUNDTEXT + + self:SetText(Text) + self:SetDuration(Duration) + self:SetGender() + self:SetCulture() + + -- Debug info: + self:I(string.format("New SOUNDTEXT: text=%s, duration=%.1f sec", self.text, self.duration)) + + return self + end + + --- Set text. + -- @param #SOUNDTEXT self + -- @param #string Text Text to speak. Default "Hello World!". + -- @return #SOUNDTEXT self + function SOUNDTEXT:SetText(Text) + + self.text=Text or "Hello World!" + + return self + end + + --- Set duration, how long it takes to speak the text. + -- @param #SOUNDTEXT self + -- @param #number Duration Duration in seconds. Default 3 seconds. + -- @return #SOUNDTEXT self + function SOUNDTEXT:SetDuration(Duration) + + self.duration=Duration or 3 + + return self + end + + --- Set gender. + -- @param #SOUNDTEXT self + -- @param #string Gender Gender: "male" or "female" (default). + -- @return #SOUNDTEXT self + function SOUNDTEXT:SetGender(Gender) + + self.gender=Gender or "female" + + return self + end + + --- Set the voice name. See the list from --help or if using google see: https://cloud.google.com/text-to-speech/docs/voices + -- @param #SOUNDTEXT self + -- @param #string Voice + -- @return #SOUNDTEXT self + function SOUNDTEXT:SetVoice(Voice) + + self.voice=Voice + + return self + end + + --- Set TTS culture - local for the voice. + -- @param #SOUNDTEXT self + -- @param #string Culture TTS culture. Default "en-GB". + -- @return #SOUNDTEXT self + function SOUNDTEXT:SetCulture(Culture) + + self.culture=Culture or "en-GB" + + return self + end + end \ No newline at end of file From 4c3d44a63bd44fd14efb4a70c554f836e00695fe Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 30 May 2021 01:36:22 +0200 Subject: [PATCH 025/111] Sound update --- Moose Development/Moose/Core/Beacon.lua | 442 ++++++++++++++ Moose Development/Moose/Core/Settings.lua | 1 + Moose Development/Moose/Globals.lua | 32 +- Moose Development/Moose/Modules.lua | 4 +- Moose Development/Moose/Ops/ATIS.lua | 546 ++++++++++-------- Moose Development/Moose/Sound/Radio.lua | 462 +-------------- Moose Development/Moose/Sound/RadioQueue.lua | 6 +- Moose Development/Moose/Sound/SRS.lua | 160 ++++- .../Sound/{SoundFile.lua => SoundOutput.lua} | 58 +- Moose Development/Moose/Utilities/STTS.lua | 256 ++++++++ Moose Setup/Moose.files | 4 +- 11 files changed, 1235 insertions(+), 736 deletions(-) create mode 100644 Moose Development/Moose/Core/Beacon.lua rename Moose Development/Moose/Sound/{SoundFile.lua => SoundOutput.lua} (72%) create mode 100644 Moose Development/Moose/Utilities/STTS.lua diff --git a/Moose Development/Moose/Core/Beacon.lua b/Moose Development/Moose/Core/Beacon.lua new file mode 100644 index 000000000..37dba8c76 --- /dev/null +++ b/Moose Development/Moose/Core/Beacon.lua @@ -0,0 +1,442 @@ +--- **Core** - TACAN and other beacons. +-- +-- === +-- +-- ## Features: +-- +-- * Provide beacon functionality to assist pilots. +-- +-- === +-- +-- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky +-- +-- @module Core.Beacon +-- @image Core_Radio.JPG + +--- *In order for the light to shine so brightly, the darkness must be present.* -- Francis Bacon +-- +-- After attaching a @{#BEACON} to your @{Wrapper.Positionable#POSITIONABLE}, you need to select the right function to activate the kind of beacon you want. +-- There are two types of BEACONs available : the AA TACAN Beacon and the general purpose Radio Beacon. +-- Note that in both case, you can set an optional parameter : the `BeaconDuration`. This can be very usefull to simulate the battery time if your BEACON is +-- attach to a cargo crate, for exemple. +-- +-- ## AA TACAN Beacon usage +-- +-- This beacon only works with airborne @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}. Use @{#BEACON:AATACAN}() to set the beacon parameters and start the beacon. +-- Use @#BEACON:StopAATACAN}() to stop it. +-- +-- ## General Purpose Radio Beacon usage +-- +-- This beacon will work with any @{Wrapper.Positionable#POSITIONABLE}, but **it won't follow the @{Wrapper.Positionable#POSITIONABLE}** ! This means that you should only use it with +-- @{Wrapper.Positionable#POSITIONABLE} that don't move, or move very slowly. Use @{#BEACON:RadioBeacon}() to set the beacon parameters and start the beacon. +-- Use @{#BEACON:StopRadioBeacon}() to stop it. +-- +-- @type BEACON +-- @field #string ClassName Name of the class "BEACON". +-- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will receive radio capabilities. +-- @extends Core.Base#BASE +BEACON = { + ClassName = "BEACON", + Positionable = nil, + name = nil, +} + +--- Beacon types supported by DCS. +-- @type BEACON.Type +-- @field #number NULL +-- @field #number VOR +-- @field #number DME +-- @field #number VOR_DME +-- @field #number TACAN TACtical Air Navigation system. +-- @field #number VORTAC +-- @field #number RSBN +-- @field #number BROADCAST_STATION +-- @field #number HOMER +-- @field #number AIRPORT_HOMER +-- @field #number AIRPORT_HOMER_WITH_MARKER +-- @field #number ILS_FAR_HOMER +-- @field #number ILS_NEAR_HOMER +-- @field #number ILS_LOCALIZER +-- @field #number ILS_GLIDESLOPE +-- @field #number PRMG_LOCALIZER +-- @field #number PRMG_GLIDESLOPE +-- @field #number ICLS Same as ICLS glideslope. +-- @field #number ICLS_LOCALIZER +-- @field #number ICLS_GLIDESLOPE +-- @field #number NAUTICAL_HOMER +BEACON.Type={ + NULL = 0, + VOR = 1, + DME = 2, + VOR_DME = 3, + TACAN = 4, + VORTAC = 5, + RSBN = 128, + BROADCAST_STATION = 1024, + HOMER = 8, + AIRPORT_HOMER = 4104, + AIRPORT_HOMER_WITH_MARKER = 4136, + ILS_FAR_HOMER = 16408, + ILS_NEAR_HOMER = 16424, + ILS_LOCALIZER = 16640, + ILS_GLIDESLOPE = 16896, + PRMG_LOCALIZER = 33024, + PRMG_GLIDESLOPE = 33280, + ICLS = 131584, --leaving this in here but it is the same as ICLS_GLIDESLOPE + ICLS_LOCALIZER = 131328, + ICLS_GLIDESLOPE = 131584, + NAUTICAL_HOMER = 65536, +} + +--- Beacon systems supported by DCS. https://wiki.hoggitworld.com/view/DCS_command_activateBeacon +-- @type BEACON.System +-- @field #number PAR_10 ? +-- @field #number RSBN_5 Russian VOR/DME system. +-- @field #number TACAN TACtical Air Navigation system on ground. +-- @field #number TACAN_TANKER_X TACtical Air Navigation system for tankers on X band. +-- @field #number TACAN_TANKER_Y TACtical Air Navigation system for tankers on Y band. +-- @field #number VOR Very High Frequency Omni-Directional Range +-- @field #number ILS_LOCALIZER ILS localizer +-- @field #number ILS_GLIDESLOPE ILS glideslope. +-- @field #number PRGM_LOCALIZER PRGM localizer. +-- @field #number PRGM_GLIDESLOPE PRGM glideslope. +-- @field #number BROADCAST_STATION Broadcast station. +-- @field #number VORTAC Radio-based navigational aid for aircraft pilots consisting of a co-located VHF omnidirectional range (VOR) beacon and a tactical air navigation system (TACAN) beacon. +-- @field #number TACAN_AA_MODE_X TACtical Air Navigation for aircraft on X band. +-- @field #number TACAN_AA_MODE_Y TACtical Air Navigation for aircraft on Y band. +-- @field #number VORDME Radio beacon that combines a VHF omnidirectional range (VOR) with a distance measuring equipment (DME). +-- @field #number ICLS_LOCALIZER Carrier landing system. +-- @field #number ICLS_GLIDESLOPE Carrier landing system. +BEACON.System={ + PAR_10 = 1, + RSBN_5 = 2, + TACAN = 3, + TACAN_TANKER_X = 4, + TACAN_TANKER_Y = 5, + VOR = 6, + ILS_LOCALIZER = 7, + ILS_GLIDESLOPE = 8, + PRMG_LOCALIZER = 9, + PRMG_GLIDESLOPE = 10, + BROADCAST_STATION = 11, + VORTAC = 12, + TACAN_AA_MODE_X = 13, + TACAN_AA_MODE_Y = 14, + VORDME = 15, + ICLS_LOCALIZER = 16, + ICLS_GLIDESLOPE = 17, +} + +--- Create a new BEACON Object. This doesn't activate the beacon, though, use @{#BEACON.ActivateTACAN} etc. +-- If you want to create a BEACON, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetBeacon}() instead. +-- @param #BEACON self +-- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities. +-- @return #BEACON Beacon object or #nil if the positionable is invalid. +function BEACON:New(Positionable) + + -- Inherit BASE. + local self=BASE:Inherit(self, BASE:New()) --#BEACON + + -- Debug. + self:F(Positionable) + + -- Set positionable. + if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid + self.Positionable = Positionable + self.name=Positionable:GetName() + self:I(string.format("New BEACON %s", tostring(self.name))) + return self + end + + self:E({"The passed positionable is invalid, no BEACON created", Positionable}) + return nil +end + + +--- Activates a TACAN BEACON. +-- @param #BEACON self +-- @param #number Channel TACAN channel, i.e. the "10" part in "10Y". +-- @param #string Mode TACAN mode, i.e. the "Y" part in "10Y". +-- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon. +-- @param #boolean Bearing If true, beacon provides bearing information. If false (or nil), only distance information is available. +-- @param #number Duration How long will the beacon last in seconds. Omit for forever. +-- @return #BEACON self +-- @usage +-- -- Let's create a TACAN Beacon for a tanker +-- local myUnit = UNIT:FindByName("MyUnit") +-- local myBeacon = myUnit:GetBeacon() -- Creates the beacon +-- +-- myBeacon:ActivateTACAN(20, "Y", "TEXACO", true) -- Activate the beacon +function BEACON:ActivateTACAN(Channel, Mode, Message, Bearing, Duration) + self:T({channel=Channel, mode=Mode, callsign=Message, bearing=Bearing, duration=Duration}) + + -- Get frequency. + local Frequency=UTILS.TACANToFrequency(Channel, Mode) + + -- Check. + if not Frequency then + self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"}) + return self + end + + -- Beacon type. + local Type=BEACON.Type.TACAN + + -- Beacon system. + local System=BEACON.System.TACAN + + -- Check if unit is an aircraft and set system accordingly. + local AA=self.Positionable:IsAir() + if AA then + System=5 --NOTE: 5 is how you cat the correct tanker behaviour! --BEACON.System.TACAN_TANKER + -- Check if "Y" mode is selected for aircraft. + if Mode~="Y" then + self:E({"WARNING: The POSITIONABLE you want to attach the AA Tacan Beacon is an aircraft: Mode should Y !The BEACON is not emitting.", self.Positionable}) + end + end + + -- Attached unit. + local UnitID=self.Positionable:GetID() + + -- Debug. + self:I({string.format("BEACON Activating TACAN %s: Channel=%d%s, Morse=%s, Bearing=%s, Duration=%s!", tostring(self.name), Channel, Mode, Message, tostring(Bearing), tostring(Duration))}) + + -- Start beacon. + self.Positionable:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, Mode, AA, Message, Bearing) + + -- Stop sheduler. + if Duration then + self.Positionable:DeactivateBeacon(Duration) + end + + return self +end + +--- Activates an ICLS BEACON. The unit the BEACON is attached to should be an aircraft carrier supporting this system. +-- @param #BEACON self +-- @param #number Channel ICLS channel. +-- @param #string Callsign The Message that is going to be coded in Morse and broadcasted by the beacon. +-- @param #number Duration How long will the beacon last in seconds. Omit for forever. +-- @return #BEACON self +function BEACON:ActivateICLS(Channel, Callsign, Duration) + self:F({Channel=Channel, Callsign=Callsign, Duration=Duration}) + + -- Attached unit. + local UnitID=self.Positionable:GetID() + + -- Debug + self:T2({"ICLS BEACON started!"}) + + -- Start beacon. + self.Positionable:CommandActivateICLS(Channel, UnitID, Callsign) + + -- Stop sheduler + if Duration then -- Schedule the stop of the BEACON if asked by the MD + self.Positionable:DeactivateBeacon(Duration) + end + + return self +end + +--- Activates a TACAN BEACON on an Aircraft. +-- @param #BEACON self +-- @param #number TACANChannel (the "10" part in "10Y"). Note that AA TACAN are only available on Y Channels +-- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon +-- @param #boolean Bearing Can the BEACON be homed on ? +-- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever. +-- @return #BEACON self +-- @usage +-- -- Let's create a TACAN Beacon for a tanker +-- local myUnit = UNIT:FindByName("MyUnit") +-- local myBeacon = myUnit:GetBeacon() -- Creates the beacon +-- +-- myBeacon:AATACAN(20, "TEXACO", true) -- Activate the beacon +function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration) + self:F({TACANChannel, Message, Bearing, BeaconDuration}) + + local IsValid = true + + if not self.Positionable:IsAir() then + self:E({"The POSITIONABLE you want to attach the AA Tacan Beacon is not an aircraft ! The BEACON is not emitting", self.Positionable}) + IsValid = false + end + + local Frequency = self:_TACANToFrequency(TACANChannel, "Y") + if not Frequency then + self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"}) + IsValid = false + end + + -- I'm using the beacon type 4 (BEACON_TYPE_TACAN). For System, I'm using 5 (TACAN_TANKER_MODE_Y) if the bearing shows its bearing + -- or 14 (TACAN_AA_MODE_Y) if it does not + local System + if Bearing then + System = 5 + else + System = 14 + end + + if IsValid then -- Starts the BEACON + self:T2({"AA TACAN BEACON started !"}) + self.Positionable:SetCommand({ + id = "ActivateBeacon", + params = { + type = 4, + system = System, + callsign = Message, + frequency = Frequency, + } + }) + + if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD + SCHEDULER:New(nil, + function() + self:StopAATACAN() + end, {}, BeaconDuration) + end + end + + return self +end + +--- Stops the AA TACAN BEACON +-- @param #BEACON self +-- @return #BEACON self +function BEACON:StopAATACAN() + self:F() + if not self.Positionable then + self:E({"Start the beacon first before stoping it !"}) + else + self.Positionable:SetCommand({ + id = 'DeactivateBeacon', + params = { + } + }) + end +end + + +--- Activates a general pupose Radio Beacon +-- This uses the very generic singleton function "trigger.action.radioTransmission()" provided by DCS to broadcast a sound file on a specific frequency. +-- Although any frequency could be used, only 2 DCS Modules can home on radio beacons at the time of writing : the Huey and the Mi-8. +-- They can home in on these specific frequencies : +-- * **Mi8** +-- * R-828 -> 20-60MHz +-- * ARKUD -> 100-150MHz (canal 1 : 114166, canal 2 : 114333, canal 3 : 114583, canal 4 : 121500, canal 5 : 123100, canal 6 : 124100) AM +-- * ARK9 -> 150-1300KHz +-- * **Huey** +-- * AN/ARC-131 -> 30-76 Mhz FM +-- @param #BEACON self +-- @param #string FileName The name of the audio file +-- @param #number Frequency in MHz +-- @param #number Modulation either radio.modulation.AM or radio.modulation.FM +-- @param #number Power in W +-- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever. +-- @return #BEACON self +-- @usage +-- -- Let's create a beacon for a unit in distress. +-- -- Frequency will be 40MHz FM (home-able by a Huey's AN/ARC-131) +-- -- The beacon they use is battery-powered, and only lasts for 5 min +-- local UnitInDistress = UNIT:FindByName("Unit1") +-- local UnitBeacon = UnitInDistress:GetBeacon() +-- +-- -- Set the beacon and start it +-- UnitBeacon:RadioBeacon("MySoundFileSOS.ogg", 40, radio.modulation.FM, 20, 5*60) +function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDuration) + self:F({FileName, Frequency, Modulation, Power, BeaconDuration}) + local IsValid = false + + -- Check the filename + if type(FileName) == "string" then + if FileName:find(".ogg") or FileName:find(".wav") then + if not FileName:find("l10n/DEFAULT/") then + FileName = "l10n/DEFAULT/" .. FileName + end + IsValid = true + end + end + if not IsValid then + self:E({"File name invalid. Maybe something wrong with the extension ? ", FileName}) + end + + -- Check the Frequency + if type(Frequency) ~= "number" and IsValid then + self:E({"Frequency invalid. ", Frequency}) + IsValid = false + end + Frequency = Frequency * 1000000 -- Conversion to Hz + + -- Check the modulation + if Modulation ~= radio.modulation.AM and Modulation ~= radio.modulation.FM and IsValid then --TODO Maybe make this future proof if ED decides to add an other modulation ? + self:E({"Modulation is invalid. Use DCS's enum radio.modulation.", Modulation}) + IsValid = false + end + + -- Check the Power + if type(Power) ~= "number" and IsValid then + self:E({"Power is invalid. ", Power}) + IsValid = false + end + Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that + + if IsValid then + self:T2({"Activating Beacon on ", Frequency, Modulation}) + -- Note that this is looped. I have to give this transmission a unique name, I use the class ID + trigger.action.radioTransmission(FileName, self.Positionable:GetPositionVec3(), Modulation, true, Frequency, Power, tostring(self.ID)) + + if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD + SCHEDULER:New( nil, + function() + self:StopRadioBeacon() + end, {}, BeaconDuration) + end + end +end + +--- Stops the AA TACAN BEACON +-- @param #BEACON self +-- @return #BEACON self +function BEACON:StopRadioBeacon() + self:F() + -- The unique name of the transmission is the class ID + trigger.action.stopRadioTransmission(tostring(self.ID)) + return self +end + +--- Converts a TACAN Channel/Mode couple into a frequency in Hz +-- @param #BEACON self +-- @param #number TACANChannel +-- @param #string TACANMode +-- @return #number Frequecy +-- @return #nil if parameters are invalid +function BEACON:_TACANToFrequency(TACANChannel, TACANMode) + self:F3({TACANChannel, TACANMode}) + + if type(TACANChannel) ~= "number" then + if TACANMode ~= "X" and TACANMode ~= "Y" then + return nil -- error in arguments + end + end + +-- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137. +-- I have no idea what it does but it seems to work + local A = 1151 -- 'X', channel >= 64 + local B = 64 -- channel >= 64 + + if TACANChannel < 64 then + B = 1 + end + + if TACANMode == 'Y' then + A = 1025 + if TACANChannel < 64 then + A = 1088 + end + else -- 'X' + if TACANChannel < 64 then + A = 962 + end + end + + return (A + TACANChannel - B) * 1000000 +end \ No newline at end of file diff --git a/Moose Development/Moose/Core/Settings.lua b/Moose Development/Moose/Core/Settings.lua index 3a557de65..74b5fa54b 100644 --- a/Moose Development/Moose/Core/Settings.lua +++ b/Moose Development/Moose/Core/Settings.lua @@ -236,6 +236,7 @@ do -- SETTINGS --- SETTINGS constructor. -- @param #SETTINGS self + -- @param #string PlayerName (Optional) Set settings for this player. -- @return #SETTINGS function SETTINGS:Set( PlayerName ) diff --git a/Moose Development/Moose/Globals.lua b/Moose Development/Moose/Globals.lua index bdde44b6d..fdc6db2c7 100644 --- a/Moose Development/Moose/Globals.lua +++ b/Moose Development/Moose/Globals.lua @@ -1,5 +1,4 @@ --- The order of the declarations is important here. Don't touch it. - +--- GLOBALS: The order of the declarations is important here. Don't touch it. --- Declare the event dispatcher based on the EVENT class _EVENTDISPATCHER = EVENT:New() -- Core.Event#EVENT @@ -10,9 +9,38 @@ _SCHEDULEDISPATCHER = SCHEDULEDISPATCHER:New() -- Core.ScheduleDispatcher#SCHEDU --- Declare the main database object, which is used internally by the MOOSE classes. _DATABASE = DATABASE:New() -- Core.Database#DATABASE +--- Settings _SETTINGS = SETTINGS:Set() _SETTINGS:SetPlayerMenuOn() +--- Register cargos. _DATABASE:_RegisterCargos() + +--- Register zones. _DATABASE:_RegisterZones() +--- Check if os etc is available. +BASE:I("Checking de-sanitization of os, io and lfs (Check /Scripts/MissionScripting.lua and commend out sanitizeModule(''). Use at your own risk!)") + +local __na=false +if os then + BASE:I("- os available") +else + BASE:I("- os NOT available! Some functions may not work.") + __na=true +end +if io then + BASE:I("- io available") +else + BASE:I("- io NOT available! Some functions may not work.") + __na=true +end +if lfs then + BASE:I("- lfs available") +else + BASE:I("- lfs NOT available! Some functions may not work.") + __na=true +end +if __na then + BASE:I("Check /Scripts/MissionScripting.lua and commend out the lines with sanitizeModule(''). Use at your own risk!)") +end \ No newline at end of file diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 06f53d723..2ee24ee38 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -3,8 +3,10 @@ __Moose.Include( 'Scripts/Moose/Utilities/Routines.lua' ) __Moose.Include( 'Scripts/Moose/Utilities/Utils.lua' ) __Moose.Include( 'Scripts/Moose/Utilities/Profiler.lua' ) __Moose.Include( 'Scripts/Moose/Utilities/Templates.lua' ) +__Moose.Include( 'Scripts/Moose/Utilities/STTS.lua' ) __Moose.Include( 'Scripts/Moose/Core/Base.lua' ) +__Moose.Include( 'Scripts/Moose/Core/Beacon.lua' ) __Moose.Include( 'Scripts/Moose/Core/UserFlag.lua' ) __Moose.Include( 'Scripts/Moose/Core/Report.lua' ) __Moose.Include( 'Scripts/Moose/Core/Scheduler.lua' ) @@ -110,7 +112,7 @@ __Moose.Include( 'Scripts/Moose/Actions/Act_Account.lua' ) __Moose.Include( 'Scripts/Moose/Actions/Act_Assist.lua' ) __Moose.Include( 'Scripts/Moose/Sound/UserSound.lua' ) -__Moose.Include( 'Scripts/Moose/Sound/SoundFile.lua' ) +__Moose.Include( 'Scripts/Moose/Sound/SoundOutput.lua' ) __Moose.Include( 'Scripts/Moose/Sound/Radio.lua' ) __Moose.Include( 'Scripts/Moose/Sound/RadioQueue.lua' ) __Moose.Include( 'Scripts/Moose/Sound/RadioSpeech.lua' ) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 96353d11d..3870ba197 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -88,6 +88,8 @@ -- @field #boolean usemarker Use mark on the F10 map. -- @field #number markerid Numerical ID of the F10 map mark point. -- @field #number relHumidity Relative humidity (used to approximately calculate the dew point). +-- @field #boolean useSRS If true, use SRS for transmission. +-- @field Sound.SRS#MSRS msrs Moose SRS object. -- @extends Core.Fsm#FSM --- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde @@ -1100,6 +1102,22 @@ function ATIS:MarkRunways(markall) end end +--- Use SRS Simple-Text-To-Speech for transmissions. No sound files necessary. +-- @param #ATIS self +-- @param #string PathToSRS Path to SRS directory. +-- @param #string Gender Gender: "male" or "female" (default). +-- @param #string Culture Culture, e.g. "en-GB" (default). +-- @param #string Voice Specific voice. Overrides `Gender` and `Culture`. +-- @return #ATIS self +function ATIS:SetSTTS(PathToSRS, Gender, Culture, Voice, Port) + self.useSRS=true + self.msrs=MSRS:New(PathToSRS, self.frequency, self.modulation) + self.msrs:SetGender(Gender) + self.msrs:SetCulture(Culture) + self.msrs:SetVoice(Voice) + return self +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Start & Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1188,15 +1206,25 @@ end -- @param #string To To state. function ATIS:onafterCheckQueue(From, Event, To) - if #self.radioqueue.queue==0 then - self:T(self.lid..string.format("Radio queue empty. Repeating message.")) + if self.useSRS then + self:Broadcast() + + self:__CheckQueue(-120) + else - self:T2(self.lid..string.format("Radio queue %d transmissions queued.", #self.radioqueue.queue)) + + if #self.radioqueue.queue==0 then + self:T(self.lid..string.format("Radio queue empty. Repeating message.")) + self:Broadcast() + else + self:T2(self.lid..string.format("Radio queue %d transmissions queued.", #self.radioqueue.queue)) + end + + -- Check back in 5 seconds. + self:__CheckQueue(-5) + end - - -- Check back in 5 seconds. - self:__CheckQueue(-5) end --- Broadcast ATIS radio message. @@ -1591,36 +1619,46 @@ function ATIS:onafterBroadcast(From, Event, To) if self.airbasename:find("AFB")==nil and self.airbasename:find("Airport")==nil and self.airbasename:find("Airstrip")==nil and self.airbasename:find("airfield")==nil and self.airbasename:find("AB")==nil then subtitle=subtitle.." Airport" end - self.radioqueue:NewTransmission(string.format("%s/%s.ogg", self.theatre, self.airbasename), 3.0, self.soundpath, nil, nil, subtitle, self.subduration) + if not self.useSRS then + self.radioqueue:NewTransmission(string.format("%s/%s.ogg", self.theatre, self.airbasename), 3.0, self.soundpath, nil, nil, subtitle, self.subduration) + end local alltext=subtitle -- Information tag subtitle=string.format("Information %s", NATO) local _INFORMATION=subtitle - self:Transmission(ATIS.Sound.Information, 0.5, subtitle) - self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg", NATO), 0.75, self.soundpath) + if not self.useSRS then + self:Transmission(ATIS.Sound.Information, 0.5, subtitle) + self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg", NATO), 0.75, self.soundpath) + end alltext=alltext..";\n"..subtitle -- Zulu Time subtitle=string.format("%s Zulu", ZULU) - self.radioqueue:Number2Transmission(ZULU, nil, 0.5) - self:Transmission(ATIS.Sound.Zulu, 0.2, subtitle) + if not self.useSRS then + self.radioqueue:Number2Transmission(ZULU, nil, 0.5) + self:Transmission(ATIS.Sound.Zulu, 0.2, subtitle) + end alltext=alltext..";\n"..subtitle if not self.zulutimeonly then -- Sunrise Time subtitle=string.format("Sunrise at %s local time", SUNRISE) - self:Transmission(ATIS.Sound.SunriseAt, 0.5, subtitle) - self.radioqueue:Number2Transmission(SUNRISE, nil, 0.2) - self:Transmission(ATIS.Sound.TimeLocal, 0.2) + if not self.useSRS then + self:Transmission(ATIS.Sound.SunriseAt, 0.5, subtitle) + self.radioqueue:Number2Transmission(SUNRISE, nil, 0.2) + self:Transmission(ATIS.Sound.TimeLocal, 0.2) + end alltext=alltext..";\n"..subtitle -- Sunset Time subtitle=string.format("Sunset at %s local time", SUNSET) - self:Transmission(ATIS.Sound.SunsetAt, 0.5, subtitle) - self.radioqueue:Number2Transmission(SUNSET, nil, 0.5) - self:Transmission(ATIS.Sound.TimeLocal, 0.2) + if not self.useSRS then + self:Transmission(ATIS.Sound.SunsetAt, 0.5, subtitle) + self.radioqueue:Number2Transmission(SUNSET, nil, 0.5) + self:Transmission(ATIS.Sound.TimeLocal, 0.2) + end alltext=alltext..";\n"..subtitle end @@ -1634,17 +1672,19 @@ function ATIS:onafterBroadcast(From, Event, To) subtitle=subtitle..", gusting" end local _WIND=subtitle - self:Transmission(ATIS.Sound.WindFrom, 1.0, subtitle) - self.radioqueue:Number2Transmission(WINDFROM) - self:Transmission(ATIS.Sound.At, 0.2) - self.radioqueue:Number2Transmission(WINDSPEED) - if self.metric then - self:Transmission(ATIS.Sound.MetersPerSecond, 0.2) - else - self:Transmission(ATIS.Sound.Knots, 0.2) - end - if turbulence>0 then - self:Transmission(ATIS.Sound.Gusting, 0.2) + if not self.useSRS then + self:Transmission(ATIS.Sound.WindFrom, 1.0, subtitle) + self.radioqueue:Number2Transmission(WINDFROM) + self:Transmission(ATIS.Sound.At, 0.2) + self.radioqueue:Number2Transmission(WINDSPEED) + if self.metric then + self:Transmission(ATIS.Sound.MetersPerSecond, 0.2) + else + self:Transmission(ATIS.Sound.Knots, 0.2) + end + if turbulence>0 then + self:Transmission(ATIS.Sound.Gusting, 0.2) + end end alltext=alltext..";\n"..subtitle @@ -1654,12 +1694,14 @@ function ATIS:onafterBroadcast(From, Event, To) else subtitle=string.format("Visibility %s SM", VISIBILITY) end - self:Transmission(ATIS.Sound.Visibilty, 1.0, subtitle) - self.radioqueue:Number2Transmission(VISIBILITY) - if self.metric then - self:Transmission(ATIS.Sound.Kilometers, 0.2) - else - self:Transmission(ATIS.Sound.StatuteMiles, 0.2) + if not self.useSRS then + self:Transmission(ATIS.Sound.Visibilty, 1.0, subtitle) + self.radioqueue:Number2Transmission(VISIBILITY) + if self.metric then + self:Transmission(ATIS.Sound.Kilometers, 0.2) + else + self:Transmission(ATIS.Sound.StatuteMiles, 0.2) + end end alltext=alltext..";\n"..subtitle @@ -1699,57 +1741,63 @@ function ATIS:onafterBroadcast(From, Event, To) -- Actual output if wp then subtitle=string.format("Weather phenomena:%s", wpsub) - self:Transmission(ATIS.Sound.WeatherPhenomena, 1.0, subtitle) - if precepitation==1 then - self:Transmission(ATIS.Sound.Rain, 0.5) - elseif precepitation==2 then - self:Transmission(ATIS.Sound.ThunderStorm, 0.5) - elseif precepitation==3 then - self:Transmission(ATIS.Sound.Snow, 0.5) - elseif precepitation==4 then - self:Transmission(ATIS.Sound.SnowStorm, 0.5) - end - if fog then - self:Transmission(ATIS.Sound.Fog, 0.5) - end - if dust then - self:Transmission(ATIS.Sound.Dust, 0.5) + if not self.useSRS then + self:Transmission(ATIS.Sound.WeatherPhenomena, 1.0, subtitle) + if precepitation==1 then + self:Transmission(ATIS.Sound.Rain, 0.5) + elseif precepitation==2 then + self:Transmission(ATIS.Sound.ThunderStorm, 0.5) + elseif precepitation==3 then + self:Transmission(ATIS.Sound.Snow, 0.5) + elseif precepitation==4 then + self:Transmission(ATIS.Sound.SnowStorm, 0.5) + end + if fog then + self:Transmission(ATIS.Sound.Fog, 0.5) + end + if dust then + self:Transmission(ATIS.Sound.Dust, 0.5) + end end alltext=alltext..";\n"..subtitle end -- Cloud base - self:Transmission(CloudCover, 1.0, CLOUDSsub) + if not self.useSRS then + self:Transmission(CloudCover, 1.0, CLOUDSsub) + end if CLOUDBASE and static then -- Base if self.metric then - subtitle=string.format("Cloudbase %s, ceiling %s meters", CLOUDBASE, CLOUDCEIL) + subtitle=string.format("Cloud base %s, ceiling %s meters", CLOUDBASE, CLOUDCEIL) else - subtitle=string.format("Cloudbase %s, ceiling %s ft", CLOUDBASE, CLOUDCEIL) + subtitle=string.format("Cloud base %s, ceiling %s feet", CLOUDBASE, CLOUDCEIL) end - self:Transmission(ATIS.Sound.CloudBase, 1.0, subtitle) - if tonumber(CLOUDBASE1000)>0 then - self.radioqueue:Number2Transmission(CLOUDBASE1000) - self:Transmission(ATIS.Sound.Thousand, 0.1) - end - if tonumber(CLOUDBASE0100)>0 then - self.radioqueue:Number2Transmission(CLOUDBASE0100) - self:Transmission(ATIS.Sound.Hundred, 0.1) - end - -- Ceiling - self:Transmission(ATIS.Sound.CloudCeiling, 0.5) - if tonumber(CLOUDCEIL1000)>0 then - self.radioqueue:Number2Transmission(CLOUDCEIL1000) - self:Transmission(ATIS.Sound.Thousand, 0.1) - end - if tonumber(CLOUDCEIL0100)>0 then - self.radioqueue:Number2Transmission(CLOUDCEIL0100) - self:Transmission(ATIS.Sound.Hundred, 0.1) - end - if self.metric then - self:Transmission(ATIS.Sound.Meters, 0.1) - else - self:Transmission(ATIS.Sound.Feet, 0.1) + if not self.useSRS then + self:Transmission(ATIS.Sound.CloudBase, 1.0, subtitle) + if tonumber(CLOUDBASE1000)>0 then + self.radioqueue:Number2Transmission(CLOUDBASE1000) + self:Transmission(ATIS.Sound.Thousand, 0.1) + end + if tonumber(CLOUDBASE0100)>0 then + self.radioqueue:Number2Transmission(CLOUDBASE0100) + self:Transmission(ATIS.Sound.Hundred, 0.1) + end + -- Ceiling + self:Transmission(ATIS.Sound.CloudCeiling, 0.5) + if tonumber(CLOUDCEIL1000)>0 then + self.radioqueue:Number2Transmission(CLOUDCEIL1000) + self:Transmission(ATIS.Sound.Thousand, 0.1) + end + if tonumber(CLOUDCEIL0100)>0 then + self.radioqueue:Number2Transmission(CLOUDCEIL0100) + self:Transmission(ATIS.Sound.Hundred, 0.1) + end + if self.metric then + self:Transmission(ATIS.Sound.Meters, 0.1) + else + self:Transmission(ATIS.Sound.Feet, 0.1) + end end end alltext=alltext..";\n"..subtitle @@ -1769,15 +1817,17 @@ function ATIS:onafterBroadcast(From, Event, To) end end local _TEMPERATURE=subtitle - self:Transmission(ATIS.Sound.Temperature, 1.0, subtitle) - if temperature<0 then - self:Transmission(ATIS.Sound.Minus, 0.2) - end - self.radioqueue:Number2Transmission(TEMPERATURE) - if self.TDegF then - self:Transmission(ATIS.Sound.DegreesFahrenheit, 0.2) - else - self:Transmission(ATIS.Sound.DegreesCelsius, 0.2) + if not self.useSRS then + self:Transmission(ATIS.Sound.Temperature, 1.0, subtitle) + if temperature<0 then + self:Transmission(ATIS.Sound.Minus, 0.2) + end + self.radioqueue:Number2Transmission(TEMPERATURE) + if self.TDegF then + self:Transmission(ATIS.Sound.DegreesFahrenheit, 0.2) + else + self:Transmission(ATIS.Sound.DegreesCelsius, 0.2) + end end alltext=alltext..";\n"..subtitle @@ -1796,15 +1846,17 @@ function ATIS:onafterBroadcast(From, Event, To) end end local _DEWPOINT=subtitle - self:Transmission(ATIS.Sound.DewPoint, 1.0, subtitle) - if dewpoint<0 then - self:Transmission(ATIS.Sound.Minus, 0.2) - end - self.radioqueue:Number2Transmission(DEWPOINT) - if self.TDegF then - self:Transmission(ATIS.Sound.DegreesFahrenheit, 0.2) - else - self:Transmission(ATIS.Sound.DegreesCelsius, 0.2) + if not self.useSRS then + self:Transmission(ATIS.Sound.DewPoint, 1.0, subtitle) + if dewpoint<0 then + self:Transmission(ATIS.Sound.Minus, 0.2) + end + self.radioqueue:Number2Transmission(DEWPOINT) + if self.TDegF then + self:Transmission(ATIS.Sound.DegreesFahrenheit, 0.2) + else + self:Transmission(ATIS.Sound.DegreesCelsius, 0.2) + end end alltext=alltext..";\n"..subtitle @@ -1813,51 +1865,53 @@ function ATIS:onafterBroadcast(From, Event, To) if self.qnhonly then subtitle=string.format("Altimeter %s.%s mmHg", QNH[1], QNH[2]) else - subtitle=string.format("Altimeter QNH %s.%s, QFE %s.%s mmHg", QNH[1], QNH[2], QFE[1], QFE[2]) + subtitle=string.format("Altimeter: QNH %s.%s, QFE %s.%s mmHg", QNH[1], QNH[2], QFE[1], QFE[2]) end else if self.metric then if self.qnhonly then subtitle=string.format("Altimeter %s.%s hPa", QNH[1], QNH[2]) else - subtitle=string.format("Altimeter QNH %s.%s, QFE %s.%s hPa", QNH[1], QNH[2], QFE[1], QFE[2]) + subtitle=string.format("Altimeter: QNH %s.%s, QFE %s.%s hPa", QNH[1], QNH[2], QFE[1], QFE[2]) end else if self.qnhonly then subtitle=string.format("Altimeter %s.%s inHg", QNH[1], QNH[2]) else - subtitle=string.format("Altimeter QNH %s.%s, QFE %s.%s inHg", QNH[1], QNH[2], QFE[1], QFE[2]) + subtitle=string.format("Altimeter: QNH %s.%s, QFE %s.%s inHg", QNH[1], QNH[2], QFE[1], QFE[2]) end end end local _ALTIMETER=subtitle - self:Transmission(ATIS.Sound.Altimeter, 1.0, subtitle) - if not self.qnhonly then - self:Transmission(ATIS.Sound.QNH, 0.5) - end - self.radioqueue:Number2Transmission(QNH[1]) - - if ATIS.ICAOPhraseology[UTILS.GetDCSMap()] then - self:Transmission(ATIS.Sound.Decimal, 0.2) - end - self.radioqueue:Number2Transmission(QNH[2]) - - if not self.qnhonly then - self:Transmission(ATIS.Sound.QFE, 0.75) - self.radioqueue:Number2Transmission(QFE[1]) - if ATIS.ICAOPhraseology[UTILS.GetDCSMap()] then - self:Transmission(ATIS.Sound.Decimal, 0.2) + if not self.useSRS then + self:Transmission(ATIS.Sound.Altimeter, 1.0, subtitle) + if not self.qnhonly then + self:Transmission(ATIS.Sound.QNH, 0.5) end - self.radioqueue:Number2Transmission(QFE[2]) - end + self.radioqueue:Number2Transmission(QNH[1]) + + if ATIS.ICAOPhraseology[UTILS.GetDCSMap()] then + self:Transmission(ATIS.Sound.Decimal, 0.2) + end + self.radioqueue:Number2Transmission(QNH[2]) - if self.PmmHg then - self:Transmission(ATIS.Sound.MillimetersOfMercury, 0.1) - else - if self.metric then - self:Transmission(ATIS.Sound.HectoPascal, 0.1) + if not self.qnhonly then + self:Transmission(ATIS.Sound.QFE, 0.75) + self.radioqueue:Number2Transmission(QFE[1]) + if ATIS.ICAOPhraseology[UTILS.GetDCSMap()] then + self:Transmission(ATIS.Sound.Decimal, 0.2) + end + self.radioqueue:Number2Transmission(QFE[2]) + end + + if self.PmmHg then + self:Transmission(ATIS.Sound.MillimetersOfMercury, 0.1) else - self:Transmission(ATIS.Sound.InchesOfMercury, 0.1) + if self.metric then + self:Transmission(ATIS.Sound.HectoPascal, 0.1) + else + self:Transmission(ATIS.Sound.InchesOfMercury, 0.1) + end end end alltext=alltext..";\n"..subtitle @@ -1870,12 +1924,14 @@ function ATIS:onafterBroadcast(From, Event, To) subtitle=subtitle.." Right" end local _RUNACT=subtitle - self:Transmission(ATIS.Sound.ActiveRunway, 1.0, subtitle) - self.radioqueue:Number2Transmission(runway) - if rwyLeft==true then - self:Transmission(ATIS.Sound.Left, 0.2) - elseif rwyLeft==false then - self:Transmission(ATIS.Sound.Right, 0.2) + if not self.useSRS then + self:Transmission(ATIS.Sound.ActiveRunway, 1.0, subtitle) + self.radioqueue:Number2Transmission(runway) + if rwyLeft==true then + self:Transmission(ATIS.Sound.Left, 0.2) + elseif rwyLeft==false then + self:Transmission(ATIS.Sound.Right, 0.2) + end end alltext=alltext..";\n"..subtitle @@ -1900,21 +1956,22 @@ function ATIS:onafterBroadcast(From, Event, To) end -- Transmit. - self:Transmission(ATIS.Sound.RunwayLength, 1.0, subtitle) - if tonumber(L1000)>0 then - self.radioqueue:Number2Transmission(L1000) - self:Transmission(ATIS.Sound.Thousand, 0.1) + if not self.useSRS then + self:Transmission(ATIS.Sound.RunwayLength, 1.0, subtitle) + if tonumber(L1000)>0 then + self.radioqueue:Number2Transmission(L1000) + self:Transmission(ATIS.Sound.Thousand, 0.1) + end + if tonumber(L0100)>0 then + self.radioqueue:Number2Transmission(L0100) + self:Transmission(ATIS.Sound.Hundred, 0.1) + end + if self.metric then + self:Transmission(ATIS.Sound.Meters, 0.1) + else + self:Transmission(ATIS.Sound.Feet, 0.1) + end end - if tonumber(L0100)>0 then - self.radioqueue:Number2Transmission(L0100) - self:Transmission(ATIS.Sound.Hundred, 0.1) - end - if self.metric then - self:Transmission(ATIS.Sound.Meters, 0.1) - else - self:Transmission(ATIS.Sound.Feet, 0.1) - end - alltext=alltext..";\n"..subtitle end @@ -1937,22 +1994,23 @@ function ATIS:onafterBroadcast(From, Event, To) subtitle=subtitle.." feet" end - -- Transmitt. - self:Transmission(ATIS.Sound.Elevation, 1.0, subtitle) - if tonumber(L1000)>0 then - self.radioqueue:Number2Transmission(L1000) - self:Transmission(ATIS.Sound.Thousand, 0.1) + -- Transmit. + if not self.useSRS then + self:Transmission(ATIS.Sound.Elevation, 1.0, subtitle) + if tonumber(L1000)>0 then + self.radioqueue:Number2Transmission(L1000) + self:Transmission(ATIS.Sound.Thousand, 0.1) + end + if tonumber(L0100)>0 then + self.radioqueue:Number2Transmission(L0100) + self:Transmission(ATIS.Sound.Hundred, 0.1) + end + if self.metric then + self:Transmission(ATIS.Sound.Meters, 0.1) + else + self:Transmission(ATIS.Sound.Feet, 0.1) + end end - if tonumber(L0100)>0 then - self.radioqueue:Number2Transmission(L0100) - self:Transmission(ATIS.Sound.Hundred, 0.1) - end - if self.metric then - self:Transmission(ATIS.Sound.Meters, 0.1) - else - self:Transmission(ATIS.Sound.Feet, 0.1) - end - alltext=alltext..";\n"..subtitle end @@ -1966,9 +2024,47 @@ function ATIS:onafterBroadcast(From, Event, To) end end subtitle=string.format("Tower frequency %s", freqs) - self:Transmission(ATIS.Sound.TowerFrequency, 1.0, subtitle) - for _,freq in pairs(self.towerfrequency) do - local f=string.format("%.3f", freq) + if not self.useSRS then + self:Transmission(ATIS.Sound.TowerFrequency, 1.0, subtitle) + for _,freq in pairs(self.towerfrequency) do + local f=string.format("%.3f", freq) + f=UTILS.Split(f, ".") + self.radioqueue:Number2Transmission(f[1], nil, 0.5) + if tonumber(f[2])>0 then + self:Transmission(ATIS.Sound.Decimal, 0.2) + self.radioqueue:Number2Transmission(f[2]) + end + self:Transmission(ATIS.Sound.MegaHertz, 0.2) + end + end + alltext=alltext..";\n"..subtitle + end + + -- ILS + local ils=self:GetNavPoint(self.ils, runway, rwyLeft) + if ils then + subtitle=string.format("ILS frequency %.2f MHz", ils.frequency) + if not self.useSRS then + self:Transmission(ATIS.Sound.ILSFrequency, 1.0, subtitle) + local f=string.format("%.2f", ils.frequency) + f=UTILS.Split(f, ".") + self.radioqueue:Number2Transmission(f[1], nil, 0.5) + if tonumber(f[2])>0 then + self:Transmission(ATIS.Sound.Decimal, 0.2) + self.radioqueue:Number2Transmission(f[2]) + end + self:Transmission(ATIS.Sound.MegaHertz, 0.2) + end + alltext=alltext..";\n"..subtitle + end + + -- Outer NDB + local ndb=self:GetNavPoint(self.ndbouter, runway, rwyLeft) + if ndb then + subtitle=string.format("Outer NDB frequency %.2f MHz", ndb.frequency) + if not self.useSRS then + self:Transmission(ATIS.Sound.OuterNDBFrequency, 1.0, subtitle) + local f=string.format("%.2f", ndb.frequency) f=UTILS.Split(f, ".") self.radioqueue:Number2Transmission(f[1], nil, 0.5) if tonumber(f[2])>0 then @@ -1977,41 +2073,6 @@ function ATIS:onafterBroadcast(From, Event, To) end self:Transmission(ATIS.Sound.MegaHertz, 0.2) end - - alltext=alltext..";\n"..subtitle - end - - -- ILS - local ils=self:GetNavPoint(self.ils, runway, rwyLeft) - if ils then - subtitle=string.format("ILS frequency %.2f MHz", ils.frequency) - self:Transmission(ATIS.Sound.ILSFrequency, 1.0, subtitle) - local f=string.format("%.2f", ils.frequency) - f=UTILS.Split(f, ".") - self.radioqueue:Number2Transmission(f[1], nil, 0.5) - if tonumber(f[2])>0 then - self:Transmission(ATIS.Sound.Decimal, 0.2) - self.radioqueue:Number2Transmission(f[2]) - end - self:Transmission(ATIS.Sound.MegaHertz, 0.2) - - alltext=alltext..";\n"..subtitle - end - - -- Outer NDB - local ndb=self:GetNavPoint(self.ndbouter, runway, rwyLeft) - if ndb then - subtitle=string.format("Outer NDB frequency %.2f MHz", ndb.frequency) - self:Transmission(ATIS.Sound.OuterNDBFrequency, 1.0, subtitle) - local f=string.format("%.2f", ndb.frequency) - f=UTILS.Split(f, ".") - self.radioqueue:Number2Transmission(f[1], nil, 0.5) - if tonumber(f[2])>0 then - self:Transmission(ATIS.Sound.Decimal, 0.2) - self.radioqueue:Number2Transmission(f[2]) - end - self:Transmission(ATIS.Sound.MegaHertz, 0.2) - alltext=alltext..";\n"..subtitle end @@ -2019,51 +2080,55 @@ function ATIS:onafterBroadcast(From, Event, To) local ndb=self:GetNavPoint(self.ndbinner, runway, rwyLeft) if ndb then subtitle=string.format("Inner NDB frequency %.2f MHz", ndb.frequency) - self:Transmission(ATIS.Sound.InnerNDBFrequency, 1.0, subtitle) - local f=string.format("%.2f", ndb.frequency) - f=UTILS.Split(f, ".") - self.radioqueue:Number2Transmission(f[1], nil, 0.5) - if tonumber(f[2])>0 then - self:Transmission(ATIS.Sound.Decimal, 0.2) - self.radioqueue:Number2Transmission(f[2]) - end - self:Transmission(ATIS.Sound.MegaHertz, 0.2) - + if not self.useSRS then + self:Transmission(ATIS.Sound.InnerNDBFrequency, 1.0, subtitle) + local f=string.format("%.2f", ndb.frequency) + f=UTILS.Split(f, ".") + self.radioqueue:Number2Transmission(f[1], nil, 0.5) + if tonumber(f[2])>0 then + self:Transmission(ATIS.Sound.Decimal, 0.2) + self.radioqueue:Number2Transmission(f[2]) + end + self:Transmission(ATIS.Sound.MegaHertz, 0.2) + end alltext=alltext..";\n"..subtitle end -- VOR if self.vor then subtitle=string.format("VOR frequency %.2f MHz", self.vor) - self:Transmission(ATIS.Sound.VORFrequency, 1.0, subtitle) - local f=string.format("%.2f", self.vor) - f=UTILS.Split(f, ".") - self.radioqueue:Number2Transmission(f[1], nil, 0.5) - if tonumber(f[2])>0 then - self:Transmission(ATIS.Sound.Decimal, 0.2) - self.radioqueue:Number2Transmission(f[2]) + if not self.useSRS then + self:Transmission(ATIS.Sound.VORFrequency, 1.0, subtitle) + local f=string.format("%.2f", self.vor) + f=UTILS.Split(f, ".") + self.radioqueue:Number2Transmission(f[1], nil, 0.5) + if tonumber(f[2])>0 then + self:Transmission(ATIS.Sound.Decimal, 0.2) + self.radioqueue:Number2Transmission(f[2]) + end + self:Transmission(ATIS.Sound.MegaHertz, 0.2) end - self:Transmission(ATIS.Sound.MegaHertz, 0.2) - alltext=alltext..";\n"..subtitle end -- TACAN if self.tacan then subtitle=string.format("TACAN channel %dX", self.tacan) - self:Transmission(ATIS.Sound.TACANChannel, 1.0, subtitle) - self.radioqueue:Number2Transmission(tostring(self.tacan), nil, 0.2) - self.radioqueue:NewTransmission("NATO Alphabet/Xray.ogg", 0.75, self.soundpath, nil, 0.2) - + if not self.useSRS then + self:Transmission(ATIS.Sound.TACANChannel, 1.0, subtitle) + self.radioqueue:Number2Transmission(tostring(self.tacan), nil, 0.2) + self.radioqueue:NewTransmission("NATO Alphabet/Xray.ogg", 0.75, self.soundpath, nil, 0.2) + end alltext=alltext..";\n"..subtitle end -- RSBN if self.rsbn then subtitle=string.format("RSBN channel %d", self.rsbn) - self:Transmission(ATIS.Sound.RSBNChannel, 1.0, subtitle) - self.radioqueue:Number2Transmission(tostring(self.rsbn), nil, 0.2) - + if not self.useSRS then + self:Transmission(ATIS.Sound.RSBNChannel, 1.0, subtitle) + self.radioqueue:Number2Transmission(tostring(self.rsbn), nil, 0.2) + end alltext=alltext..";\n"..subtitle end @@ -2071,17 +2136,19 @@ function ATIS:onafterBroadcast(From, Event, To) local ndb=self:GetNavPoint(self.prmg, runway, rwyLeft) if ndb then subtitle=string.format("PRMG channel %d", ndb.frequency) - self:Transmission(ATIS.Sound.PRMGChannel, 1.0, subtitle) - self.radioqueue:Number2Transmission(tostring(ndb.frequency), nil, 0.5) - + if not self.useSRS then + self:Transmission(ATIS.Sound.PRMGChannel, 1.0, subtitle) + self.radioqueue:Number2Transmission(tostring(ndb.frequency), nil, 0.5) + end alltext=alltext..";\n"..subtitle end -- Advice on initial... subtitle=string.format("Advise on initial contact, you have information %s", NATO) - self:Transmission(ATIS.Sound.AdviceOnInitial, 0.5, subtitle) - self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg", NATO), 0.75, self.soundpath) - + if not self.useSRS then + self:Transmission(ATIS.Sound.AdviceOnInitial, 0.5, subtitle) + self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg", NATO), 0.75, self.soundpath) + end alltext=alltext..";\n"..subtitle -- Report ATIS text. @@ -2103,12 +2170,31 @@ end function ATIS:onafterReport(From, Event, To, Text) self:T(self.lid..string.format("Report:\n%s", Text)) - -- Remove line breaks - local text=string.gsub(Text, "[\r\n]", "") - env.info("FF: "..text) + if self.useSRS and self.msrs then - local msrs=MSRS:New("D:\\DCS\\_SRS\\", 305, Modulation) - msrs:PlayText(text) + -- Remove line breaks + local text=string.gsub(Text, "[\r\n]", "") + + -- Replace other stuff. + local text=string.gsub(text, "SM", "statute miles") + local text=string.gsub(text, "°C", "degrees Celsius") + local text=string.gsub(text, "°F", "degrees Fahrenheit") + local text=string.gsub(text, "inHg", "inches of Mercury") + local text=string.gsub(text, "mmHg", "millimeters of Mercury") + local text=string.gsub(text, "hPa", "hecto Pascals") + local text=string.gsub(text, "m/s", "meters per second") + + -- Replace ";" by "." + local text=string.gsub(text, ";", ". ") + env.info("FF: "..text) + + --local msrs=MSRS:New("D:\\DCS\\_SRS\\", 305, Modulation) + --msrs:PlayText(text) + + -- Play text-to-speech report. + self.msrs:PlayText(text) + + end end diff --git a/Moose Development/Moose/Sound/Radio.lua b/Moose Development/Moose/Sound/Radio.lua index 175bc2106..872ad6e26 100644 --- a/Moose Development/Moose/Sound/Radio.lua +++ b/Moose Development/Moose/Sound/Radio.lua @@ -1,13 +1,10 @@ ---- **Core** - Is responsible for everything that is related to radio transmission and you can hear in DCS, be it TACAN beacons, Radio transmissions. +--- **Sound** - Radio transmissions. -- -- === -- -- ## Features: -- -- * Provide radio functionality to broadcast radio transmissions. --- * Provide beacon functionality to assist pilots. --- --- The Radio contains 2 classes : RADIO and BEACON -- -- What are radio communications in DCS? -- @@ -35,13 +32,13 @@ -- -- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky -- --- @module Core.Radio +-- @module Sound.Radio -- @image Core_Radio.JPG ---- Models the radio capability. +--- *It's not true I had nothing on, I had the radio on.* -- Marilyn Monroe -- --- ## RADIO usage +-- # RADIO usage -- -- There are 3 steps to a successful radio transmission. -- @@ -87,15 +84,15 @@ -- @field #string alias Name of the radio transmitter. -- @extends Core.Base#BASE RADIO = { - ClassName = "RADIO", - FileName = "", - Frequency = 0, - Modulation = radio.modulation.AM, - Subtitle = "", + ClassName = "RADIO", + FileName = "", + Frequency = 0, + Modulation = radio.modulation.AM, + Subtitle = "", SubtitleDuration = 0, - Power = 100, - Loop = false, - alias=nil, + Power = 100, + Loop = false, + alias = nil, } --- Create a new RADIO Object. This doesn't broadcast a transmission, though, use @{#RADIO.Broadcast} to actually broadcast. @@ -395,438 +392,3 @@ function RADIO:StopBroadcast() end return self end - - ---- After attaching a @{#BEACON} to your @{Wrapper.Positionable#POSITIONABLE}, you need to select the right function to activate the kind of beacon you want. --- There are two types of BEACONs available : the AA TACAN Beacon and the general purpose Radio Beacon. --- Note that in both case, you can set an optional parameter : the `BeaconDuration`. This can be very usefull to simulate the battery time if your BEACON is --- attach to a cargo crate, for exemple. --- --- ## AA TACAN Beacon usage --- --- This beacon only works with airborne @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}. Use @{#BEACON:AATACAN}() to set the beacon parameters and start the beacon. --- Use @#BEACON:StopAATACAN}() to stop it. --- --- ## General Purpose Radio Beacon usage --- --- This beacon will work with any @{Wrapper.Positionable#POSITIONABLE}, but **it won't follow the @{Wrapper.Positionable#POSITIONABLE}** ! This means that you should only use it with --- @{Wrapper.Positionable#POSITIONABLE} that don't move, or move very slowly. Use @{#BEACON:RadioBeacon}() to set the beacon parameters and start the beacon. --- Use @{#BEACON:StopRadioBeacon}() to stop it. --- --- @type BEACON --- @field #string ClassName Name of the class "BEACON". --- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will receive radio capabilities. --- @extends Core.Base#BASE -BEACON = { - ClassName = "BEACON", - Positionable = nil, - name=nil, -} - ---- Beacon types supported by DCS. --- @type BEACON.Type --- @field #number NULL --- @field #number VOR --- @field #number DME --- @field #number VOR_DME --- @field #number TACAN TACtical Air Navigation system. --- @field #number VORTAC --- @field #number RSBN --- @field #number BROADCAST_STATION --- @field #number HOMER --- @field #number AIRPORT_HOMER --- @field #number AIRPORT_HOMER_WITH_MARKER --- @field #number ILS_FAR_HOMER --- @field #number ILS_NEAR_HOMER --- @field #number ILS_LOCALIZER --- @field #number ILS_GLIDESLOPE --- @field #number PRMG_LOCALIZER --- @field #number PRMG_GLIDESLOPE --- @field #number ICLS Same as ICLS glideslope. --- @field #number ICLS_LOCALIZER --- @field #number ICLS_GLIDESLOPE --- @field #number NAUTICAL_HOMER -BEACON.Type={ - NULL = 0, - VOR = 1, - DME = 2, - VOR_DME = 3, - TACAN = 4, - VORTAC = 5, - RSBN = 128, - BROADCAST_STATION = 1024, - HOMER = 8, - AIRPORT_HOMER = 4104, - AIRPORT_HOMER_WITH_MARKER = 4136, - ILS_FAR_HOMER = 16408, - ILS_NEAR_HOMER = 16424, - ILS_LOCALIZER = 16640, - ILS_GLIDESLOPE = 16896, - PRMG_LOCALIZER = 33024, - PRMG_GLIDESLOPE = 33280, - ICLS = 131584, --leaving this in here but it is the same as ICLS_GLIDESLOPE - ICLS_LOCALIZER = 131328, - ICLS_GLIDESLOPE = 131584, - NAUTICAL_HOMER = 65536, - -} - ---- Beacon systems supported by DCS. https://wiki.hoggitworld.com/view/DCS_command_activateBeacon --- @type BEACON.System --- @field #number PAR_10 ? --- @field #number RSBN_5 Russian VOR/DME system. --- @field #number TACAN TACtical Air Navigation system on ground. --- @field #number TACAN_TANKER_X TACtical Air Navigation system for tankers on X band. --- @field #number TACAN_TANKER_Y TACtical Air Navigation system for tankers on Y band. --- @field #number VOR Very High Frequency Omni-Directional Range --- @field #number ILS_LOCALIZER ILS localizer --- @field #number ILS_GLIDESLOPE ILS glideslope. --- @field #number PRGM_LOCALIZER PRGM localizer. --- @field #number PRGM_GLIDESLOPE PRGM glideslope. --- @field #number BROADCAST_STATION Broadcast station. --- @field #number VORTAC Radio-based navigational aid for aircraft pilots consisting of a co-located VHF omnidirectional range (VOR) beacon and a tactical air navigation system (TACAN) beacon. --- @field #number TACAN_AA_MODE_X TACtical Air Navigation for aircraft on X band. --- @field #number TACAN_AA_MODE_Y TACtical Air Navigation for aircraft on Y band. --- @field #number VORDME Radio beacon that combines a VHF omnidirectional range (VOR) with a distance measuring equipment (DME). --- @field #number ICLS_LOCALIZER Carrier landing system. --- @field #number ICLS_GLIDESLOPE Carrier landing system. -BEACON.System={ - PAR_10 = 1, - RSBN_5 = 2, - TACAN = 3, - TACAN_TANKER_X = 4, - TACAN_TANKER_Y = 5, - VOR = 6, - ILS_LOCALIZER = 7, - ILS_GLIDESLOPE = 8, - PRMG_LOCALIZER = 9, - PRMG_GLIDESLOPE = 10, - BROADCAST_STATION = 11, - VORTAC = 12, - TACAN_AA_MODE_X = 13, - TACAN_AA_MODE_Y = 14, - VORDME = 15, - ICLS_LOCALIZER = 16, - ICLS_GLIDESLOPE = 17, -} - ---- Create a new BEACON Object. This doesn't activate the beacon, though, use @{#BEACON.ActivateTACAN} etc. --- If you want to create a BEACON, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetBeacon}() instead. --- @param #BEACON self --- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities. --- @return #BEACON Beacon object or #nil if the positionable is invalid. -function BEACON:New(Positionable) - - -- Inherit BASE. - local self=BASE:Inherit(self, BASE:New()) --#BEACON - - -- Debug. - self:F(Positionable) - - -- Set positionable. - if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid - self.Positionable = Positionable - self.name=Positionable:GetName() - self:I(string.format("New BEACON %s", tostring(self.name))) - return self - end - - self:E({"The passed positionable is invalid, no BEACON created", Positionable}) - return nil -end - - ---- Activates a TACAN BEACON. --- @param #BEACON self --- @param #number Channel TACAN channel, i.e. the "10" part in "10Y". --- @param #string Mode TACAN mode, i.e. the "Y" part in "10Y". --- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon. --- @param #boolean Bearing If true, beacon provides bearing information. If false (or nil), only distance information is available. --- @param #number Duration How long will the beacon last in seconds. Omit for forever. --- @return #BEACON self --- @usage --- -- Let's create a TACAN Beacon for a tanker --- local myUnit = UNIT:FindByName("MyUnit") --- local myBeacon = myUnit:GetBeacon() -- Creates the beacon --- --- myBeacon:ActivateTACAN(20, "Y", "TEXACO", true) -- Activate the beacon -function BEACON:ActivateTACAN(Channel, Mode, Message, Bearing, Duration) - self:T({channel=Channel, mode=Mode, callsign=Message, bearing=Bearing, duration=Duration}) - - -- Get frequency. - local Frequency=UTILS.TACANToFrequency(Channel, Mode) - - -- Check. - if not Frequency then - self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"}) - return self - end - - -- Beacon type. - local Type=BEACON.Type.TACAN - - -- Beacon system. - local System=BEACON.System.TACAN - - -- Check if unit is an aircraft and set system accordingly. - local AA=self.Positionable:IsAir() - if AA then - System=5 --NOTE: 5 is how you cat the correct tanker behaviour! --BEACON.System.TACAN_TANKER - -- Check if "Y" mode is selected for aircraft. - if Mode~="Y" then - self:E({"WARNING: The POSITIONABLE you want to attach the AA Tacan Beacon is an aircraft: Mode should Y !The BEACON is not emitting.", self.Positionable}) - end - end - - -- Attached unit. - local UnitID=self.Positionable:GetID() - - -- Debug. - self:I({string.format("BEACON Activating TACAN %s: Channel=%d%s, Morse=%s, Bearing=%s, Duration=%s!", tostring(self.name), Channel, Mode, Message, tostring(Bearing), tostring(Duration))}) - - -- Start beacon. - self.Positionable:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, Mode, AA, Message, Bearing) - - -- Stop sheduler. - if Duration then - self.Positionable:DeactivateBeacon(Duration) - end - - return self -end - ---- Activates an ICLS BEACON. The unit the BEACON is attached to should be an aircraft carrier supporting this system. --- @param #BEACON self --- @param #number Channel ICLS channel. --- @param #string Callsign The Message that is going to be coded in Morse and broadcasted by the beacon. --- @param #number Duration How long will the beacon last in seconds. Omit for forever. --- @return #BEACON self -function BEACON:ActivateICLS(Channel, Callsign, Duration) - self:F({Channel=Channel, Callsign=Callsign, Duration=Duration}) - - -- Attached unit. - local UnitID=self.Positionable:GetID() - - -- Debug - self:T2({"ICLS BEACON started!"}) - - -- Start beacon. - self.Positionable:CommandActivateICLS(Channel, UnitID, Callsign) - - -- Stop sheduler - if Duration then -- Schedule the stop of the BEACON if asked by the MD - self.Positionable:DeactivateBeacon(Duration) - end - - return self -end - - - - - - ---- Activates a TACAN BEACON on an Aircraft. --- @param #BEACON self --- @param #number TACANChannel (the "10" part in "10Y"). Note that AA TACAN are only available on Y Channels --- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon --- @param #boolean Bearing Can the BEACON be homed on ? --- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever. --- @return #BEACON self --- @usage --- -- Let's create a TACAN Beacon for a tanker --- local myUnit = UNIT:FindByName("MyUnit") --- local myBeacon = myUnit:GetBeacon() -- Creates the beacon --- --- myBeacon:AATACAN(20, "TEXACO", true) -- Activate the beacon -function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration) - self:F({TACANChannel, Message, Bearing, BeaconDuration}) - - local IsValid = true - - if not self.Positionable:IsAir() then - self:E({"The POSITIONABLE you want to attach the AA Tacan Beacon is not an aircraft ! The BEACON is not emitting", self.Positionable}) - IsValid = false - end - - local Frequency = self:_TACANToFrequency(TACANChannel, "Y") - if not Frequency then - self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"}) - IsValid = false - end - - -- I'm using the beacon type 4 (BEACON_TYPE_TACAN). For System, I'm using 5 (TACAN_TANKER_MODE_Y) if the bearing shows its bearing - -- or 14 (TACAN_AA_MODE_Y) if it does not - local System - if Bearing then - System = 5 - else - System = 14 - end - - if IsValid then -- Starts the BEACON - self:T2({"AA TACAN BEACON started !"}) - self.Positionable:SetCommand({ - id = "ActivateBeacon", - params = { - type = 4, - system = System, - callsign = Message, - frequency = Frequency, - } - }) - - if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD - SCHEDULER:New(nil, - function() - self:StopAATACAN() - end, {}, BeaconDuration) - end - end - - return self -end - ---- Stops the AA TACAN BEACON --- @param #BEACON self --- @return #BEACON self -function BEACON:StopAATACAN() - self:F() - if not self.Positionable then - self:E({"Start the beacon first before stoping it !"}) - else - self.Positionable:SetCommand({ - id = 'DeactivateBeacon', - params = { - } - }) - end -end - - ---- Activates a general pupose Radio Beacon --- This uses the very generic singleton function "trigger.action.radioTransmission()" provided by DCS to broadcast a sound file on a specific frequency. --- Although any frequency could be used, only 2 DCS Modules can home on radio beacons at the time of writing : the Huey and the Mi-8. --- They can home in on these specific frequencies : --- * **Mi8** --- * R-828 -> 20-60MHz --- * ARKUD -> 100-150MHz (canal 1 : 114166, canal 2 : 114333, canal 3 : 114583, canal 4 : 121500, canal 5 : 123100, canal 6 : 124100) AM --- * ARK9 -> 150-1300KHz --- * **Huey** --- * AN/ARC-131 -> 30-76 Mhz FM --- @param #BEACON self --- @param #string FileName The name of the audio file --- @param #number Frequency in MHz --- @param #number Modulation either radio.modulation.AM or radio.modulation.FM --- @param #number Power in W --- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever. --- @return #BEACON self --- @usage --- -- Let's create a beacon for a unit in distress. --- -- Frequency will be 40MHz FM (home-able by a Huey's AN/ARC-131) --- -- The beacon they use is battery-powered, and only lasts for 5 min --- local UnitInDistress = UNIT:FindByName("Unit1") --- local UnitBeacon = UnitInDistress:GetBeacon() --- --- -- Set the beacon and start it --- UnitBeacon:RadioBeacon("MySoundFileSOS.ogg", 40, radio.modulation.FM, 20, 5*60) -function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDuration) - self:F({FileName, Frequency, Modulation, Power, BeaconDuration}) - local IsValid = false - - -- Check the filename - if type(FileName) == "string" then - if FileName:find(".ogg") or FileName:find(".wav") then - if not FileName:find("l10n/DEFAULT/") then - FileName = "l10n/DEFAULT/" .. FileName - end - IsValid = true - end - end - if not IsValid then - self:E({"File name invalid. Maybe something wrong with the extension ? ", FileName}) - end - - -- Check the Frequency - if type(Frequency) ~= "number" and IsValid then - self:E({"Frequency invalid. ", Frequency}) - IsValid = false - end - Frequency = Frequency * 1000000 -- Conversion to Hz - - -- Check the modulation - if Modulation ~= radio.modulation.AM and Modulation ~= radio.modulation.FM and IsValid then --TODO Maybe make this future proof if ED decides to add an other modulation ? - self:E({"Modulation is invalid. Use DCS's enum radio.modulation.", Modulation}) - IsValid = false - end - - -- Check the Power - if type(Power) ~= "number" and IsValid then - self:E({"Power is invalid. ", Power}) - IsValid = false - end - Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that - - if IsValid then - self:T2({"Activating Beacon on ", Frequency, Modulation}) - -- Note that this is looped. I have to give this transmission a unique name, I use the class ID - trigger.action.radioTransmission(FileName, self.Positionable:GetPositionVec3(), Modulation, true, Frequency, Power, tostring(self.ID)) - - if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD - SCHEDULER:New( nil, - function() - self:StopRadioBeacon() - end, {}, BeaconDuration) - end - end -end - ---- Stops the AA TACAN BEACON --- @param #BEACON self --- @return #BEACON self -function BEACON:StopRadioBeacon() - self:F() - -- The unique name of the transmission is the class ID - trigger.action.stopRadioTransmission(tostring(self.ID)) - return self -end - ---- Converts a TACAN Channel/Mode couple into a frequency in Hz --- @param #BEACON self --- @param #number TACANChannel --- @param #string TACANMode --- @return #number Frequecy --- @return #nil if parameters are invalid -function BEACON:_TACANToFrequency(TACANChannel, TACANMode) - self:F3({TACANChannel, TACANMode}) - - if type(TACANChannel) ~= "number" then - if TACANMode ~= "X" and TACANMode ~= "Y" then - return nil -- error in arguments - end - end - --- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137. --- I have no idea what it does but it seems to work - local A = 1151 -- 'X', channel >= 64 - local B = 64 -- channel >= 64 - - if TACANChannel < 64 then - B = 1 - end - - if TACANMode == 'Y' then - A = 1025 - if TACANChannel < 64 then - A = 1088 - end - else -- 'X' - if TACANChannel < 64 then - A = 962 - end - end - - return (A + TACANChannel - B) * 1000000 -end - - diff --git a/Moose Development/Moose/Sound/RadioQueue.lua b/Moose Development/Moose/Sound/RadioQueue.lua index 1f5e9cd07..53207850f 100644 --- a/Moose Development/Moose/Sound/RadioQueue.lua +++ b/Moose Development/Moose/Sound/RadioQueue.lua @@ -77,7 +77,7 @@ RADIOQUEUE = { --- Create a new RADIOQUEUE object for a given radio frequency/modulation. -- @param #RADIOQUEUE self -- @param #number frequency The radio frequency in MHz. --- @param #number modulation (Optional) The radio modulation. Default radio.modulation.AM. +-- @param #number modulation (Optional) The radio modulation. Default `radio.modulation.AM` (=0). -- @param #string alias (Optional) Name of the radio queue. -- @return #RADIOQUEUE self The RADIOQUEUE object. function RADIOQUEUE:New(frequency, modulation, alias) @@ -245,8 +245,6 @@ end -- @return #RADIOQUEUE.Transmission Radio transmission table. function RADIOQUEUE:NewTransmission(filename, duration, path, tstart, interval, subtitle, subduration) - env.info("FF new transmission.") - -- Sanity checks. if not filename then self:E(self.lid.."ERROR: No filename specified.") @@ -288,7 +286,7 @@ end --- Create a new transmission and add it to the radio queue. -- @param #RADIOQUEUE self --- @param Sound.SoundFile#SOUNDFILE soundfile Sound file object to be added. +-- @param Sound.SoundOutput#SOUNDFILE soundfile Sound file object to be added. -- @param #number tstart Start time (abs) seconds. Default now. -- @param #number interval Interval in seconds after the last transmission finished. -- @return #RADIOQUEUE self diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index c4fc31c15..78cbaeaf2 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -71,7 +71,7 @@ MSRS = { modulations = {}, coalition = 0, gender = "female", - culture = "en-GB", + culture = nil, voice = nil, volume = 1, speed = 1, @@ -81,15 +81,19 @@ MSRS = { altitude = nil, } +--- Counter. +_MSRSuuid=0 + --- MSRS class version. -- @field #string version -MSRS.version="0.0.2" +MSRS.version="0.0.3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: A lot. +-- TODO: Add functions to add/remove freqs and modulations. +-- TODO: Add coordinate. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -144,7 +148,7 @@ function MSRS:SetPath(Path) n=n+1 end - self.path=self.path.."/" + self.path=self.path --.."/" self:I(string.format("SRS path=%s", self:GetPath())) @@ -225,16 +229,40 @@ end -- @param #string Gender Gender: "male" or "female" (default). -- @return #MSRS self function MSRS:SetGender(Gender) - + self:I("Input gender to "..tostring(Gender)) + Gender=Gender or "female" - Gender=Gender:lower() + self.gender=Gender:lower() - self.gender=Gender + self:I("Setting gender to "..tostring(self.gender)) return self end +--- Set culture. +-- @param #MSRS self +-- @param #string Culture Culture, e.g. "en-GB" (default). +-- @return #MSRS self +function MSRS:SetCulture(Culture) + + self.culture=Culture + + return self +end + +--- Set to use a specific voice. Will override gender and culture settings. +-- @param #MSRS self +-- @param #string Voice Voice. +-- @return #MSRS self +function MSRS:SetVoice(Voice) + + self.voice=Voice + + return self +end + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Transmission Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -250,16 +278,20 @@ function MSRS:PlaySoundFile(Soundfile, Delay) self:ScheduleOnce(Delay, MSRS.PlaySoundFile, self, Soundfile, 0) else + -- Sound file name. local soundfile=Soundfile:GetName() + -- Get command. local command=self:_GetCommand() + -- Append file. command=command.." --file="..tostring(soundfile) - env.info(string.format("FF PlaySoundfile command=%s", command)) + -- Debug output. + self:I(string.format("MSRS PlaySoundfile command=%s", command)) -- Execute SRS command. - os.execute(command) + local x=os.execute(command) end @@ -277,14 +309,17 @@ function MSRS:PlaySoundText(SoundText, Delay) self:ScheduleOnce(Delay, MSRS.PlaySoundText, self, SoundText, 0) else + -- Get command. local command=self:_GetCommand(nil, nil, nil, SoundText.gender, SoundText.voice, SoundText.culture, SoundText.volume, SoundText.speed) + -- Append text. command=command..string.format(" --text=\"%s\"", tostring(SoundText.text)) - env.info(string.format("FF PlaySoundfile command=%s", command)) + -- Debug putput. + self:I(string.format("MSRS PlaySoundfile command=%s", command)) -- Execute SRS command. - os.execute(command) + local x=os.execute(command) end @@ -302,17 +337,79 @@ function MSRS:PlayText(Text, Delay) self:ScheduleOnce(Delay, MSRS.PlayText, self, Text, 0) else - local text=string.format("\"%s\"", Text) - + -- Get command line. local command=self:_GetCommand() + -- Append text. command=command..string.format(" --text=\"%s\"", tostring(Text)) - env.info(string.format("FF Text command=%s", command)) + -- Check that length of command is max 255 chars or os.execute() will not work! + if string.len(command)>255 then + + -- Create a tmp file. + local filename = os.getenv('TMP') .. "\\MSRS-"..STTS.uuid()..".bat" + + local script = io.open(filename, "w+") + script:write(command.." && exit") + script:close() + + -- Play command. + command=string.format("\"%s\"", filename) + + -- Play file in 0.05 seconds + timer.scheduleFunction(os.execute, command, timer.getTime()+0.05) + + -- Remove file in 1 second. + timer.scheduleFunction(os.remove, filename, timer.getTime()+1) + else + + -- Debug output. + self:I(string.format("MSRS Text command=%s", command)) + + -- Execute SRS command. + local x=os.execute(command) + + end + + + end + + return self +end + + +--- Play text file via STTS. +-- @param #MSRS self +-- @param #string TextFile Full path to the file. +-- @param #number Delay Delay in seconds, before the message is played. +-- @return #MSRS self +function MSRS:PlayTextFile(TextFile, Delay) + + if Delay and Delay>0 then + self:ScheduleOnce(Delay, MSRS.PlayTextFile, self, TextFile, 0) + else + + -- First check if text file exists! + local exists=UTILS.FileExists(TextFile) + if not exists then + self:E("ERROR: MSRS Text file does not exist! File="..tostring(TextFile)) + return self + end + + -- Get command line. + local command=self:_GetCommand() + + -- Append text file. + command=command..string.format(" --textFile=\"%s\"", tostring(TextFile)) + + -- Debug output. + self:I(string.format("MSRS TextFile command=%s", command)) + + -- Count length of command. + local l=string.len(command) -- Execute SRS command. local x=os.execute(command) - env.info(x) end @@ -338,8 +435,8 @@ end -- @return #string Command. function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, speed, port) - - local exe=self:GetPath().."DCS-SR-ExternalAudio.exe" + local path=self:GetPath() or STTS.DIRECTORY + local exe=STTS.EXECUTABLE or "DCS-SR-ExternalAudio.exe" freqs=table.concat(freqs or self.frequencies, ",") modus=table.concat(modus or self.modulations, ",") coal=coal or self.coalition @@ -348,19 +445,34 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp culture=culture or self.culture volume=volume or self.volume speed=speed or self.speed - port=port or self.port + port=port or self.port - local command=string.format("%s --freqs=%s --modulations=%s --coalition=%d --port=%d --gender=%s --volume=%.2f --speed=%d", exe, freqs, modus, coal, port, gender, volume, speed) + env.info("FF gender="..tostring(gender)) + env.info("FF gender="..tostring(self.gender)) + -- This did not work well. Stopped if the transmission was a bit longer with no apparent error. + --local command=string.format("%s --freqs=%s --modulations=%s --coalition=%d --port=%d --volume=%.2f --speed=%d", exe, freqs, modus, coal, port, volume, speed) + + -- Command from orig STTS script. Works better for some unknown reason! + local command=string.format("start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\"", path, exe, freqs, modus, coal, port, "ROBOT") + + -- Set voice or gender/culture. if voice then + -- Use a specific voice (no need for gender and/or culture. command=command..string.format(" --voice=\"%s\"", tostring(voice)) + else + -- Add gender. + if gender and gender~="female" then + command=command..string.format(" --gender=%s", tostring(gender)) + end + -- Add culture. + if culture and culture~="en-GB" then + command=command..string.format(" -l %s", tostring(culture)) + end end - if culture then - command=command.." --culture="..tostring(culture) - end - - env.info("FF command="..command) + -- Debug output. + self:I("MSRS command="..command) return command end diff --git a/Moose Development/Moose/Sound/SoundFile.lua b/Moose Development/Moose/Sound/SoundOutput.lua similarity index 72% rename from Moose Development/Moose/Sound/SoundFile.lua rename to Moose Development/Moose/Sound/SoundOutput.lua index 884eeab6a..697d63941 100644 --- a/Moose Development/Moose/Sound/SoundFile.lua +++ b/Moose Development/Moose/Sound/SoundOutput.lua @@ -4,7 +4,8 @@ -- -- ## Features: -- --- * Add a sound file to the +-- * Create a SOUNDFILE object (mp3 or ogg) to be played via DCS or SRS transmissions +-- * Create a SOUNDTEXT object for text-to-speech output -- -- === -- @@ -12,22 +13,19 @@ -- -- === -- --- @module Sound.Soundfile --- @image Sound_Soundfile.png --- +-- @module Sound.SoundOutput +-- @image Sound_SoundOutput.png do -- Sound Base --- @type SOUNDBASE - -- @field #string ClassName Name of the class + -- @field #string ClassName Name of the class. -- @extends Core.Base#BASE - --- Sound files used by other classes. + --- Basic sound output inherited by other classes. -- - -- # 1. USERFLAG constructor - -- - -- * @{#USERFLAG.New}(): Creates a new USERFLAG object. + -- This class is **not** meant to be used by "ordinary" users. -- -- @field #SOUNDBASE SOUNDBASE={ @@ -40,7 +38,7 @@ do -- Sound Base function SOUNDBASE:New() -- Inherit BASE. - local self=BASE:Inherit(self, BASE:New()) -- #SOUNDFILE + local self=BASE:Inherit(self, BASE:New()) -- #SOUNDBASE @@ -94,10 +92,11 @@ do -- Sound File -- Set file name. self:SetFileName(FileName) - -- Set path + -- Set path. self:SetPath(Path) - self.duration=Duration or 3 + -- Set duration. + self:SetDuration(Duration) -- Debug info: self:I(string.format("New SOUNDFILE: file name=%s, path=%s", self.filename, self.path)) @@ -137,11 +136,11 @@ do -- Sound File --- Set sound file name. This must be a .ogg or .mp3 file! -- @param #SOUNDFILE self - -- @param #string FileName Name of the file. + -- @param #string FileName Name of the file. Default is "Hello World.mp3". -- @return #SOUNDFILE self function SOUNDFILE:SetFileName(FileName) --TODO: check that sound file is really .ogg or .mp3 - self.filename=FileName or "HelloWorld.mp3" + self.filename=FileName or "Hello World.mp3" return self end @@ -187,17 +186,27 @@ do -- Text-To-Speech -- @field #string ClassName Name of the class -- @field #string text Text to speak. -- @field #number duration Duration in seconds. - -- @field #string gender Gender. - -- @field #string voice Voice. - -- @field #string culture Culture. + -- @field #string gender Gender: "male", "female". + -- @field #string culture Culture, e.g. "en-GB". + -- @field #string voice Specific voice to use. Overrules `gender` and `culture` settings. -- @extends Core.Base#BASE - --- Sound files used by other classes. + --- Text-to-speech objects for other classes. -- - -- # 1. USERFLAG constructor + -- # Constructor -- - -- * @{#USERFLAG.New}(): Creates a new USERFLAG object. + -- * @{#SOUNDTEXT.New}(*Text, Duration*): Creates a new SOUNDTEXT object. + -- + -- Name: Microsoft Hazel Desktop, Culture: en-GB, Gender: Female, Age: Adult, Desc: Microsoft Hazel Desktop - English (Great Britain) + -- Name: Microsoft David Desktop, Culture: en-US, Gender: Male, Age: Adult, Desc: Microsoft David Desktop - English (United States) + -- Name: Microsoft Zira Desktop, Culture: en-US, Gender: Female, Age: Adult, Desc: Microsoft Zira Desktop - English (United States) + -- Name: Microsoft Hedda Desktop, Culture: de-DE, Gender: Female, Age: Adult, Desc: Microsoft Hedda Desktop - German + -- Name: Microsoft Helena Desktop, Culture: es-ES, Gender: Female, Age: Adult, Desc: Microsoft Helena Desktop - Spanish (Spain) + -- Name: Microsoft Hortense Desktop, Culture: fr-FR, Gender: Female, Age: Adult, Desc: Microsoft Hortense Desktop - French + -- Name: Microsoft Elsa Desktop, Culture: it-IT, Gender: Female, Age: Adult, Desc: Microsoft Elsa Desktop - Italian (Italy) + -- Name: Microsoft Irina Desktop, Culture: ru-RU, Gender: Female, Age: Adult, Desc: Microsoft Irina Desktop - Russian + -- Name: Microsoft Huihui Desktop, Culture: zh-CN, Gender: Female, Age: Adult, Desc: Microsoft Huihui Desktop - Chinese (Simplified) -- -- @field #SOUNDTEXT SOUNDTEXT={ @@ -216,8 +225,8 @@ do -- Text-To-Speech self:SetText(Text) self:SetDuration(Duration) - self:SetGender() - self:SetCulture() + --self:SetGender() + --self:SetCulture() -- Debug info: self:I(string.format("New SOUNDTEXT: text=%s, duration=%.1f sec", self.text, self.duration)) @@ -258,9 +267,10 @@ do -- Text-To-Speech return self end - --- Set the voice name. See the list from --help or if using google see: https://cloud.google.com/text-to-speech/docs/voices + --- Set to use a specific voice name. + -- See the list from `DCS-SR-ExternalAudio.exe --help` or if using google see https://cloud.google.com/text-to-speech/docs/voices -- @param #SOUNDTEXT self - -- @param #string Voice + -- @param #string Voice Voice name. Note that this will overrule `Gender` and `Culture`. -- @return #SOUNDTEXT self function SOUNDTEXT:SetVoice(Voice) diff --git a/Moose Development/Moose/Utilities/STTS.lua b/Moose Development/Moose/Utilities/STTS.lua new file mode 100644 index 000000000..97836a1ee --- /dev/null +++ b/Moose Development/Moose/Utilities/STTS.lua @@ -0,0 +1,256 @@ +--- **Utilities** DCS Simple Text-To-Speech (STTS). +-- +-- +-- +-- @module Utils.STTS +-- @image MOOSE.JPG + +--- [DCS Enum world](https://wiki.hoggitworld.com/view/DCS_enum_world) +-- @type STTS +-- @field #string DIRECTORY Path of the SRS directory. + +--- Simple Text-To-Speech +-- +-- Version 0.4 - Compatible with SRS version 1.9.6.0+ +-- +-- # DCS Modification Required +-- +-- You will need to edit MissionScripting.lua in DCS World/Scripts/MissionScripting.lua and remove the sanitisation. +-- To do this remove all the code below the comment - the line starts "local function sanitizeModule(name)" +-- Do this without DCS running to allow mission scripts to use os functions. +-- +-- *You WILL HAVE TO REAPPLY AFTER EVERY DCS UPDATE* +-- +-- # USAGE: +-- +-- Add this script into the mission as a DO SCRIPT or DO SCRIPT FROM FILE to initialise it +-- Make sure to edit the STTS.SRS_PORT and STTS.DIRECTORY to the correct values before adding to the mission. +-- Then its as simple as calling the correct function in LUA as a DO SCRIPT or in your own scripts. +-- +-- Example calls: +-- +-- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2) +-- +-- Arguments in order are: +-- +-- * Message to say, make sure not to use a newline (\n) ! +-- * Frequency in MHz +-- * Modulation - AM/FM +-- * Volume - 1.0 max, 0.5 half +-- * Name of the transmitter - ATC, RockFM etc +-- * Coalition - 0 spectator, 1 red 2 blue +-- * OPTIONAL - Vec3 Point i.e Unit.getByName("A UNIT"):getPoint() - needs Vec3 for Height! OR null if not needed +-- * OPTIONAL - Speed -10 to +10 +-- * OPTIONAL - Gender male, female or neuter +-- * OPTIONAL - Culture - en-US, en-GB etc +-- * OPTIONAL - Voice - a specfic voice by name. Run DCS-SR-ExternalAudio.exe with --help to get the ones you can use on the command line +-- * OPTIONAL - Google TTS - Switch to Google Text To Speech - Requires STTS.GOOGLE_CREDENTIALS path and Google project setup correctly +-- +-- +-- ## Example +-- +-- This example will say the words "Hello DCS WORLD" on 251 MHz AM at maximum volume with a client called SRS and to the Blue coalition only +-- +-- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2,null,-5,"male","en-GB") +-- +-- ## Example +-- +--This example will say the words "Hello DCS WORLD" on 251 MHz AM at maximum volume with a client called SRS and to the Blue coalition only centered on the position of the Unit called "A UNIT" +-- +-- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2,Unit.getByName("A UNIT"):getPoint(),-5,"male","en-GB") +-- +-- Arguments in order are: +-- +-- * FULL path to the MP3 OR OGG to play +-- * Frequency in MHz - to use multiple separate with a comma - Number of frequencies MUST match number of Modulations +-- * Modulation - AM/FM - to use multiple +-- * Volume - 1.0 max, 0.5 half +-- * Name of the transmitter - ATC, RockFM etc +-- * Coalition - 0 spectator, 1 red 2 blue +-- +-- ## Example +-- +-- This will play that MP3 on 255MHz AM & 31 FM at half volume with a client called "Multiple" and to Spectators only +-- +-- STTS.PlayMP3("C:\\Users\\Ciaran\\Downloads\\PR-Music.mp3","255,31","AM,FM","0.5","Multiple",0) +-- +-- @field #STTS +STTS={ + ClassName="STTS", + DIRECTORY="", + SRS_PORT=5002, + GOOGLE_CREDENTIALS="C:\\Users\\Ciaran\\Downloads\\googletts.json", + EXECUTABLE="DCS-SR-ExternalAudio.exe", +} + +--- FULL Path to the FOLDER containing DCS-SR-ExternalAudio.exe - EDIT TO CORRECT FOLDER +STTS.DIRECTORY = "D:/DCS/_SRS" + +--- LOCAL SRS PORT - DEFAULT IS 5002 +STTS.SRS_PORT = 5002 + +--- Google credentials file +STTS.GOOGLE_CREDENTIALS = "C:\\Users\\Ciaran\\Downloads\\googletts.json" + +--- DONT CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING +STTS.EXECUTABLE = "DCS-SR-ExternalAudio.exe" + + +--- Function for UUID. +function STTS.uuid() + local random = math.random + local template ='yxxx-xxxxxxxxxxxx' + return string.gsub(template, '[xy]', function (c) + local v = (c == 'x') and random(0, 0xf) or random(8, 0xb) + return string.format('%x', v) + end) +end + +--- Round a number. +-- @param #number x Number. +-- @param #number n Precision. +function STTS.round(x, n) + n = math.pow(10, n or 0) + x = x * n + if x >= 0 then x = math.floor(x + 0.5) else x = math.ceil(x - 0.5) end + return x / n +end + +--- Function returns estimated speech time in seconds. +-- Assumptions for time calc: 100 Words per min, avarage of 5 letters for english word so +-- +-- * 5 chars * 100wpm = 500 characters per min = 8.3 chars per second +-- +-- So lengh of msg / 8.3 = number of seconds needed to read it. rounded down to 8 chars per sec map function: +-- +-- * (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min +-- +function STTS.getSpeechTime(length,speed,isGoogle) + + local maxRateRatio = 3 + + speed = speed or 1.0 + isGoogle = isGoogle or false + + local speedFactor = 1.0 + if isGoogle then + speedFactor = speed + else + if speed ~= 0 then + speedFactor = math.abs(speed) * (maxRateRatio - 1) / 10 + 1 + end + if speed < 0 then + speedFactor = 1/speedFactor + end + end + + local wpm = math.ceil(100 * speedFactor) + local cps = math.floor((wpm * 5)/60) + + if type(length) == "string" then + length = string.len(length) + end + + return math.ceil(length/cps) +end + +--- Text to speech function. +function STTS.TextToSpeech(message, freqs, modulations, volume, name, coalition, point, speed, gender, culture, voice, googleTTS) + if os == nil or io == nil then + env.info("[DCS-STTS] LUA modules os or io are sanitized. skipping. ") + return + end + + speed = speed or 1 + gender = gender or "female" + culture = culture or "" + voice = voice or "" + coalition=coalition or "0" + name=name or "ROBOT" + volume=1 + speed=1 + + + message = message:gsub("\"","\\\"") + + local cmd = string.format("start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -h", STTS.DIRECTORY, STTS.EXECUTABLE, freqs or "305", modulations or "AM", coalition, STTS.SRS_PORT, name) + + if voice ~= "" then + cmd = cmd .. string.format(" -V \"%s\"",voice) + else + + if culture ~= "" then + cmd = cmd .. string.format(" -l %s",culture) + end + + if gender ~= "" then + cmd = cmd .. string.format(" -g %s",gender) + end + end + + if googleTTS == true then + cmd = cmd .. string.format(" -G \"%s\"",STTS.GOOGLE_CREDENTIALS) + end + + if speed ~= 1 then + cmd = cmd .. string.format(" -s %s",speed) + end + + if volume ~= 1.0 then + cmd = cmd .. string.format(" -v %s",volume) + end + + if point and type(point) == "table" and point.x then + local lat, lon, alt = coord.LOtoLL(point) + + lat = STTS.round(lat,4) + lon = STTS.round(lon,4) + alt = math.floor(alt) + + cmd = cmd .. string.format(" -L %s -O %s -A %s",lat,lon,alt) + end + + cmd = cmd ..string.format(" -t \"%s\"",message) + + if string.len(cmd) > 255 then + local filename = os.getenv('TMP') .. "\\DCS_STTS-" .. STTS.uuid() .. ".bat" + local script = io.open(filename,"w+") + script:write(cmd .. " && exit" ) + script:close() + cmd = string.format("\"%s\"",filename) + timer.scheduleFunction(os.remove, filename, timer.getTime() + 1) + end + + if string.len(cmd) > 255 then + env.info("[DCS-STTS] - cmd string too long") + env.info("[DCS-STTS] TextToSpeech Command :\n" .. cmd.."\n") + end + os.execute(cmd) + + return STTS.getSpeechTime(message,speed,googleTTS) +end + +--- Play mp3 function. +-- @param #string pathToMP3 Path to the sound file. +-- @param #string freqs Frequencies, e.g. "305, 256". +-- @param #string modulations Modulations, e.g. "AM, FM". +-- @param #string volume Volume, e.g. "0.5". +function STTS.PlayMP3(pathToMP3, freqs, modulations, volume, name, coalition, point) + + local cmd = string.format("start \"\" /d \"%s\" /b /min \"%s\" -i \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -v %s -h", + STTS.DIRECTORY, STTS.EXECUTABLE, pathToMP3, freqs or "305", modulations or "AM", coalition or "0", STTS.SRS_PORT, name or "ROBOT", volume or "1") + + if point and type(point) == "table" and point.x then + local lat, lon, alt = coord.LOtoLL(point) + + lat = STTS.round(lat,4) + lon = STTS.round(lon,4) + alt = math.floor(alt) + + cmd = cmd .. string.format(" -L %s -O %s -A %s",lat,lon,alt) + end + + env.info("[DCS-STTS] MP3/OGG Command :\n" .. cmd.."\n") + os.execute(cmd) + +end \ No newline at end of file diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 939bf7770..0bdf83161 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -4,8 +4,10 @@ Utilities/Utils.lua Utilities/Enums.lua Utilities/Profiler.lua Utilities/Templates.lua +Utilities/STTS.lua Core/Base.lua +Core/Beacon.lua Core/UserFlag.lua Core/Report.lua Core/Scheduler.lua @@ -111,7 +113,7 @@ Actions/Act_Account.lua Actions/Act_Assist.lua Sound/UserSound.lua -Sound/SoundFile.lua +Sound/SoundOutput.lua Sound/Radio.lua Sound/RadioQueue.lua Sound/RadioSpeech.lua From cfcd7d7588bf4d4cbb6aecc13e2c1e6e13a7bd76 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 30 May 2021 22:36:52 +0200 Subject: [PATCH 026/111] Sound update --- Moose Development/Moose/Globals.lua | 5 ++-- Moose Development/Moose/Ops/ATIS.lua | 4 +-- Moose Development/Moose/Sound/RadioQueue.lua | 30 ++++++++++++++----- Moose Development/Moose/Sound/SRS.lua | 5 +--- Moose Development/Moose/Sound/SoundOutput.lua | 19 ++++++++++-- Moose Development/Moose/Sound/UserSound.lua | 4 +-- 6 files changed, 45 insertions(+), 22 deletions(-) diff --git a/Moose Development/Moose/Globals.lua b/Moose Development/Moose/Globals.lua index fdc6db2c7..734f7c1df 100644 --- a/Moose Development/Moose/Globals.lua +++ b/Moose Development/Moose/Globals.lua @@ -20,8 +20,7 @@ _DATABASE:_RegisterCargos() _DATABASE:_RegisterZones() --- Check if os etc is available. -BASE:I("Checking de-sanitization of os, io and lfs (Check /Scripts/MissionScripting.lua and commend out sanitizeModule(''). Use at your own risk!)") - +BASE:I("Checking de-sanitization of os, io and lfs:") local __na=false if os then BASE:I("- os available") @@ -42,5 +41,5 @@ else __na=true end if __na then - BASE:I("Check /Scripts/MissionScripting.lua and commend out the lines with sanitizeModule(''). Use at your own risk!)") + BASE:I("Check /Scripts/MissionScripting.lua and comment out the lines with sanitizeModule(''). Use at your own risk!)") end \ No newline at end of file diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 3870ba197..c278539fc 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -1115,6 +1115,7 @@ function ATIS:SetSTTS(PathToSRS, Gender, Culture, Voice, Port) self.msrs:SetGender(Gender) self.msrs:SetCulture(Culture) self.msrs:SetVoice(Voice) + self.msrs:SetPort(Port) return self end @@ -2187,9 +2188,6 @@ function ATIS:onafterReport(From, Event, To, Text) -- Replace ";" by "." local text=string.gsub(text, ";", ". ") env.info("FF: "..text) - - --local msrs=MSRS:New("D:\\DCS\\_SRS\\", 305, Modulation) - --msrs:PlayText(text) -- Play text-to-speech report. self.msrs:PlayText(text) diff --git a/Moose Development/Moose/Sound/RadioQueue.lua b/Moose Development/Moose/Sound/RadioQueue.lua index 53207850f..11cea9de8 100644 --- a/Moose Development/Moose/Sound/RadioQueue.lua +++ b/Moose Development/Moose/Sound/RadioQueue.lua @@ -70,8 +70,8 @@ RADIOQUEUE = { -- @field #boolean isplaying If true, transmission is currently playing. -- @field #number Tplay Mission time (abs) in seconds when the transmission should be played. -- @field #number interval Interval in seconds before next transmission. --- @field Sound.SoundFile#SOUNDFILE soundfile Sound file object to play via SRS. --- @field Sound.SoundFile#SOUNDTEXT soundtext Sound TTS object to play via SRS. +-- @field Sound.SoundOutput#SOUNDFILE soundfile Sound file object to play via SRS. +-- @field Sound.SoundOutput#SOUNDTEXT soundtext Sound TTS object to play via SRS. --- Create a new RADIOQUEUE object for a given radio frequency/modulation. @@ -176,9 +176,11 @@ end --- Set SRS. -- @param #RADIOQUEUE self -- @param #string PathToSRS Path to SRS. +-- @param #number Port SRS port. Default 5002. -- @return #RADIOQUEUE self The RADIOQUEUE object. -function RADIOQUEUE:SetSRS(PathToSRS) +function RADIOQUEUE:SetSRS(PathToSRS, Port) self.msrs=MSRS:New(PathToSRS, self.frequency/1000000, self.modulation) + self.msrs:SetPort(Port) return self end @@ -284,19 +286,33 @@ function RADIOQUEUE:NewTransmission(filename, duration, path, tstart, interval, return transmission end ---- Create a new transmission and add it to the radio queue. +--- Add a SOUNDFILE to the radio queue. -- @param #RADIOQUEUE self -- @param Sound.SoundOutput#SOUNDFILE soundfile Sound file object to be added. -- @param #number tstart Start time (abs) seconds. Default now. -- @param #number interval Interval in seconds after the last transmission finished. -- @return #RADIOQUEUE self function RADIOQUEUE:AddSoundFile(soundfile, tstart, interval) - env.info(string.format("FF add soundfile: name=%s%s", soundfile:GetPath(), soundfile:GetFileName())) + --env.info(string.format("FF add soundfile: name=%s%s", soundfile:GetPath(), soundfile:GetFileName())) local transmission=self:NewTransmission(soundfile:GetFileName(), soundfile.duration, soundfile:GetPath(), tstart, interval, soundfile.subtitle, soundfile.subduration) transmission.soundfile=soundfile return self end +--- Add a SOUNDTEXT to the radio queue. +-- @param #RADIOQUEUE self +-- @param Sound.SoundOutput#SOUNDTEXT soundtext Text-to-speech text. +-- @param #number tstart Start time (abs) seconds. Default now. +-- @param #number interval Interval in seconds after the last transmission finished. +-- @return #RADIOQUEUE self +function RADIOQUEUE:AddSoundText(soundtext, tstart, interval) + + local transmission=self:NewTransmission("SoundText.ogg", soundtext.duration, nil, tstart, interval, soundtext.subtitle, soundtext.subduration) + transmission.soundtext=soundtext + return self +end + + --- Convert a number (as string) into a radio transmission. -- E.g. for board number or headings. -- @param #RADIOQUEUE self @@ -340,7 +356,7 @@ end -- @param #RADIOQUEUE.Transmission transmission The transmission. function RADIOQUEUE:Broadcast(transmission) - if (transmission.soundfile or transmission.soundtext) and self.msrs then + if ((transmission.soundfile and transmission.soundfile.useSRS) or transmission.soundtext) and self.msrs then self:_BroadcastSRS(transmission) return end @@ -441,7 +457,7 @@ end -- @param #RADIOQUEUE.Transmission transmission The transmission. function RADIOQUEUE:_BroadcastSRS(transmission) - if transmission.soundfile then + if transmission.soundfile and transmission.soundfile.useSRS then self.msrs:PlaySoundFile(transmission.soundfile) elseif transmission.soundtext then self.msrs:PlaySoundText(transmission.soundtext) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 78cbaeaf2..119f9b167 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -447,14 +447,11 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp speed=speed or self.speed port=port or self.port - env.info("FF gender="..tostring(gender)) - env.info("FF gender="..tostring(self.gender)) - -- This did not work well. Stopped if the transmission was a bit longer with no apparent error. --local command=string.format("%s --freqs=%s --modulations=%s --coalition=%d --port=%d --volume=%.2f --speed=%d", exe, freqs, modus, coal, port, volume, speed) -- Command from orig STTS script. Works better for some unknown reason! - local command=string.format("start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\"", path, exe, freqs, modus, coal, port, "ROBOT") + local command=string.format("start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -h", path, exe, freqs, modus, coal, port, "ROBOT") -- Set voice or gender/culture. if voice then diff --git a/Moose Development/Moose/Sound/SoundOutput.lua b/Moose Development/Moose/Sound/SoundOutput.lua index 697d63941..bb974af08 100644 --- a/Moose Development/Moose/Sound/SoundOutput.lua +++ b/Moose Development/Moose/Sound/SoundOutput.lua @@ -57,7 +57,7 @@ do -- Sound File -- @field #string duration Duration of the sound file in seconds. -- @field #string subtitle Subtitle of the transmission. -- @field #number subduration Duration in seconds how long the subtitle is displayed. - -- @field #boolean insideMiz If true (default), the sound file is located inside the mission .miz file. + -- @field #boolean useSRS If true, sound file is played via SRS. Sound file needs to be on local disk not inside the miz file! -- @extends Core.Base#BASE @@ -75,7 +75,7 @@ do -- Sound File duration = 3, subtitle = nil, subduration = 0, - insideMiz = true, + useSRS = false, } --- Constructor to create a new SOUNDFILE object. @@ -178,6 +178,19 @@ do -- Sound File return name end + --- Get the complete sound file name inlcuding its path. + -- @param #SOUNDFILE self + -- @param #boolean Switch If true or nil, use SRS. If false, use DCS transmission. + -- @return #SOUNDFILE self + function SOUNDFILE:UseSRS(Switch) + if Switch==true or Switch==nil then + self.useSRS=true + else + self.useSRS=false + end + return self + end + end do -- Text-To-Speech @@ -224,7 +237,7 @@ do -- Text-To-Speech local self=BASE:Inherit(self, BASE:New()) -- #SOUNDTEXT self:SetText(Text) - self:SetDuration(Duration) + self:SetDuration(Duration or STTS.getSpeechTime(Text)) --self:SetGender() --self:SetCulture() diff --git a/Moose Development/Moose/Sound/UserSound.lua b/Moose Development/Moose/Sound/UserSound.lua index b0f6fb393..8b94ad114 100644 --- a/Moose Development/Moose/Sound/UserSound.lua +++ b/Moose Development/Moose/Sound/UserSound.lua @@ -1,4 +1,4 @@ ---- **Core** - Manage user sound. +--- **Sound** - Manage user sound. -- -- === -- @@ -16,7 +16,7 @@ -- -- === -- --- @module Core.UserSound +-- @module Sound.UserSound -- @image Core_Usersound.JPG do -- UserSound From 05f95796f6abec34c23f61c652d977fa0ef94c67 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 31 May 2021 23:47:43 +0200 Subject: [PATCH 027/111] Sound update --- Moose Development/Moose/Ops/ATIS.lua | 1 + Moose Development/Moose/Sound/SRS.lua | 36 +++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index c278539fc..8f6d3045b 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -18,6 +18,7 @@ -- * Option to present information in imperial or metric units -- * Runway length and airfield elevation (optional) -- * Frequencies/channels of nav aids (ILS, VOR, NDB, TACAN, PRMG, RSBN) (optional) +-- * SRS Simple-Text-To-Speech (STTS) integration (no sound files necessary) -- -- === -- diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 119f9b167..04eaa762d 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -57,9 +57,40 @@ -- -- This class allows to broadcast sound files or text via Simple Radio Standalone (SRS). -- --- # Prerequisites +-- ## Prerequisites -- --- This script needs SRS version >= 0.9.6. +-- This script needs SRS version >= 1.9.6. +-- +-- # Play Sound Files +-- +-- local soundfile=SOUNDFILE:New("My Soundfile.ogg", "D:\\Sounds For DCS") +-- local msrs=MSRS:New("C:\\Path To SRS", 251, radio.modulation.AM) +-- msrs:PlaySoundFile(soundfile) +-- +-- # Play Text-To-Speech +-- +-- Basic example: +-- +-- -- Create a SOUNDTEXT object. +-- local text=SOUNDTEXT:New("All Enemies destroyed") +-- +-- -- MOOSE SRS +-- local msrs=MSRS:New("D:\\DCS\\_SRS\\", 305, radio.modulation.AM) +-- +-- -- Text-to speech with default voice after 2 seconds. +-- msrs:PlaySoundText(text, 2) +-- +-- ## Set Gender +-- +-- Use a specific gender by :SetGender("male") or :SetGender("female"). +-- +-- ## Set Culture +-- +-- Use a specific "culture" by :SetCulture("en-US") or :SetGender("de-DE"). +-- +-- ## Set Voice +-- +-- Use a specifc voice by :SetVoice("Microsoft Hedda Desktop"). Note that this must be installed on your windows system. -- -- @field #MSRS MSRS = { @@ -94,6 +125,7 @@ MSRS.version="0.0.3" -- TODO: Add functions to add/remove freqs and modulations. -- TODO: Add coordinate. +-- TODO: Add google. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor From bb43b08190a3d63bbe1f8720e0b6399f3929b2d0 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 1 Jun 2021 23:20:38 +0200 Subject: [PATCH 028/111] Sound update --- Moose Development/Moose/Sound/SRS.lua | 6 +++- Moose Development/Moose/Sound/SoundOutput.lua | 31 ++++++++++++------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 04eaa762d..40732802f 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -86,7 +86,7 @@ -- -- ## Set Culture -- --- Use a specific "culture" by :SetCulture("en-US") or :SetGender("de-DE"). +-- Use a specific "culture" by :SetCulture("en-US") or :SetCulture("de-DE"). -- -- ## Set Voice -- @@ -479,6 +479,10 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp speed=speed or self.speed port=port or self.port + -- Replace modulation + modus=modus:gsub("0", "AM") + modus=modus:gsub("1", "FM") + -- This did not work well. Stopped if the transmission was a bit longer with no apparent error. --local command=string.format("%s --freqs=%s --modulations=%s --coalition=%d --port=%d --volume=%.2f --speed=%d", exe, freqs, modus, coal, port, volume, speed) diff --git a/Moose Development/Moose/Sound/SoundOutput.lua b/Moose Development/Moose/Sound/SoundOutput.lua index bb974af08..bbe93cfa6 100644 --- a/Moose Development/Moose/Sound/SoundOutput.lua +++ b/Moose Development/Moose/Sound/SoundOutput.lua @@ -211,15 +211,22 @@ do -- Text-To-Speech -- -- * @{#SOUNDTEXT.New}(*Text, Duration*): Creates a new SOUNDTEXT object. -- - -- Name: Microsoft Hazel Desktop, Culture: en-GB, Gender: Female, Age: Adult, Desc: Microsoft Hazel Desktop - English (Great Britain) - -- Name: Microsoft David Desktop, Culture: en-US, Gender: Male, Age: Adult, Desc: Microsoft David Desktop - English (United States) - -- Name: Microsoft Zira Desktop, Culture: en-US, Gender: Female, Age: Adult, Desc: Microsoft Zira Desktop - English (United States) - -- Name: Microsoft Hedda Desktop, Culture: de-DE, Gender: Female, Age: Adult, Desc: Microsoft Hedda Desktop - German - -- Name: Microsoft Helena Desktop, Culture: es-ES, Gender: Female, Age: Adult, Desc: Microsoft Helena Desktop - Spanish (Spain) - -- Name: Microsoft Hortense Desktop, Culture: fr-FR, Gender: Female, Age: Adult, Desc: Microsoft Hortense Desktop - French - -- Name: Microsoft Elsa Desktop, Culture: it-IT, Gender: Female, Age: Adult, Desc: Microsoft Elsa Desktop - Italian (Italy) - -- Name: Microsoft Irina Desktop, Culture: ru-RU, Gender: Female, Age: Adult, Desc: Microsoft Irina Desktop - Russian - -- Name: Microsoft Huihui Desktop, Culture: zh-CN, Gender: Female, Age: Adult, Desc: Microsoft Huihui Desktop - Chinese (Simplified) + -- + -- # Specific Voice + -- + -- You can use a specific voice for the transmission with the @{SOUNDTEXT.SetVoice}(*VoiceName*) function. Here are some examples + -- + -- * Name: Microsoft Hazel Desktop, Culture: en-GB, Gender: Female, Age: Adult, Desc: Microsoft Hazel Desktop - English (Great Britain) + -- * Name: Microsoft David Desktop, Culture: en-US, Gender: Male, Age: Adult, Desc: Microsoft David Desktop - English (United States) + -- * Name: Microsoft Zira Desktop, Culture: en-US, Gender: Female, Age: Adult, Desc: Microsoft Zira Desktop - English (United States) + -- * Name: Microsoft Hedda Desktop, Culture: de-DE, Gender: Female, Age: Adult, Desc: Microsoft Hedda Desktop - German + -- * Name: Microsoft Helena Desktop, Culture: es-ES, Gender: Female, Age: Adult, Desc: Microsoft Helena Desktop - Spanish (Spain) + -- * Name: Microsoft Hortense Desktop, Culture: fr-FR, Gender: Female, Age: Adult, Desc: Microsoft Hortense Desktop - French + -- * Name: Microsoft Elsa Desktop, Culture: it-IT, Gender: Female, Age: Adult, Desc: Microsoft Elsa Desktop - Italian (Italy) + -- * Name: Microsoft Irina Desktop, Culture: ru-RU, Gender: Female, Age: Adult, Desc: Microsoft Irina Desktop - Russian + -- * Name: Microsoft Huihui Desktop, Culture: zh-CN, Gender: Female, Age: Adult, Desc: Microsoft Huihui Desktop - Chinese (Simplified) + -- + -- Note that this must be installed on your windos machine. Also note that this overrides any culture and gender settings. -- -- @field #SOUNDTEXT SOUNDTEXT={ @@ -283,11 +290,11 @@ do -- Text-To-Speech --- Set to use a specific voice name. -- See the list from `DCS-SR-ExternalAudio.exe --help` or if using google see https://cloud.google.com/text-to-speech/docs/voices -- @param #SOUNDTEXT self - -- @param #string Voice Voice name. Note that this will overrule `Gender` and `Culture`. + -- @param #string VoiceName Voice name. Note that this will overrule `Gender` and `Culture`. -- @return #SOUNDTEXT self - function SOUNDTEXT:SetVoice(Voice) + function SOUNDTEXT:SetVoice(VoiceName) - self.voice=Voice + self.voice=VoiceName return self end From 5a00f461e96ce7db3fa60b56d2d7d3ba84b37551 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 4 Jun 2021 23:04:49 +0200 Subject: [PATCH 029/111] Sound update --- Moose Development/Moose/Ops/ATIS.lua | 10 +++++++++- Moose Development/Moose/Sound/SRS.lua | 3 +-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 8f6d3045b..3799d974a 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -255,6 +255,13 @@ -- # Marks on the F10 Map -- -- You can place marks on the F10 map via the @{#ATIS.SetMapMarks}() function. These will contain info about the ATIS frequency, the currently active runway and some basic info about the weather (wind, pressure and temperature). +-- +-- # Text-To-Speech +-- +-- You can enable text-to-speech ATIS information with the @{#ATIS.SetSTTS}() function. This uses [SRS](http://dcssimpleradio.com/) (Version >= 1.9.6.0) for broadcasing. +-- Advantages are that no sound files are necessary. Also the issue that FC3 aircraft hear all transmissions will be circumvented. +-- +-- The @{#ATIS.SetSTTS}() requires you to specify the path to the SRS install directory. -- -- # Examples -- @@ -1109,8 +1116,9 @@ end -- @param #string Gender Gender: "male" or "female" (default). -- @param #string Culture Culture, e.g. "en-GB" (default). -- @param #string Voice Specific voice. Overrides `Gender` and `Culture`. +-- @param #number Port SRS port. Default 5002. -- @return #ATIS self -function ATIS:SetSTTS(PathToSRS, Gender, Culture, Voice, Port) +function ATIS:SetSRS(PathToSRS, Gender, Culture, Voice, Port) self.useSRS=true self.msrs=MSRS:New(PathToSRS, self.frequency, self.modulation) self.msrs:SetGender(Gender) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 40732802f..fe3e13211 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -21,8 +21,7 @@ -- -- === -- --- Automatic terminal information service, or ATIS, is a continuous broadcast of recorded aeronautical information in busier terminal areas, *i.e.* airports and their immediate surroundings. --- ATIS broadcasts contain essential information, such as current weather information, active runways, and any other information required by the pilots. +-- The goal of the [SRS](https://github.com/ciribob/DCS-SimpleRadioStandalone) project is to bring VoIP communication into DCS and to make communication as frictionless as possible. -- -- === -- From cf83abfe90813d541094c9a8b4c6cd75526348c9 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 5 Jun 2021 23:40:50 +0200 Subject: [PATCH 030/111] Sound update docs --- Moose Development/Moose/Sound/RadioQueue.lua | 6 +- Moose Development/Moose/Sound/SoundOutput.lua | 92 +++++++++++++++---- 2 files changed, 78 insertions(+), 20 deletions(-) diff --git a/Moose Development/Moose/Sound/RadioQueue.lua b/Moose Development/Moose/Sound/RadioQueue.lua index 11cea9de8..fbe183fd6 100644 --- a/Moose Development/Moose/Sound/RadioQueue.lua +++ b/Moose Development/Moose/Sound/RadioQueue.lua @@ -4,7 +4,7 @@ -- -- ## Features: -- --- * Managed Radio Transmissions. +-- * Manage Radio Transmissions -- -- === -- @@ -15,6 +15,10 @@ --- Manages radio transmissions. -- +-- The main goal of the RADIOQUEUE class is to string together multiple sound files to play a complete sentence. +-- The underlying problem is that radio transmissions in DCS are not queued but played "on top" of each other. +-- Therefore, to achive the goal, it is vital to know the precise duration how long it takes to play the sound file. +-- -- @type RADIOQUEUE -- @field #string ClassName Name of the class "RADIOQUEUE". -- @field #boolean Debugmode Debug mode. More info. diff --git a/Moose Development/Moose/Sound/SoundOutput.lua b/Moose Development/Moose/Sound/SoundOutput.lua index bbe93cfa6..2efb9e015 100644 --- a/Moose Development/Moose/Sound/SoundOutput.lua +++ b/Moose Development/Moose/Sound/SoundOutput.lua @@ -5,7 +5,7 @@ -- ## Features: -- -- * Create a SOUNDFILE object (mp3 or ogg) to be played via DCS or SRS transmissions --- * Create a SOUNDTEXT object for text-to-speech output +-- * Create a SOUNDTEXT object for text-to-speech output vis SRS Simple-Text-To-Speech -- -- === -- @@ -13,6 +13,12 @@ -- -- === -- +-- There are two classes, SOUNDFILE and SOUNDTEXT, defined in this section that deal with playing +-- sound files or arbitrary text (via SRS Simple-Text-To-Speech), respectively. +-- +-- The SOUNDFILE and SOUNDTEXT objects can be defined and used in other MOOSE classes. +-- +-- -- @module Sound.SoundOutput -- @image Sound_SoundOutput.png @@ -63,9 +69,39 @@ do -- Sound File --- Sound files used by other classes. -- - -- # 1. USERFLAG constructor + -- # The SOUNDFILE Concept -- - -- * @{#USERFLAG.New}(): Creates a new USERFLAG object. + -- A SOUNDFILE object hold the important properties that are necessary to play the sound file, e.g. its file name, path, duration. + -- + -- It can be created with the @{#SOUNDFILE.New}(*FileName*, *Path*, *Duration*) function: + -- + -- local soundfile=SOUNDFILE:New("My Soundfile.ogg", "Sound File/", 3.5) + -- + -- ## SRS + -- + -- If sound files are supposed to be played via SRS, you need to use the @{#SOUNDFILE.SetPlayWithSRS}() function. + -- + -- # Location/Path + -- + -- ## DCS + -- + -- DCS can only play sound files that are located inside the mission (.miz) file. In particular, DCS cannot make use of files that are stored on + -- your hard drive. + -- + -- The default location where sound files are stored in DCS is the directory "l10n/DEFAULT/". This is where sound files are placed, if they are + -- added via the mission editor (TRIGGERS-->ACTIONS-->SOUND TO ALL). Note however, that sound files which are not added with a trigger command, + -- will be deleted each time the mission is saved! Therefore, this directory is not ideal to be used especially if many sound files are to + -- be included since for each file a trigger action needs to be created. Which is cumbersome, to say the least. + -- + -- The recommended way is to create a new folder inside the mission (.miz) file (a miz file is essentially zip file and can be opened, e.g., with 7-Zip) + -- and to place the sound files in there. Sound files in these folders are not wiped out by DCS on the next save. + -- + -- ## SRS + -- + -- SRS sound files need to be located on your local drive (not inside the miz). Therefore, you need to specify the full path. + -- + -- + -- ## SRS -- -- @field #SOUNDFILE SOUNDFILE={ @@ -178,11 +214,11 @@ do -- Sound File return name end - --- Get the complete sound file name inlcuding its path. + --- Set whether sound files should be played via SRS. -- @param #SOUNDFILE self -- @param #boolean Switch If true or nil, use SRS. If false, use DCS transmission. -- @return #SOUNDFILE self - function SOUNDFILE:UseSRS(Switch) + function SOUNDFILE:SetPlayWithSRS(Switch) if Switch==true or Switch==nil then self.useSRS=true else @@ -207,12 +243,30 @@ do -- Text-To-Speech --- Text-to-speech objects for other classes. -- - -- # Constructor + -- # The SOUNDTEXT Concept + -- + -- A SOUNDTEXT object holds all necessary information to play a general text via SRS Simple-Text-To-Speech. + -- + -- It can be created with the @{#SOUNDTEXT.New}(*Text*, *Duration*) function. -- -- * @{#SOUNDTEXT.New}(*Text, Duration*): Creates a new SOUNDTEXT object. -- + -- # Options + -- + -- ## Gender + -- + -- You can choose a gender ("male" or "femal") with the @{#SOUNDTEXT.SetGender}(*Gender*) function. + -- Note that the gender voice needs to be installed on your windows machine for the used culture (see below). + -- + -- ## Culture + -- + -- You can choose a "culture" (accent) with the @{#SOUNDTEXT.SetCulture}(*Culture*) function, where the default (SRS) culture is "en-GB". + -- + -- Other examples for culture are: "en-US" (US accent), "de-DE" (German), "it-IT" (Italian), "ru-RU" (Russian), "zh-CN" (Chinese). + -- + -- Note that the chosen culture needs to be installed on your windows machine. -- - -- # Specific Voice + -- ## Specific Voice -- -- You can use a specific voice for the transmission with the @{SOUNDTEXT.SetVoice}(*VoiceName*) function. Here are some examples -- @@ -287,8 +341,19 @@ do -- Text-To-Speech return self end + --- Set TTS culture - local for the voice. + -- @param #SOUNDTEXT self + -- @param #string Culture TTS culture. Default "en-GB". + -- @return #SOUNDTEXT self + function SOUNDTEXT:SetCulture(Culture) + + self.culture=Culture or "en-GB" + + return self + end + --- Set to use a specific voice name. - -- See the list from `DCS-SR-ExternalAudio.exe --help` or if using google see https://cloud.google.com/text-to-speech/docs/voices + -- See the list from `DCS-SR-ExternalAudio.exe --help` or if using google see [google voices](https://cloud.google.com/text-to-speech/docs/voices). -- @param #SOUNDTEXT self -- @param #string VoiceName Voice name. Note that this will overrule `Gender` and `Culture`. -- @return #SOUNDTEXT self @@ -299,15 +364,4 @@ do -- Text-To-Speech return self end - --- Set TTS culture - local for the voice. - -- @param #SOUNDTEXT self - -- @param #string Culture TTS culture. Default "en-GB". - -- @return #SOUNDTEXT self - function SOUNDTEXT:SetCulture(Culture) - - self.culture=Culture or "en-GB" - - return self - end - end \ No newline at end of file From e03e87f501f7aebcbee7ace099c0884fe1503383 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 5 Jun 2021 23:44:25 +0200 Subject: [PATCH 031/111] Sound update docs --- Moose Development/Moose/Sound/SRS.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index fe3e13211..6260c62ca 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -81,15 +81,16 @@ -- -- ## Set Gender -- --- Use a specific gender by :SetGender("male") or :SetGender("female"). +-- Use a specific gender with the @{#MSRS.SetGender} function, e.g. `SetGender("male")` or `:SetGender("female")`. -- -- ## Set Culture -- --- Use a specific "culture" by :SetCulture("en-US") or :SetCulture("de-DE"). +-- Use a specific "culture" with the @{#MSRS.SetCulture} function, e.g. `:SetCulture("en-US")` or `:SetCulture("de-DE")`. -- -- ## Set Voice -- --- Use a specifc voice by :SetVoice("Microsoft Hedda Desktop"). Note that this must be installed on your windows system. +-- Use a specifc voice with the @{#MSRS.SetVoice} function, e.g, `:SetVoice("Microsoft Hedda Desktop")`. +-- Note that this must be installed on your windows system. -- -- @field #MSRS MSRS = { From 06c3f7998b465ac46f3b69beac8a9b79aae2273a Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 7 Jun 2021 15:09:30 +0200 Subject: [PATCH 032/111] Added function for message duration (#1542) ... and correct flash status setting --- .../Moose/Tasking/CommandCenter.lua | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Tasking/CommandCenter.lua b/Moose Development/Moose/Tasking/CommandCenter.lua index 0673facb1..2bcfe972c 100644 --- a/Moose Development/Moose/Tasking/CommandCenter.lua +++ b/Moose Development/Moose/Tasking/CommandCenter.lua @@ -202,6 +202,7 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName ) self:SetAutoAcceptTasks( true ) self:SetAutoAssignMethod( COMMANDCENTER.AutoAssignMethods.Distance ) self:SetFlashStatus( false ) + self:SetMessageDuration(10) self:HandleEvent( EVENTS.Birth, --- @param #COMMANDCENTER self @@ -682,7 +683,7 @@ end -- @param #string Message The message text. function COMMANDCENTER:MessageToAll( Message ) - self:GetPositionable():MessageToAll( Message, 20, self:GetName() ) + self:GetPositionable():MessageToAll( Message, self.MessageDuration, self:GetName() ) end @@ -692,7 +693,7 @@ end -- @param Wrapper.Group#GROUP MessageGroup The group to receive the message. function COMMANDCENTER:MessageToGroup( Message, MessageGroup ) - self:GetPositionable():MessageToGroup( Message, 15, MessageGroup, self:GetShortText() ) + self:GetPositionable():MessageToGroup( Message, self.MessageDuration, MessageGroup, self:GetShortText() ) end @@ -715,7 +716,7 @@ function COMMANDCENTER:MessageToCoalition( Message ) local CCCoalition = self:GetPositionable():GetCoalition() --TODO: Fix coalition bug! - self:GetPositionable():MessageToCoalition( Message, 15, CCCoalition, self:GetShortText() ) + self:GetPositionable():MessageToCoalition( Message, self.MessageDuration, CCCoalition, self:GetShortText() ) end @@ -795,9 +796,18 @@ end --- Let the command center flash a report of the status of the subscribed task to a group. -- @param #COMMANDCENTER self +-- @param Flash #boolean function COMMANDCENTER:SetFlashStatus( Flash ) self:F() - self.FlashStatus = Flash or true - + self.FlashStatus = Flash and true +end + +--- Duration a command center message is shown. +-- @param #COMMANDCENTER self +-- @param seconds #number +function COMMANDCENTER:SetMessageDuration(seconds) + self:F() + + self.MessageDuration = 10 or seconds end From 82d78c98bba379ce491da3e28744c38cfb9f84e9 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 7 Jun 2021 15:16:04 +0200 Subject: [PATCH 033/111] Update Spawn.lua (#1544) --- Moose Development/Moose/Core/Spawn.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 89d6d681c..c865f5dbc 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -3406,10 +3406,10 @@ function SPAWN:_SpawnCleanUpScheduler() self:T( { SpawnUnitName, Stamp } ) if Stamp.Vec2 then - if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1 then + if (Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y) or (SpawnUnit:GetLife() <= 1) then local NewVec2 = SpawnUnit:GetVec2() if Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y then - -- If the plane is not moving, and is on the ground, assign it with a timestamp... + -- If the plane is not moving or dead, and is on the ground, assign it with a timestamp... if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } ) self:ReSpawn( SpawnCursor ) From 858b00336bdb744de5f20420b94d1d75e0279bf1 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 7 Jun 2021 18:07:14 +0200 Subject: [PATCH 034/111] Update Spawn.lua --- Moose Development/Moose/Core/Spawn.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index c865f5dbc..d25186b66 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -3406,10 +3406,10 @@ function SPAWN:_SpawnCleanUpScheduler() self:T( { SpawnUnitName, Stamp } ) if Stamp.Vec2 then - if (Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y) or (SpawnUnit:GetLife() <= 1) then + if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1 then local NewVec2 = SpawnUnit:GetVec2() - if Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y then - -- If the plane is not moving or dead, and is on the ground, assign it with a timestamp... + if (Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y) or (SpawnUnit:GetLife() <= 1) then + -- If the plane is not moving or dead , and is on the ground, assign it with a timestamp... if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } ) self:ReSpawn( SpawnCursor ) @@ -3427,7 +3427,7 @@ function SPAWN:_SpawnCleanUpScheduler() else if SpawnUnit:InAir() == false then Stamp.Vec2 = SpawnUnit:GetVec2() - if SpawnUnit:GetVelocityKMH() < 1 then + if (SpawnUnit:GetVelocityKMH() < 1) then Stamp.Time = timer.getTime() end else From af9324dd5fda66f70b1c297511b734575a9b264c Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 7 Jun 2021 18:26:43 +0200 Subject: [PATCH 035/111] master updates to develop for InitCleanup() and CC Messages (#1545) * Added function for message duration (#1542) ... and correct flash status setting * Update Spawn.lua (#1544) * Update Spawn.lua --- Moose Development/Moose/Core/Spawn.lua | 6 +++--- .../Moose/Tasking/CommandCenter.lua | 20 ++++++++++++++----- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 89d6d681c..d25186b66 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -3408,8 +3408,8 @@ function SPAWN:_SpawnCleanUpScheduler() if Stamp.Vec2 then if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1 then local NewVec2 = SpawnUnit:GetVec2() - if Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y then - -- If the plane is not moving, and is on the ground, assign it with a timestamp... + if (Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y) or (SpawnUnit:GetLife() <= 1) then + -- If the plane is not moving or dead , and is on the ground, assign it with a timestamp... if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } ) self:ReSpawn( SpawnCursor ) @@ -3427,7 +3427,7 @@ function SPAWN:_SpawnCleanUpScheduler() else if SpawnUnit:InAir() == false then Stamp.Vec2 = SpawnUnit:GetVec2() - if SpawnUnit:GetVelocityKMH() < 1 then + if (SpawnUnit:GetVelocityKMH() < 1) then Stamp.Time = timer.getTime() end else diff --git a/Moose Development/Moose/Tasking/CommandCenter.lua b/Moose Development/Moose/Tasking/CommandCenter.lua index 0673facb1..2bcfe972c 100644 --- a/Moose Development/Moose/Tasking/CommandCenter.lua +++ b/Moose Development/Moose/Tasking/CommandCenter.lua @@ -202,6 +202,7 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName ) self:SetAutoAcceptTasks( true ) self:SetAutoAssignMethod( COMMANDCENTER.AutoAssignMethods.Distance ) self:SetFlashStatus( false ) + self:SetMessageDuration(10) self:HandleEvent( EVENTS.Birth, --- @param #COMMANDCENTER self @@ -682,7 +683,7 @@ end -- @param #string Message The message text. function COMMANDCENTER:MessageToAll( Message ) - self:GetPositionable():MessageToAll( Message, 20, self:GetName() ) + self:GetPositionable():MessageToAll( Message, self.MessageDuration, self:GetName() ) end @@ -692,7 +693,7 @@ end -- @param Wrapper.Group#GROUP MessageGroup The group to receive the message. function COMMANDCENTER:MessageToGroup( Message, MessageGroup ) - self:GetPositionable():MessageToGroup( Message, 15, MessageGroup, self:GetShortText() ) + self:GetPositionable():MessageToGroup( Message, self.MessageDuration, MessageGroup, self:GetShortText() ) end @@ -715,7 +716,7 @@ function COMMANDCENTER:MessageToCoalition( Message ) local CCCoalition = self:GetPositionable():GetCoalition() --TODO: Fix coalition bug! - self:GetPositionable():MessageToCoalition( Message, 15, CCCoalition, self:GetShortText() ) + self:GetPositionable():MessageToCoalition( Message, self.MessageDuration, CCCoalition, self:GetShortText() ) end @@ -795,9 +796,18 @@ end --- Let the command center flash a report of the status of the subscribed task to a group. -- @param #COMMANDCENTER self +-- @param Flash #boolean function COMMANDCENTER:SetFlashStatus( Flash ) self:F() - self.FlashStatus = Flash or true - + self.FlashStatus = Flash and true +end + +--- Duration a command center message is shown. +-- @param #COMMANDCENTER self +-- @param seconds #number +function COMMANDCENTER:SetMessageDuration(seconds) + self:F() + + self.MessageDuration = 10 or seconds end From 65ed8825b0ef5426f14e2a1f40b430e32a9835ed Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 8 Jun 2021 15:59:36 +0200 Subject: [PATCH 036/111] Update Intelligence.lua Added functionality to calculate the position of a cluster after x seconds, based on trajectory (average speed and heading) of a cluster * INTEL:CalcClusterFuturePosition(cluster,seconds) Will also draw arrows on the map if `self.clustermarkers` is true and `self.verbose > 1` Change cluster coordinate updates to better suite cluster movement --- Moose Development/Moose/Ops/Intelligence.lua | 96 +++++++++++++++++--- 1 file changed, 84 insertions(+), 12 deletions(-) diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index 9c4a3a876..7ee0a79fd 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -35,6 +35,7 @@ -- @field #number clustercounter Running number of clusters. -- @field #number dTforget Time interval in seconds before a known contact which is not detected any more is forgotten. -- @field #number clusterradius Radius im kilometers in which groups/units are considered to belong to a cluster +-- @field #number prediction Seconds default to be used with CalcClusterFuturePosition. -- @extends Core.Fsm#FSM --- Top Secret! @@ -95,6 +96,9 @@ INTEL = { Clusters = {}, clustercounter = 1, clusterradius = 15, + clusteranalysis = true, + clustermarkers = false, + prediction = 300, } --- Detected item info. @@ -131,7 +135,7 @@ INTEL = { --- INTEL class version. -- @field #string version -INTEL.version="0.2.1" +INTEL.version="0.2.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -898,7 +902,7 @@ end -- Cluster Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Paint picture of the battle field. +--- [Internal] Paint picture of the battle field. Does Cluster analysis and updates clusters. Sets markers if markers are enabled. -- @param #INTEL self function INTEL:PaintPicture() @@ -918,9 +922,13 @@ function INTEL:PaintPicture() else local mission = _cluster.mission or nil local marker = _cluster.marker + local markerID = _cluster.markerID if marker then marker:Remove() end + if markerID then + COORDINATE:RemoveMark(markerID) + end self:LostCluster(_cluster, mission) end end @@ -989,12 +997,10 @@ function INTEL:PaintPicture() if self.clustermarkers then for _,_cluster in pairs(self.Clusters) do local cluster=_cluster --#INTEL.Cluster - - local coordinate=self:GetClusterCoordinate(cluster) - - - -- Update F10 marker. - self:UpdateClusterMarker(cluster) + --local coordinate=self:GetClusterCoordinate(cluster) + -- Update F10 marker. + self:UpdateClusterMarker(cluster) + self:CalcClusterFuturePosition(cluster,self.prediction) end end end @@ -1108,6 +1114,7 @@ function INTEL:CalcClusterThreatlevelMax(cluster) local threatlevel=0 for _,_contact in pairs(cluster.Contacts) do + local contact=_contact --#INTEL.Contact if contact.threatlevel>threatlevel then @@ -1119,6 +1126,65 @@ function INTEL:CalcClusterThreatlevelMax(cluster) return threatlevel end +--- Calculate cluster heading. +-- @param #INTEL self +-- @param #INTEL.Cluster cluster The cluster of contacts. +-- @return #number Heading average of all groups in the cluster. +function INTEL:CalcClusterDirection(cluster) + + local direction = 0 + local n=0 + for _,_contact in pairs(cluster.Contacts) do + local group = _contact.group -- Wrapper.Group#GROUP + if group:IsAlive() then + direction = direction + group:GetHeading() + n=n+1 + end + end + return math.floor(direction / n) + +end + +--- Calculate cluster speed. +-- @param #INTEL self +-- @param #INTEL.Cluster cluster The cluster of contacts. +-- @return #number Speed average of all groups in the cluster in MPS. +function INTEL:CalcClusterSpeed(cluster) + + local velocity = 0 + local n=0 + for _,_contact in pairs(cluster.Contacts) do + local group = _contact.group -- Wrapper.Group#GROUP + if group:IsAlive() then + velocity = velocity + group:GetVelocityMPS() + n=n+1 + end + end + return math.floor(velocity / n) + +end + +--- Calculate cluster future position after given seconds. +-- @param #INTEL self +-- @param #INTEL.Cluster cluster The cluster of contacts. +-- @param #number seconds Timeframe in seconds. +-- @return Core.Point#COORDINATE Calculated future position of the cluster. +function INTEL:CalcClusterFuturePosition(cluster,seconds) + local speed = self:CalcClusterSpeed(cluster) -- #number MPS + local direction = self:CalcClusterDirection(cluster) -- #number heading + -- local currposition = cluster.coordinate -- Core.Point#COORDINATE + local currposition = self:GetClusterCoordinate(cluster) -- Core.Point#COORDINATE + local distance = speed * seconds -- #number in meters the cluster will travel + local futureposition = currposition:Translate(distance,direction,true,false) + if self.clustermarkers and (self.verbose > 1) then + if cluster.markerID then + COORDINATE:RemoveMark(cluster.markerID) + end + cluster.markerID = currposition:ArrowToAll(futureposition,self.coalition,{1,0,0},1,{1,1,0},0.5,2,true,"Postion Calc") + end + return futureposition +end + --- Check if contact is in any known cluster. -- @param #INTEL self @@ -1216,10 +1282,16 @@ function INTEL:GetClusterCoordinate(cluster) for _,_contact in pairs(cluster.Contacts) do local contact=_contact --#INTEL.Contact - - x=x+contact.position.x - y=y+contact.position.y - z=z+contact.position.z + local group = contact.group --Wrapper.Group#GROUP + local coord = {} + if group:IsAlive() then + coord = group:GetCoordinate() + else + coord = contact.position + end + x=x+coord.x + y=y+coord.y + z=z+coord.z n=n+1 end From 4fa525a4aedf419885cb9d0d2646dad39178fa27 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 8 Jun 2021 22:06:33 +0200 Subject: [PATCH 037/111] Update Airbase.lua --- Moose Development/Moose/Wrapper/Airbase.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 220e3a254..55f5cc632 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -1391,7 +1391,8 @@ function AIRBASE:GetRunwayData(magvar, mark) name==AIRBASE.PersianGulf.Abu_Dhabi_International_Airport or name==AIRBASE.PersianGulf.Dubai_Intl or name==AIRBASE.PersianGulf.Shiraz_International_Airport or - name==AIRBASE.PersianGulf.Kish_International_Airport then + name==AIRBASE.PersianGulf.Kish_International_Airport or + name==AIRBASE.MarianaIslands.Andersen then -- 1-->4, 2-->3, 3-->2, 4-->1 exception=1 From 0c9390914ae491680c9b9d602b7419f9f333aef8 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 8 Jun 2021 23:51:03 +0200 Subject: [PATCH 038/111] Update SRS.lua --- Moose Development/Moose/Sound/SRS.lua | 51 +++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 6260c62ca..d4e4ad2f7 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -319,11 +319,19 @@ function MSRS:PlaySoundFile(Soundfile, Delay) -- Append file. command=command.." --file="..tostring(soundfile) + self:_ExectCommand(command) + + --[[ + + command=command.." > bla.txt" + -- Debug output. self:I(string.format("MSRS PlaySoundfile command=%s", command)) -- Execute SRS command. local x=os.execute(command) + + ]] end @@ -347,11 +355,17 @@ function MSRS:PlaySoundText(SoundText, Delay) -- Append text. command=command..string.format(" --text=\"%s\"", tostring(SoundText.text)) + self:_ExectCommand(command) + + --[[ + command=command.." > bla.txt" + -- Debug putput. self:I(string.format("MSRS PlaySoundfile command=%s", command)) -- Execute SRS command. local x=os.execute(command) + ]] end @@ -375,6 +389,10 @@ function MSRS:PlayText(Text, Delay) -- Append text. command=command..string.format(" --text=\"%s\"", tostring(Text)) + self:_ExectCommand(command) + + --[[ + -- Check that length of command is max 255 chars or os.execute() will not work! if string.len(command)>255 then @@ -403,7 +421,7 @@ function MSRS:PlayText(Text, Delay) end - + ]] end return self @@ -441,7 +459,8 @@ function MSRS:PlayTextFile(TextFile, Delay) local l=string.len(command) -- Execute SRS command. - local x=os.execute(command) + self:_ExectCommand(command) +-- local x=os.execute(command) end @@ -453,6 +472,32 @@ end -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Execute SRS command to play sound using the `DCS-SR-ExternalAudio.exe`. +-- @param #MSRS self +-- @param #string command Command to executer +-- @return #string Command. +function MSRS:_ExectCommand(command) + + -- Create a tmp file. + local filename = os.getenv('TMP') .. "\\MSRS-"..STTS.uuid()..".bat" + + local script = io.open(filename, "w+") + script:write(command.." && exit") + script:close() + + -- Play command. + command=string.format('start /b "" "\"%s\"', filename) + + -- Play file in 0.05 seconds + timer.scheduleFunction(os.execute, command, timer.getTime()+0.01) + + -- Remove file in 1 second. + timer.scheduleFunction(os.remove, filename, timer.getTime()+1) + + return res +end + + --- Get SRS command to play sound using the `DCS-SR-ExternalAudio.exe`. -- @param #MSRS self -- @param #table freqs Frequencies in MHz. @@ -488,6 +533,8 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp -- Command from orig STTS script. Works better for some unknown reason! local command=string.format("start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -h", path, exe, freqs, modus, coal, port, "ROBOT") + + --local command=string.format('start /b "" /d "%s" "%s" -f %s -m %s -c %s -p %s -n "%s" > bla.txt', path, exe, freqs, modus, coal, port, "ROBOT") -- Set voice or gender/culture. if voice then From 8a44fae3d4367cf7b9af3b50dcbd34d0e84839a7 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 9 Jun 2021 13:01:48 +0200 Subject: [PATCH 039/111] Update SRS.lua - VBS script --- Moose Development/Moose/Sound/SRS.lua | 67 ++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 12 deletions(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index d4e4ad2f7..069ee3d34 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -319,7 +319,7 @@ function MSRS:PlaySoundFile(Soundfile, Delay) -- Append file. command=command.." --file="..tostring(soundfile) - self:_ExectCommand(command) + self:_ExecCommand(command) --[[ @@ -355,7 +355,7 @@ function MSRS:PlaySoundText(SoundText, Delay) -- Append text. command=command..string.format(" --text=\"%s\"", tostring(SoundText.text)) - self:_ExectCommand(command) + self:_ExecCommand(command) --[[ command=command.." > bla.txt" @@ -389,7 +389,7 @@ function MSRS:PlayText(Text, Delay) -- Append text. command=command..string.format(" --text=\"%s\"", tostring(Text)) - self:_ExectCommand(command) + self:_ExecCommand(command) --[[ @@ -459,7 +459,7 @@ function MSRS:PlayTextFile(TextFile, Delay) local l=string.len(command) -- Execute SRS command. - self:_ExectCommand(command) + self:_ExecCommand(command) -- local x=os.execute(command) end @@ -476,7 +476,7 @@ end -- @param #MSRS self -- @param #string command Command to executer -- @return #string Command. -function MSRS:_ExectCommand(command) +function MSRS:_ExecCommand(command) -- Create a tmp file. local filename = os.getenv('TMP') .. "\\MSRS-"..STTS.uuid()..".bat" @@ -484,15 +484,56 @@ function MSRS:_ExectCommand(command) local script = io.open(filename, "w+") script:write(command.." && exit") script:close() - + -- Play command. - command=string.format('start /b "" "\"%s\"', filename) - - -- Play file in 0.05 seconds - timer.scheduleFunction(os.execute, command, timer.getTime()+0.01) + command=string.format('start /b "" "%s"', filename) + + if true then + + -- Create a tmp file. + local filenvbs = os.getenv('TMP') .. "\\MSRS-"..STTS.uuid()..".vbs" + + -- VBS script + local script = io.open(filenvbs, "w+") + script:write(string.format('Dim WinScriptHost\n')) + script:write(string.format('Set WinScriptHost = CreateObject("WScript.Shell")\n')) + script:write(string.format('WinScriptHost.Run Chr(34) & "%s" & Chr(34), 0\n', filename)) + script:write(string.format('Set WinScriptHost = Nothing')) + script:close() + + -- Run visual basic script. This still pops up a window but very briefly and does not put the DCS window out of focus. + local runvbs=string.format('cscript.exe //Nologo //B "%s"', filenvbs) + + -- Debug output. + self:I("MSRS execute command="..command) + self:I("MSRS execute VBS command="..runvbs) + + -- Now create powershell process and feed your script to its stdin + --local pipe = io.popen("cscript.exe //Nologo //B", "w") + --pipe:write(script) + --pipe:close() + + -- Play file in 0.01 seconds + os.execute(runvbs) + + -- Remove file in 1 second. + timer.scheduleFunction(os.remove, filename, timer.getTime()+1) + timer.scheduleFunction(os.remove, filenvbs, timer.getTime()+1) + + + else + + -- Debug output. + self:I("MSRS execute command="..command) + + -- Play file in 0.05 seconds + timer.scheduleFunction(os.execute, command, timer.getTime()+0.01) + + -- Remove file in 1 second. + timer.scheduleFunction(os.remove, filename, timer.getTime()+1) + + end - -- Remove file in 1 second. - timer.scheduleFunction(os.remove, filename, timer.getTime()+1) return res end @@ -535,6 +576,8 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp local command=string.format("start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -h", path, exe, freqs, modus, coal, port, "ROBOT") --local command=string.format('start /b "" /d "%s" "%s" -f %s -m %s -c %s -p %s -n "%s" > bla.txt', path, exe, freqs, modus, coal, port, "ROBOT") + + local command=string.format('%s/%s -f %s -m %s -c %s -p %s -n "%s"', path, exe, freqs, modus, coal, port, "ROBOT") -- Set voice or gender/culture. if voice then From e7936950f43bba97c3f0cdc7108948e410a58846 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 10 Jun 2021 00:18:49 +0200 Subject: [PATCH 040/111] ATIS --- Moose Development/Moose/Ops/ATIS.lua | 67 +++++++++++++++---- Moose Development/Moose/Sound/RadioQueue.lua | 2 +- Moose Development/Moose/Sound/RadioSpeech.lua | 34 +++++----- Moose Development/Moose/Sound/SRS.lua | 7 +- Moose Development/Moose/Sound/SoundOutput.lua | 3 - Moose Development/Moose/Utilities/Utils.lua | 5 +- 6 files changed, 77 insertions(+), 41 deletions(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 3799d974a..310d42a9d 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -35,7 +35,7 @@ -- -- === -- --- ## Sound files: Check out the pinned messages in the Moose discord #ops-atis channel. +-- ## Sound files: [MOOSE Sound Files](https://github.com/FlightControl-Master/MOOSE_SOUND/releases) -- -- === -- @@ -91,6 +91,7 @@ -- @field #number relHumidity Relative humidity (used to approximately calculate the dew point). -- @field #boolean useSRS If true, use SRS for transmission. -- @field Sound.SRS#MSRS msrs Moose SRS object. +-- @field #number dTQueueCheck Time interval to check the radio queue. Default 5 sec or 90 sec if SRS is used. -- @extends Core.Fsm#FSM --- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde @@ -258,10 +259,13 @@ -- -- # Text-To-Speech -- --- You can enable text-to-speech ATIS information with the @{#ATIS.SetSTTS}() function. This uses [SRS](http://dcssimpleradio.com/) (Version >= 1.9.6.0) for broadcasing. --- Advantages are that no sound files are necessary. Also the issue that FC3 aircraft hear all transmissions will be circumvented. +-- You can enable text-to-speech ATIS information with the @{#ATIS.SetSRS}() function. This uses [SRS](http://dcssimpleradio.com/) (Version >= 1.9.6.0) for broadcasing. +-- Advantages are that **no sound files** or radio relay units are necessary. Also the issue that FC3 aircraft hear all transmissions will be circumvented. -- --- The @{#ATIS.SetSTTS}() requires you to specify the path to the SRS install directory. +-- The @{#ATIS.SetSRS}() requires you to specify the path to the SRS install directory or more specifically the path to the DCS-SR-ExternalAudio.exe file. +-- +-- Unfortunately, it is not possible to determine the duration of the complete transmission. So once the transmission is finished, there might be some radio silence before +-- the next iteration begins. You can fine tune the time interval between transmissions with the @{#ATIS.SetQueueUpdateTime}() function. The default interval is 90 seconds. -- -- # Examples -- @@ -293,7 +297,14 @@ -- atisAbuDhabi:SetTowerFrequencies({250.5, 119.2}) -- atisAbuDhabi:SetVOR(114.25) -- atisAbuDhabi:Start() +-- +-- ## SRS +-- +-- atis=ATIS:New("Batumi", 305, radio.modulation.AM) +-- atis:SetSRS("D:\\DCS\\_SRS\\", "male", "en-US") +-- atis:Start() -- +-- This uses a male voice with US accent. It requires SRS to be installed in the `D:\DCS\_SRS\` directory. Not that backslashes need to be escaped or simply use slashes (as in linux). -- -- @field #ATIS ATIS = { @@ -378,6 +389,7 @@ ATIS.Alphabet = { -- @field #number PersianGulf +2° (East). -- @field #number TheChannel -10° (West). -- @field #number Syria +5° (East). +-- @field #number MarianaIslands +2° (East). ATIS.RunwayM2T={ Caucasus=0, Nevada=12, @@ -385,6 +397,7 @@ ATIS.RunwayM2T={ PersianGulf=2, TheChannel=-10, Syria=5, + MarianaIslands=2, } --- Whether ICAO phraseology is used for ATIS broadcasts. @@ -395,6 +408,7 @@ ATIS.RunwayM2T={ -- @field #boolean PersianGulf true. -- @field #boolean TheChannel true. -- @field #boolean Syria true. +-- @field #boolean MarianaIslands true. ATIS.ICAOPhraseology={ Caucasus=true, Nevada=false, @@ -402,6 +416,7 @@ ATIS.ICAOPhraseology={ PersianGulf=true, TheChannel=true, Syria=true, + MarianaIslands=true, } --- Nav point data. @@ -574,7 +589,7 @@ _ATIS={} --- ATIS class version. -- @field #string version -ATIS.version="0.9.1" +ATIS.version="0.9.3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -638,6 +653,7 @@ function ATIS:New(airbasename, frequency, modulation) self:SetAltimeterQNH(true) self:SetMapMarks(false) self:SetRelativeHumidity() + self:SetQueueUpdateTime() -- Start State. self:SetStartState("Stopped") @@ -965,7 +981,9 @@ end -- * 170° on the Normany map -- * 182° on the Persian Gulf map -- --- Likewise, to convert *magnetic* into *true* heading, one has to substract easterly and add westerly variation. +-- Likewise, to convert *true* into *magnetic* heading, one has to substract easterly and add westerly variation. +-- +-- Or you make your life simple and just include the sign so you don't have to bother about East/West. -- -- @param #ATIS self -- @param #number magvar Magnetic variation in degrees. Positive for easterly and negative for westerly variation. Default is magnatic declinaton of the used map, c.f. @{Utilities.UTils#UTILS.GetMagneticDeclination}. @@ -1125,9 +1143,20 @@ function ATIS:SetSRS(PathToSRS, Gender, Culture, Voice, Port) self.msrs:SetCulture(Culture) self.msrs:SetVoice(Voice) self.msrs:SetPort(Port) + if self.dTQueueCheck<=10 then + self:SetQueueUpdateTime(90) + end return self end +--- Set the time interval between radio queue updates. +-- @param #ATIS self +-- @param #number TimeInterval Interval in seconds. Default 5 sec. +-- @return #ATIS self +function ATIS:SetQueueUpdateTime(TimeInterval) + self.dTQueueCheck=TimeInterval or 5 +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Start & Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1199,7 +1228,13 @@ function ATIS:onafterStatus(From, Event, To) end -- Info text. - local text=string.format("State %s: Freq=%.3f MHz %s, Relay unit=%s (alive=%s)", fsmstate, self.frequency, UTILS.GetModulationName(self.modulation), tostring(self.relayunitname), relayunitstatus) + local text=string.format("State %s: Freq=%.3f MHz %s", fsmstate, self.frequency, UTILS.GetModulationName(self.modulation)) + if self.useSRS then + text=text..string.format(", SRS path=%s (%s), gender=%s, culture=%s, voice=%s", + tostring(self.msrs.path), tostring(self.msrs.port), tostring(self.msrs.gender), tostring(self.msrs.culture), tostring(self.msrs.voice)) + else + text=text..string.format(", Relay unit=%s (alive=%s)", tostring(self.relayunitname), relayunitstatus) + end self:I(self.lid..text) self:__Status(-60) @@ -1220,8 +1255,6 @@ function ATIS:onafterCheckQueue(From, Event, To) self:Broadcast() - self:__CheckQueue(-120) - else if #self.radioqueue.queue==0 then @@ -1231,10 +1264,12 @@ function ATIS:onafterCheckQueue(From, Event, To) self:T2(self.lid..string.format("Radio queue %d transmissions queued.", #self.radioqueue.queue)) end - -- Check back in 5 seconds. - self:__CheckQueue(-5) + end + + -- Check back in 5 seconds. + self:__CheckQueue(-math.abs(self.dTQueueCheck)) end --- Broadcast ATIS radio message. @@ -1778,10 +1813,14 @@ function ATIS:onafterBroadcast(From, Event, To) end if CLOUDBASE and static then -- Base + local cbase=tostring(tonumber(CLOUDBASE1000)*1000+tonumber(CLOUDBASE0100)*100) + local cceil=tostring(tonumber(CLOUDCEIL1000)*1000+tonumber(CLOUDCEIL0100)*100) if self.metric then - subtitle=string.format("Cloud base %s, ceiling %s meters", CLOUDBASE, CLOUDCEIL) + --subtitle=string.format("Cloud base %s, ceiling %s meters", CLOUDBASE, CLOUDCEIL) + subtitle=string.format("Cloud base %s, ceiling %s meters", cbase, cceil) else - subtitle=string.format("Cloud base %s, ceiling %s feet", CLOUDBASE, CLOUDCEIL) + --subtitle=string.format("Cloud base %s, ceiling %s feet", CLOUDBASE, CLOUDCEIL) + subtitle=string.format("Cloud base %s, ceiling %s feet", cbase, cceil) end if not self.useSRS then self:Transmission(ATIS.Sound.CloudBase, 1.0, subtitle) @@ -2195,7 +2234,7 @@ function ATIS:onafterReport(From, Event, To, Text) local text=string.gsub(text, "m/s", "meters per second") -- Replace ";" by "." - local text=string.gsub(text, ";", ". ") + local text=string.gsub(text, ";", " . ") env.info("FF: "..text) -- Play text-to-speech report. diff --git a/Moose Development/Moose/Sound/RadioQueue.lua b/Moose Development/Moose/Sound/RadioQueue.lua index fbe183fd6..80ac49752 100644 --- a/Moose Development/Moose/Sound/RadioQueue.lua +++ b/Moose Development/Moose/Sound/RadioQueue.lua @@ -11,7 +11,7 @@ -- ### Authors: funkyfranky -- -- @module Sound.RadioQueue --- @image Sound_Radio.JPG +-- @image Core_Radio.JPG --- Manages radio transmissions. -- diff --git a/Moose Development/Moose/Sound/RadioSpeech.lua b/Moose Development/Moose/Sound/RadioSpeech.lua index d4cf22af1..f77c446a2 100644 --- a/Moose Development/Moose/Sound/RadioSpeech.lua +++ b/Moose Development/Moose/Sound/RadioSpeech.lua @@ -11,7 +11,7 @@ -- -- ### Authors: FlightControl -- --- @module Core.RadioSpeech +-- @module Sound.RadioSpeech -- @image Core_Radio.JPG --- Makes the radio speak. @@ -162,31 +162,31 @@ RADIOSPEECH.Vocabulary.RU = { ["8000"] = { "8000", 0.92 }, ["9000"] = { "9000", 0.87 }, - ["Ñтепени"] = { "degrees", 0.5 }, - ["километров"] = { "kilometers", 0.65 }, + ["�тõÿõýø"] = { "degrees", 0.5 }, + ["úøûþüõтрþò"] = { "kilometers", 0.65 }, ["km"] = { "kilometers", 0.65 }, - ["миль"] = { "miles", 0.45 }, + ["üøûь"] = { "miles", 0.45 }, ["mi"] = { "miles", 0.45 }, - ["метры"] = { "meters", 0.41 }, + ["üõтры"] = { "meters", 0.41 }, ["m"] = { "meters", 0.41 }, - ["ноги"] = { "feet", 0.37 }, + ["ýþóø"] = { "feet", 0.37 }, ["br"] = { "br", 1.1 }, ["bra"] = { "bra", 0.3 }, - ["возвращаÑÑÑŒ на базу"] = { "returning_to_base", 1.40 }, - ["на пути к наземной цели"] = { "on_route_to_ground_target", 1.45 }, - ["перехват Ñамолетов"] = { "intercepting_bogeys", 1.22 }, - ["поражение наземной цели"] = { "engaging_ground_target", 1.53 }, - ["захватывающие Ñамолеты"] = { "engaging_bogeys", 1.68 }, - ["колеÑа вверх"] = { "wheels_up", 0.92 }, - ["поÑадка на базу"] = { "landing at base", 1.04 }, - ["патрулирующий"] = { "patrolling", 0.96 }, + ["òþ÷òрðщðÑ�Ñ�ÑŒ ýð ñð÷у"] = { "returning_to_base", 1.40 }, + ["ýð ÿутø ú ýð÷õüýþù цõûø"] = { "on_route_to_ground_target", 1.45 }, + ["ÿõрõхòðт �ðüþûõтþò"] = { "intercepting_bogeys", 1.22 }, + ["ÿþрðöõýøõ ýð÷õüýþù цõûø"] = { "engaging_ground_target", 1.53 }, + ["÷ðхòðтыòðющøõ �ðüþûõты"] = { "engaging_bogeys", 1.68 }, + ["úþûõÑ�ð òòõрх"] = { "wheels_up", 0.92 }, + ["ÿþÑ�ðôúð ýð ñð÷у"] = { "landing at base", 1.04 }, + ["ÿðтруûøрующøù"] = { "patrolling", 0.96 }, - ["за"] = { "for", 0.27 }, - ["и"] = { "and", 0.17 }, - ["в"] = { "at", 0.19 }, + ["÷ð"] = { "for", 0.27 }, + ["ø"] = { "and", 0.17 }, + ["ò"] = { "at", 0.19 }, ["dot"] = { "dot", 0.51 }, ["defender"] = { "defender", 0.45 }, } diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 069ee3d34..44c5a4c3f 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -26,8 +26,8 @@ -- === -- -- ### Author: **funkyfranky** --- @module Addons.SRS --- @image Addons_SRS.png +-- @module Sound.MSRS +-- @image Sound_MSRS.png --- MSRS class. -- @type MSRS @@ -112,9 +112,6 @@ MSRS = { altitude = nil, } ---- Counter. -_MSRSuuid=0 - --- MSRS class version. -- @field #string version MSRS.version="0.0.3" diff --git a/Moose Development/Moose/Sound/SoundOutput.lua b/Moose Development/Moose/Sound/SoundOutput.lua index 2efb9e015..af0e0675b 100644 --- a/Moose Development/Moose/Sound/SoundOutput.lua +++ b/Moose Development/Moose/Sound/SoundOutput.lua @@ -100,9 +100,6 @@ do -- Sound File -- -- SRS sound files need to be located on your local drive (not inside the miz). Therefore, you need to specify the full path. -- - -- - -- ## SRS - -- -- @field #SOUNDFILE SOUNDFILE={ ClassName = "SOUNDFILE", diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index af7642406..52eb6ada7 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1182,6 +1182,9 @@ end -- * NTTR +12 (East), year ~ 2011 -- * Normandy -10 (West), year ~ 1944 -- * Persian Gulf +2 (East), year ~ 2011 +-- * The Cannel Map -10 (West) +-- * Syria +5 (East) +-- * Mariana Islands +2 (East) -- @param #string map (Optional) Map for which the declination is returned. Default is from env.mission.theatre -- @return #number Declination in degrees. function UTILS.GetMagneticDeclination(map) @@ -1203,7 +1206,7 @@ function UTILS.GetMagneticDeclination(map) elseif map==DCSMAP.Syria then declination=5 elseif map==DCSMAP.MarianaIslands then - declination=-2 + declination=2 else declination=0 end From 832d6b1c083bff4f287dfd62f536b81f3cbf8eef Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Thu, 10 Jun 2021 11:20:01 +0200 Subject: [PATCH 041/111] Update Group.lua (#1546) Added invisible and immortal commands on GROUP level. --- Moose Development/Moose/Wrapper/Group.lua | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 263b3a0ec..a9c678ab5 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -2570,6 +2570,30 @@ function GROUP:EnableEmission(switch) return self end +--- Switch on/off invisible flag for the group. +-- @param #GROUP self +-- @param #boolean switch If true, emission is enabled. If false, emission is disabled. +-- @return #GROUP self +function GROUP:SetCommandInvisible(switch) + self:F2( self.GroupName ) + local switch = switch or false + local SetInvisible = {id = 'SetInvisible', params = {value = true}} + self:SetCommand(SetInvisible) + return self +end + +--- Switch on/off immortal flag for the group. +-- @param #GROUP self +-- @param #boolean switch If true, emission is enabled. If false, emission is disabled. +-- @return #GROUP self +function GROUP:SetCommandImmortal(switch) + self:F2( self.GroupName ) + local switch = switch or false + local SetInvisible = {id = 'SetImmortal', params = {value = true}} + self:SetCommand(SetInvisible) + return self +end + --do -- Smoke -- ----- Signal a flare at the position of the GROUP. From c8d2a7e833368e3434c5e47908920ae86e048820 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Thu, 10 Jun 2021 11:59:50 +0200 Subject: [PATCH 042/111] Added commands for immortal and invisible to Wrapper.Group#GROUP (#1547) * Added function for message duration (#1542) ... and correct flash status setting * Update Spawn.lua (#1544) * Update Spawn.lua * Update Group.lua (#1546) Added invisible and immortal commands on GROUP level. --- Moose Development/Moose/Wrapper/Group.lua | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 263b3a0ec..a9c678ab5 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -2570,6 +2570,30 @@ function GROUP:EnableEmission(switch) return self end +--- Switch on/off invisible flag for the group. +-- @param #GROUP self +-- @param #boolean switch If true, emission is enabled. If false, emission is disabled. +-- @return #GROUP self +function GROUP:SetCommandInvisible(switch) + self:F2( self.GroupName ) + local switch = switch or false + local SetInvisible = {id = 'SetInvisible', params = {value = true}} + self:SetCommand(SetInvisible) + return self +end + +--- Switch on/off immortal flag for the group. +-- @param #GROUP self +-- @param #boolean switch If true, emission is enabled. If false, emission is disabled. +-- @return #GROUP self +function GROUP:SetCommandImmortal(switch) + self:F2( self.GroupName ) + local switch = switch or false + local SetInvisible = {id = 'SetImmortal', params = {value = true}} + self:SetCommand(SetInvisible) + return self +end + --do -- Smoke -- ----- Signal a flare at the position of the GROUP. From 3e8db6a1fad274c1ad59bb8a6b23e5729d6c0d04 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 10 Jun 2021 23:23:16 +0200 Subject: [PATCH 043/111] ATIS Improved time TTS format --- Moose Development/Moose/Ops/ATIS.lua | 12 ++++- Moose Development/Moose/Sound/SRS.lua | 46 ++++++++++++------- Moose Development/Moose/Sound/SoundOutput.lua | 4 +- 3 files changed, 43 insertions(+), 19 deletions(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 310d42a9d..92683b48e 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -1395,11 +1395,14 @@ function ATIS:onafterBroadcast(From, Event, To) if time < 0 then time = 24*60*60 + time --avoid negative time around midnight - end + end local clock=UTILS.SecondsToClock(time) local zulu=UTILS.Split(clock, ":") local ZULU=string.format("%s%s", zulu[1], zulu[2]) + if self.useSRS then + ZULU=string.format("%s hours", zulu[1]) + end -- NATO time stamp. 0=Alfa, 1=Bravo, 2=Charlie, etc. @@ -1419,10 +1422,17 @@ function ATIS:onafterBroadcast(From, Event, To) local sunrise=coord:GetSunrise() sunrise=UTILS.Split(sunrise, ":") local SUNRISE=string.format("%s%s", sunrise[1], sunrise[2]) + if self.useSRS then + SUNRISE=string.format("%s %s hours", sunrise[1], sunrise[2]) + end local sunset=coord:GetSunset() sunset=UTILS.Split(sunset, ":") local SUNSET=string.format("%s%s", sunset[1], sunset[2]) + if self.useSRS then + SUNSET=string.format("%s %s hours", sunset[1], sunset[2]) + end + --------------------------------- --- Temperature and Dew Point --- diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 44c5a4c3f..04b785b6c 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -158,7 +158,7 @@ end --- Set path to SRS install directory. More precisely, path to where the DCS- -- @param #MSRS self --- @param #string Path Path to the directory, where the sound file is located. +-- @param #string Path Path to the directory, where the sound file is located. This does **not** contain a final backslash or slash. -- @return #MSRS self function MSRS:SetPath(Path) @@ -177,9 +177,8 @@ function MSRS:SetPath(Path) n=n+1 end - self.path=self.path --.."/" - - self:I(string.format("SRS path=%s", self:GetPath())) + -- Debug output. + self:T(string.format("SRS path=%s", self:GetPath())) return self end @@ -258,13 +257,13 @@ end -- @param #string Gender Gender: "male" or "female" (default). -- @return #MSRS self function MSRS:SetGender(Gender) - self:I("Input gender to "..tostring(Gender)) Gender=Gender or "female" self.gender=Gender:lower() - self:I("Setting gender to "..tostring(self.gender)) + -- Debug output. + self:T("Setting gender to "..tostring(self.gender)) return self end @@ -291,6 +290,26 @@ function MSRS:SetVoice(Voice) return self end +--- Opens a new command window and prints the SRS STTS help. +-- @param #MSRS self +-- @return #MSRS self +function MSRS:Help() + local path=self:GetPath() or STTS.DIRECTORY + local exe=STTS.EXECUTABLE or "DCS-SR-ExternalAudio.exe" + + local filename = os.getenv('TMP') .. "\\MSRS-help-"..STTS.uuid()..".txt" + + local command=string.format("%s/%s --help > %s", path, exe, filename) + os.execute(command) + + local f=assert(io.open(filename, "rb")) + local data=f:read("*all") + f:close() + + env.info(data) + + return self +end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Transmission Functions @@ -450,7 +469,7 @@ function MSRS:PlayTextFile(TextFile, Delay) command=command..string.format(" --textFile=\"%s\"", tostring(TextFile)) -- Debug output. - self:I(string.format("MSRS TextFile command=%s", command)) + self:T(string.format("MSRS TextFile command=%s", command)) -- Count length of command. local l=string.len(command) @@ -502,13 +521,8 @@ function MSRS:_ExecCommand(command) local runvbs=string.format('cscript.exe //Nologo //B "%s"', filenvbs) -- Debug output. - self:I("MSRS execute command="..command) - self:I("MSRS execute VBS command="..runvbs) - - -- Now create powershell process and feed your script to its stdin - --local pipe = io.popen("cscript.exe //Nologo //B", "w") - --pipe:write(script) - --pipe:close() + self:T("MSRS execute command="..command) + self:T("MSRS execute VBS command="..runvbs) -- Play file in 0.01 seconds os.execute(runvbs) @@ -521,7 +535,7 @@ function MSRS:_ExecCommand(command) else -- Debug output. - self:I("MSRS execute command="..command) + self:T("MSRS execute command="..command) -- Play file in 0.05 seconds timer.scheduleFunction(os.execute, command, timer.getTime()+0.01) @@ -592,7 +606,7 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp end -- Debug output. - self:I("MSRS command="..command) + self:T("MSRS command="..command) return command end diff --git a/Moose Development/Moose/Sound/SoundOutput.lua b/Moose Development/Moose/Sound/SoundOutput.lua index af0e0675b..f581e9fe3 100644 --- a/Moose Development/Moose/Sound/SoundOutput.lua +++ b/Moose Development/Moose/Sound/SoundOutput.lua @@ -132,7 +132,7 @@ do -- Sound File self:SetDuration(Duration) -- Debug info: - self:I(string.format("New SOUNDFILE: file name=%s, path=%s", self.filename, self.path)) + self:T(string.format("New SOUNDFILE: file name=%s, path=%s", self.filename, self.path)) return self end @@ -300,7 +300,7 @@ do -- Text-To-Speech --self:SetCulture() -- Debug info: - self:I(string.format("New SOUNDTEXT: text=%s, duration=%.1f sec", self.text, self.duration)) + self:T(string.format("New SOUNDTEXT: text=%s, duration=%.1f sec", self.text, self.duration)) return self end From 6d61c5ee9443b7cedbfef73c3316131b3ce637a7 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 10 Jun 2021 23:33:14 +0200 Subject: [PATCH 044/111] Update Utils.lua --- Moose Development/Moose/Utilities/Utils.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 52eb6ada7..d01707797 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1336,7 +1336,7 @@ function UTILS.GMTToLocalTimeDifference() elseif theatre==DCSMAP.Syria then return 3 -- Damascus is UTC+3 hours elseif theatre==DCSMAP.MarianaIslands then - return 2 -- Guam is UTC+10 hours but is +2 for now. + return 10 -- Guam is UTC+10 hours. else BASE:E(string.format("ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0", tostring(theatre))) return 0 From 973b8323c99683b60aefe9051d6a8219a63d1558 Mon Sep 17 00:00:00 2001 From: cammel tech <47948096+cammeltech@users.noreply.github.com> Date: Fri, 11 Jun 2021 13:38:25 +0200 Subject: [PATCH 045/111] Ratmanager documentation add :SetTspawn(dt) infite spawns - see discord func.rat discussion (#1548) Co-authored-by: wob3155@posteo.de --- Moose Development/Moose/Functional/RAT.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index 327e2e518..691f53338 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -5786,6 +5786,8 @@ end -- If desired, the @{#RATMANAGER} can be stopped by the @{#RATMANAGER.Stop}(stoptime) function. The parameter "stoptime" specifies the time delay in seconds after which the manager stops. -- When this happens, no new aircraft will be spawned and the population will eventually decrease to zero. -- +-- When you are using a time intervall like @{#RATMANAGER.dTspawn}(delay), @{#RATMANAGER} will ignore the amount set with @{#RATMANAGER.New}(). @{#RATMANAGER.dTspawn}(delay) will spawn infinite groups. +-- -- ## Example -- In this example, three different @{#RAT} objects are created (but not spawned manually). The @{#RATMANAGER} takes care that at least five aircraft of each type are alive and that the total number of aircraft -- spawned is 25. The @{#RATMANAGER} is started after 30 seconds and stopped after two hours. From 078573629d168310d17805e76b48ed3251bbde97 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 11 Jun 2021 23:41:37 +0200 Subject: [PATCH 046/111] SRS - added coordinate - added google --- Moose Development/Moose/Functional/Range.lua | 6 +- Moose Development/Moose/Ops/ATIS.lua | 3 + Moose Development/Moose/Sound/SRS.lua | 89 +++++++++++++++---- Moose Development/Moose/Sound/SoundOutput.lua | 48 +++++++++- Moose Development/Moose/Wrapper/Airbase.lua | 83 +++++++++++------ 5 files changed, 180 insertions(+), 49 deletions(-) diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index fc850743c..7ba9dcfc7 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -36,7 +36,7 @@ -- -- === -- --- ## Sound files: Check out the pinned messages in the Moose discord *#func-range* channel. +-- ## Sound files: [MOOSE Sound Files](https://github.com/FlightControl-Master/MOOSE_SOUND/releases) -- -- === -- @@ -91,9 +91,9 @@ -- @field #boolean defaultsmokebomb If true, initialize player settings to smoke bomb. -- @field #boolean autosave If true, automatically save results every X seconds. -- @field #number instructorfreq Frequency on which the range control transmitts. --- @field Core.RadioQueue#RADIOQUEUE instructor Instructor radio queue. +-- @field Sound.RadioQueue#RADIOQUEUE instructor Instructor radio queue. -- @field #number rangecontrolfreq Frequency on which the range control transmitts. --- @field Core.RadioQueue#RADIOQUEUE rangecontrol Range control radio queue. +-- @field Sound.RadioQueue#RADIOQUEUE rangecontrol Range control radio queue. -- @field #string rangecontrolrelayname Name of relay unit. -- @field #string instructorrelayname Name of relay unit. -- @field #string soundpath Path inside miz file where the sound files are located. Default is "Range Soundfiles/". diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 92683b48e..65b0475e5 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -2156,6 +2156,9 @@ function ATIS:onafterBroadcast(From, Event, To) -- VOR if self.vor then subtitle=string.format("VOR frequency %.2f MHz", self.vor) + if self.useSRS then + subtitle=string.format("V O R frequency %.2f MHz", self.vor) + end if not self.useSRS then self:Transmission(ATIS.Sound.VORFrequency, 1.0, subtitle) local f=string.format("%.2f", self.vor) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 04b785b6c..d4253971c 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -17,7 +17,7 @@ -- -- === -- --- ## Sound files: None yet. +-- ## Sound files: [MOOSE Sound Files](https://github.com/FlightControl-Master/MOOSE_SOUND/releases) -- -- === -- @@ -44,6 +44,7 @@ -- @field #string voice Specifc voce. -- @field Core.Point#COORDINATE coordinate Coordinate from where the transmission is send. -- @field #string path Path to the SRS exe. This includes the final slash "/". +-- @field #string google Full path google credentials JSON file, e.g. "C:\Users\username\Downloads\service-account-file.json". -- @extends Core.Base#BASE --- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde @@ -91,6 +92,10 @@ -- -- Use a specifc voice with the @{#MSRS.SetVoice} function, e.g, `:SetVoice("Microsoft Hedda Desktop")`. -- Note that this must be installed on your windows system. +-- +-- ## Set Coordinate +-- +-- Use @{#MSRS.SetCoordinate} to define the origin from where the transmission is broadcasted. -- -- @field #MSRS MSRS = { @@ -107,9 +112,6 @@ MSRS = { volume = 1, speed = 1, coordinate = nil, - latitude = nil, - longitude = nil, - altitude = nil, } --- MSRS class version. @@ -121,8 +123,8 @@ MSRS.version="0.0.3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Add functions to add/remove freqs and modulations. --- TODO: Add coordinate. --- TODO: Add google. +-- DONE: Add coordinate. +-- DONE: Add google. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -290,15 +292,41 @@ function MSRS:SetVoice(Voice) return self end ---- Opens a new command window and prints the SRS STTS help. +--- Set the coordinate from which the transmissions will be broadcasted. +-- @param #MSRS self +-- @param Core.Point#COORDINATE Coordinate Origin of the transmission. +-- @return #MSRS self +function MSRS:SetCoordinate(Coordinate) + + self.coordinate=Coordinate + + return self +end + +--- Use google text-to-speech. +-- @param #MSRS self +-- @param PathToCredentials Full path to the google credentials JSON file, e.g. "C:\Users\username\Downloads\service-account-file.json". +-- @return #MSRS self +function MSRS:SetGoogle(PathToCredentials) + + self.google=PathToCredentials + + return self +end + +--- Print SRS STTS help to DCS log file. -- @param #MSRS self -- @return #MSRS self function MSRS:Help() + + -- Path and exe. local path=self:GetPath() or STTS.DIRECTORY local exe=STTS.EXECUTABLE or "DCS-SR-ExternalAudio.exe" + -- Text file for output. local filename = os.getenv('TMP') .. "\\MSRS-help-"..STTS.uuid()..".txt" + -- Print help. local command=string.format("%s/%s --help > %s", path, exe, filename) os.execute(command) @@ -306,7 +334,11 @@ function MSRS:Help() local data=f:read("*all") f:close() + -- Print to log file. + env.info("SRS STTS help output:") + env.info("======================================================================") env.info(data) + env.info("======================================================================") return self end @@ -371,6 +403,7 @@ function MSRS:PlaySoundText(SoundText, Delay) -- Append text. command=command..string.format(" --text=\"%s\"", tostring(SoundText.text)) + -- Execute command. self:_ExecCommand(command) --[[ @@ -405,6 +438,7 @@ function MSRS:PlayText(Text, Delay) -- Append text. command=command..string.format(" --text=\"%s\"", tostring(Text)) + -- Execute command. self:_ExecCommand(command) --[[ @@ -474,9 +508,8 @@ function MSRS:PlayTextFile(TextFile, Delay) -- Count length of command. local l=string.len(command) - -- Execute SRS command. + -- Execute command. self:_ExecCommand(command) --- local x=os.execute(command) end @@ -491,19 +524,20 @@ end --- Execute SRS command to play sound using the `DCS-SR-ExternalAudio.exe`. -- @param #MSRS self -- @param #string command Command to executer --- @return #string Command. +-- @return #number Return value of os.execute() command. function MSRS:_ExecCommand(command) -- Create a tmp file. - local filename = os.getenv('TMP') .. "\\MSRS-"..STTS.uuid()..".bat" + local filename=os.getenv('TMP').."\\MSRS-"..STTS.uuid()..".bat" - local script = io.open(filename, "w+") + local script=io.open(filename, "w+") script:write(command.." && exit") script:close() -- Play command. command=string.format('start /b "" "%s"', filename) + local res=nil if true then -- Create a tmp file. @@ -525,7 +559,7 @@ function MSRS:_ExecCommand(command) self:T("MSRS execute VBS command="..runvbs) -- Play file in 0.01 seconds - os.execute(runvbs) + res=os.execute(runvbs) -- Remove file in 1 second. timer.scheduleFunction(os.remove, filename, timer.getTime()+1) @@ -537,8 +571,8 @@ function MSRS:_ExecCommand(command) -- Debug output. self:T("MSRS execute command="..command) - -- Play file in 0.05 seconds - timer.scheduleFunction(os.execute, command, timer.getTime()+0.01) + -- Execute command + res=os.execute(command) -- Remove file in 1 second. timer.scheduleFunction(os.remove, filename, timer.getTime()+1) @@ -549,6 +583,19 @@ function MSRS:_ExecCommand(command) return res end +--- Get lat, long and alt from coordinate. +-- @param #MSRS self +-- @param Core.Point#Coordinate Coordinate Coordinate. Can also be a DCS#Vec3. +-- @return #number Latitude. +-- @return #number Longitude. +-- @return #number Altitude. +function MSRS:_GetLatLongAlt(Coordinate) + + local lat, lon, alt=coord.LOtoLL(Coordinate) + + return lat, lon, math.floor(alt) +end + --- Get SRS command to play sound using the `DCS-SR-ExternalAudio.exe`. -- @param #MSRS self @@ -588,6 +635,7 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp --local command=string.format('start /b "" /d "%s" "%s" -f %s -m %s -c %s -p %s -n "%s" > bla.txt', path, exe, freqs, modus, coal, port, "ROBOT") + -- Command. local command=string.format('%s/%s -f %s -m %s -c %s -p %s -n "%s"', path, exe, freqs, modus, coal, port, "ROBOT") -- Set voice or gender/culture. @@ -605,6 +653,17 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp end end + -- Set coordinate. + if self.coordinate then + local lat,lon,alt=self:_GetLatLongAlt(self.coordinate) + command=command..string.format(" -L %.4f -O %.4f -A %d", lat, lon, alt) + end + + -- Set google. + if self.google then + command=command..string.format(' -G "%s"', self.google) + end + -- Debug output. self:T("MSRS command="..command) diff --git a/Moose Development/Moose/Sound/SoundOutput.lua b/Moose Development/Moose/Sound/SoundOutput.lua index f581e9fe3..01fd00483 100644 --- a/Moose Development/Moose/Sound/SoundOutput.lua +++ b/Moose Development/Moose/Sound/SoundOutput.lua @@ -5,7 +5,7 @@ -- ## Features: -- -- * Create a SOUNDFILE object (mp3 or ogg) to be played via DCS or SRS transmissions --- * Create a SOUNDTEXT object for text-to-speech output vis SRS Simple-Text-To-Speech +-- * Create a SOUNDTEXT object for text-to-speech output vis SRS Simple-Text-To-Speech (STTS) -- -- === -- @@ -29,7 +29,7 @@ do -- Sound Base -- @extends Core.Base#BASE - --- Basic sound output inherited by other classes. + --- Basic sound output inherited by other classes suche as SOUNDFILE and SOUNDTEXT. -- -- This class is **not** meant to be used by "ordinary" users. -- @@ -51,6 +51,50 @@ do -- Sound Base return self end + --- Function returns estimated speech time in seconds. + -- Assumptions for time calc: 100 Words per min, avarage of 5 letters for english word so + -- + -- * 5 chars * 100wpm = 500 characters per min = 8.3 chars per second + -- + -- So lengh of msg / 8.3 = number of seconds needed to read it. rounded down to 8 chars per sec map function: + -- + -- * (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min + -- + -- @param #string Text The text string to analyze. + -- @param #number Speed Speed factor. Default 1. + -- @param #boolean isGoogle If true, google text-to-speech is used. + function SOUNDBASE:GetSpeechTime(length,speed,isGoogle) + + local maxRateRatio = 3 + + speed = speed or 1.0 + isGoogle = isGoogle or false + + local speedFactor = 1.0 + if isGoogle then + speedFactor = speed + else + if speed ~= 0 then + speedFactor = math.abs(speed) * (maxRateRatio - 1) / 10 + 1 + end + if speed < 0 then + speedFactor = 1/speedFactor + end + end + + -- Words per minute. + local wpm = math.ceil(100 * speedFactor) + + -- Characters per second. + local cps = math.floor((wpm * 5)/60) + + if type(length) == "string" then + length = string.len(length) + end + + return math.ceil(length/cps) + end + end diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 55f5cc632..615cf6870 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -336,41 +336,53 @@ AIRBASE.TheChannel = { -- * AIRBASE.Syria.Incirlik -- * AIRBASE.Syria.Damascus -- * AIRBASE.Syria.Bassel_Al_Assad +-- * AIRBASE.Syria.Rosh_Pina -- * AIRBASE.Syria.Aleppo --- * AIRBASE.Syria.Qabr_as_Sitt +-- * AIRBASE.Syria.Al_Qusayr -- * AIRBASE.Syria.Wujah_Al_Hajar -- * AIRBASE.Syria.Al_Dumayr +-- * AIRBASE.Syria.Gazipasa +-- * AIRBASE.Syria.Ru_Convoy_4 -- * AIRBASE.Syria.Hatay +-- * AIRBASE.Syria.Nicosia +-- * AIRBASE.Syria.Pinarbashi +-- * AIRBASE.Syria.Paphos +-- * AIRBASE.Syria.Kingsfield +-- * AIRBASE.Syria.Tha'lah -- * AIRBASE.Syria.Haifa -- * AIRBASE.Syria.Khalkhalah -- * AIRBASE.Syria.Megiddo +-- * AIRBASE.Syria.Lakatamia -- * AIRBASE.Syria.Rayak +-- * AIRBASE.Syria.Larnaca -- * AIRBASE.Syria.Mezzeh --- * AIRBASE.Syria.King_Hussein_Air_College --- * AIRBASE.Syria.Jirah +-- * AIRBASE.Syria.Gecitkale +-- * AIRBASE.Syria.Akrotiri +-- * AIRBASE.Syria.Naqoura +-- * AIRBASE.Syria.Gaziantep +-- * AIRBASE.Syria.CVN_71 +-- * AIRBASE.Syria.Sayqal +-- * AIRBASE.Syria.Tiyas +-- * AIRBASE.Syria.Shayrat -- * AIRBASE.Syria.Taftanaz +-- * AIRBASE.Syria.H4 +-- * AIRBASE.Syria.King_Hussein_Air_College -- * AIRBASE.Syria.Rene_Mouawad +-- * AIRBASE.Syria.Jirah -- * AIRBASE.Syria.Ramat_David +-- * AIRBASE.Syria.Qabr_as_Sitt -- * AIRBASE.Syria.Minakh -- * AIRBASE.Syria.Adana_Sakirpasa --- * AIRBASE.Syria.Marj_as_Sultan_South --- * AIRBASE.Syria.Hama --- * AIRBASE.Syria.Al_Qusayr -- * AIRBASE.Syria.Palmyra +-- * AIRBASE.Syria.Hama +-- * AIRBASE.Syria.Ercan +-- * AIRBASE.Syria.Marj_as_Sultan_South -- * AIRBASE.Syria.Tabqa -- * AIRBASE.Syria.Beirut_Rafic_Hariri -- * AIRBASE.Syria.An_Nasiriyah -- * AIRBASE.Syria.Abu_al_Duhur --- * AIRBASE.Syria.H4 --- * AIRBASE.Syria.Gaziantep --- * AIRBASE.Syria.Rosh_Pina --- * AIRBASE.Syria.Sayqal --- * AIRBASE.Syria.Shayrat --- * AIRBASE.Syria.Tiyas --- * AIRBASE.Syria.Tha_lah --- * AIRBASE.Syria.Naqoura -- --- @field Syria +--@field Syria AIRBASE.Syria={ ["Kuweires"]="Kuweires", ["Marj_Ruhayyil"]="Marj Ruhayyil", @@ -380,42 +392,55 @@ AIRBASE.Syria={ ["Incirlik"]="Incirlik", ["Damascus"]="Damascus", ["Bassel_Al_Assad"]="Bassel Al-Assad", + ["Rosh_Pina"]="Rosh Pina", ["Aleppo"]="Aleppo", - ["Qabr_as_Sitt"]="Qabr as Sitt", + ["Al_Qusayr"]="Al Qusayr", ["Wujah_Al_Hajar"]="Wujah Al Hajar", ["Al_Dumayr"]="Al-Dumayr", + ["Gazipasa"]="Gazipasa", + ["Ru_Convoy_4"]="Ru Convoy-4", ["Hatay"]="Hatay", + ["Nicosia"]="Nicosia", + ["Pinarbashi"]="Pinarbashi", + ["Paphos"]="Paphos", + ["Kingsfield"]="Kingsfield", + ["Tha'lah"]="Tha'lah", ["Haifa"]="Haifa", ["Khalkhalah"]="Khalkhalah", ["Megiddo"]="Megiddo", + ["Lakatamia"]="Lakatamia", ["Rayak"]="Rayak", + ["Larnaca"]="Larnaca", ["Mezzeh"]="Mezzeh", - ["King_Hussein_Air_College"]="King Hussein Air College", - ["Jirah"]="Jirah", + ["Gecitkale"]="Gecitkale", + ["Akrotiri"]="Akrotiri", + ["Naqoura"]="Naqoura", + ["Gaziantep"]="Gaziantep", + ["CVN_71"]="CVN-71", + ["Sayqal"]="Sayqal", + ["Tiyas"]="Tiyas", + ["Shayrat"]="Shayrat", ["Taftanaz"]="Taftanaz", + ["H4"]="H4", + ["King_Hussein_Air_College"]="King Hussein Air College", ["Rene_Mouawad"]="Rene Mouawad", + ["Jirah"]="Jirah", ["Ramat_David"]="Ramat David", + ["Qabr_as_Sitt"]="Qabr as Sitt", ["Minakh"]="Minakh", ["Adana_Sakirpasa"]="Adana Sakirpasa", - ["Marj_as_Sultan_South"]="Marj as Sultan South", - ["Hama"]="Hama", - ["Al_Qusayr"]="Al Qusayr", ["Palmyra"]="Palmyra", + ["Hama"]="Hama", + ["Ercan"]="Ercan", + ["Marj_as_Sultan_South"]="Marj as Sultan South", ["Tabqa"]="Tabqa", ["Beirut_Rafic_Hariri"]="Beirut-Rafic Hariri", ["An_Nasiriyah"]="An Nasiriyah", ["Abu_al_Duhur"]="Abu al-Duhur", - ["H4"]="H4", - ["Gaziantep"]="Gaziantep", - ["Rosh_Pina"]="Rosh Pina", - ["Sayqal"]="Sayqal", - ["Shayrat"]="Shayrat", - ["Tiyas"]="Tiyas", - ["Tha_lah"]="Tha'lah", - ["Naqoura"]="Naqoura", } + --- Airbases of the Mariana Islands map. -- -- * AIRBASE.MarianaIslands.Rota_International_Airport From 246935622c23e5f909fb615a020ca292a02213cc Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 12 Jun 2021 21:54:42 +0200 Subject: [PATCH 047/111] Update ATIS.lua --- Moose Development/Moose/Ops/ATIS.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 65b0475e5..368d3c488 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -589,7 +589,7 @@ _ATIS={} --- ATIS class version. -- @field #string version -ATIS.version="0.9.3" +ATIS.version="0.9.5" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -2248,7 +2248,9 @@ function ATIS:onafterReport(From, Event, To, Text) -- Replace ";" by "." local text=string.gsub(text, ";", " . ") - env.info("FF: "..text) + + --Debug output. + self:T("SRS TTS: "..text) -- Play text-to-speech report. self.msrs:PlayText(text) From 117cf8888ac74b0c6e8e30323f17566def370696 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sun, 13 Jun 2021 10:39:07 +0200 Subject: [PATCH 048/111] Update Globals.lua --- Moose Development/Moose/Globals.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Globals.lua b/Moose Development/Moose/Globals.lua index 21d0c7eaa..f63f57e84 100644 --- a/Moose Development/Moose/Globals.lua +++ b/Moose Development/Moose/Globals.lua @@ -43,4 +43,4 @@ else end if __na then BASE:I("Check /Scripts/MissionScripting.lua and comment out the lines with sanitizeModule(''). Use at your own risk!)") -end \ No newline at end of file +end From 8d8070bbd725d031ff842dc84204c259fa19f91f Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 13 Jun 2021 10:59:46 +0200 Subject: [PATCH 049/111] Update Globals.lua Added CR at the end. --- Moose Development/Moose/Globals.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Globals.lua b/Moose Development/Moose/Globals.lua index 734f7c1df..df7fafe36 100644 --- a/Moose Development/Moose/Globals.lua +++ b/Moose Development/Moose/Globals.lua @@ -42,4 +42,4 @@ else end if __na then BASE:I("Check /Scripts/MissionScripting.lua and comment out the lines with sanitizeModule(''). Use at your own risk!)") -end \ No newline at end of file +end From 0396741f3da4557d3da05c980c52828878802661 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 14 Jun 2021 13:18:27 +0200 Subject: [PATCH 050/111] Create CSAR.lua Initial Release --- Moose Development/Moose/Ops/CSAR.lua | 1919 ++++++++++++++++++++++++++ 1 file changed, 1919 insertions(+) create mode 100644 Moose Development/Moose/Ops/CSAR.lua diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua new file mode 100644 index 000000000..9bc98340c --- /dev/null +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -0,0 +1,1919 @@ +--- **Ops** -- Combat Search and Rescue. +-- +-- === +-- +-- **CSAR** - MOOSE based Helicopter CSAR Operations. +-- +-- === +-- +-- ## Missions: +-- +-- ### [CSAR - Combat Search & Rescue](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tbd) +-- +-- === +-- +-- **Main Features:** +-- +-- * MOOSE based Helicopter CSAR Operations. +-- +-- === +-- +-- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original) +-- @module Ops.CSAR +-- @image OPS_CSAR.jpg + +-- Date: June 2021 + +------------------------------------------------------------------------- +--- **CSAR** class, extends #Core.Base#BASE, #Core.Fsm#FSM +-- @type CSAR +-- @field #string ClassName Name of the class. +-- @field #number verbose Verbosity level. +-- @field #string lid Class id string for output to DCS log file. +-- @field #number coalition Coalition side number, e.g. `coalition.side.RED`. +-- @extends Core.Fsm#FSM + +--- *Combat search and rescue (CSAR) are search and rescue operations that are carried out during war that are within or near combat zones.* (Wikipedia) +-- +-- === +-- +-- ![Banner Image](OPS_CSAR.jpg) +-- +-- # CSAR Concept +-- +-- * Object oriented refactoring of Ciribob's fantastic CSAR script. +-- * No need for extra MIST loading. +-- * Additional events to tailor your mission. +-- +-- ## 0. Prerequisites +-- +-- You need to load an .ogg soundfile for the pilot's beacons into the mission, e.g. "beacon.ogg", use a once trigger, "sound to country" for that. +-- Create a late activated single infantry unit as template in the mission editor and name it e.g. "Downed Pilot". +-- +-- ## 1. Basic Setup +-- +-- A basic setup example is the following: +-- +-- -- Instantiate and start a CSAR for the blue side, with template "Downed Pilot" and alias "Luftrettung" +-- local my_csar = CSAR:New(coalition.side.BLUE,"Downed Pilot","Luftrettung") +-- -- options +-- my_csar.immortalcrew = true -- downed pilot spawn is immortal +-- my_csar.invisiblevrew = false -- downed pilot spawn is visible +-- -- start the FSM +-- my_csar:__Start(5) +-- +-- ## 2. Options +-- +-- The following options are available (with their defaults): +-- +-- self.allowDownedPilotCAcontrol = false -- Set to false if you don't want to allow control by Combined Arms. +-- self.allowFARPRescue = true -- allows pilot to be rescued by landing at a FARP or Airbase. Else MASH only. +-- self.autosmoke = false -- automatically smoke downed pilot location when a heli is near. +-- self.coordtype = 1 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. +-- self.csarOncrash = true -- If set to true, will generate a downed pilot when a plane crashes as well. +-- self.csarPrefix = { "helicargo", "MEDEVAC"} -- prefixes used for useprefix=true - DO NOT use # in helicopter names in the Mission Editor! +-- self.enableForAI = true -- set to false to disable AI units from being rescued. +-- self.extractDistance = 500 -- Distance the Downed pilot will run to the rescue helicopter. +-- self.immortalcrew = true -- Set to true to make wounded crew immortal. +-- self.invisiblecrew = false -- Set to true to make wounded crew insvisible. +-- self.loadDistance = 75 -- configure distance for pilots to get into helicopter in meters. +-- self.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes. +-- self.max_units = 6 -- number of pilots that can be carried if #CSAR.AircraftType is undefined. +-- self.messageTime = 30 -- Time to show longer messages for in seconds. +-- self.pilotRuntoExtractPoint = true -- Downed Pilot will run to the rescue helicopter up to self.extractDistance in meters. +-- self.radioSound = "beacon.ogg" -- the name of the sound file to use for the Pilot radio beacons. +-- self.smokecolor = 4 -- Color of smokemarker for blue side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue. +-- self.template = Template or "generic" -- late activated template for downed pilot, usually single infantry soldier. +-- self.useprefix = true -- Use the Prefixed defined below, Requires Unit have the Prefix defined below. +-- +-- ## 3. Events +-- +-- The class comes with a number of FSM-based events that missions designers can use to shape their mission. +-- These are: +-- +-- ### 1. PilotDown. +-- +-- The event is triggered when a new downed pilot is detected. Use e.g. `function my_csar:OnAfterPilotDown(...)` to link into this event: +-- +-- function my_csar:OnAfterPilotDown(from, event, to, spawnedgroup, frequency, groupname, coordinates_text) +-- ... your code here ... +-- end +-- +-- ### 2. Approach. +-- +-- A CSAR helicpoter is closing in on a downed pilot. Use e.g. `function my_csar:OnAfterApproach(...)` to link into this event: +-- +-- function my_csar:OnAfterApproach(from, event, to, heliname, groupname) +-- ... your code here ... +-- end +-- +-- ### 3. Boarded. +-- +-- The pilot has been boarded to the helicopter. Use e.g. `function my_csar:OnAfterBoarded(...)` to link into this event: +-- +-- function my_csar:OnAfterBoarded(from, event, to, heliname, groupname) +-- ... your code here ... +-- end +-- +-- ### 4. Returning. +-- +-- The CSAR helicopter is ready to return to an Airbase, FARP or MASH. Use e.g. `function my_csar:OnAfterReturning(...)` to link into this event: +-- +-- function my_csar:OnAfterReturning(from, event, to, heliname, groupname) +-- ... your code here ... +-- end +-- +-- ### 5. Rescued. +-- +-- The CSAR helicopter has landed close to an Airbase/MASH/FARP and the pilots are safe. Use e.g. `function my_csar:OnAfterRescued(...)` to link into this event: +-- +-- function my_csar:OnAfterRescued(from, event, to, heliunit, heliname) +-- ... your code here ... +-- end +-- +-- ## 4. Spawn downed pilots at location to be picked up. +-- +-- If missions designers want to spawn downed pilots into the field, e.g. at mission begin to give the helicopter guys works, they can do this like so: +-- +-- -- Create downed "Pilot Wagner" in #ZONE "CSAR_Start_1" at a random point for the blue coalition +-- my_csar:_SpawnCsarAtZone( "CSAR_Start_1", coalition.side.BLUE, "Pilot Wagner", true ) +-- +-- +-- @field #CSAR +CSAR = { + ClassName = "CSAR", + verbose = 1, + lid = "", + coalition = 1, + coalitiontxt = "blue", + FreeVHFFrequencies = {}, + UsedVHFFrequencies = {}, + takenOff = {}, + csarUnits = {}, -- table of unit names + downedPilots = {}, + woundedGroups = {}, + landedStatus = {}, + addedTo = {}, + woundedGroups = {}, -- contains the new group of units + inTransitGroups = {}, -- contain a table for each SAR with all units he has with the original names + smokeMarkers = {}, -- tracks smoke markers for groups + heliVisibleMessage = {}, -- tracks if the first message has been sent of the heli being visible + heliCloseMessage = {}, -- tracks heli close message ie heli < 500m distance + max_units = 6, --number of pilots that can be carried + hoverStatus = {}, -- tracks status of a helis hover above a downed pilot + pilotDisabled = {}, -- tracks what aircraft a pilot is disabled for + pilotLives = {}, -- tracks how many lives a pilot has + useprefix = true, -- Use the Prefixed defined below, Requires Unit have the Prefix defined below + csarPrefix = {}, + template = nil, + bluemash = {}, + smokecolor = 4, + rescues = 0, +} + +--- Downed pilots info. +-- @type CSAR.DownedPilot +-- @field #number index Pilot index. +-- @field #string name Name of the spawned group. +-- @field #number side Coalition. +-- @field #string originalUnit Name of the original unit. +-- @field #string desc Description. +-- @field #string typename Typename of Unit. +-- @field #number frequency Frequency of the NDB. +-- @field #string player Player name if applicable. +-- @field Wrapper.Group#GROUP group Spawned group object. +-- @field #number timestamp Timestamp for approach process + +--- Known beacons from the available maps +-- @field #CSAR.SkipFrequencies +CSAR.SkipFrequencies = { + 745,381,384,300.50,312.5,1175,342,735,300.50,353.00, + 440,795,525,520,690,625,291.5,300.50, + 435,309.50,920,1065,274,312.50, + 580,602,297.50,750,485,950,214, + 1025,730,995,455,307,670,329,395,770, + 380,705,300.5,507,740,1030,515,330,309.5,348,462,905,352,1210,942,435, + 324,320,420,311,389,396,862,680,297.5,920,662,866,907,309.5,822,515,470,342,1182,309.5,720,528, + 337,312.5,830,740,309.5,641,312,722,682,1050, + 1116,935,1000,430,577,540,550,560,570, + } + +--- All slot / Limit settings +-- @type CSAR.AircraftType +-- @field #string typename Unit type name. +CSAR.AircraftType = {} -- Type and limit +CSAR.AircraftType["SA342Mistral"] = 2 +CSAR.AircraftType["SA342Minigun"] = 2 +CSAR.AircraftType["SA342L"] = 4 +CSAR.AircraftType["SA342M"] = 4 +CSAR.AircraftType["UH-1H"] = 4 +CSAR.AircraftType["Mi-8MT"] = 8 +CSAR.AircraftType["Mi-24"] = 8 + +--- CSAR class version. +-- @field #string version +CSAR.version="0.1.0r1" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- ToDo list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: Documentation +-- WONTDO: Slot blocker etc + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new CSAR object and start the FSM. +-- @param #CSAR self +-- @param #number Coalition Coalition side. Can also be passed as a string "red", "blue" or "neutral". +-- @param #string Template Name of the late activated infantry unit standing in for the downed pilot. +-- @param #string Alias An *optional* alias how this object is called in the logs etc. +-- @return #CSAR self +function CSAR:New(Coalition, Template, Alias) + + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, FSM:New()) -- #CSAR + + --set Coalition + if Coalition and type(Coalition)=="string" then + if Coalition=="blue" then + self.coalition=coalition.side.BLUE + self.coalitiontxt = Coalition + elseif Coalition=="red" then + self.coalition=coalition.side.RED + self.coalitiontxt = Coalition + elseif Coalition=="neutral" then + self.coalition=coalition.side.NEUTRAL + self.coalitiontxt = Coalition + else + self:E("ERROR: Unknown coalition in CSAR!") + end + else + self.coalition = Coalition + end + + -- Set alias. + if Alias then + self.alias=tostring(Alias) + else + self.alias="Red Cross" + if self.coalition then + if self.coalition==coalition.side.RED then + self.alias="СпаÑение" + elseif self.coalition==coalition.side.BLUE then + self.alias="CSAR" + end + end + end + + -- Set some string id for output to DCS.log file. + self.lid=string.format("%s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown") + + -- Start State. + self:SetStartState("Stopped") + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("Stopped", "Start", "Running") -- Start FSM. + self:AddTransition("*", "Status", "*") -- CSAR status update. + self:AddTransition("*", "PilotDown", "*") -- Downed Pilot added + self:AddTransition("*", "Approach", "*") -- CSAR heli closing in. + self:AddTransition("*", "Boarded", "*") -- Pilot boarded. + self:AddTransition("*", "Returning", "*") -- CSAR returning to base. + self:AddTransition("*", "Rescued", "*") -- Pilot at MASH. + self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. + + -- tables, mainly for tracking actions + self.addedTo = {} + self.allheligroupset = {} -- GROUP_SET of all helis + self.csarUnits = {} -- table of CSAR unit names + self.FreeVHFFrequencies = {} + self.heliVisibleMessage = {} -- tracks if the first message has been sent of the heli being visible + self.heliCloseMessage = {} -- tracks heli close message ie heli < 500m distance + self.hoverStatus = {} -- tracks status of a helis hover above a downed pilot + self.inTransitGroups = {} -- contain a table for each SAR with all units he has with the original names + self.landedStatus = {} + self.lastCrash = {} + self.takenOff = {} + self.smokeMarkers = {} -- tracks smoke markers for groups + self.UsedVHFFrequencies = {} + self.woundedGroups = {} -- contains the new group of units + self.downedPilots = {} -- Replacement woundedGroups + self.downedpilotcounter = 1 + + -- settings, counters etc + self.rescues = 0 -- counter for successful rescue landings at FARP/AFB/MASH + self.csarOncrash = true -- If set to true, will generate a csar when a plane crashes as well. + self.allowDownedPilotCAcontrol = false -- Set to false if you don't want to allow control by Combined arms. + self.enableForAI = true -- set to false to disable AI units from being rescued. + self.smokecolor = 4 -- Color of smokemarker for blue side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue + self.coordtype = 1 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. + self.immortalcrew = true -- Set to true to make wounded crew immortal + self.invisiblecrew = false -- Set to true to make wounded crew insvisible + self.messageTime = 30 -- Time to show longer messages for in seconds + self.pilotRuntoExtractPoint = true -- Downed Pilot will run to the rescue helicopter up to self.extractDistance METERS + self.loadDistance = 75 -- configure distance for pilot to get in helicopter in meters. + self.extractDistance = 500 -- Distance the Downed pilot will run to the rescue helicopter + self.loadtimemax = 135 -- seconds + self.radioSound = "beacon.ogg" -- the name of the sound file to use for the Pilot radio beacons. If this isnt added to the mission BEACONS WONT WORK! + self.allowFARPRescue = true --allows pilot to be rescued by landing at a FARP or Airbase + self.max_units = 6 --number of pilots that can be carried + self.useprefix = true -- Use the Prefixed defined below, Requires Unit have the Prefix defined below + self.csarPrefix = { "helicargo", "MEDEVAC"} -- prefixes used for useprefix=true - DON'T use # in names! + self.template = Template or "generic" -- template for downed pilot + self.mashprefix = {"MASH"} -- prefixes used to find MASHes + self.bluemash = SET_GROUP:New():FilterCoalitions(self.coalition):FilterPrefixes(self.mashprefix):FilterOnce() -- currently only GROUP objects, maybe support STATICs also? + self.autosmoke = false -- automatically smoke location when heli is near + + ------------------------ + --- Pseudo Functions --- + ------------------------ + + --- Triggers the FSM event "Start". Starts the CSAR. Initializes parameters and starts event handlers. + -- @function [parent=#CSAR] Start + -- @param #CSAR self + + --- Triggers the FSM event "Start" after a delay. Starts the CSAR. Initializes parameters and starts event handlers. + -- @function [parent=#CSAR] __Start + -- @param #CSAR self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Stop". Stops the CSAR and all its event handlers. + -- @param #CSAR self + + --- Triggers the FSM event "Stop" after a delay. Stops the CSAR and all its event handlers. + -- @function [parent=#CSAR] __Stop + -- @param #CSAR self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Status". + -- @function [parent=#CSAR] Status + -- @param #CSAR self + + --- Triggers the FSM event "Status" after a delay. + -- @function [parent=#CSAR] __Status + -- @param #CSAR self + -- @param #number delay Delay in seconds. + + --- On After "PilotDown" event. Downed Pilot detected. + -- @function [parent=#CSAR] OnAfterPilotDown + -- @param #CSAR self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Wrapper.Group#GROUP Group Group object of the downed pilot. + -- @param #number Frequency Beacon frequency in kHz. + -- @param #string Leadername Name of the #UNIT of the downed pilot. + -- @param #string CoordinatesText String of the position of the pilot. Format determined by self.coordtype. + + --- On After "Aproach" event. Heli close to downed Pilot. + -- @function [parent=#CSAR] OnAfterApproach + -- @param #CSAR self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string Heliname Name of the helicopter group. + -- @param #string Woundedgroupname Name of the downed pilot's group. + + --- On After "Boarded" event. Downed pilot boarded heli. + -- @function [parent=#CSAR] OnAfterBoarded + -- @param #CSAR self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string Heliname Name of the helicopter group + -- @param #string Woundedgroupname Name of the downed pilot's group + + --- On After "Returning" event. Heli can return home with downed pilot(s). + -- @function [parent=#CSAR] OnAfterReturning + -- @param #CSAR self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string Heliname Name of the helicopter group + -- @param #string Woundedgroupname Name of the downed pilot's group + + --- On After "Rescued" event. Pilot(s) have been brought to the MASH/FARP/AFB. + -- @function [parent=#CSAR] OnAfterRescued + -- @param #CSAR self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Wrapper.Unit#UNIT HeliUnit Unit of the helicopter + -- @param #string HeliName Name of the helicopter group + + return self +end + +------------------------ +--- Helper Functions --- +------------------------ + +--- Function to insert downed pilot tracker object. +-- @param #CSAR self +-- @param Wrapper.Group#GROUP Group The #GROUP object +-- @param #string Groupname Name of the spawned group. +-- @param #number Side Coalition. +-- @param #string OriginalUnit Name of original Unit. +-- @param #string Description Descriptive text. +-- @param #string Typename Typename of unit. +-- @param #number Frequency Frequency of the NDB in Hz +-- @param #string Playername Name of Player (if applicable) +-- @return #CSAR self. +function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername) + self:T({"_CreateDownedPilotTrack",Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername}) + + -- create new entry + local DownedPilot = {} -- #CSAR.DownedPilot + DownedPilot.desc = Description or "" + DownedPilot.frequency = Frequency or 0 + DownedPilot.index = self.downedpilotcounter + DownedPilot.name = Groupname or "" + DownedPilot.originalUnit = OriginalUnit or "" + DownedPilot.player = Playername or "" + DownedPilot.side = Side or 0 + DownedPilot.typename = Typename or "" + DownedPilot.group = Group + DownedPilot.timestamp = 0 + + -- Add Pilot + local PilotTable = self.downedPilots + local counter = self.downedpilotcounter + PilotTable[counter] = {} + PilotTable[counter] = DownedPilot + --self.downedPilots[self.downedpilotcounter]=DownedPilot + self:T({Table=PilotTable}) + self.downedPilots = PilotTable + -- Increase counter + self.downedpilotcounter = self.downedpilotcounter+1 +end + +--- Count pilots on board. +-- @param #CSAR self +-- @param #string _heliName +-- @return #number count +function CSAR:_PilotsOnboard(_heliName) + self:T(self.lid .. " _PilotsOnboard") + local count = 0 + if self.inTransitGroups[_heliName] then + for _, _group in pairs(self.inTransitGroups[_heliName]) do + count = count + 1 + end + end + return count +end + +--- Function to check for dupe eject events. +-- @param #CSAR self +-- @param #string _unitname Name of unit. +-- @return #boolean Outcome +function CSAR:_DoubleEjection(_unitname) + + if self.lastCrash[_unitname] then + local _time = self.lastCrash[_unitname] + + if timer.getTime() - _time < 10 then + self:E(self.lid.."Caught double ejection!") + return true + end + end + + self.lastCrash[_unitname] = timer.getTime() + return false +end + +--- Spawn a downed pilot +-- @param #CSAR self +-- @param #number country Country for template. +-- @param Core.Point#COORDINATE point Coordinate to spawn at. +-- @return Wrapper.Group#GROUP group The #GROUP object. +-- @return #string alias The alias name. +function CSAR:_SpawnPilotInField(country,point) + self:T({country,point}) + local template = self.template + local alias = string.format("Downed Pilot-%d",math.random(1,10000)) + local coalition = self.coalition + local pilotcacontrol = self.allowDownedPilotCAcontrol -- is this really correct? + local _spawnedGroup = SPAWN + :NewWithAlias(template,alias) + :InitCoalition(coalition) + :InitCountry(country) + :InitAIOnOff(pilotcacontrol) + :InitDelayOff() + :SpawnFromCoordinate(point) + + --return object + return _spawnedGroup, alias -- Wrapper.Group#GROUP object +end + +--- Add options to a downed pilot +-- @param #CSAR self +-- @param Wrapper.Group#GROUP group Group to use. +function CSAR:_AddSpecialOptions(group) + self:T(self.lid.." _AddSpecialOptions") + self:T({group}) + + local immortalcrew = self.immortalcrew + local invisiblecrew = self.invisiblecrew + if immortalcrew then + local _setImmortal = { + id = 'SetImmortal', + params = { + value = true + } + } + group:SetCommand(_setImmortal) + end + + if invisiblecrew then + -- invisible + local _setInvisible = { + id = 'SetInvisible', + params = { + value = true + } + } + group:SetCommand(_setInvisible) + end + + group:OptionAlarmStateGreen() + group:OptionROEHoldFire() + +end + +--- Function to spawn a CSAR object into the scene. +-- @param #CSAR self +-- @param #number _coalition Coalition +-- @param DCS#country.id _country Country ID +-- @param Core.Point#COORDINATE _point Coordinate +-- @param #string _typeName Typename +-- @param #string _unitName Unitname +-- @param #string _playerName Playername +-- @param #number _freq Frequency +-- @param #boolean noMessage +-- @param #string _description Description +function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description ) + self:T(self.lid .. " _AddCsar") + self:T({_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description}) + -- local _spawnedGroup = self:_SpawnGroup( _coalition, _country, _point, _typeName ) + local template = self.template + local alias = string.format("Downed Pilot-%d",math.random(1,10000)) + local immortalcrew = self.immortalcrew + local invisiblecrew = self.invisiblecrew + local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point) + local _typeName = _typeName or "PoW" + if not noMessage then + local m = MESSAGE:New("MAYDAY MAYDAY! " .. _typeName .. " is down. ",10,"INFO"):ToCoalition(self.coalition) + end + + if not _freq then + _freq = self:_GenerateADFFrequency() + if not _freq then _freq = "333.25" end --noob catch + end + + if _freq then + self:_AddBeaconToGroup(_spawnedGroup, _freq) + end + + self:_AddSpecialOptions(_spawnedGroup) + -- Generate DESCRIPTION text + local _text = " " + if _playerName ~= nil then + _text = "Pilot " .. _playerName .. " of " .. _unitName .. " - " .. _typeName + elseif _typeName ~= nil then + _text = "AI Pilot of " .. _unitName .. " - " .. _typeName + else + _text = _description + end + + + self:T({_spawnedGroup, _alias}) + + local _GroupName = _spawnedGroup:GetName() or _alias + --local _GroupStructure = { side = _coalition, originalUnit = _unitName, desc = _text, typename = _typeName, frequency = _freq, player = _playerName } + --self.woundedGroups[_GroupName]=_GroupStructure + self:_CreateDownedPilotTrack(_spawnedGroup,_GroupName,_coalition,_unitName,_text,_typeName,_freq,_playerName) + + --self.woundedGroups[_spawnedGroup:GetName()] = { side = _coalition, originalUnit = _unitName, desc = _text, typename = _typeName, frequency = _freq, player = _playerName } + self:_InitSARForPilot(_spawnedGroup, _GroupName, _freq, noMessage) + +end + +--- Function to add a CSAR object into the scene at a zone coordinate. For mission designers wanting to add e.g. PoWs to the scene. +-- @param #CSAR self +-- @param #string _zone Name of the zone. +-- @param #number _coalition Coalition. +-- @param #string _description (optional) Description. +-- @param #boolean _randomPoint (optional) Random yes or no. +-- @param #boolean _nomessage (optional) If true, don't send a message to SAR. +function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _nomessage) + self:T(self.lid .. " _SpawnCsarAtZone") + local freq = self:_GenerateADFFrequency() + local _triggerZone = ZONE:New(_zone) -- trigger to use as reference position + if _triggerZone == nil then + self:E("CSAR - ERROR: Can\'t find zone called " .. _zone, 10) + return + end + + local _description = _description or "none" + + local pos = {} + if _randomPoint then + local _pos = _triggerZone:GetRandomPointVec3() + pos = COORDINATE:NewFromVec3(_pos) + else + pos = _triggerZone:GetCoordinate() + end + + local _country = 0 + if _coalition == coalition.side.BLUE then + _country = country.id.USA + elseif _coalition == coalition.side.RED then + _country = country.id.RUSSIA + else + _country = country.id.UN_PEACEKEEPERS + end + + self:_AddCsar(_coalition, _country, pos, "PoW", "Unknown", nil, freq, _nomessage, _description) +end + +-- TODO: Split in functions per Event type +--- Event handler. +-- @param #CSAR self +function CSAR:_EventHandler(EventData) + self:T(self.lid .. " _EventHandler") + self:T({Event = EventData.id}) + + local _event = EventData -- Core.Event#EVENTDATA + + -- no event + if _event == nil or _event.initiator == nil then + return false + + -- take off + elseif _event.id == EVENTS.Takeoff then -- taken off + self:T(self.lid .. " Event unit - Takeoff") + + local _coalition = _event.IniCoalition + if _coalition ~= self.coalition then + return --ignore! + end + + if _event.IniGroupName then + self.takenOff[_event.IniUnitName] = true + end + + return true + + -- player enter unit + elseif _event.id == EVENTS.PlayerEnterAircraft or _event.id == EVENTS.PlayerEnterUnit then --player entered unit + self:T(self.lid .. " Event unit - Player Enter") + + local _coalition = _event.IniCoalition + if _coalition ~= self.coalition then + return --ignore! + end + + if _event.IniPlayerName then + self.takenOff[_event.IniPlayerName] = nil + end + + -- if its a sar heli, re-add check status script + for _, _heliName in pairs(self.csarUnits) do + if _heliName == _event.IniPlayerName then + -- add back the status script + local DownedPilotTable = self.downedPilots + for _, _groupInfo in pairs(DownedPilotTable) do -- #CSAR.DownedPilot + if _groupInfo.side == _event.IniCoalition then + local _woundedName = _groupInfo.name + self:_CheckWoundedGroupStatus(_heliName,_woundedName) + end + end + end + end + + return true + + elseif (_event.id == EVENTS.PilotDead and self.csarOncrash == false) then + -- Pilot dead + + self:T(self.lid .. " Event unit - Pilot Dead") + + local _unit = _event.IniUnit + local _unitname = _event.IniUnitName + local _group = _event.IniGroup + + if _unit == nil then + return -- error! + end + + local _coalition = _event.IniCoalition + if _coalition ~= self.coalition then + return --ignore! + end + + -- Catch multiple events here? + if self.takenOff[_event.IniUnitName] == true or _group:IsAirborne() then + if self:_DoubleEjection(_unitname) then + return + end + + local m = MESSAGE:New("MAYDAY MAYDAY! " .. _unit:GetTypeName() .. " shot down. No Chute!",10,"Info"):ToCoalition(self.coalition) + -- self:_HandleEjectOrCrash(_unit, true) + else + self:T(self.lid .. " Pilot has not taken off, ignore") + end + + return + + elseif _event.id == EVENTS.PilotDead or _event.id == EVENTS.Ejection then + if _event.id == EVENTS.PilotDead and self.csarOncrash == false then + return + end + self:T(self.lid .. " Event unit - Pilot Ejected") + + local _unit = _event.IniUnit + local _unitname = _event.IniUnitName + local _group = _event.IniGroup + + if _unit == nil then + return -- error! + end + + local _coalition = _unit:GetCoalition() + if _coalition ~= self.coalition then + return --ignore! + end + + if self.enableForAI == false and _event.IniPlayerName == nil then + return + end + + if not self.takenOff[_event.IniUnitName] and not _group:IsAirborne() then + self:T(self.lid .. " Pilot has not taken off, ignore") + return -- give up, pilot hasnt taken off + end + + if self:_DoubleEjection(_unitname) then + return + end + + local _freq = self:_GenerateADFFrequency() + self:_AddCsar(_coalition, _unit:GetCountry(), _unit:GetCoordinate() , _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, false, 0) + + return true + + elseif _event.id == EVENTS.Land then + self:T(self.lid .. " Landing") + + if _event.IniUnitName then + self.takenOff[_event.IniUnitName] = nil + end + + if self.allowFARPRescue then + + local _unit = _event.IniUnit -- Wrapper.Unit#UNIT + --local _unit = _event.initiator + + if _unit == nil then + self:T(self.lid .. " Unit nil on landing") + return -- error! + end + + local _coalition = _event.IniCoalition + if _coalition ~= self.coalition then + return --ignore! + end + + self.takenOff[_event.IniUnitName] = nil + + local _place = _event.Place -- Wrapper.Airbase#AIRBASE + + if _place == nil then + self:T(self.lid .. " Landing Place Nil") + return -- error! + end + + if _place:GetCoalition() == self.coalition or _place:GetCoalition() == coalition.side.NEUTRAL then + self:_RescuePilots(_unit) + else + self:T(string.format("Airfield %d, Unit %d", _place:GetCoalition(), _unit:GetCoalition())) + end + end + + return true + end + +end + +--- Initialize the action for a pilot. +-- @param #CSAR self +-- @param Wrapper.Group#GROUP _downedGroup The group to rescue. +-- @param #string _GroupName Name of the Group +-- @param #number _freq Beacon frequency. +-- @param #boolean _nomessage Send message true or false. +function CSAR:_InitSARForPilot(_downedGroup, _GroupName, _freq, _nomessage) + self:T(self.lid .. " _InitSARForPilot") + local _leader = _downedGroup:GetUnit(1) + --local _groupName = _downedGroup:GetName() + local _groupName = _GroupName + local _freqk = _freq / 1000 + local _coordinatesText = self:_GetPositionOfWounded(_downedGroup) + local _leadername = _leader:GetName() + + if not _nomessage then + local _text = string.format("%s requests SAR at %s, beacon at %.2f KHz", _leadername, _coordinatesText, _freqk) + self:_DisplayToAllSAR(_text) + end + + for _,_heliName in pairs(self.csarUnits) do + self:_CheckWoundedGroupStatus(_heliName, _groupName) + end + + -- trigger FSM event + self:__PilotDown(2,_downedGroup, _freqk, _leadername, _coordinatesText) +end + +--- Check if a name is in downed pilot table +-- @param #CSAR self +-- @param #string name Name to search for. +-- @return #boolean Outcome. +-- @return #CSAR.DownedPilot Table if found else nil. +function CSAR:_CheckNameInDownedPilots(name) + local PilotTable = self.downedPilots --#CSAR.DownedPilot + local found = false + local table = nil + for _,_pilot in pairs(PilotTable) do + if _pilot.name == name then + found = true + table = _pilot + break + end + end + return found, table +end + +--- Check if a name is in downed pilot table and remove it. +-- @param #CSAR self +-- @param #string name Name to search for. +-- @param #boolean force Force removal. +-- @return #boolean Outcome. +function CSAR:_RemoveNameFromDownedPilots(name,force) + local PilotTable = self.downedPilots --#CSAR.DownedPilot + local found = false + for _,_pilot in pairs(PilotTable) do + if _pilot.name == name then + local group = _pilot.group -- Wrapper.Group#GROUP + if group then + if (not group:IsAlive()) or ( force == true) then -- don't delete groups which still exist + found = true + _pilot.desc = nil + _pilot.frequency = nil + _pilot.index = nil + _pilot.name = nil + _pilot.originalUnit = nil + _pilot.player = nil + _pilot.side = nil + _pilot.typename = nil + _pilot.group = nil + _pilot.timestamp = nil + end + end + end + end + return found +end + +--- Check state of wounded group. +-- @param #CSAR self +-- @param #string heliname heliname +-- @param #string woundedgroupname woundedgroupname +function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) + self:T(self.lid .. " _CheckWoundedGroupStatus") + local _heliName = heliname + local _woundedGroupName = woundedgroupname + self:T({Heli = _heliName, Downed = _woundedGroupName}) + -- if wounded group is not here then message alread been sent to SARs + -- stop processing any further + local _found, _downedpilot = self:_CheckNameInDownedPilots(_woundedGroupName) + if not _found then + self:T("...not found in list!") + return + end + + --local _woundedGroup = self:_GetWoundedGroup(_woundedGroupName) + --local _woundedGroup = GROUP:FindByName(_woundedGroupName) -- Wrapper.Group#GROUP + local _woundedGroup = _downedpilot.group + if _woundedGroup ~= nil then + local _heliUnit = self:_GetSARHeli(_heliName) -- Wrapper.Unit#UNIT + + --local _woundedLeader = _woundedGroup:GetUnit(1) -- Wrapper.Unit#UNIT + + local _lookupKeyHeli = _heliName .. "_" .. _woundedGroupName --lookup key for message state tracking + + if _heliUnit == nil then + self.heliVisibleMessage[_lookupKeyHeli] = nil + self.heliCloseMessage[_lookupKeyHeli] = nil + self.landedStatus[_lookupKeyHeli] = nil + self:T("...helinunit nil!") + return + end + + --if self:_CheckGroupNotKIA(_woundedGroup, _woundedGroupName, _heliUnit, _heliName) then + local _heliCoord = _heliUnit:GetCoordinate() + local _leaderCoord = _woundedGroup:GetCoordinate() + local _distance = self:_GetDistance(_heliCoord,_leaderCoord) + if _distance < 3000 and _distance > 0 then + if self:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName) == true then + -- we're close, reschedule + _downedpilot.timestamp = timer.getAbsTime() + self:__Approach(-5,heliname,woundedgroupname) + end + else + self.heliVisibleMessage[_lookupKeyHeli] = nil + --reschedule as units aren't dead yet , schedule for a bit slower though as we're far away + _downedpilot.timestamp = timer.getAbsTime() + self:__Approach(-10,heliname,woundedgroupname) + end + else + self:T("...Downed Pilot KIA?!") + self:_RemoveNameFromDownedPilots(_downedpilot.name) + end +end + +--- Function to pop a smoke at a wounded pilot's positions. +-- @param #CSAR self +-- @param #string _woundedGroupName Name of the group. +-- @param Wrapper.Group#GROUP _woundedLeader Object of the group. +function CSAR:_PopSmokeForGroup(_woundedGroupName, _woundedLeader) + self:T(self.lid .. " _PopSmokeForGroup") + -- have we popped smoke already in the last 5 mins + local _lastSmoke = self.smokeMarkers[_woundedGroupName] + if _lastSmoke == nil or timer.getTime() > _lastSmoke then + + local _smokecolor = self.smokecolor + local _smokecoord = _woundedLeader:GetCoordinate() + _smokecoord:Smoke(_smokecolor) + self.smokeMarkers[_woundedGroupName] = timer.getTime() + 300 -- next smoke time + end +end + +--- Function to pickup the wounded pilot from the ground. +-- @param #CSAR self +-- @param Wrapper.Unit#UNIT _heliUnit Object of the group. +-- @param #string _pilotName Name of the pilot. +-- @param Wrapper.Group#GROUP _woundedGroup Object of the group. +-- @param #string _woundedGroupName Name of the group. +function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) + self:T(self.lid .. " _PickupUnit") + --local _woundedLeader = _woundedGroup:GetUnit(1) + + -- GET IN! + local _heliName = _heliUnit:GetName() + local _groups = self.inTransitGroups[_heliName] + local _unitsInHelicopter = self:_PilotsOnboard(_heliName) + + -- init table if there is none for this helicopter + if not _groups then + self.inTransitGroups[_heliName] = {} + _groups = self.inTransitGroups[_heliName] + end + + -- if the heli can't pick them up, show a message and return + local _maxUnits = self.AircraftType[_heliUnit:GetTypeName()] + if _maxUnits == nil then + _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), 10) + return true + end + + local found,downedgrouptable = self:_CheckNameInDownedPilots(_woundedGroupName) + local grouptable = downedgrouptable --#CSAR.DownedPilot + self.inTransitGroups[_heliName][_woundedGroupName] = + { + -- DONE: Fix with #CSAR.DownedPilot + originalUnit = grouptable.originalUnit, + woundedGroup = _woundedGroupName, + side = self.coalition, + desc = grouptable.desc, + player = grouptable.player, + } + + _woundedGroup:Destroy() + self:_RemoveNameFromDownedPilots(_woundedGroupName,true) + + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s I'm in! Get to the MASH ASAP! ", _heliName, _pilotName), 10) + + self:__Boarded(5,_heliName,_woundedGroupName) + + return true +end + +--- Move group to destination. +-- @param #CSAR self +-- @param Wrapper.Group#GROUP _leader +-- @param Core.Point#COORDINATE _destination +function CSAR:_OrderGroupToMoveToPoint(_leader, _destination) + self:T(self.lid .. " _OrderGroupToMoveToPoint") + local group = _leader + local coordinate = _destination:GetVec2() + --group:RouteGroundTo(_destination,5,"Vee",5) + group:SetAIOn() + group:RouteToVec2(coordinate,5) +end + +--- Function to check if heli is close to group. +-- @param #CSAR self +-- @param #number _distance +-- @param Wrapper.Unit#UNIT _heliUnit +-- @param #string _heliName +-- @param Wrapper.Group#GROUP _woundedGroup +-- @param #string _woundedGroupName +-- @return #boolean Outcome +function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName) + self:T(self.lid .. " _CheckCloseWoundedGroup") + --local _woundedLeader = _woundedGroup:GetUnit(1) -- Wrapper.Unit#UNIT + local _woundedLeader = _woundedGroup + local _lookupKeyHeli = _heliUnit:GetName() .. "_" .. _woundedGroupName --lookup key for message state tracking + + local _found, _pilotable = self:_CheckNameInDownedPilots(_woundedGroupName) -- #boolean, #CSAR.DownedPilot + local _pilotName = _pilotable.desc + --local _pilotName = self.woundedGroups[_woundedGroupName].desc + --local _pilotName = _woundedGroup:GetName() + + local _reset = true + + if (self.autosmoke == true) and (_distance < 500) then + self:_PopSmokeForGroup(_woundedGroupName, _woundedLeader) + end + + if self.heliVisibleMessage[_lookupKeyHeli] == nil then + if self.autosmoke == true then + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud! Land or hover by the smoke.", _heliName, _pilotName), self.messageTime) + else + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud! Request a Flare or Smoke if you need", _heliName, _pilotName), self.messageTime) + end + --mark as shown for THIS heli and THIS group + self.heliVisibleMessage[_lookupKeyHeli] = true + end + + if (_distance < 500) then + + 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), 10) + else + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You're close now! Land in a safe place, I will go there ", _heliName, _pilotName), 10) + end + --mark as shown for THIS heli and THIS group + self.heliCloseMessage[_lookupKeyHeli] = true + end + + -- have we landed close enough? + if not _heliUnit:InAir() then + + -- if you land on them, doesnt matter if they were heading to someone else as you're closer, you win! :) + if self.pilotRuntoExtractPoint == true then + if (_distance < self.extractDistance) then + local _time = self.landedStatus[_lookupKeyHeli] + if _time == nil then + --self.displayMessageToSAR(_heliUnit, "Landed at " .. _distance, 10, true) + self.landedStatus[_lookupKeyHeli] = math.floor( (_distance * self.loadtimemax ) / self.extractDistance ) + _time = self.landedStatus[_lookupKeyHeli] + self:_OrderGroupToMoveToPoint(_woundedGroup, _heliUnit:GetCoordinate()) + self:_DisplayMessageToSAR(_heliUnit, "Wait till " .. _pilotName .. ". Gets in \n" .. _time .. " more seconds.", 10, true) + else + _time = self.landedStatus[_lookupKeyHeli] - 10 + self.landedStatus[_lookupKeyHeli] = _time + end + if _time <= 0 or _distance < self.loadDistance then + self.landedStatus[_lookupKeyHeli] = nil + self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) + return false + end + end + else + if (_distance < self.loadDistance) then + self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) + return false + end + end + else + + local _unitsInHelicopter = self:_PilotsOnboard(_heliName) + local _maxUnits = self.AircraftType[_heliUnit:GetTypeName()] + if _maxUnits == nil then + _maxUnits = self.max_units + end + + if _heliUnit:InAir() and _unitsInHelicopter + 1 <= _maxUnits then + + if _distance < 8.0 then + + --check height! + local leaderheight = _woundedLeader:GetHeight() + if leaderheight < 0 then leaderheight = 0 end + local _height = _heliUnit:GetHeight() - leaderheight + + if _height <= 20.0 then + + local _time = self.hoverStatus[_lookupKeyHeli] + + if _time == nil then + self.hoverStatus[_lookupKeyHeli] = 10 + _time = 10 + else + _time = self.hoverStatus[_lookupKeyHeli] - 10 + self.hoverStatus[_lookupKeyHeli] = _time + end + + if _time > 0 then + self:_DisplayMessageToSAR(_heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you're too far away!", 10, true) + else + self.hoverStatus[_lookupKeyHeli] = nil + self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) + return false + end + _reset = false + else + self:_DisplayMessageToSAR(_heliUnit, "Too high to winch " .. _pilotName .. " \nReduce height and hover for 10 seconds!", 5, true) + end + end + + end + end + end + + if _reset then + self.hoverStatus[_lookupKeyHeli] = nil + end + + if _distance < 500 then + return true + else + return false + end +end + +--- Check if group not KIA. +-- @param #CSAR self +-- @param Wrapper.Group#GROUP _woundedGroup +-- @param #string _woundedGroupName +-- @param Wrapper.Unit#UNIT _heliUnit +-- @param #string _heliName +-- @return #boolean Outcome +function CSAR:_CheckGroupNotKIA(_woundedGroup, _woundedGroupName, _heliUnit, _heliName) + self:T(self.lid .. " _CheckGroupNotKIA") + -- check if unit has died or been picked up + local inTransit = false + if _woundedGroup and _heliUnit then + for _currentHeli, _groups in pairs(self.inTransitGroups) do + if _groups[_woundedGroupName] then + --local _group = _groups[_woundedGroupName] + inTransit = true + self:_DisplayToAllSAR(string.format("%s has been picked up by %s", _woundedGroupName, _currentHeli), self.coalition, _heliName) + break + end -- end name check + end -- end loop + if not inTransit then + -- KIA + self:_DisplayToAllSAR(string.format("%s is KIA ", _woundedGroupName), self.coalition, _heliName) + end + --stops the message being displayed again + self:_RemoveNameFromDownedPilots(_woundedGroupName) + --self.woundedGroups[_woundedGroupName] = nil + end + --continue + return inTransit +end + +--- Monitor in-flight returning groups. +-- @param #CSAR self +-- @param #string heliname Heli name +-- @param #string groupname Group name +function CSAR:_ScheduledSARFlight(heliname,groupname) + self:T(self.lid .. " _ScheduledSARFlight") + self:T({heliname,groupname}) + local _heliUnit = self:_GetSARHeli(heliname) + local _woundedGroupName = groupname + + if (_heliUnit == nil) then + --helicopter crashed? + self.inTransitGroups[heliname] = nil + return + end + + if self.inTransitGroups[heliname] == nil or self.inTransitGroups[heliname][_woundedGroupName] == nil then + -- Groups already rescued + return + end + + local _dist = self:_GetClosestMASH(_heliUnit) + + if _dist == -1 then + return + end + + if _dist < 200 and _heliUnit:InAir() == false then + self:_RescuePilots(_heliUnit) + return + end + + --queue up + self:__Returning(-5,heliname,_woundedGroupName) +end + +--- Mark pilot as rescued and remove from tables. +-- @param #CSAR self +-- @param Wrapper.Unit#UNIT _heliUnit +function CSAR:_RescuePilots(_heliUnit) + self:T(self.lid .. " _RescuePilots") + local _heliName = _heliUnit:GetName() + local _rescuedGroups = self.inTransitGroups[_heliName] + + if _rescuedGroups == nil then + -- Groups already rescued + return + end + + self.inTransitGroups[_heliName] = nil + + local _txt = string.format("%s: The pilots have been taken to the\nmedical clinic. Good job!", _heliName) + + self:_DisplayMessageToSAR(_heliUnit, _txt, 10) + -- trigger event + self:__Rescued(-1,_heliUnit,_heliName) +end + +--- Check and return Wrappe.Unit#UNIT based on the name if alive. +-- @param #CSAR self +-- @param #string _unitname Name of Unit +-- @return #UNIT or nil +function CSAR:_GetSARHeli(_unitName) + self:T(self.lid .. " _GetSARHeli") + local unit = UNIT:FindByName(_unitName) + if unit and unit:IsAlive() then + return unit + else + return nil + end +end + +--- Display message to single Unit. +-- @param #CSAR self +-- @param Wrapper.Unit#UNIT _unit +-- @param #string _text +-- @param #number _time +-- @param #boolean _clear +function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear) + self:T(self.lid .. " _DisplayMessageToSAR") + local group = _unit:GetGroup() + local _clear = _clear or nil + local m = MESSAGE:New(_text,_time,"Info",_clear):ToGroup(group) +end + +--- Function to get string of a group's position. +-- @param #CSAR self +-- @param Wrapper.Controllable#CONTROLLABLE _woundedGroup Group or Unit object. +-- @return #string Coordinates as Text +function CSAR:_GetPositionOfWounded(_woundedGroup) + self:T(self.lid .. " _GetPositionOfWounded") + local _coordinate = _woundedGroup:GetCoordinate() + local _coordinatesText = "None" + if _coordinate then + if self.coordtype == 0 then -- Lat/Long DMTM + _coordinatesText = _coordinate:ToStringLLDDM() + elseif self.coordtype == 1 then -- Lat/Long DMS + _coordinatesText = _coordinate:ToStringLLDMS() + elseif self.coordtype == 2 then -- MGRS + _coordinatesText = _coordinate:ToStringMGRS() + elseif self.coordtype == 3 then -- Bullseye Imperial + local Settings = _SETTINGS:SetImperial() + _coordinatesText = _coordinate:ToStringBULLS(self.coalition,Settings) + else -- Bullseye Metric --(medevac.coordtype == 4) + local Settings = _SETTINGS:SetMetric() + _coordinatesText = _coordinate:ToStringBULLS(self.coalition,Settings) + end + end + return _coordinatesText +end + +--- Display active SAR tasks to player. +-- @param #CSAR self +-- @param #string _unitName Unit to display to +function CSAR:_DisplayActiveSAR(_unitName) + self:T(self.lid .. " _DisplayActiveSAR") + local _msg = "Active MEDEVAC/SAR:" + local _heli = self:_GetSARHeli(_unitName) -- Wrapper.Unit#UNIT + if _heli == nil then + return + end + + local _heliSide = self.coalition + local _csarList = {} + + local _DownedPilotTable = self.downedPilots + self:T({Table=_DownedPilotTable}) + for _, _value in pairs(_DownedPilotTable) do + local _groupName = _value.name + self:T(string.format("Display Active Pilot: %s", tostring(_groupName))) + self:T({Table=_value}) + --local _woundedGroup = GROUP:FindByName(_groupName) + local _woundedGroup = _value.group + if _woundedGroup then + local _coordinatesText = self:_GetPositionOfWounded(_woundedGroup) + local _helicoord = _heli:GetCoordinate() + local _woundcoord = _woundedGroup:GetCoordinate() + local _distance = self:_GetDistance(_helicoord, _woundcoord) + self:T({_distance = _distance}) + table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %.3fKM ", _value.desc, _coordinatesText, _value.frequency / 1000, _distance / 1000.0) }) + end + end + + local function sortDistance(a, b) + return a.dist < b.dist + end + + table.sort(_csarList, sortDistance) + + for _, _line in pairs(_csarList) do + _msg = _msg .. "\n" .. _line.msg + end + + self:_DisplayMessageToSAR(_heli, _msg, 20) +end + +--- Find the closest downed pilot to a heli. +-- @param #CSAR self +-- @param Wrapper.Unit#UNIT _heli Helicopter #UNIT +-- @return #table Table of results +function CSAR:_GetClosestDownedPilot(_heli) + self:T(self.lid .. " _GetClosestDownedPilot") + local _side = self.coalition + local _closestGroup = nil + local _shortestDistance = -1 + local _distance = 0 + local _closestGroupInfo = nil + local _heliCoord = _heli:GetCoordinate() + + local DownedPilotsTable = self.downedPilots + for _, _groupInfo in pairs(DownedPilotsTable) do + local _woundedName = _groupInfo.name + --local _tempWounded = GROUP:FindByName(_woundedName) + local _tempWounded = _groupInfo.group + + -- check group exists and not moving to someone else + if _tempWounded then + local _tempCoord = _tempWounded:GetCoordinate() + _distance = self:_GetDistance(_heliCoord, _tempCoord) + + if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) then + _shortestDistance = _distance + _closestGroup = _tempWounded + _closestGroupInfo = _groupInfo + end + end + end + + return { pilot = _closestGroup, distance = _shortestDistance, groupInfo = _closestGroupInfo } +end + +--- Fire a flare at the point of a downed pilot. +-- @param #CSAR self +-- @param #string _unitName Name of the unit. +function CSAR:_SignalFlare(_unitName) + self:T(self.lid .. " _SignalFlare") + local _heli = self:_GetSARHeli(_unitName) + if _heli == nil then + return + end + + local _closest = self:_GetClosestDownedPilot(_heli) + + if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < 8000.0 then + + local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) + + local _msg = string.format("%s - %.2f KHz ADF - %.3fM - Popping Signal Flare at your %s o\'clock", _closest.groupInfo.desc, _closest.groupInfo.frequency / 1000, _closest.distance, _clockDir) + self:_DisplayMessageToSAR(_heli, _msg, 20) + + local _coord = _closest.pilot:GetCoordinate() + _coord:FlareRed(_clockDir) + else + self:_DisplayMessageToSAR(_heli, "No Pilots within 8KM", 20) + end +end + +--- Display info to all SAR groups. +-- @param #CSAR self +-- @param #string _message +-- @param #number _side +-- @param #string _ignore +function CSAR:_DisplayToAllSAR(_message, _side, _ignore) + self:T(self.lid .. " _DisplayToAllSAR") + for _, _unitName in pairs(self.csarUnits) do + local _unit = self:_GetSARHeli(_unitName) + if _unit then + if not _ignore then + self:_DisplayMessageToSAR(_unit, _message, 10) + end + end + end +end + +---Request smoke at closest downed pilot. +--@param #CSAR self +--@param #string _unitName Name of the helicopter +function CSAR:_Reqsmoke( _unitName ) + self:T(self.lid .. " _Reqsmoke") + local _heli = self:_GetSARHeli(_unitName) + if _heli == nil then + return + end + local _closest = self:_GetClosestDownedPilot(_heli) + if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < 8000.0 then + local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) + local _msg = string.format("%s - %.2f KHz ADF - %.3fM - Popping Blue smoke at your %s o\'clock", _closest.groupInfo.desc, _closest.groupInfo.frequency / 1000, _closest.distance, _clockDir) + self:_DisplayMessageToSAR(_heli, _msg, 20) + local _coord = _closest.pilot:GetCoordinate() + local color = self.smokecolor + _coord:Smoke(color) + else + self:_DisplayMessageToSAR(_heli, "No Pilots within 8KM", 20) + end +end + +--- Determine distance to closest MASH. +-- @param #CSAR self +-- @param Wrapper.Unit#UNIT _heli Helicopter #UNIT +-- @retunr +function CSAR:_GetClosestMASH(_heli) + self:T(self.lid .. " _GetClosestMASH") + local _mashset = self.bluemash -- Core.Set#SET_GROUP + local _mashes = _mashset:GetSetObjects() -- #table + local _shortestDistance = -1 + local _distance = 0 + local _helicoord = _heli:GetCoordinate() + + local function GetCloseAirbase(coordinate,Coalition,Category) + + local a=coordinate: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) + + if dist 0 then + self:_AddBeaconToGroup(group,frequency) + end + end +end + + ------------------------------ + --- FSM internal Functions --- + ------------------------------ + +--- Function called after Start() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +function CSAR:onafterStart(From, Event, To) + self:T({From, Event, To}) + self:I(self.lid .. "Started.") + -- event handler + self:HandleEvent(EVENTS.Takeoff, self._EventHandler) + self:HandleEvent(EVENTS.Land, self._EventHandler) + self:HandleEvent(EVENTS.Ejection, self._EventHandler) + self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) + self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) + self:HandleEvent(EVENTS.Dead, self._EventHandler) + self:_GenerateVHFrequencies() + if self.useprefix then + local prefixes = self.csarPrefix or {} + self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefixes):FilterCategoryHelicopter():FilterStart() + else + self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart() + end + self:__Status(-10) + return self +end + +--- Function called before Status() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +function CSAR:onbeforeStatus(From, Event, To) + self:T({From, Event, To}) + -- housekeeping + self:_AddMedevacMenuItem() + self:_RefreshRadioBeacons() + for _,_sar in pairs (self.csarUnits) do + local PilotTable = self.downedPilots + for _,_entry in pairs (PilotTable) do + local entry = _entry -- #CSAR.DownedPilot + local name = entry.name + local timestamp = entry.timestamp or 0 + local now = timer.getAbsTime() + if now - timestamp > 17 then -- only check if we're not in approach mode, which is iterations of 5 and 10. + self:_CheckWoundedGroupStatus(_sar,name) + end + end + end + return self +end + +--- Function called after Status() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +function CSAR:onafterStatus(From, Event, To) + self:T({From, Event, To}) + -- collect some stats + local NumberOfSARPilots = 0 + for _, _unitName in pairs(self.csarUnits) do + NumberOfSARPilots = NumberOfSARPilots + 1 + end + + local PilotsInFieldN = 0 + for _, _unitName in pairs(self.downedPilots) do + self:T({_unitName}) + if _unitName.name ~= nil then + PilotsInFieldN = PilotsInFieldN + 1 + end + end + + local PilotsBoarded = 0 + for _, _unitName in pairs(self.inTransitGroups) do + for _,_units in pairs(_unitName) do + PilotsBoarded = PilotsBoarded + 1 + end + end + + if self.verbose > 0 then + local text = string.format("%s Active SAR: %d | Downed Pilots in field: %d | Pilots boarded: %d | Rescue (landings): %d",self.lid,NumberOfSARPilots,PilotsInFieldN,PilotsBoarded,self.rescues) + self:T(text) + if self.verbose > 1 then + local m = MESSAGE:New(text,"10","Status"):ToAll() + end + end + self:__Status(-20) + return self +end + +--- Function called after Stop() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +function CSAR:onafterStop(From, Event, To) + self:T({From, Event, To}) + -- event handler + self:UnHandleEvent(EVENTS.Takeoff) + self:UnHandleEvent(EVENTS.Land) + self:UnHandleEvent(EVENTS.Ejection) + self:UnHandleEvent(EVENTS.PlayerEnterUnit) + self:UnHandleEvent(EVENTS.PlayerEnterAircraft) + self:UnHandleEvent(EVENTS.Dead) + self:T(self.lid .. "Stopped.") + return self +end + +--- Function called before Approach() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +-- @param #string Heliname Name of the helicopter group. +-- @param #string Woundedgroupname Name of the downed pilot's group. +function CSAR:onbeforeApproach(From, Event, To, Heliname, Woundedgroupname) + self:T({From, Event, To, Heliname, Woundedgroupname}) + self:_CheckWoundedGroupStatus(Heliname,Woundedgroupname) + return self +end + +--- Function called before Boarded() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +-- @param #string Heliname Name of the helicopter group. +-- @param #string Woundedgroupname Name of the downed pilot's group. +function CSAR:onbeforeBoarded(From, Event, To, Heliname, Woundedgroupname) + self:T({From, Event, To, Heliname, Woundedgroupname}) + self:_ScheduledSARFlight(Heliname,Woundedgroupname) + return self +end + +--- Function called before Returning() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +-- @param #string Heliname Name of the helicopter group. +-- @param #string Woundedgroupname Name of the downed pilot's group. +function CSAR:onbeforeReturning(From, Event, To, Heliname, Woundedgroupname) + self:T({From, Event, To, Heliname, Woundedgroupname}) + self:_ScheduledSARFlight(Heliname,Woundedgroupname) + return self +end + +--- Function called before Rescued() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +-- @param Wrapper.Unit#UNIT HeliUnit Unit of the helicopter. +-- @param #string HeliName Name of the helicopter group. +function CSAR:onbeforeRescued(From, Event, To, HeliUnit, HeliName) + self:T({From, Event, To, HeliName, HeliUnit}) + self.rescues = self.rescues + 1 + return self +end + +--- Function called before PilotDown() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +-- @param Wrapper.Group#GROUP Group Group object of the downed pilot. +-- @param #number Frequency Beacon frequency in kHz. +-- @param #string Leadername Name of the #UNIT of the downed pilot. +-- @param #string CoordinatesText String of the position of the pilot. Format determined by self.coordtype. +function CSAR:onbeforePilotDown(From, Event, To, Group, Frequency, Leadername, CoordinatesText) + self:T({From, Event, To, Group, Frequency, Leadername, CoordinatesText}) + return self +end +-------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 7cde279be1e067248ce7171830ed303e1bfbb283 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 14 Jun 2021 13:20:00 +0200 Subject: [PATCH 051/111] Update Modules.lua added CSAR module --- Moose Development/Moose/Modules.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index ff11712bf..fef4fc982 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -84,6 +84,7 @@ __Moose.Include( 'Scripts/Moose/Ops/ArmyGroup.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Squadron.lua' ) __Moose.Include( 'Scripts/Moose/Ops/AirWing.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Intelligence.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/CSAR.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' ) From 1b37af321febd383b54268e1d3a705c3b422639b Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 14 Jun 2021 16:21:47 +0200 Subject: [PATCH 052/111] Update CSAR.lua Small updates --- Moose Development/Moose/Ops/CSAR.lua | 44 ++++++++++++++++++---------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 9bc98340c..41dc944ab 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -8,7 +8,7 @@ -- -- ## Missions: -- --- ### [CSAR - Combat Search & Rescue](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tbd) +-- ### [CSAR - Combat Search & Rescue](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20CSAR) -- -- === -- @@ -127,7 +127,7 @@ -- -- The CSAR helicopter has landed close to an Airbase/MASH/FARP and the pilots are safe. Use e.g. `function my_csar:OnAfterRescued(...)` to link into this event: -- --- function my_csar:OnAfterRescued(from, event, to, heliunit, heliname) +-- function my_csar:OnAfterRescued(from, event, to, heliunit, heliname, pilotssaved) -- ... your code here ... -- end -- @@ -212,7 +212,7 @@ CSAR.AircraftType["Mi-24"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.0r1" +CSAR.version="0.1.0r2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -309,7 +309,7 @@ function CSAR:New(Coalition, Template, Alias) self.allowDownedPilotCAcontrol = false -- Set to false if you don't want to allow control by Combined arms. self.enableForAI = true -- set to false to disable AI units from being rescued. self.smokecolor = 4 -- Color of smokemarker for blue side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue - self.coordtype = 1 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. + self.coordtype = 2 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. self.immortalcrew = true -- Set to true to make wounded crew immortal self.invisiblecrew = false -- Set to true to make wounded crew insvisible self.messageTime = 30 -- Time to show longer messages for in seconds @@ -383,8 +383,8 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - -- @param #string Heliname Name of the helicopter group - -- @param #string Woundedgroupname Name of the downed pilot's group + -- @param #string Heliname Name of the helicopter group. + -- @param #string Woundedgroupname Name of the downed pilot's group. --- On After "Returning" event. Heli can return home with downed pilot(s). -- @function [parent=#CSAR] OnAfterReturning @@ -392,8 +392,8 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - -- @param #string Heliname Name of the helicopter group - -- @param #string Woundedgroupname Name of the downed pilot's group + -- @param #string Heliname Name of the helicopter group. + -- @param #string Woundedgroupname Name of the downed pilot's group. --- On After "Rescued" event. Pilot(s) have been brought to the MASH/FARP/AFB. -- @function [parent=#CSAR] OnAfterRescued @@ -401,8 +401,9 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - -- @param Wrapper.Unit#UNIT HeliUnit Unit of the helicopter - -- @param #string HeliName Name of the helicopter group + -- @param Wrapper.Unit#UNIT HeliUnit Unit of the helicopter. + -- @param #string HeliName Name of the helicopter group. + -- @param #number PilotsSaved Number of the save pilots on board when landing. return self end @@ -492,10 +493,13 @@ end -- @return #string alias The alias name. function CSAR:_SpawnPilotInField(country,point) self:T({country,point}) + for i=1,10 do + math.random(i,10000) + end local template = self.template local alias = string.format("Downed Pilot-%d",math.random(1,10000)) local coalition = self.coalition - local pilotcacontrol = self.allowDownedPilotCAcontrol -- is this really correct? + local pilotcacontrol = self.allowDownedPilotCAcontrol -- Switch AI on/oof - is this really correct for CA? local _spawnedGroup = SPAWN :NewWithAlias(template,alias) :InitCoalition(coalition) @@ -559,9 +563,9 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla self:T({_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description}) -- local _spawnedGroup = self:_SpawnGroup( _coalition, _country, _point, _typeName ) local template = self.template - local alias = string.format("Downed Pilot-%d",math.random(1,10000)) - local immortalcrew = self.immortalcrew - local invisiblecrew = self.invisiblecrew + --local alias = string.format("Downed Pilot-%d",math.random(1,10000)) + --local immortalcrew = self.immortalcrew + --local invisiblecrew = self.invisiblecrew local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point) local _typeName = _typeName or "PoW" if not noMessage then @@ -1240,13 +1244,20 @@ function CSAR:_RescuePilots(_heliUnit) return end + -- TODO: count saved units? + local PilotsSaved = 0 + for _,_units in pairs(_rescuedGroups) do + PilotsSaved = PilotsSaved + 1 + end + + self.inTransitGroups[_heliName] = nil local _txt = string.format("%s: The pilots have been taken to the\nmedical clinic. Good job!", _heliName) self:_DisplayMessageToSAR(_heliUnit, _txt, 10) -- trigger event - self:__Rescued(-1,_heliUnit,_heliName) + self:__Rescued(-1,_heliUnit,_heliName, PilotsSaved) end --- Check and return Wrappe.Unit#UNIT based on the name if alive. @@ -1917,3 +1928,6 @@ function CSAR:onbeforePilotDown(From, Event, To, Group, Frequency, Leadername, C return self end -------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- End Ops.CSAR +-------------------------------------------------------------------------------------------------------------------------------------------------------------------- + From 10a0793af730dc32b66987eeb4192a9e17543285 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 14 Jun 2021 19:15:58 +0200 Subject: [PATCH 053/111] Update CSAR.lua Small updates to reflect correct measurements. Options to SRS TTS. --- Moose Development/Moose/Ops/CSAR.lua | 191 ++++++++++++++------------- 1 file changed, 96 insertions(+), 95 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 41dc944ab..f95c319da 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -48,7 +48,7 @@ -- ## 0. Prerequisites -- -- You need to load an .ogg soundfile for the pilot's beacons into the mission, e.g. "beacon.ogg", use a once trigger, "sound to country" for that. --- Create a late activated single infantry unit as template in the mission editor and name it e.g. "Downed Pilot". +-- Create a late-activated single infantry unit as template in the mission editor and name it e.g. "Downed Pilot". -- -- ## 1. Basic Setup -- @@ -79,7 +79,7 @@ -- self.loadDistance = 75 -- configure distance for pilots to get into helicopter in meters. -- self.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes. -- self.max_units = 6 -- number of pilots that can be carried if #CSAR.AircraftType is undefined. --- self.messageTime = 30 -- Time to show longer messages for in seconds. +-- self.messageTime = 10 -- Time to show messages for in seconds. Doubled for long messages. -- self.pilotRuntoExtractPoint = true -- Downed Pilot will run to the rescue helicopter up to self.extractDistance in meters. -- self.radioSound = "beacon.ogg" -- the name of the sound file to use for the Pilot radio beacons. -- self.smokecolor = 4 -- Color of smokemarker for blue side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue. @@ -212,13 +212,13 @@ CSAR.AircraftType["Mi-24"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.0r2" +CSAR.version="0.1.2r1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Documentation +-- TODO: SRS Integration (to be tested) -- WONTDO: Slot blocker etc ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -281,7 +281,7 @@ function CSAR:New(Coalition, Template, Alias) self:AddTransition("*", "PilotDown", "*") -- Downed Pilot added self:AddTransition("*", "Approach", "*") -- CSAR heli closing in. self:AddTransition("*", "Boarded", "*") -- Pilot boarded. - self:AddTransition("*", "Returning", "*") -- CSAR returning to base. + self:AddTransition("*", "Returning", "*") -- CSAR able to return to base. self:AddTransition("*", "Rescued", "*") -- Pilot at MASH. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. @@ -312,7 +312,7 @@ function CSAR:New(Coalition, Template, Alias) self.coordtype = 2 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. self.immortalcrew = true -- Set to true to make wounded crew immortal self.invisiblecrew = false -- Set to true to make wounded crew insvisible - self.messageTime = 30 -- Time to show longer messages for in seconds + self.messageTime = 15 -- Time to show longer messages for in seconds self.pilotRuntoExtractPoint = true -- Downed Pilot will run to the rescue helicopter up to self.extractDistance METERS self.loadDistance = 75 -- configure distance for pilot to get in helicopter in meters. self.extractDistance = 500 -- Distance the Downed pilot will run to the rescue helicopter @@ -327,6 +327,14 @@ function CSAR:New(Coalition, Template, Alias) self.bluemash = SET_GROUP:New():FilterCoalitions(self.coalition):FilterPrefixes(self.mashprefix):FilterOnce() -- currently only GROUP objects, maybe support STATICs also? self.autosmoke = false -- automatically smoke location when heli is near + -- WARNING - here'll be dragons + -- for this to work you need to de-sanitize your mission environment in \Scripts\MissionScripting.lua + -- needs SRS => 1.9.6 to work (works on the *server* side) + self.useSRS = false -- Use FF's SRS integration + self.SRSPath = "E:\\Program Files\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your server(!) + self.SRSchannel = 300 -- radio channel + self.SRSModulation = radio.modulation.AM -- modulation + ------------------------ --- Pseudo Functions --- ------------------------ @@ -403,7 +411,7 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string To To state. -- @param Wrapper.Unit#UNIT HeliUnit Unit of the helicopter. -- @param #string HeliName Name of the helicopter group. - -- @param #number PilotsSaved Number of the save pilots on board when landing. + -- @param #number PilotsSaved Number of the saved pilots on board when landing. return self end @@ -444,7 +452,6 @@ function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Descript local counter = self.downedpilotcounter PilotTable[counter] = {} PilotTable[counter] = DownedPilot - --self.downedPilots[self.downedpilotcounter]=DownedPilot self:T({Table=PilotTable}) self.downedPilots = PilotTable -- Increase counter @@ -471,16 +478,13 @@ end -- @param #string _unitname Name of unit. -- @return #boolean Outcome function CSAR:_DoubleEjection(_unitname) - if self.lastCrash[_unitname] then local _time = self.lastCrash[_unitname] - if timer.getTime() - _time < 10 then self:E(self.lid.."Caught double ejection!") return true end end - self.lastCrash[_unitname] = timer.getTime() return false end @@ -508,7 +512,6 @@ function CSAR:_SpawnPilotInField(country,point) :InitDelayOff() :SpawnFromCoordinate(point) - --return object return _spawnedGroup, alias -- Wrapper.Group#GROUP object end @@ -532,7 +535,6 @@ function CSAR:_AddSpecialOptions(group) end if invisiblecrew then - -- invisible local _setInvisible = { id = 'SetInvisible', params = { @@ -561,11 +563,9 @@ end function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description ) self:T(self.lid .. " _AddCsar") self:T({_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description}) - -- local _spawnedGroup = self:_SpawnGroup( _coalition, _country, _point, _typeName ) + local template = self.template - --local alias = string.format("Downed Pilot-%d",math.random(1,10000)) - --local immortalcrew = self.immortalcrew - --local invisiblecrew = self.invisiblecrew + local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point) local _typeName = _typeName or "PoW" if not noMessage then @@ -582,7 +582,7 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla end self:_AddSpecialOptions(_spawnedGroup) - -- Generate DESCRIPTION text + local _text = " " if _playerName ~= nil then _text = "Pilot " .. _playerName .. " of " .. _unitName .. " - " .. _typeName @@ -591,16 +591,13 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla else _text = _description end - - + self:T({_spawnedGroup, _alias}) local _GroupName = _spawnedGroup:GetName() or _alias - --local _GroupStructure = { side = _coalition, originalUnit = _unitName, desc = _text, typename = _typeName, frequency = _freq, player = _playerName } - --self.woundedGroups[_GroupName]=_GroupStructure + self:_CreateDownedPilotTrack(_spawnedGroup,_GroupName,_coalition,_unitName,_text,_typeName,_freq,_playerName) - - --self.woundedGroups[_spawnedGroup:GetName()] = { side = _coalition, originalUnit = _unitName, desc = _text, typename = _typeName, frequency = _freq, player = _playerName } + self:_InitSARForPilot(_spawnedGroup, _GroupName, _freq, noMessage) end @@ -617,7 +614,7 @@ function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _ local freq = self:_GenerateADFFrequency() local _triggerZone = ZONE:New(_zone) -- trigger to use as reference position if _triggerZone == nil then - self:E("CSAR - ERROR: Can\'t find zone called " .. _zone, 10) + self:E(self.lid.."ERROR: Can\'t find zone called " .. _zone, 10) return end @@ -722,10 +719,8 @@ function CSAR:_EventHandler(EventData) if self.takenOff[_event.IniUnitName] == true or _group:IsAirborne() then if self:_DoubleEjection(_unitname) then return - end - + end local m = MESSAGE:New("MAYDAY MAYDAY! " .. _unit:GetTypeName() .. " shot down. No Chute!",10,"Info"):ToCoalition(self.coalition) - -- self:_HandleEjectOrCrash(_unit, true) else self:T(self.lid .. " Pilot has not taken off, ignore") end @@ -779,7 +774,6 @@ function CSAR:_EventHandler(EventData) if self.allowFARPRescue then local _unit = _event.IniUnit -- Wrapper.Unit#UNIT - --local _unit = _event.initiator if _unit == nil then self:T(self.lid .. " Unit nil on landing") @@ -829,7 +823,7 @@ function CSAR:_InitSARForPilot(_downedGroup, _GroupName, _freq, _nomessage) if not _nomessage then local _text = string.format("%s requests SAR at %s, beacon at %.2f KHz", _leadername, _coordinatesText, _freqk) - self:_DisplayToAllSAR(_text) + self:_DisplayToAllSAR(_text,self.coalition,self.messageTime) end for _,_heliName in pairs(self.csarUnits) do @@ -899,7 +893,7 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) local _heliName = heliname local _woundedGroupName = woundedgroupname self:T({Heli = _heliName, Downed = _woundedGroupName}) - -- if wounded group is not here then message alread been sent to SARs + -- if wounded group is not here then message already been sent to SARs -- stop processing any further local _found, _downedpilot = self:_CheckNameInDownedPilots(_woundedGroupName) if not _found then @@ -907,14 +901,10 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) return end - --local _woundedGroup = self:_GetWoundedGroup(_woundedGroupName) - --local _woundedGroup = GROUP:FindByName(_woundedGroupName) -- Wrapper.Group#GROUP local _woundedGroup = _downedpilot.group if _woundedGroup ~= nil then local _heliUnit = self:_GetSARHeli(_heliName) -- Wrapper.Unit#UNIT - --local _woundedLeader = _woundedGroup:GetUnit(1) -- Wrapper.Unit#UNIT - local _lookupKeyHeli = _heliName .. "_" .. _woundedGroupName --lookup key for message state tracking if _heliUnit == nil then @@ -972,9 +962,7 @@ end -- @param #string _woundedGroupName Name of the group. function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) self:T(self.lid .. " _PickupUnit") - --local _woundedLeader = _woundedGroup:GetUnit(1) - - -- GET IN! + -- board local _heliName = _heliUnit:GetName() local _groups = self.inTransitGroups[_heliName] local _unitsInHelicopter = self:_PilotsOnboard(_heliName) @@ -991,7 +979,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), 10) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s, %s. We're already crammed with %d guys! Sorry!", _pilotName, _heliName, _unitsInHelicopter, _unitsInHelicopter), self.messageTime) return true end @@ -1010,7 +998,7 @@ function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupNam _woundedGroup:Destroy() self:_RemoveNameFromDownedPilots(_woundedGroupName,true) - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s I'm in! Get to the MASH ASAP! ", _heliName, _pilotName), 10) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s I'm in! Get to the MASH ASAP! ", _heliName, _pilotName), self.messageTime,true,true) self:__Boarded(5,_heliName,_woundedGroupName) @@ -1025,7 +1013,7 @@ function CSAR:_OrderGroupToMoveToPoint(_leader, _destination) self:T(self.lid .. " _OrderGroupToMoveToPoint") local group = _leader local coordinate = _destination:GetVec2() - --group:RouteGroundTo(_destination,5,"Vee",5) + group:SetAIOn() group:RouteToVec2(coordinate,5) end @@ -1040,14 +1028,13 @@ end -- @return #boolean Outcome function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName) self:T(self.lid .. " _CheckCloseWoundedGroup") - --local _woundedLeader = _woundedGroup:GetUnit(1) -- Wrapper.Unit#UNIT + local _woundedLeader = _woundedGroup local _lookupKeyHeli = _heliUnit:GetName() .. "_" .. _woundedGroupName --lookup key for message state tracking local _found, _pilotable = self:_CheckNameInDownedPilots(_woundedGroupName) -- #boolean, #CSAR.DownedPilot local _pilotName = _pilotable.desc - --local _pilotName = self.woundedGroups[_woundedGroupName].desc - --local _pilotName = _woundedGroup:GetName() + local _reset = true @@ -1057,9 +1044,9 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG if self.heliVisibleMessage[_lookupKeyHeli] == nil then if self.autosmoke == true then - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud! Land or hover by the smoke.", _heliName, _pilotName), self.messageTime) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud! Land or hover by the smoke.", _heliName, _pilotName), self.messageTime,true,true) else - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud! Request a Flare or Smoke if you need", _heliName, _pilotName), self.messageTime) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud! Request a Flare or Smoke if you need", _heliName, _pilotName), self.messageTime,true,true) end --mark as shown for THIS heli and THIS group self.heliVisibleMessage[_lookupKeyHeli] = true @@ -1069,9 +1056,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), 10) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You're close now! Land or hover at the smoke.", _heliName, _pilotName), self.messageTime,true,true) else - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You're close now! Land in a safe place, I will go there ", _heliName, _pilotName), 10) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You're close now! Land in a safe place, I will go there ", _heliName, _pilotName), self.messageTime,true,true) end --mark as shown for THIS heli and THIS group self.heliCloseMessage[_lookupKeyHeli] = true @@ -1085,11 +1072,10 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG if (_distance < self.extractDistance) then local _time = self.landedStatus[_lookupKeyHeli] if _time == nil then - --self.displayMessageToSAR(_heliUnit, "Landed at " .. _distance, 10, true) self.landedStatus[_lookupKeyHeli] = math.floor( (_distance * self.loadtimemax ) / self.extractDistance ) _time = self.landedStatus[_lookupKeyHeli] self:_OrderGroupToMoveToPoint(_woundedGroup, _heliUnit:GetCoordinate()) - self:_DisplayMessageToSAR(_heliUnit, "Wait till " .. _pilotName .. ". Gets in \n" .. _time .. " more seconds.", 10, true) + self:_DisplayMessageToSAR(_heliUnit, "Wait till " .. _pilotName .. " gets in. \nETA " .. _time .. " more seconds.", self.messageTime, true) else _time = self.landedStatus[_lookupKeyHeli] - 10 self.landedStatus[_lookupKeyHeli] = _time @@ -1136,7 +1122,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG end if _time > 0 then - self:_DisplayMessageToSAR(_heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you're too far away!", 10, true) + self:_DisplayMessageToSAR(_heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you're too far away!", self.messageTime, true) else self.hoverStatus[_lookupKeyHeli] = nil self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) @@ -1144,7 +1130,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG end _reset = false else - self:_DisplayMessageToSAR(_heliUnit, "Too high to winch " .. _pilotName .. " \nReduce height and hover for 10 seconds!", 5, true) + self:_DisplayMessageToSAR(_heliUnit, "Too high to winch " .. _pilotName .. " \nReduce height and hover for 10 seconds!", self.messageTime, true,true) end end @@ -1177,19 +1163,17 @@ function CSAR:_CheckGroupNotKIA(_woundedGroup, _woundedGroupName, _heliUnit, _he if _woundedGroup and _heliUnit then for _currentHeli, _groups in pairs(self.inTransitGroups) do if _groups[_woundedGroupName] then - --local _group = _groups[_woundedGroupName] inTransit = true - self:_DisplayToAllSAR(string.format("%s has been picked up by %s", _woundedGroupName, _currentHeli), self.coalition, _heliName) + self:_DisplayToAllSAR(string.format("%s has been picked up by %s", _woundedGroupName, _currentHeli), self.coalition, self.messageTime) break end -- end name check end -- end loop if not inTransit then -- KIA - self:_DisplayToAllSAR(string.format("%s is KIA ", _woundedGroupName), self.coalition, _heliName) + self:_DisplayToAllSAR(string.format("%s is KIA ", _woundedGroupName), self.coalition, self.messageTime) end --stops the message being displayed again self:_RemoveNameFromDownedPilots(_woundedGroupName) - --self.woundedGroups[_woundedGroupName] = nil end --continue return inTransit @@ -1244,18 +1228,14 @@ function CSAR:_RescuePilots(_heliUnit) return end - -- TODO: count saved units? - local PilotsSaved = 0 - for _,_units in pairs(_rescuedGroups) do - PilotsSaved = PilotsSaved + 1 - end - + -- DONE: count saved units? + local PilotsSaved = self:_PilotsOnboard(_heliName) self.inTransitGroups[_heliName] = nil - local _txt = string.format("%s: The pilots have been taken to the\nmedical clinic. Good job!", _heliName) + local _txt = string.format("%s: The %d pilot(s) have been taken to the\nmedical clinic. Good job!", _heliName, PilotsSaved) - self:_DisplayMessageToSAR(_heliUnit, _txt, 10) + self:_DisplayMessageToSAR(_heliUnit, _txt, self.messageTime) -- trigger event self:__Rescued(-1,_heliUnit,_heliName, PilotsSaved) end @@ -1276,15 +1256,26 @@ end --- Display message to single Unit. -- @param #CSAR self --- @param Wrapper.Unit#UNIT _unit --- @param #string _text --- @param #number _time --- @param #boolean _clear -function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear) +-- @param Wrapper.Unit#UNIT _unit Unit #UNIT to display to. +-- @param #string _text Text of message. +-- @param #number _time Message show duration. +-- @param #boolean _clear (optional) Clear screen. +-- @param #boolean _speak (optional) Speak message via SRS. +function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak) self:T(self.lid .. " _DisplayMessageToSAR") local group = _unit:GetGroup() local _clear = _clear or nil + local _time = _time or self.messageTime local m = MESSAGE:New(_text,_time,"Info",_clear):ToGroup(group) + -- integrate SRS + if _speak and self.useSRS then + local srstext = SOUNDTEXT:New(_text) + local path = self.SRSPath + local modulation = self.SRSModulation + local channel = self.SRSchannel + local msrs = MSRS:New(path,channel,modulation) + msrs:PlaySoundText(srstext, 2) + end end --- Function to get string of a group's position. @@ -1341,7 +1332,14 @@ function CSAR:_DisplayActiveSAR(_unitName) local _woundcoord = _woundedGroup:GetCoordinate() local _distance = self:_GetDistance(_helicoord, _woundcoord) self:T({_distance = _distance}) - table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %.3fKM ", _value.desc, _coordinatesText, _value.frequency / 1000, _distance / 1000.0) }) + -- change distance to miles if self.coordtype < 4 + local distancetext = "" + if self.coordtype < 4 then + distancetext = string.format("%.3fnm",UTILS.MetersToNM(_distance)) + else + distancetext = string.format("%.3fkm", _distance/1000.0) + end + table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %s ", _value.desc, _coordinatesText, _value.frequency / 1000, distancetext) }) end end @@ -1355,7 +1353,7 @@ function CSAR:_DisplayActiveSAR(_unitName) _msg = _msg .. "\n" .. _line.msg end - self:_DisplayMessageToSAR(_heli, _msg, 20) + self:_DisplayMessageToSAR(_heli, _msg, self.messageTime*2) end --- Find the closest downed pilot to a heli. @@ -1374,7 +1372,6 @@ function CSAR:_GetClosestDownedPilot(_heli) local DownedPilotsTable = self.downedPilots for _, _groupInfo in pairs(DownedPilotsTable) do local _woundedName = _groupInfo.name - --local _tempWounded = GROUP:FindByName(_woundedName) local _tempWounded = _groupInfo.group -- check group exists and not moving to someone else @@ -1408,29 +1405,34 @@ function CSAR:_SignalFlare(_unitName) if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < 8000.0 then local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) - - local _msg = string.format("%s - %.2f KHz ADF - %.3fM - Popping Signal Flare at your %s o\'clock", _closest.groupInfo.desc, _closest.groupInfo.frequency / 1000, _closest.distance, _clockDir) - self:_DisplayMessageToSAR(_heli, _msg, 20) + local _distance = 0 + if self.coordtype < 4 then + _distance = string.format("%.3fnm",UTILS.MetersToNM(_closest.distance)) + else + _distance = string.format("%.3fkm",_closest.distance) + end + local _msg = string.format("%s - Popping signal flare at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance) + self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true) local _coord = _closest.pilot:GetCoordinate() _coord:FlareRed(_clockDir) else - self:_DisplayMessageToSAR(_heli, "No Pilots within 8KM", 20) + self:_DisplayMessageToSAR(_heli, "No Pilots within 8km/4.32nm", self.messageTime) end end --- Display info to all SAR groups. -- @param #CSAR self --- @param #string _message --- @param #number _side --- @param #string _ignore -function CSAR:_DisplayToAllSAR(_message, _side, _ignore) +-- @param #string _message Message to display. +-- @param #number _side Coalition of message. +-- @param #number _messagetime How long to show. +function CSAR:_DisplayToAllSAR(_message, _side, _messagetime) self:T(self.lid .. " _DisplayToAllSAR") for _, _unitName in pairs(self.csarUnits) do local _unit = self:_GetSARHeli(_unitName) if _unit then - if not _ignore then - self:_DisplayMessageToSAR(_unit, _message, 10) + if not _messagetime then + self:_DisplayMessageToSAR(_unit, _message, _messagetime) end end end @@ -1448,13 +1450,19 @@ function CSAR:_Reqsmoke( _unitName ) local _closest = self:_GetClosestDownedPilot(_heli) if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < 8000.0 then local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) - local _msg = string.format("%s - %.2f KHz ADF - %.3fM - Popping Blue smoke at your %s o\'clock", _closest.groupInfo.desc, _closest.groupInfo.frequency / 1000, _closest.distance, _clockDir) - self:_DisplayMessageToSAR(_heli, _msg, 20) + local _distance = 0 + if self.coordtype < 4 then + _distance = string.format("%.3fnm",UTILS.MetersToNM(_closest.distance)) + else + _distance = string.format("%.3fkm",_closest.distance) + end + local _msg = string.format("%s - Popping signal smoke at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance) + self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true) local _coord = _closest.pilot:GetCoordinate() local color = self.smokecolor _coord:Smoke(color) else - self:_DisplayMessageToSAR(_heli, "No Pilots within 8KM", 20) + self:_DisplayMessageToSAR(_heli, "No Pilots within 8km/4.32nm", self.messageTime) end end @@ -1493,7 +1501,6 @@ function CSAR:_GetClosestMASH(_heli) if self.allowFARPRescue then local position = _heli:GetCoordinate() local afb,distance = position:GetClosestAirbase2(nil,self.coalition) - --local distance = GetCloseAirbase(position,self.coalition,nil) _shortestDistance = distance end @@ -1532,7 +1539,7 @@ function CSAR:_CheckOnboard(_unitName) for _, _onboard in pairs(self.inTransitGroups[_unitName]) do _text = _text .. "\n" .. _onboard.desc end - self:_DisplayMessageToSAR(_unit, _text, self.messageTime) + self:_DisplayMessageToSAR(_unit, _text, self.messageTime*2) end end @@ -1552,10 +1559,7 @@ function CSAR:_AddMedevacMenuItem() if _unit then if _unit:IsAlive() then local unitName = _unit:GetName() - --if not self.csarUnits[unitName] then - --self.csarUnits[unitName] = unitName _UnitList[unitName] = unitName - --end end -- end isAlive end -- end if _unit end -- end for @@ -1700,9 +1704,7 @@ function CSAR:_GetClockDirection(_heli, _group) if _heading then local Aspect = Angle - _heading if Aspect == 0 then Aspect = 360 end - --clock = math.abs(math.floor(Aspect / 30)) clock = math.floor(Aspect / 30) - --clock = UTILS.Round(clock,-2) end return clock end @@ -1730,7 +1732,6 @@ function CSAR:_AddBeaconToGroup(_group, _freq) local Frequency = _freq -- Freq in Hertz local Sound = "l10n/DEFAULT/"..self.radioSound trigger.action.radioTransmission(Sound, _radioUnit:GetPositionVec3(), 0, false, Frequency, 1000) -- Beacon in MP only runs for exactly 30secs straight - --timer.scheduleFunction(self._RefreshRadioBeacons, { _group, _freq }, timer.getTime() + 30) end end @@ -1908,7 +1909,8 @@ end -- @param #string To To state. -- @param Wrapper.Unit#UNIT HeliUnit Unit of the helicopter. -- @param #string HeliName Name of the helicopter group. -function CSAR:onbeforeRescued(From, Event, To, HeliUnit, HeliName) +-- @param #number PilotsSaved Number of the saved pilots on board when landing. +function CSAR:onbeforeRescued(From, Event, To, HeliUnit, HeliName, PilotsSaved) self:T({From, Event, To, HeliName, HeliUnit}) self.rescues = self.rescues + 1 return self @@ -1930,4 +1932,3 @@ end -------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- End Ops.CSAR -------------------------------------------------------------------------------------------------------------------------------------------------------------------- - From 270c69344f5fff637fcec68a755e4908aaf30311 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 15 Jun 2021 07:42:45 +0200 Subject: [PATCH 054/111] Update CSAR.lua Updated documentation / clarification. Added feature docu on using SRS --- Moose Development/Moose/Ops/CSAR.lua | 40 +++++++++++++++++----------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index f95c319da..55ac59b82 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -64,27 +64,37 @@ -- -- ## 2. Options -- --- The following options are available (with their defaults): +-- The following options are available (with their defaults). Only set the ones you want changed: -- -- self.allowDownedPilotCAcontrol = false -- Set to false if you don't want to allow control by Combined Arms. --- self.allowFARPRescue = true -- allows pilot to be rescued by landing at a FARP or Airbase. Else MASH only. --- self.autosmoke = false -- automatically smoke downed pilot location when a heli is near. +-- self.allowFARPRescue = true -- allows pilots to be rescued by landing at a FARP or Airbase. Else MASH only! +-- self.autosmoke = false -- automatically smoke a downed pilot's location when a heli is near. -- self.coordtype = 1 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. --- self.csarOncrash = true -- If set to true, will generate a downed pilot when a plane crashes as well. --- self.csarPrefix = { "helicargo", "MEDEVAC"} -- prefixes used for useprefix=true - DO NOT use # in helicopter names in the Mission Editor! --- self.enableForAI = true -- set to false to disable AI units from being rescued. --- self.extractDistance = 500 -- Distance the Downed pilot will run to the rescue helicopter. +-- self.csarOncrash = true -- (WIP) If set to true, will generate a downed pilot when a plane crashes as well. +-- self.enableForAI = true -- set to false to disable AI pilots from being rescued. +-- self.pilotRuntoExtractPoint = true -- Downed pilot will run to the rescue helicopter up to self.extractDistance in meters. +-- self.extractDistance = 500 -- Distance the downed pilot will start to run to the rescue helicopter. -- self.immortalcrew = true -- Set to true to make wounded crew immortal. -- self.invisiblecrew = false -- Set to true to make wounded crew insvisible. -- self.loadDistance = 75 -- configure distance for pilots to get into helicopter in meters. -- self.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes. -- self.max_units = 6 -- number of pilots that can be carried if #CSAR.AircraftType is undefined. --- self.messageTime = 10 -- Time to show messages for in seconds. Doubled for long messages. --- self.pilotRuntoExtractPoint = true -- Downed Pilot will run to the rescue helicopter up to self.extractDistance in meters. --- self.radioSound = "beacon.ogg" -- the name of the sound file to use for the Pilot radio beacons. --- self.smokecolor = 4 -- Color of smokemarker for blue side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue. --- self.template = Template or "generic" -- late activated template for downed pilot, usually single infantry soldier. --- self.useprefix = true -- Use the Prefixed defined below, Requires Unit have the Prefix defined below. +-- self.messageTime = 15 -- Time to show messages for in seconds. Doubled for long messages. +-- self.radioSound = "beacon.ogg" -- the name of the sound file to use for the pilots' radio beacons. +-- self.smokecolor = 4 -- Color of smokemarker, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue. +-- self.useprefix = true -- Requires CSAR helicopter #GROUP names to have the prefix(es) defined below. +-- self.csarPrefix = { "helicargo", "MEDEVAC"} -- #GROUP name prefixes used for useprefix=true - DO NOT use # in helicopter names in the Mission Editor! +-- self.verbose = 0 -- set to > 1 for stats output for debugging. +-- +-- ## 2.1 Experimental Features +-- +-- WARNING - Here'll be dragons! +-- DANGER - For this to work you need to de-sanitize your mission environment (all three entries) in \Scripts\MissionScripting.lua +-- Needs SRS => 1.9.6 to work (works on the *server* side of SRS) +-- self.useSRS = false -- Set true to use FF's SRS integration +-- self.SRSPath = "E:\\Program Files\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your SRS installation -- server(!) +-- self.SRSchannel = 300 -- radio channel +-- self.SRSModulation = radio.modulation.AM -- modulation -- -- ## 3. Events -- @@ -142,7 +152,7 @@ -- @field #CSAR CSAR = { ClassName = "CSAR", - verbose = 1, + verbose = 0, lid = "", coalition = 1, coalitiontxt = "blue", @@ -212,7 +222,7 @@ CSAR.AircraftType["Mi-24"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.2r1" +CSAR.version="0.1.2r2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list From 64262d6eccd6cda8bdc1eed8a9b2b11545046f57 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 15 Jun 2021 10:32:52 +0200 Subject: [PATCH 055/111] Update CSAR.lua (#1550) --- Moose Development/Moose/Ops/CSAR.lua | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 55ac59b82..a915d984c 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -80,7 +80,7 @@ -- self.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes. -- self.max_units = 6 -- number of pilots that can be carried if #CSAR.AircraftType is undefined. -- self.messageTime = 15 -- Time to show messages for in seconds. Doubled for long messages. --- self.radioSound = "beacon.ogg" -- the name of the sound file to use for the pilots' radio beacons. +-- self.radioSound = "beacon.ogg" -- the name of the sound file to use for the pilots\' radio beacons. -- self.smokecolor = 4 -- Color of smokemarker, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue. -- self.useprefix = true -- Requires CSAR helicopter #GROUP names to have the prefix(es) defined below. -- self.csarPrefix = { "helicargo", "MEDEVAC"} -- #GROUP name prefixes used for useprefix=true - DO NOT use # in helicopter names in the Mission Editor! @@ -88,10 +88,10 @@ -- -- ## 2.1 Experimental Features -- --- WARNING - Here'll be dragons! +-- "WARNING - Here\'ll be dragons! -- DANGER - For this to work you need to de-sanitize your mission environment (all three entries) in \Scripts\MissionScripting.lua --- Needs SRS => 1.9.6 to work (works on the *server* side of SRS) --- self.useSRS = false -- Set true to use FF's SRS integration +-- Needs SRS => 1.9.6 to work (works on the *server* side of SRS)" +-- self.useSRS = false -- Set true to use FF\'s SRS integration -- self.SRSPath = "E:\\Program Files\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your SRS installation -- server(!) -- self.SRSchannel = 300 -- radio channel -- self.SRSModulation = radio.modulation.AM -- modulation @@ -222,7 +222,7 @@ CSAR.AircraftType["Mi-24"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.2r2" +CSAR.version="0.1.3r1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -262,6 +262,7 @@ function CSAR:New(Coalition, Template, Alias) end else self.coalition = Coalition + self.coalitiontxt = string.lower(UTILS.GetCoalitionName(self.coalition)) end -- Set alias. @@ -1082,7 +1083,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG if (_distance < self.extractDistance) then local _time = self.landedStatus[_lookupKeyHeli] if _time == nil then - self.landedStatus[_lookupKeyHeli] = math.floor( (_distance * self.loadtimemax ) / self.extractDistance ) + self.landedStatus[_lookupKeyHeli] = math.floor( (_distance - self.loadDistance) / 1.94 ) _time = self.landedStatus[_lookupKeyHeli] self:_OrderGroupToMoveToPoint(_woundedGroup, _heliUnit:GetCoordinate()) self:_DisplayMessageToSAR(_heliUnit, "Wait till " .. _pilotName .. " gets in. \nETA " .. _time .. " more seconds.", self.messageTime, true) @@ -1427,7 +1428,11 @@ function CSAR:_SignalFlare(_unitName) local _coord = _closest.pilot:GetCoordinate() _coord:FlareRed(_clockDir) else - self:_DisplayMessageToSAR(_heli, "No Pilots within 8km/4.32nm", self.messageTime) + local disttext = "4.3nm" + if self.coordtype == 4 then + disttext = "8km" + end + self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",disttext), self.messageTime) end end @@ -1472,7 +1477,11 @@ function CSAR:_Reqsmoke( _unitName ) local color = self.smokecolor _coord:Smoke(color) else - self:_DisplayMessageToSAR(_heli, "No Pilots within 8km/4.32nm", self.messageTime) + local disttext = "4.3nm" + if self.coordtype == 4 then + disttext = "8km" + end + self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",disttext), self.messageTime) end end @@ -1715,6 +1724,7 @@ function CSAR:_GetClockDirection(_heli, _group) local Aspect = Angle - _heading if Aspect == 0 then Aspect = 360 end clock = math.floor(Aspect / 30) + if clock == 0 then clock = 12 end end return clock end From cd1935be1d939a780e9f5d16dd6e1ed359b4ef66 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 15 Jun 2021 15:49:58 +0200 Subject: [PATCH 056/111] Update CSAR.lua (#1551) --- Moose Development/Moose/Ops/CSAR.lua | 63 +++++++++++++++------------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index a915d984c..c7600e156 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -96,12 +96,19 @@ -- self.SRSchannel = 300 -- radio channel -- self.SRSModulation = radio.modulation.AM -- modulation -- --- ## 3. Events +-- ## 3. Results +-- +-- Number of successful landings with save pilots and aggregated number of saved pilots is stored in these variables in the object: +-- +-- self.rescues -- number of successful landings *with* saved pilots +-- self.rescuedpilots -- aggregated number of pilots rescued from the field (of *all* players) +-- +-- ## 4. Events -- -- The class comes with a number of FSM-based events that missions designers can use to shape their mission. -- These are: -- --- ### 1. PilotDown. +-- ### 4.1. PilotDown. -- -- The event is triggered when a new downed pilot is detected. Use e.g. `function my_csar:OnAfterPilotDown(...)` to link into this event: -- @@ -109,7 +116,7 @@ -- ... your code here ... -- end -- --- ### 2. Approach. +-- ### 4.2. Approach. -- -- A CSAR helicpoter is closing in on a downed pilot. Use e.g. `function my_csar:OnAfterApproach(...)` to link into this event: -- @@ -117,7 +124,7 @@ -- ... your code here ... -- end -- --- ### 3. Boarded. +-- ### 4.3. Boarded. -- -- The pilot has been boarded to the helicopter. Use e.g. `function my_csar:OnAfterBoarded(...)` to link into this event: -- @@ -125,7 +132,7 @@ -- ... your code here ... -- end -- --- ### 4. Returning. +-- ### 4.4. Returning. -- -- The CSAR helicopter is ready to return to an Airbase, FARP or MASH. Use e.g. `function my_csar:OnAfterReturning(...)` to link into this event: -- @@ -133,7 +140,7 @@ -- ... your code here ... -- end -- --- ### 5. Rescued. +-- ### 4.5. Rescued. -- -- The CSAR helicopter has landed close to an Airbase/MASH/FARP and the pilots are safe. Use e.g. `function my_csar:OnAfterRescued(...)` to link into this event: -- @@ -141,7 +148,7 @@ -- ... your code here ... -- end -- --- ## 4. Spawn downed pilots at location to be picked up. +-- ## 5. Spawn downed pilots at location to be picked up. -- -- If missions designers want to spawn downed pilots into the field, e.g. at mission begin to give the helicopter guys works, they can do this like so: -- @@ -179,6 +186,7 @@ CSAR = { bluemash = {}, smokecolor = 4, rescues = 0, + rescuedpilots = 0, } --- Downed pilots info. @@ -222,14 +230,13 @@ CSAR.AircraftType["Mi-24"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.3r1" +CSAR.version="0.1.3r2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: SRS Integration (to be tested) --- WONTDO: Slot blocker etc +-- DONE: SRS Integration (to be tested) ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -316,6 +323,7 @@ function CSAR:New(Coalition, Template, Alias) -- settings, counters etc self.rescues = 0 -- counter for successful rescue landings at FARP/AFB/MASH + self.rescuedpilots = 0 -- counter for saved pilots self.csarOncrash = true -- If set to true, will generate a csar when a plane crashes as well. self.allowDownedPilotCAcontrol = false -- Set to false if you don't want to allow control by Combined arms. self.enableForAI = true -- set to false to disable AI units from being rescued. @@ -692,19 +700,11 @@ function CSAR:_EventHandler(EventData) self.takenOff[_event.IniPlayerName] = nil end - -- if its a sar heli, re-add check status script - for _, _heliName in pairs(self.csarUnits) do - if _heliName == _event.IniPlayerName then - -- add back the status script - local DownedPilotTable = self.downedPilots - for _, _groupInfo in pairs(DownedPilotTable) do -- #CSAR.DownedPilot - if _groupInfo.side == _event.IniCoalition then - local _woundedName = _groupInfo.name - self:_CheckWoundedGroupStatus(_heliName,_woundedName) - end - end - end - end + local _unit = _event.IniUnit + local _group = _event.IniGroup + if _unit:IsHelicopter() or _group:IsHelicopter() then + self:_AddMedevacMenuItem() + end return true @@ -1083,7 +1083,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG if (_distance < self.extractDistance) then local _time = self.landedStatus[_lookupKeyHeli] if _time == nil then - self.landedStatus[_lookupKeyHeli] = math.floor( (_distance - self.loadDistance) / 1.94 ) + self.landedStatus[_lookupKeyHeli] = math.floor( (_distance - self.loadDistance) / 3.6 ) _time = self.landedStatus[_lookupKeyHeli] self:_OrderGroupToMoveToPoint(_woundedGroup, _heliUnit:GetCoordinate()) self:_DisplayMessageToSAR(_heliUnit, "Wait till " .. _pilotName .. " gets in. \nETA " .. _time .. " more seconds.", self.messageTime, true) @@ -1789,7 +1789,7 @@ function CSAR:onafterStart(From, Event, To) self:HandleEvent(EVENTS.Ejection, self._EventHandler) self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) - self:HandleEvent(EVENTS.Dead, self._EventHandler) + self:HandleEvent(EVENTS.PilotDead, self._EventHandler) self:_GenerateVHFrequencies() if self.useprefix then local prefixes = self.csarPrefix or {} @@ -1855,10 +1855,14 @@ function CSAR:onafterStatus(From, Event, To) end if self.verbose > 0 then - local text = string.format("%s Active SAR: %d | Downed Pilots in field: %d | Pilots boarded: %d | Rescue (landings): %d",self.lid,NumberOfSARPilots,PilotsInFieldN,PilotsBoarded,self.rescues) + local text = string.format("%s Active SAR: %d | Downed Pilots in field: %d | Pilots boarded: %d | Landings: %d | Pilots rescued: %d", + self.lid,NumberOfSARPilots,PilotsInFieldN,PilotsBoarded,self.rescues,self.rescuedpilots) self:T(text) - if self.verbose > 1 then - local m = MESSAGE:New(text,"10","Status"):ToAll() + if self.verbose < 2 then + self:I(text) + elseif self.verbose > 1 then + self:I(text) + local m = MESSAGE:New(text,"10","Status",true):ToCoalition(self.coalition) end end self:__Status(-20) @@ -1878,7 +1882,7 @@ function CSAR:onafterStop(From, Event, To) self:UnHandleEvent(EVENTS.Ejection) self:UnHandleEvent(EVENTS.PlayerEnterUnit) self:UnHandleEvent(EVENTS.PlayerEnterAircraft) - self:UnHandleEvent(EVENTS.Dead) + self:UnHandleEvent(EVENTS.PilotDead) self:T(self.lid .. "Stopped.") return self end @@ -1933,6 +1937,7 @@ end 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 return self end From 77a3c7369d7ec062f078c43aee058451fce0d3f5 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Fri, 18 Jun 2021 12:00:15 +0200 Subject: [PATCH 057/111] Update Shorad.lua --- Moose Development/Moose/Functional/Shorad.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Functional/Shorad.lua b/Moose Development/Moose/Functional/Shorad.lua index 5dc7e769f..011a4989b 100644 --- a/Moose Development/Moose/Functional/Shorad.lua +++ b/Moose Development/Moose/Functional/Shorad.lua @@ -13,7 +13,7 @@ -- -- === -- --- ### Author : **applevangelist ** +-- ### Author : **applevangelist** -- -- @module Functional.Shorad -- @image Functional.Shorad.jpg @@ -94,7 +94,7 @@ SHORAD = { lid = "", DefendHarms = true, DefendMavs = true, - DefenseLowProb = 70, + DefenseLowProb = 75, DefenseHighProb = 90, UseEmOnOff = false, } From bf33e4ed4fea8ee73da71a02f8be7aa49e28d0de Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Fri, 18 Jun 2021 12:12:54 +0200 Subject: [PATCH 058/111] Update CSAR.lua (#1552) --- Moose Development/Moose/Ops/CSAR.lua | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index c7600e156..8d6e7292c 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -224,13 +224,14 @@ CSAR.AircraftType["SA342Mistral"] = 2 CSAR.AircraftType["SA342Minigun"] = 2 CSAR.AircraftType["SA342L"] = 4 CSAR.AircraftType["SA342M"] = 4 -CSAR.AircraftType["UH-1H"] = 4 -CSAR.AircraftType["Mi-8MT"] = 8 -CSAR.AircraftType["Mi-24"] = 8 +CSAR.AircraftType["UH-1H"] = 8 +CSAR.AircraftType["Mi-8MT"] = 12 +CSAR.AircraftType["Mi-24P"] = 8 +CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.3r2" +CSAR.version="0.1.3r3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -1748,7 +1749,7 @@ function CSAR:_AddBeaconToGroup(_group, _freq) end if _group:IsAlive() then - local _radioUnit = _group:GetUnit(1) + local _radioUnit = _group:GetUnit(1) local Frequency = _freq -- Freq in Hertz local Sound = "l10n/DEFAULT/"..self.radioSound trigger.action.radioTransmission(Sound, _radioUnit:GetPositionVec3(), 0, false, Frequency, 1000) -- Beacon in MP only runs for exactly 30secs straight From 0e8732fd449a3eecac3dacb0b462432a24be15d4 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 19 Jun 2021 22:27:12 +0200 Subject: [PATCH 059/111] ATIS - ATIS MSRS uses coalition of airbase - update coalition if base was captured --- Moose Development/Moose/Ops/ATIS.lua | 45 ++++++++++++++++++++++++++- Moose Development/Moose/Sound/SRS.lua | 17 ++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 368d3c488..e8b475195 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -589,7 +589,7 @@ _ATIS={} --- ATIS class version. -- @field #string version -ATIS.version="0.9.5" +ATIS.version="0.9.6" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -1143,6 +1143,7 @@ function ATIS:SetSRS(PathToSRS, Gender, Culture, Voice, Port) self.msrs:SetCulture(Culture) self.msrs:SetVoice(Voice) self.msrs:SetPort(Port) + self.msrs:SetCoalition(self:GetCoalition()) if self.dTQueueCheck<=10 then self:SetQueueUpdateTime(90) end @@ -1157,6 +1158,14 @@ function ATIS:SetQueueUpdateTime(TimeInterval) self.dTQueueCheck=TimeInterval or 5 end +--- Get the coalition of the associated airbase. +-- @param #ATIS self +-- @return #number Coalition of the associcated airbase. +function ATIS:GetCoalition() + local coal=self.airbase and self.airbase:GetCoalition() or nil + return coal +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Start & Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1203,6 +1212,10 @@ function ATIS:onafterStart(From, Event, To) -- Start radio queue. self.radioqueue:Start(1, 0.1) + + -- Handle airbase capture + -- Handle events. + self:HandleEvent(EVENTS.BaseCaptured) -- Init status updates. self:__Status(-2) @@ -2259,6 +2272,36 @@ function ATIS:onafterReport(From, Event, To, Text) end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Event Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Base captured +-- @param #ATIS self +-- @param Core.Event#EVENTDATA EventData Event data. +function ATIS:OnEventBaseCaptured(EventData) + + if EventData and EventData.Place then + + -- Place is the airbase that was captured. + local airbase=EventData.Place --Wrapper.Airbase#AIRBASE + + -- Check that this airbase belongs or did belong to this warehouse. + if EventData.PlaceName==self.airbasename then + + -- New coalition of airbase after it was captured. + local NewCoalitionAirbase=airbase:GetCoalition() + + if self.useSRS and self.msrs and self.msrs.coalition~=NewCoalitionAirbase then + self.msrs:SetCoalition(NewCoalitionAirbase) + end + + end + + end + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index d4253971c..9be975a78 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -150,6 +150,7 @@ function MSRS:New(PathToSRS, Frequency, Modulation) self:SetFrequencies(Frequency) self:SetModulations(Modulation) self:SetGender() + self:SetCoalition() return self end @@ -207,6 +208,22 @@ function MSRS:GetPort() return self.port end +--- Set coalition. +-- @param #MSRS self +-- @param #number Coalition Coalition. Default 0. +-- @return #MSRS self +function MSRS:SetCoalition(Coalition) + self.coalition=Coalition or 0 +end + +--- Get coalition. +-- @param #MSRS self +-- @return #number Coalition. +function MSRS:GetCoalition() + return self.coalition +end + + --- Set frequencies. -- @param #MSRS self -- @param #table Frequencies Frequencies in MHz. Can also be given as a #number if only one frequency should be used. From 0cd1cd97a67eee08e266944a64029e1d9ff0100f Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 22 Jun 2021 13:11:12 +0200 Subject: [PATCH 060/111] Update CSAR.lua (#1553) --- Moose Development/Moose/Ops/CSAR.lua | 98 +++++++++++++++------------- 1 file changed, 51 insertions(+), 47 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 8d6e7292c..ad6ae1a97 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -14,7 +14,7 @@ -- -- **Main Features:** -- --- * MOOSE based Helicopter CSAR Operations. +-- * MOOSE-based Helicopter CSAR Operations for Players. -- -- === -- @@ -41,13 +41,14 @@ -- -- # CSAR Concept -- --- * Object oriented refactoring of Ciribob's fantastic CSAR script. +-- * MOOSE-based Helicopter CSAR Operations for Players. +-- * Object oriented refactoring of Ciribob\'s fantastic CSAR script. -- * No need for extra MIST loading. -- * Additional events to tailor your mission. -- -- ## 0. Prerequisites -- --- You need to load an .ogg soundfile for the pilot's beacons into the mission, e.g. "beacon.ogg", use a once trigger, "sound to country" for that. +-- You need to load an .ogg soundfile for the pilot\'s beacons into the mission, e.g. "beacon.ogg", use a once trigger, "sound to country" for that. -- Create a late-activated single infantry unit as template in the mission editor and name it e.g. "Downed Pilot". -- -- ## 1. Basic Setup @@ -66,19 +67,19 @@ -- -- The following options are available (with their defaults). Only set the ones you want changed: -- --- self.allowDownedPilotCAcontrol = false -- Set to false if you don't want to allow control by Combined Arms. +-- self.allowDownedPilotCAcontrol = false -- Set to false if you don\'t want to allow control by Combined Arms. -- self.allowFARPRescue = true -- allows pilots to be rescued by landing at a FARP or Airbase. Else MASH only! --- self.autosmoke = false -- automatically smoke a downed pilot's location when a heli is near. +-- self.autosmoke = false -- automatically smoke a downed pilot\'s location when a heli is near. -- self.coordtype = 1 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. --- self.csarOncrash = true -- (WIP) If set to true, will generate a downed pilot when a plane crashes as well. --- self.enableForAI = true -- set to false to disable AI pilots from being rescued. +-- self.csarOncrash = false -- (WIP) If set to true, will generate a downed pilot when a plane crashes as well. +-- self.enableForAI = false -- set to false to disable AI pilots from being rescued. -- self.pilotRuntoExtractPoint = true -- Downed pilot will run to the rescue helicopter up to self.extractDistance in meters. -- self.extractDistance = 500 -- Distance the downed pilot will start to run to the rescue helicopter. -- self.immortalcrew = true -- Set to true to make wounded crew immortal. -- self.invisiblecrew = false -- Set to true to make wounded crew insvisible. -- self.loadDistance = 75 -- configure distance for pilots to get into helicopter in meters. -- self.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes. --- self.max_units = 6 -- number of pilots that can be carried if #CSAR.AircraftType is undefined. +-- self.max_units = 6 -- max number of pilots that can be carried if #CSAR.AircraftType is undefined. -- self.messageTime = 15 -- Time to show messages for in seconds. Doubled for long messages. -- self.radioSound = "beacon.ogg" -- the name of the sound file to use for the pilots\' radio beacons. -- self.smokecolor = 4 -- Color of smokemarker, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue. @@ -202,18 +203,21 @@ CSAR = { -- @field Wrapper.Group#GROUP group Spawned group object. -- @field #number timestamp Timestamp for approach process ---- Known beacons from the available maps +--- Updated and sorted list of known NDB beacons (in kHz!) from the available maps. -- @field #CSAR.SkipFrequencies CSAR.SkipFrequencies = { - 745,381,384,300.50,312.5,1175,342,735,300.50,353.00, - 440,795,525,520,690,625,291.5,300.50, - 435,309.50,920,1065,274,312.50, - 580,602,297.50,750,485,950,214, - 1025,730,995,455,307,670,329,395,770, - 380,705,300.5,507,740,1030,515,330,309.5,348,462,905,352,1210,942,435, - 324,320,420,311,389,396,862,680,297.5,920,662,866,907,309.5,822,515,470,342,1182,309.5,720,528, - 337,312.5,830,740,309.5,641,312,722,682,1050, - 1116,935,1000,430,577,540,550,560,570, + 214,274,291.5,295,297.5, + 300.5,304,307,309.5,311,312,312.5,316, + 320,324,328,329,330,336,337, + 342,343,348,351,352,353,358, + 363,365,368,372.5,374, + 380,381,384,389,395,396, + 414,420,430,432,435,440,450,455,462,470,485, + 507,515,520,525,528,540,550,560,570,577,580,602,625,641,662,670,680,682,690, + 705,720,722,730,735,740,745,750,770,795, + 822,830,862,866, + 905,907,920,935,942,950,995, + 1000,1025,1030,1050,1065,1116,1175,1182,1210 } --- All slot / Limit settings @@ -231,7 +235,7 @@ CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.3r3" +CSAR.version="0.1.3r4" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -325,9 +329,9 @@ function CSAR:New(Coalition, Template, Alias) -- settings, counters etc self.rescues = 0 -- counter for successful rescue landings at FARP/AFB/MASH self.rescuedpilots = 0 -- counter for saved pilots - self.csarOncrash = true -- If set to true, will generate a csar when a plane crashes as well. - self.allowDownedPilotCAcontrol = false -- Set to false if you don't want to allow control by Combined arms. - self.enableForAI = true -- set to false to disable AI units from being rescued. + self.csarOncrash = false -- If set to true, will generate a csar when a plane crashes as well. + self.allowDownedPilotCAcontrol = false -- Set to false if you don\'t want to allow control by Combined arms. + self.enableForAI = false -- set to false to disable AI units from being rescued. self.smokecolor = 4 -- Color of smokemarker for blue side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue self.coordtype = 2 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. self.immortalcrew = true -- Set to true to make wounded crew immortal @@ -339,18 +343,18 @@ function CSAR:New(Coalition, Template, Alias) self.loadtimemax = 135 -- seconds self.radioSound = "beacon.ogg" -- the name of the sound file to use for the Pilot radio beacons. If this isnt added to the mission BEACONS WONT WORK! self.allowFARPRescue = true --allows pilot to be rescued by landing at a FARP or Airbase - self.max_units = 6 --number of pilots that can be carried + self.max_units = 6 --max number of pilots that can be carried self.useprefix = true -- Use the Prefixed defined below, Requires Unit have the Prefix defined below - self.csarPrefix = { "helicargo", "MEDEVAC"} -- prefixes used for useprefix=true - DON'T use # in names! + self.csarPrefix = { "helicargo", "MEDEVAC"} -- prefixes used for useprefix=true - DON\'T use # in names! self.template = Template or "generic" -- template for downed pilot self.mashprefix = {"MASH"} -- prefixes used to find MASHes self.bluemash = SET_GROUP:New():FilterCoalitions(self.coalition):FilterPrefixes(self.mashprefix):FilterOnce() -- currently only GROUP objects, maybe support STATICs also? self.autosmoke = false -- automatically smoke location when heli is near - -- WARNING - here'll be dragons + -- WARNING - here\'ll be dragons -- for this to work you need to de-sanitize your mission environment in \Scripts\MissionScripting.lua -- needs SRS => 1.9.6 to work (works on the *server* side) - self.useSRS = false -- Use FF's SRS integration + self.useSRS = false -- Use FF\'s SRS integration self.SRSPath = "E:\\Program Files\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your server(!) self.SRSchannel = 300 -- radio channel self.SRSModulation = radio.modulation.AM -- modulation @@ -403,7 +407,7 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string Event Event. -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. - -- @param #string Woundedgroupname Name of the downed pilot's group. + -- @param #string Woundedgroupname Name of the downed pilot\'s group. --- On After "Boarded" event. Downed pilot boarded heli. -- @function [parent=#CSAR] OnAfterBoarded @@ -412,7 +416,7 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string Event Event. -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. - -- @param #string Woundedgroupname Name of the downed pilot's group. + -- @param #string Woundedgroupname Name of the downed pilot\'s group. --- On After "Returning" event. Heli can return home with downed pilot(s). -- @function [parent=#CSAR] OnAfterReturning @@ -421,7 +425,7 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string Event Event. -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. - -- @param #string Woundedgroupname Name of the downed pilot's group. + -- @param #string Woundedgroupname Name of the downed pilot\'s group. --- On After "Rescued" event. Pilot(s) have been brought to the MASH/FARP/AFB. -- @function [parent=#CSAR] OnAfterRescued @@ -628,7 +632,7 @@ end -- @param #number _coalition Coalition. -- @param #string _description (optional) Description. -- @param #boolean _randomPoint (optional) Random yes or no. --- @param #boolean _nomessage (optional) If true, don't send a message to SAR. +-- @param #boolean _nomessage (optional) If true, don\'t send a message to SAR. function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _nomessage) self:T(self.lid .. " _SpawnCsarAtZone") local freq = self:_GenerateADFFrequency() @@ -877,7 +881,7 @@ function CSAR:_RemoveNameFromDownedPilots(name,force) if _pilot.name == name then local group = _pilot.group -- Wrapper.Group#GROUP if group then - if (not group:IsAlive()) or ( force == true) then -- don't delete groups which still exist + if (not group:IsAlive()) or ( force == true) then -- don\'t delete groups which still exist found = true _pilot.desc = nil _pilot.frequency = nil @@ -933,13 +937,13 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) local _distance = self:_GetDistance(_heliCoord,_leaderCoord) if _distance < 3000 and _distance > 0 then if self:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName) == true then - -- we're close, reschedule + -- we\'re close, reschedule _downedpilot.timestamp = timer.getAbsTime() self:__Approach(-5,heliname,woundedgroupname) end else self.heliVisibleMessage[_lookupKeyHeli] = nil - --reschedule as units aren't dead yet , schedule for a bit slower though as we're far away + --reschedule as units aren\'t dead yet , schedule for a bit slower though as we\'re far away _downedpilot.timestamp = timer.getAbsTime() self:__Approach(-10,heliname,woundedgroupname) end @@ -949,7 +953,7 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) end end ---- Function to pop a smoke at a wounded pilot's positions. +--- Function to pop a smoke at a wounded pilot\'s positions. -- @param #CSAR self -- @param #string _woundedGroupName Name of the group. -- @param Wrapper.Group#GROUP _woundedLeader Object of the group. @@ -985,13 +989,13 @@ function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupNam _groups = self.inTransitGroups[_heliName] end - -- if the heli can't pick them up, show a message and return + -- if the heli can\'t pick them up, show a message and return local _maxUnits = self.AircraftType[_heliUnit:GetTypeName()] if _maxUnits == nil then _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) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s, %s. We\'re already crammed with %d guys! Sorry!", _pilotName, _heliName, _unitsInHelicopter, _unitsInHelicopter), self.messageTime) return true end @@ -1010,7 +1014,7 @@ function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupNam _woundedGroup:Destroy() 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! ", _heliName, _pilotName), self.messageTime,true,true) self:__Boarded(5,_heliName,_woundedGroupName) @@ -1068,9 +1072,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,true,true) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land or hover at the smoke.", _heliName, _pilotName), self.messageTime,true,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,true,true) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land in a safe place, I will go there ", _heliName, _pilotName), self.messageTime,true,true) end --mark as shown for THIS heli and THIS group self.heliCloseMessage[_lookupKeyHeli] = true @@ -1079,7 +1083,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG -- have we landed close enough? if not _heliUnit:InAir() then - -- if you land on them, doesnt matter if they were heading to someone else as you're closer, you win! :) + -- if you land on them, doesnt matter if they were heading to someone else as you\'re closer, you win! :) if self.pilotRuntoExtractPoint == true then if (_distance < self.extractDistance) then local _time = self.landedStatus[_lookupKeyHeli] @@ -1134,7 +1138,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG end if _time > 0 then - self:_DisplayMessageToSAR(_heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you're too far away!", self.messageTime, true) + self:_DisplayMessageToSAR(_heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you\'re too far away!", self.messageTime, true) else self.hoverStatus[_lookupKeyHeli] = nil self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) @@ -1290,7 +1294,7 @@ function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak) end end ---- Function to get string of a group's position. +--- Function to get string of a group\'s position. -- @param #CSAR self -- @param Wrapper.Controllable#CONTROLLABLE _woundedGroup Group or Unit object. -- @return #string Coordinates as Text @@ -1672,7 +1676,7 @@ function CSAR:_GenerateVHFrequencies() -- third range _start = 850000 - while _start <= 1250000 do + while _start <= 999000 do -- updated for Gazelle -- skip existing NDB frequencies local _found = false @@ -1819,7 +1823,7 @@ function CSAR:onbeforeStatus(From, Event, To) local name = entry.name local timestamp = entry.timestamp or 0 local now = timer.getAbsTime() - if now - timestamp > 17 then -- only check if we're not in approach mode, which is iterations of 5 and 10. + if now - timestamp > 17 then -- only check if we\'re not in approach mode, which is iterations of 5 and 10. self:_CheckWoundedGroupStatus(_sar,name) end end @@ -1894,7 +1898,7 @@ end -- @param #string Event Event triggered. -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. --- @param #string Woundedgroupname Name of the downed pilot's group. +-- @param #string Woundedgroupname Name of the downed pilot\'s group. function CSAR:onbeforeApproach(From, Event, To, Heliname, Woundedgroupname) self:T({From, Event, To, Heliname, Woundedgroupname}) self:_CheckWoundedGroupStatus(Heliname,Woundedgroupname) @@ -1907,7 +1911,7 @@ end -- @param #string Event Event triggered. -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. --- @param #string Woundedgroupname Name of the downed pilot's group. +-- @param #string Woundedgroupname Name of the downed pilot\'s group. function CSAR:onbeforeBoarded(From, Event, To, Heliname, Woundedgroupname) self:T({From, Event, To, Heliname, Woundedgroupname}) self:_ScheduledSARFlight(Heliname,Woundedgroupname) @@ -1920,7 +1924,7 @@ end -- @param #string Event Event triggered. -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. --- @param #string Woundedgroupname Name of the downed pilot's group. +-- @param #string Woundedgroupname Name of the downed pilot\'s group. function CSAR:onbeforeReturning(From, Event, To, Heliname, Woundedgroupname) self:T({From, Event, To, Heliname, Woundedgroupname}) self:_ScheduledSARFlight(Heliname,Woundedgroupname) From f235037cb9999beb6752d7de27b419c9bb235738 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 22 Jun 2021 18:24:11 +0200 Subject: [PATCH 061/111] Create CTLD.lua (#1555) Player Heli Cargo and Troops Ops :) --- Moose Development/Moose/Ops/CTLD.lua | 2322 ++++++++++++++++++++++++++ 1 file changed, 2322 insertions(+) create mode 100644 Moose Development/Moose/Ops/CTLD.lua diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua new file mode 100644 index 000000000..38640518f --- /dev/null +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -0,0 +1,2322 @@ +--- **Ops** -- Combat Troops & Logistics Deployment. +-- +-- === +-- +-- **CTLD** - MOOSE based Helicopter CTLD Operations. +-- +-- === +-- +-- ## Missions: +-- +-- ### [CTLD - Combat Troop & Logistics Deployment](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/) +-- +-- === +-- +-- **Main Features:** +-- +-- * MOOSE-based Helicopter CTLD Operations for Players. +-- +-- === +-- +-- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original) +-- @module Ops.CTLD +-- @image OPS_CTLD.jpg + +-- Date: June 2021 + +do +------------------------------------------------------ +--- **CTLD_CARGO** class, extends #Core.Base#BASE +-- @type CTLD_CARGO +-- @field #number ID ID of this cargo. +-- @field #string Name Name for menu. +-- @field #table Templates Table of #POSITIONABLE objects. +-- @field #CTLD_CARGO.Enum Type Enumerator of Type. +-- @field #boolean HasBeenMoved Flag for moving. +-- @field #boolean LoadDirectly Flag for direct loading. +-- @field #number CratesNeeded Crates needed to build. +-- @field Wrapper.Positionable#POSITIONABLE Positionable Representation of cargo in the mission. +-- @field #boolean HasBeenDropped True if dropped from heli. +-- @extends Core.Fsm#FSM +CTLD_CARGO = { + ClassName = "CTLD_CARGO", + ID = 0, + Name = "none", + Templates = {}, + CargoType = "none", + HasBeenMoved = false, + LoadDirectly = false, + CratesNeeded = 0, + Positionable = nil, + HasBeenDropped = false, + } + + --- Define cargo types. + -- @type CTLD_CARGO.Enum + -- @field #string Type Type of Cargo. + CTLD_CARGO.Enum = { + VEHICLE = "Vehicle", -- #string vehicles + TROOPS = "Troops", -- #string troops + FOB = "FOB", -- #string FOB + CRATE = "CRATE", -- #string crate + } + + --- Function to create new CTLD_CARGO object. + -- @param #CTLD_CARGO self + -- @param #number ID ID of this #CTLD_CARGO + -- @param #string Name Name for menu. + -- @param #table Templates Table of #POSITIONABLE objects. + -- @param #CTLD_CARGO.Enum Sorte Enumerator of Type. + -- @param #boolean HasBeenMoved Flag for moving. + -- @param #boolean LoadDirectly Flag for direct loading. + -- @param #number CratesNeeded Crates needed to build. + -- @param Wrapper.Positionable#POSITIONABLE Positionable Representation of cargo in the mission. + -- @param #boolean Dropped Cargo/Troops have been unloaded from a chopper. + -- @return #CTLD_CARGO self + function CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped) + -- Inherit everything from BASE class. + local self=BASE:Inherit(self, BASE:New()) -- #CTLD + self:T({ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped}) + self.ID = ID or math.random(100000,1000000) + self.Name = Name or "none" -- #string + self.Templates = Templates or {} -- #table + self.CargoType = Sorte or "type" -- #CTLD_CARGO.Enum + self.HasBeenMoved = HasBeenMoved or false -- #booolean + self.LoadDirectly = LoadDirectly or false -- #booolean + self.CratesNeeded = CratesNeeded or 0 -- #number + self.Positionable = Positionable or nil -- Wrapper.Positionable#POSITIONABLE + self.HasBeenDropped = Dropped or false --#boolean + return self + end + + --- Query ID. + -- @param #CTLD_CARGO self + -- @return #number ID + function CTLD_CARGO:GetID() + return self.ID + end + + --- Query Name. + -- @param #CTLD_CARGO self + -- @return #string Name + function CTLD_CARGO:GetName() + return self.Name + end + + --- Query Templates. + -- @param #CTLD_CARGO self + -- @return #table Templates + function CTLD_CARGO:GetTemplates() + return self.Templates + end + + --- Query has moved. + -- @param #CTLD_CARGO self + -- @return #boolean Has moved + function CTLD_CARGO:HasMoved() + return self.HasBeenMoved + end + + --- Query was dropped. + -- @param #CTLD_CARGO self + -- @return #boolean Has been dropped. + function CTLD_CARGO:WasDropped() + return self.HasBeenDropped + end + + --- Query directly loadable. + -- @param #CTLD_CARGO self + -- @return #boolean loadable + function CTLD_CARGO:CanLoadDirectly() + return self.LoadDirectly + end + + --- Query number of crates or troopsize. + -- @param #CTLD_CARGO self + -- @return #number Crates or size of troops. + function CTLD_CARGO:GetCratesNeeded() + return self.CratesNeeded + end + + --- Query type. + -- @param #CTLD_CARGO self + -- @return #CTLD_CARGO.Enum Type + function CTLD_CARGO:GetType() + return self.CargoType + end + + --- Query type. + -- @param #CTLD_CARGO self + -- @return Wrapper.Positionable#POSITIONABLE Positionable + function CTLD_CARGO:GetPositionable() + return self.Positionable + end + + --- Set HasMoved. + -- @param #CTLD_CARGO self + -- @param #boolean moved + function CTLD_CARGO:SetHasMoved(moved) + self.HasBeenMoved = moved or false + end + + --- Query if cargo has been loaded. + -- @param #CTLD_CARGO self + -- @param #boolean loaded + function CTLD_CARGO:Isloaded() + if self.HasBeenMoved and not self.WasDropped() then + return true + else + return false + end + end + --- Set WasDropped. + -- @param #CTLD_CARGO self + -- @param #boolean dropped + function CTLD_CARGO:SetWasDropped(dropped) + self.HasBeenDropped = dropped or false + end + +end + +do +------------------------------------------------------------------------- +--- **CTLD** class, extends #Core.Base#BASE, #Core.Fsm#FSM +-- @type CTLD +-- @field #string ClassName Name of the class. +-- @field #number verbose Verbosity level. +-- @field #string lid Class id string for output to DCS log file. +-- @field #number coalition Coalition side number, e.g. `coalition.side.RED`. +-- @extends Core.Fsm#FSM + +--- *Combat Troop & Logistics Deployment (CTLD): Everyone wants to be a POG, until there\'s POG stuff to be done.* (Mil Saying) +-- +-- === +-- +-- ![Banner Image](OPS_CTLD.jpg) +-- +-- # CTLD Concept +-- +-- * MOOSE-based CTLD for Players. +-- * Object oriented refactoring of Ciribob\'s fantastic CTLD script. +-- * No need for extra MIST loading. +-- * Additional events to tailor your mission. +-- * ANY late activated group can serve as cargo, either as troops or crates, which have to be build on-location. +-- +-- ## 0. Prerequisites +-- +-- You need to load an .ogg soundfile for the pilot\'s beacons into the mission, e.g. "beacon.ogg", use a once trigger, "sound to country" for that. +-- Create the late-activated troops, vehicles (no statics at this point!) that will make up your deployable forces. +-- +-- ## 1. Basic Setup +-- +-- ## 1.1 Create and start a CTLD instance +-- +-- A basic setup example is the following: +-- +-- -- Instantiate and start a CTLD for the blue side, using helicopter groups named "Helicargo" and alias "Lufttransportbrigade I" +-- local my_ctld = CTLD:New(coalition.side.BLUE,{"Helicargo"},"Lufttransportbrigade I") +-- my_ctld:__Start(5) +-- +-- ## 1.2 Add cargo types available +-- +-- Add *generic* cargo types that you need for your missions, here infantry units, vehicles and a FOB. These need to be late-activated Wrapper.Group#GROUP objects: +-- +-- -- add infantry unit called "Anti-Tank Small" using template "ATS", of type TROOP with size 3 +-- -- infantry units will be loaded directly from LOAD zones into the heli (matching number of free seats needed) +-- my_ctld:AddTroopsCargo("Anti-Tank Small",{"ATS"},CTLD_CARGO.Enum.TROOPS,3) +-- +-- -- add infantry unit called "Anti-Tank" using templates "AA" and "AA"", of type TROOP with size 4 +-- my_ctld:AddTroopsCargo("Anti-Air",{"AA","AA2"},CTLD_CARGO.Enum.TROOPS,4) +-- +-- -- add vehicle called "Humvee" using template "Humvee", of type VEHICLE, size 2, i.e. needs two crates to be build +-- -- vehicles and FOB will be spawned as crates in a LOAD zone first. Once transported to DROP zones, they can be build into the objects +-- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2) +-- +-- -- add infantry unit called "Forward Ops Base" using template "FOB", of type FOB, size 4, i.e. needs four crates to be build: +-- my_ctld:AddCratesCargo("Forward Ops Base",{"FOB"},CTLD_CARGO.Enum.FOB,4) +-- +-- ## 1.3 Add logistics zones +-- +-- Add zones for loading troops and crates and dropping, building crates +-- +-- -- Add a zone of type LOAD to our setup. Players can load troops and crates. +-- -- "Loadzone" is the name of the zone from the ME. Players can load, if they are inside of the zone. +-- -- Smoke and Flare color for this zone is blue, it is active (can be used) and has a radio beacon. +-- my_ctld:AddCTLDZone("Loadzone",CTLD.CargoZoneType.LOAD,SMOKECOLOR.Blue,true,true) +-- +-- -- Add a zone of type DROP. Players can drop crates here. +-- -- Smoke and Flare color for this zone is blue, it is active (can be used) and has a radio beacon. +-- -- NOTE: Troops can be unloaded anywhere, also when hovering in parameters. +-- my_ctld:AddCTLDZone("Dropzone",CTLD.CargoZoneType.DROP,SMOKECOLOR.Red,true,true) +-- +-- -- Add two zones of type MOVE. Dropped troops and vehicles will move to the nearest one. See options. +-- -- Smoke and Flare color for this zone is blue, it is active (can be used) and has a radio beacon. +-- my_ctld:AddCTLDZone("Movezone",CTLD.CargoZoneType.MOVE,SMOKECOLOR.Orange,false,false) +-- +-- my_ctld:AddCTLDZone("Movezone2",CTLD.CargoZoneType.MOVE,SMOKECOLOR.White,true,true) +-- +-- +-- ## 2. Options +-- +-- The following options are available (with their defaults). Only set the ones you want changed: +-- +-- my_ctld.useprefix = true -- Adjust *before* starting CTLD. If set to false, *all* choppers of the coalition side will be enabled for CTLD. +-- my_ctld.CrateDistance = 30 -- List and Load crates in this radius only. +-- my_ctld.maximumHoverHeight = 15 -- Hover max this high to load. +-- my_ctld.minimumHoverHeight = 4 -- Hover min this low to load. +-- my_ctld.forcehoverload = true -- Crates (not: troops) can only be loaded while hovering. +-- my_ctld.hoverautoloading = true -- Crates in CrateDistance in a LOAD zone will be loaded automatically if space allows. +-- my_ctld.smokedistance = 2000 -- Smoke or flares can be request for zones this far away (in meters). +-- my_ctld.movetroopstowpzone = true -- Troops and vehicles will move to the nearest MOVE zone... +-- my_ctld.movetroopsdistance = 5000 -- .. but only if this far away (in meters) +-- my_ctld.smokedistance = 2000 -- Only smoke or flare zones if requesting player unit is this far away (in meters) +-- +-- ## 2.1 User functions +-- +-- ### 2.1.1 Adjust or add chopper unit-type capabilities +-- +-- Use this function to adjust what a heli type can or cannot do: +-- +-- -- E.g. update unit capabilities for testing. Please stay realistic in your mission design. +-- -- Make a Gazelle into a heavy truck, this type can load both crates and troops and eight of each type: +-- my_ctld:UnitCapabilities("SA342L", true, true, 8, 8) +-- +-- Default unit type capabilities are: +-- +-- ["SA342Mistral"] = {type="SA342Mistral", crates=false, troops=true, cratelimit = 0, trooplimit = 4}, +-- ["SA342L"] = {type="SA342L", crates=false, troops=true, cratelimit = 0, trooplimit = 2}, +-- ["SA342M"] = {type="SA342M", crates=false, troops=true, cratelimit = 0, trooplimit = 4}, +-- ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2}, +-- ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, +-- ["Mi-8MTV2"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12}, +-- ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0}, +-- ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, +-- ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, +-- +-- +-- ### 2.1.2 Activate and deactivate zones +-- +-- Activate a zone: +-- +-- -- Activate zone called Name of type #CTLD.CargoZoneType ZoneType: +-- my_ctld:ActivateZone(Name,ZoneType) +-- +-- Deactivate a zone: +-- +-- -- Deactivate zone called Name of type #CTLD.CargoZoneType ZoneType: +-- my_ctld:DeactivateZone(Name,ZoneType) +-- +-- ## 3. Events +-- +-- The class comes with a number of FSM-based events that missions designers can use to shape their mission. +-- These are: +-- +-- ## 3.1 OnAfterTroopsPickedUp +-- +-- This function is called when a player has loaded Troops: +-- +-- function CTLD:OnAfterTroopsPickedUp(From, Event, To, Group, Unit, Cargo) +-- ... your code here ... +-- end +-- +-- ## 3.2 OnAfterCratesPickedUp +-- +-- This function is called when a player has picked up crates: +-- +-- function CTLD:OnAfterCratesPickedUp(From, Event, To, Group, Unit, Cargo) +-- ... your code here ... +-- end +-- +-- ## 3.3 OnAfterTroopsTroopsDeployed +-- +-- This function is called when a player has deployed troops into the field: +-- +-- function CTLD:OnAfterTroopsDeployed(From, Event, To, Group, Unit, Troops) +-- ... your code here ... +-- end +-- +-- ## 3.4 OnAfterTroopsCratesDropped +-- +-- This function is called when a player has deployed crates to a DROP zone: +-- +-- function CTLD:OnAfterCratesDropped(From, Event, To, Group, Unit, Cargotable) +-- ... your code here ... +-- end +-- +-- ## 3.5 OnAfterTroopsCratesBuild +-- +-- This function is called when a player has build a vehicle or FOB: +-- +-- function CTLD:OnAfterCratesBuild(From, Event, To, Group, Unit, Vehicle) +-- ... your code here ... +-- end +-- +-- ## 4. F10 Menu structure +-- +-- CTLD management menu is under the F10 top menu and called "CTLD" +-- +-- ## 4.1 Manage Crates +-- +-- Use this entry to get, load, list nearby, drop, and build crates. Also @see options. +-- +-- ## 4.2 Manage Troops +-- +-- Use this entry to load and drop troops. +-- +-- ## 4.3 List boarded cargo +-- +-- Lists what you have loaded. Shows load capabilities for number of crates and number of seats for troops. +-- +-- ## 4.4 Smoke & Flare zones nearby +-- +-- Does what it says. +-- +-- ## 4.5 List active zone beacons +-- +-- Lists active radio beacons for all zones, where zones are both active and have a beacon. @see `CTLD:AddCTLDZone()` +-- +-- ## 4.6 Show hover parameters +-- +-- Lists hover parameters and indicates if these are curently fulfilled. Also @see options on hover heights. +-- +-- @field #CTLD +CTLD = { + ClassName = "CTLD", + verbose = 2, + lid = "", + coalition = 1, + coalitiontxt = "blue", + PilotGroups = {}, -- #GROUP_SET of heli pilots + CtldUnits = {}, -- Table of helicopter #GROUPs + FreeVHFFrequencies = {}, -- Table of VHF + FreeUHFFrequencies = {}, -- Table of UHF + FreeFMFrequencies = {}, -- Table of FM + CargoCounter = 0, + dropOffZones = {}, + wpZones = {}, + Cargo_Troops = {}, -- generic troops objects + Cargo_Crates = {}, -- generic crate objects + Loaded_Cargo = {}, -- cargo aboard units + Spawned_Crates = {}, -- Holds objects for crates spawned generally + Spawned_Cargo = {}, -- Binds together spawned_crates and their CTLD_CARGO objects + CrateDistance = 30, -- list crates in this radius + debug = false, + wpZones = {}, + pickupZones = {}, + dropOffZones = {}, +} + +------------------------------ +-- DONE: Zone Checks +-- DONE: TEST Hover load and unload +-- DONE: Crate unload +-- DONE: Hover (auto-)load +-- TODO: (More) Housekeeping +-- DONE: Troops running to WP Zone +-- DONE: Zone Radio Beacons +-- DONE: Stats Running +------------------------------ + +--- Radio Beacons +-- @type CTLD.ZoneBeacon +-- @field #string name -- Name of zone for the coordinate +-- @field #number frequency -- in mHz +-- @field #number modulation -- i.e.radio.modulation.FM or radio.modulation.AM + +--- Zone Info. +-- @type CTLD.CargoZone +-- @field #string name Name of Zone. +-- @field #string color Smoke color for zone, e.g. SMOKECOLOR.Red. +-- @field #boolean active Active or not. +-- @field #string type Type of zone, i.e. load,drop,move +-- @field #boolean hasbeacon Create and run radio beacons if active. +-- @field #table fmbeacon Beacon info as #CTLD.ZoneBeacon +-- @field #table uhfbeacon Beacon info as #CTLD.ZoneBeacon +-- @field #table vhfbeacon Beacon info as #CTLD.ZoneBeacon + +--- Zone Type Info. +-- @type CTLD. +CTLD.CargoZoneType = { + LOAD = "load", + DROP = "drop", + MOVE = "move", +} + +--- Buildable table info. +-- @type CTLD.Buildable +-- @field #string Name Name of the object. +-- @field #number Required Required crates. +-- @field #number Found Found crates. +-- @field #table Template Template names for this build. +-- @field #boolean CanBuild Is buildable or not. +-- @field #CTLD_CARGO.Enum Type Type enumerator (for moves). + +--- Unit capabilities. +-- @type CTLD.UnitCapabilities +-- @field #string type Unit type. +-- @field #boolean crates Can transport crate. +-- @field #boolean troops Can transport troops. +-- @field #number cratelimit Number of crates transportable. +-- @field #number trooplimit Number of troop units transportable. +CTLD.UnitTypes = { + ["SA342Mistral"] = {type="SA342Mistral", crates=false, troops=true, cratelimit = 0, trooplimit = 4}, + ["SA342L"] = {type="SA342L", crates=false, troops=true, cratelimit = 0, trooplimit = 2}, + ["SA342M"] = {type="SA342M", crates=false, troops=true, cratelimit = 0, trooplimit = 4}, + ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2}, + ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, + ["Mi-8MTV2"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12}, + ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0}, + ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8}, + ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8}, +} + +--- Updated and sorted known NDB beacons (in kHz!) from the available maps +-- @field #CTLD.SkipFrequencies +CTLD.SkipFrequencies = { + 214,274,291.5,295,297.5, + 300.5,304,307,309.5,311,312,312.5,316, + 320,324,328,329,330,336,337, + 342,343,348,351,352,353,358, + 363,365,368,372.5,374, + 380,381,384,389,395,396, + 414,420,430,432,435,440,450,455,462,470,485, + 507,515,520,525,528,540,550,560,570,577,580,602,625,641,662,670,680,682,690, + 705,720,722,730,735,740,745,750,770,795, + 822,830,862,866, + 905,907,920,935,942,950,995, + 1000,1025,1030,1050,1065,1116,1175,1182,1210 + } + +--- CTLD class version. +-- @field #string version +CTLD.version="0.1.1b1" + +--- Instantiate a new CTLD. +-- @param #CTLD self +-- @param #string Coalition Coalition of this CTLD. I.e. coalition.side.BLUE or coalition.side.RED or coalition.side.NEUTRAL +-- @param #table Prefixes Table of pilot prefixes. +-- @param #string Alias Alias of this CTLD for logging. +-- @return #CTLD self +function CTLD:New(Coalition, Prefixes, Alias) + -- TODO: CTLD Marker + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, FSM:New()) -- #CTLD + + BASE:T({Coalition, Prefixes, Alias}) + + --set Coalition + if Coalition and type(Coalition)=="string" then + if Coalition=="blue" then + self.coalition=coalition.side.BLUE + self.coalitiontxt = Coalition + elseif Coalition=="red" then + self.coalition=coalition.side.RED + self.coalitiontxt = Coalition + elseif Coalition=="neutral" then + self.coalition=coalition.side.NEUTRAL + self.coalitiontxt = Coalition + else + self:E("ERROR: Unknown coalition in CTLD!") + end + else + self.coalition = Coalition + self.coalitiontxt = string.lower(UTILS.GetCoalitionName(self.coalition)) + end + + -- Set alias. + if Alias then + self.alias=tostring(Alias) + else + self.alias="UNHCR" + if self.coalition then + if self.coalition==coalition.side.RED then + self.alias="Red CTLD" + elseif self.coalition==coalition.side.BLUE then + self.alias="Blue CTLD" + end + end + end + + -- Set some string id for output to DCS.log file. + self.lid=string.format("%s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown") + + -- Start State. + self:SetStartState("Stopped") + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("Stopped", "Start", "Running") -- Start FSM. + self:AddTransition("*", "Status", "*") -- CTLD status update. + self:AddTransition("*", "TroopsPickedUp", "*") -- CTLD pickup event. + self:AddTransition("*", "CratesPickedUp", "*") -- CTLD pickup event. + self:AddTransition("*", "TroopsDeployed", "*") -- CTLD deploy event. + self:AddTransition("*", "CratesDropped", "*") -- CTLD deploy event. + self:AddTransition("*", "CratesBuild", "*") -- CTLD build event. + self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. + + -- tables + self.PilotGroups ={} + self.CtldUnits = {} + + -- Beacons + self.FreeVHFFrequencies = {} + self.FreeUHFFrequencies = {} + self.FreeFMFrequencies = {} + self.UsedVHFFrequencies = {} + self.UsedUHFFrequencies = {} + self.UsedFMFrequencies = {} + --self.jtacGeneratedLaserCodes = {} + + -- radio beacons + self.RadioSound = "beacon.ogg" + + -- zones stuff + self.pickupZones = {} + self.dropOffZones = {} + self.wpZones = {} + + -- Cargo + self.Cargo_Crates = {} + self.Cargo_Troops = {} + self.Loaded_Cargo = {} + self.Spawned_Crates = {} + self.Spawned_Cargo = {} + self.MenusDone = {} + self.DroppedTroops = {} + self.DroppedCrates = {} + self.CargoCounter = 0 + self.CrateCounter = 0 + self.TroopCounter = 0 + + -- setup + self.CrateDistance = 30 -- list/load crates in this radius + self.prefixes = Prefixes or {"cargoheli"} + self.useprefix = true + + self.maximumHoverHeight = 15 + self.minimumHoverHeight = 4 + self.forcehoverload = true + self.hoverautoloading = true + + self.smokedistance = 2000 + self.movetroopstowpzone = true + self.movetroopsdistance = 5000 + + for i=1,100 do + math.random() + end + + self:_GenerateVHFrequencies() + self:_GenerateUHFrequencies() + self:_GenerateFMFrequencies() + --self:_GenerateLaserCodes() -- curr unused + + ------------------------ + --- Pseudo Functions --- + ------------------------ + + --- Triggers the FSM event "Start". Starts the CTLD. Initializes parameters and starts event handlers. + -- @function [parent=#CTLD] Start + -- @param #CTLD self + + --- Triggers the FSM event "Start" after a delay. Starts the CTLD. Initializes parameters and starts event handlers. + -- @function [parent=#CTLD] __Start + -- @param #CTLD self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Stop". Stops the CTLD and all its event handlers. + -- @param #CTLD self + + --- Triggers the FSM event "Stop" after a delay. Stops the CTLD and all its event handlers. + -- @function [parent=#CTLD] __Stop + -- @param #CTLD self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Status". + -- @function [parent=#CTLD] Status + -- @param #CTLD self + + --- Triggers the FSM event "Status" after a delay. + -- @function [parent=#CTLD] __Status + -- @param #CTLD self + -- @param #number delay Delay in seconds. + + --- FSM Function OnAfterTroopsPickedUp. + -- @function [parent=#CTLD] OnAfterTroopsPickedUp + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #CTLD_CARGO Cargo Cargo crate. + -- @return #CTLD self + + --- FSM Function OnAfterCratesPickedUp. + -- @function [parent=#CTLD] OnAfterCratesPickedUp + -- @param #CTLD self + -- @param #string From State . + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #CTLD_CARGO Cargo Cargo crate. + -- @return #CTLD self + + --- FSM Function OnAfterTroopsDeployed. + -- @function [parent=#CTLD] OnAfterTroopsDeployed + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param Wrapper.Group#GROUP Troops Troops #GROUP Object. + -- @return #CTLD self + + --- FSM Function OnAfterCratesDropped. + -- @function [parent=#CTLD] OnAfterCratesDropped + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #table Cargotable Table of #CTLD_CARGO objects dropped. + -- @return #CTLD self + + --- FSM Function OnAfterCratesBuild. + -- @function [parent=#CTLD] OnAfterCratesBuild + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB build. + -- @return #CTLD self + + return self +end + +------------------------------------------------------------------- +-- Helper and User Functions +------------------------------------------------------------------- + +--- Function to generate valid UHF Frequencies +-- @param #CTLD self +function CTLD:_GenerateUHFrequencies() + self:T(self.lid .. " _GenerateUHFrequencies") + self.FreeUHFFrequencies = {} + local _start = 220000000 + + while _start < 399000000 do + table.insert(self.FreeUHFFrequencies, _start) + _start = _start + 500000 + end + + return self +end + +--- Function to generate valid FM Frequencies +-- @param #CTLD sel +function CTLD:_GenerateFMFrequencies() + self:T(self.lid .. " _GenerateFMrequencies") + self.FreeFMFrequencies = {} + local _start = 220000000 + + while _start < 399000000 do + + _start = _start + 500000 + end + + for _first = 3, 7 do + for _second = 0, 5 do + for _third = 0, 9 do + local _frequency = ((100 * _first) + (10 * _second) + _third) * 100000 --extra 0 because we didnt bother with 4th digit + table.insert(self.FreeFMFrequencies, _frequency) + end + end + end + + return self +end + +--- Populate table with available VHF beacon frequencies. +-- @param #CTLD self +function CTLD:_GenerateVHFrequencies() + self:T(self.lid .. " _GenerateVHFrequencies") + local _skipFrequencies = self.SkipFrequencies + + self.FreeVHFFrequencies = {} + self.UsedVHFFrequencies = {} + + -- first range + local _start = 200000 + while _start < 400000 do + + -- skip existing NDB frequencies# + local _found = false + for _, value in pairs(_skipFrequencies) do + if value * 1000 == _start then + _found = true + break + end + end + if _found == false then + table.insert(self.FreeVHFFrequencies, _start) + end + _start = _start + 10000 + end + + -- second range + _start = 400000 + while _start < 850000 do + -- skip existing NDB frequencies + local _found = false + for _, value in pairs(_skipFrequencies) do + if value * 1000 == _start then + _found = true + break + end + end + if _found == false then + table.insert(self.FreeVHFFrequencies, _start) + end + _start = _start + 10000 + end + + -- third range + _start = 850000 + while _start <= 999000 do -- adjusted for Gazelle + -- skip existing NDB frequencies + local _found = false + for _, value in pairs(_skipFrequencies) do + if value * 1000 == _start then + _found = true + break + end + end + if _found == false then + table.insert(self.FreeVHFFrequencies, _start) + end + _start = _start + 50000 + end + + return self +end + +--- Function to generate valid laser codes. +-- @param #CTLD self +function CTLD:_GenerateLaserCodes() + self:T(self.lid .. " _GenerateLaserCodes") + self.jtacGeneratedLaserCodes = {} + -- generate list of laser codes + local _code = 1111 + local _count = 1 + while _code < 1777 and _count < 30 do + while true do + _code = _code + 1 + if not self:_ContainsDigit(_code, 8) + and not self:_ContainsDigit(_code, 9) + and not self:_ContainsDigit(_code, 0) then + table.insert(self.jtacGeneratedLaserCodes, _code) + break + end + end + _count = _count + 1 + end +end + +--- Helper function to generate laser codes. +-- @param #CTLD self +-- @param #number _number +-- @param #number _numberToFind +function CTLD:_ContainsDigit(_number, _numberToFind) + self:T(self.lid .. " _ContainsDigit") + local _thisNumber = _number + local _thisDigit = 0 + while _thisNumber ~= 0 do + _thisDigit = _thisNumber % 10 + _thisNumber = math.floor(_thisNumber / 10) + if _thisDigit == _numberToFind then + return true + end + end + return false +end + +--- Event handler function +-- @param #CTLD self +-- @param Core.Event#EVENTDATA EventData +function CTLD:_EventHandler(EventData) + -- TODO: events dead and playerleaveunit - nil table entries + self:T(string.format("%s Event = %d",self.lid, EventData.id)) + local event = EventData -- Core.Event#EVENTDATA + if event.id == EVENTS.PlayerEnterAircraft or event.id == EVENTS.PlayerEnterUnit then + local _coalition = event.IniCoalition + if _coalition ~= self.coalition then + return --ignore! + end + -- check is Helicopter + local _unit = event.IniUnit + local _group = event.IniGroup + if _unit:IsHelicopter() or _group:IsHelicopter() then + self:_RefreshF10Menus() + end + return + elseif event.id == EVENTS.PlayerLeaveUnit then + -- remove from pilot table + local unitname = event.IniUnitName or "none" + self.CtldUnits[unitname] = nil + end + return self +end + +--- Function to load troops into a heli. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @param #CTLD_CARGO Cargotype +function CTLD:_LoadTroops(Group, Unit, Cargotype) + self:T(self.lid .. " _LoadTroops") + -- landed or hovering over load zone? + local grounded = not self:IsUnitInAir(Unit) + local hoverload = self:CanHoverLoad(Unit) + -- check if we are in LOAD zone + local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) + if not inzone then + local m = MESSAGE:New("You are not close enough to a logistics zone!",15,"CTLD"):ToGroup(Group) + if not self.debug then return self end + elseif not grounded and not hoverload then + local m = MESSAGE:New("You need to land or hover in position to load!",15,"CTLD"):ToGroup(Group) + if not self.debug then return self end + end + -- load troops into heli + local group = Group -- Wrapper.Group#GROUP + local unit = Unit -- Wrapper.Unit#UNIT + local unitname = unit:GetName() + local cargotype = Cargotype -- #CTLD_CARGO + local cratename = cargotype:GetName() -- #string + self:T(self.lid .. string.format("Troops %s requested", cratename)) + -- see if this heli can load troops + local unittype = unit:GetTypeName() + local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + local cantroops = capabilities.troops -- #boolean + local trooplimit = capabilities.trooplimit -- #number + local troopsize = cargotype:GetCratesNeeded() -- #number + -- have we loaded stuff already? + local numberonboard = 0 + local loaded = {} + if self.Loaded_Cargo[unitname] then + loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo + numberonboard = loaded.Troopsloaded or 0 + else + loaded = {} -- #CTLD.LoadedCargo + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} + end + if troopsize + numberonboard > trooplimit then + local m = MESSAGE:New("Sorry, we\'re crammed already!",10,"CTLD",true):ToGroup(group) + return + else + loaded.Troopsloaded = loaded.Troopsloaded + troopsize + table.insert(loaded.Cargo,Cargotype) + self.Loaded_Cargo[unitname] = loaded + local m = MESSAGE:New("Troops boarded!",10,"CTLD",true):ToGroup(group) + self:__TroopsPickedUp(1,Group, Unit, Cargotype) + end + return self +end + +--- Function to spawn crates in front of the heli. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @param #CTLD_CARGO Cargo +-- @param #number number Number of crates to generate (for dropping) +-- @param #boolean drop If true we\'re dropping from heli rather than loading. +function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) + self:T(self.lid .. " _GetCrates") + local cgoname = Cargo:GetName() + self:T({cgoname, number, drop}) + -- check if we are in LOAD zone + local inzone = true + + if drop then + local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) + else + local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) + end + + if not inzone then + local m = MESSAGE:New("You are not close enough to a logistics zone!",15,"CTLD"):ToGroup(Group) + if not self.debug then return self end + end + + -- avoid crate spam + local capabilities = self.UnitTypes[Unit:GetTypeName()] -- #CTLD.UnitCapabilities + local canloadcratesno = capabilities.cratelimit + local loaddist = self.CrateDistance or 30 + local nearcrates, numbernearby = self:_FindCratesNearby(Group,Unit,loaddist) + if numbernearby >= canloadcratesno and not drop then + local m = MESSAGE:New("There are enough crates nearby already! Take care of those first!",15,"CTLD"):ToGroup(Group) + return self + end + -- spawn crates in front of helicopter + local cargotype = Cargo -- #CTLD_CARGO + local number = number or cargotype:GetCratesNeeded() --#number + local cratesneeded = cargotype:GetCratesNeeded() --#number + local cratename = cargotype:GetName() + self:T(self.lid .. string.format("Crate %s requested", cratename)) + local cratetemplate = "Container"-- #string + -- get position and heading of heli + local position = Unit:GetCoordinate() + local heading = Unit:GetHeading() + 1 + local height = Unit:GetHeight() + local droppedcargo = {} + -- loop crates needed + for i=1,number do + local cratealias = string.format("%s-%d", cratetemplate, math.random(1,100000)) + local cratedistance = i*4 + 6 + local rheading = math.floor(math.random(90,270) * heading + 1 / 360) + local rheading = rheading + 180 -- mirror + if rheading > 360 then rheading = rheading - 360 end -- catch > 360 + local cratecoord = position:Translate(cratedistance,rheading) + local cratevec2 = cratecoord:GetVec2() + self.CrateCounter = self.CrateCounter + 1 + self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType("container_cargo","Cargos",country.id.GERMANY) + :InitCoordinate(cratecoord) + :Spawn(270,cratealias) + + local templ = cargotype:GetTemplates() + local sorte = cargotype:GetType() + self.CargoCounter = self.CargoCounter +1 + local realcargo = nil + if drop then + realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,true,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true) + table.insert(droppedcargo,realcargo) + else + realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,false,false,cratesneeded,self.Spawned_Crates[self.CrateCounter]) + end + table.insert(self.Spawned_Cargo, realcargo) + end + local text = string.format("Crates for %s have been positioned near you!",cratename) + if drop then + text = string.format("Crates for %s have been dropped!",cratename) + self:__CratesDropped(1, Group, Unit, droppedcargo) + end + local m = MESSAGE:New(text,15,"CTLD",true):ToGroup(Group) + return self +end + +--- Function to find and list nearby crates. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @return #CTLD self +function CTLD:_ListCratesNearby( _group, _unit) + self:T(self.lid .. " _ListCratesNearby") + local finddist = self.CrateDistance or 30 + local crates,number = self:_FindCratesNearby(_group,_unit, finddist) -- #table + if number > 0 then + local text = REPORT:New("Crates Found Nearby:") + text:Add("------------------------------------------------------------") + for _,_entry in pairs (crates) do + local entry = _entry -- #CTLD_CARGO + local name = entry:GetName() --#string + -- TODO Meaningful sorting/aggregation + local dropped = entry:WasDropped() + if dropped then + text:Add(string.format("Dropped crate for %s",name)) + else + text:Add(string.format("Crate for %s",name)) + end + end + if text:GetCount() == 1 then + text:Add("--------- N O N E ------------") + end + text:Add("------------------------------------------------------------") + local m = MESSAGE:New(text:Text(),15,"CTLD",true):ToGroup(_group) + else + local m = MESSAGE:New(string.format("No (loadable) crates within %d meters!",finddist),15,"CTLD",true):ToGroup(_group) + end + return self +end + +--- Return distance in meters between two coordinates. +-- @param #CTLD self +-- @param Core.Point#COORDINATE _point1 Coordinate one +-- @param Core.Point#COORDINATE _point2 Coordinate two +-- @return #number Distance in meters +function CTLD:_GetDistance(_point1, _point2) + self:T(self.lid .. " _GetDistance") + if _point1 and _point2 then + local distance = _point1:DistanceFromPointVec2(_point2) + return distance + else + return -1 + end +end + +--- Function to find and return nearby crates. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP _group Group +-- @param Wrapper.Unit#UNIT _unit Unit +-- @param #number _dist Distance +-- @return #table Table of crates +-- @return #number Number Number of crates found +function CTLD:_FindCratesNearby( _group, _unit, _dist) + self:T(self.lid .. " _FindCratesNearby") + local finddist = _dist + local location = _group:GetCoordinate() + local existingcrates = self.Spawned_Cargo -- #table + -- cycle + local index = 0 + local found = {} + for _,_cargoobject in pairs (existingcrates) do + local cargo = _cargoobject -- #CTLD_CARGO + local static = cargo:GetPositionable() -- Wrapper.Static#STATIC -- crates + local staticid = cargo:GetID() + if static and static:IsAlive() then + local staticpos = static:GetCoordinate() + local distance = self:_GetDistance(location,staticpos) + if distance <= finddist and static then + index = index + 1 + table.insert(found, staticid, cargo) + end + end + end + self:T(string.format("Found crates = %d",index)) + -- table.sort(found) + --self:T({found}) + return found, index +end + +--- Function to get and load nearby crates. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @return #CTLD self +function CTLD:_LoadCratesNearby(Group, Unit) + self:T(self.lid .. " _LoadCratesNearby") + -- load crates into heli + local group = Group -- Wrapper.Group#GROUP + local unit = Unit -- Wrapper.Unit#UNIT + local unitname = unit:GetName() + -- see if this heli can load crates + local unittype = unit:GetTypeName() + local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + local cancrates = capabilities.crates -- #boolean + local cratelimit = capabilities.cratelimit -- #number + local grounded = not self:IsUnitInAir(Unit) + local canhoverload = self:CanHoverLoad(Unit) + --- cases ------------------------------- + -- Chopper can\'t do crates - bark & return + -- Chopper can do crates - + -- --> hover if forcedhover or bark and return + -- --> hover or land if not forcedhover + ----------------------------------------- + if not cancrates then + local m = MESSAGE:New("Sorry this chopper cannot carry crates!",10,"CTLD"):ToGroup(Group) + elseif self.forcehoverload and not canhoverload then + local m = MESSAGE:New("Hover over the crates to pick them up!",10,"CTLD"):ToGroup(Group) + elseif not grounded and not canhoverload then + local m = MESSAGE:New("Land or hover over the crates to pick them up!",10,"CTLD"):ToGroup(Group) + else + -- have we loaded stuff already? + local numberonboard = 0 + local loaded = {} + if self.Loaded_Cargo[unitname] then + loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo + numberonboard = loaded.Cratesloaded or 0 + else + loaded = {} -- #CTLD.LoadedCargo + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} + end + -- get nearby crates + local finddist = self.CrateDistance or 30 + local nearcrates,number = self:_FindCratesNearby(Group,Unit,finddist) -- #table + if number == 0 or numberonboard == cratelimit then + local m = MESSAGE:New("Sorry no loadable crates nearby or fully loaded!",10,"CTLD"):ToGroup(Group) + return -- exit + else + -- go through crates and load + local capacity = cratelimit - numberonboard + local crateidsloaded = {} + local loops = 0 + while loaded.Cratesloaded < cratelimit and loops < number do + --for _ind,_crate in pairs (nearcrates) do + loops = loops + 1 + local crateind = 0 + -- get crate with largest index + for _ind,_crate in pairs (nearcrates) do + if not _crate:HasMoved() and not _crate:WasDropped() and _crate:GetID() > crateind then + --crate = _crate + crateind = _crate:GetID() + end + end + -- load one if we found one + if crateind > 0 then + local crate = nearcrates[crateind] -- #CTLD_CARGO + loaded.Cratesloaded = loaded.Cratesloaded + 1 + crate:SetHasMoved(true) + table.insert(loaded.Cargo, crate) + table.insert(crateidsloaded,crate:GetID()) + -- destroy crate + crate:GetPositionable():Destroy() + crate.Positionable = nil + local m = MESSAGE:New(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()),10,"CTLD"):ToGroup(Group) + self:__CratesPickedUp(1, Group, Unit, crate) + end + --if loaded.Cratesloaded == cratelimit then break end + end + self.Loaded_Cargo[unitname] = loaded + -- clean up real world crates + local existingcrates = self.Spawned_Cargo -- #table + local newexcrates = {} + for _,_crate in pairs(existingcrates) do + local excrate = _crate -- #CTLD_CARGO + local ID = excrate:GetID() + for _,_ID in pairs(crateidsloaded) do + if ID ~= _ID then + table.insert(newexcrates,_crate) + end + end + end + self.Spawned_Cargo = nil + self.Spawned_Cargo = newexcrates + end + end + return self +end + +--- Function to list loaded cargo. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @return #CTLD self +function CTLD:_ListCargo(Group, Unit) + self:T(self.lid .. " _ListCargo") + local unitname = Unit:GetName() + local unittype = Unit:GetTypeName() + local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + local trooplimit = capabilities.trooplimit -- #boolean + local cratelimit = capabilities.cratelimit -- #numbe + local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo + if self.Loaded_Cargo[unitname] then + local no_troops = loadedcargo.Troopsloaded or 0 + local no_crates = loadedcargo.Cratesloaded or 0 + local cargotable = loadedcargo.Cargo or {} -- #table + local report = REPORT:New("Transport Checkout Sheet") + report:Add("------------------------------------------------------------") + report:Add(string.format("Troops: %d(%d), Crates: %d(%d)",no_troops,trooplimit,no_crates,cratelimit)) + report:Add("------------------------------------------------------------") + report:Add("-- TROOPS --") + for _,_cargo in pairs(cargotable) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if type == CTLD_CARGO.Enum.TROOPS and not cargo:WasDropped() then + report:Add(string.format("Troop: %s size %d",cargo:GetName(),cargo:GetCratesNeeded())) + end + end + if report:GetCount() == 4 then + report:Add("--------- N O N E ------------") + end + report:Add("------------------------------------------------------------") + report:Add("-- CRATES --") + local cratecount = 0 + for _,_cargo in pairs(cargotable) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if type ~= CTLD_CARGO.Enum.TROOPS then + report:Add(string.format("Crate: %s size 1",cargo:GetName())) + cratecount = cratecount + 1 + end + end + if cratecount == 0 then + report:Add("--------- N O N E ------------") + end + report:Add("------------------------------------------------------------") + local text = report:Text() + local m = MESSAGE:New(text,30,"CTLD",true):ToGroup(Group) + else + local m = MESSAGE:New(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d",trooplimit,cratelimit),10,"CTLD"):ToGroup(Group) + end + return self +end + +--- Function to unload troops from heli. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +function CTLD:_UnloadTroops(Group, Unit) + self:T(self.lid .. " _UnloadTroops") + -- check if we are in LOAD zone + local droppingatbase = false + local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) + if inzone then + droppingatbase = true + end + -- check for hover unload + local hoverunload = self:IsCorrectHover(Unit) --if true we\'re hovering in parameters + -- check if we\'re landed + local grounded = not self:IsUnitInAir(Unit) + -- Get what we have loaded + local unitname = Unit:GetName() + if self.Loaded_Cargo[unitname] and (grounded or hoverunload) then + if not droppingatbase or self.debug then + local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo + -- looking for troops + local cargotable = loadedcargo.Cargo + for _,_cargo in pairs (cargotable) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if type == CTLD_CARGO.Enum.TROOPS and not cargo:WasDropped() then + -- unload troops + local name = cargo:GetName() or "none" + local temptable = cargo:GetTemplates() or {} + local position = Group:GetCoordinate() + local zone = ZONE_GROUP:New(string.format("Unload zone-%s",unitname),Group,100) + local randomcoord = zone:GetRandomCoordinate(10,30):GetVec2() + for _,_template in pairs(temptable) do + self.TroopCounter = self.TroopCounter + 1 + local alias = string.format("%s-%d", _template, math.random(1,100000)) + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) + :InitRandomizeUnits(true,20,2) + :InitDelayOff() + :SpawnFromVec2(randomcoord) + if self.movetroopstowpzone then + self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) + end + end -- template loop + cargo:SetWasDropped(true) + local m = MESSAGE:New(string.format("Dropped Troops %s into action!",name),10,"CTLD"):ToGroup(Group) + self:__TroopsDeployed(1, Group, Unit, name, self.DroppedTroops[self.TroopCounter]) + end -- if type end + end -- cargotable loop + else -- droppingatbase + local m = MESSAGE:New("Troops have returned to base!",15,"CTLD"):ToGroup(Group) + end + -- cleanup load list + local loaded = {} -- #CTLD.LoadedCargo + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} + local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo + local cargotable = loadedcargo.Cargo or {} + for _,_cargo in pairs (cargotable) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + local dropped = cargo:WasDropped() + --local moved = cargo:HasMoved() + if type ~= CTLD_CARGO.Enum.TROOP and not dropped then + table.insert(loaded.Cargo,_cargo) + loaded.Cratesloaded = loaded.Cratesloaded + 1 + end + end + self.Loaded_Cargo[unitname] = nil + self.Loaded_Cargo[unitname] = loaded + else + local m = MESSAGE:New("Nothing loaded or not hovering within parameters!",10,"CTLD"):ToGroup(Group) + end + return self +end + +--- Function to unload crates from heli. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrappe.Unit#UNIT Unit +function CTLD:_UnloadCrates(Group, Unit) + self:T(self.lid .. " _UnloadCrates") + -- check if we are in DROP zone + local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) + if not inzone then + local m = MESSAGE:New("You are not close enough to a drop zone!",15,"CTLD"):ToGroup(Group) + if not self.debug then + return self + end + end + -- check for hover unload + local hoverunload = self:IsCorrectHover(Unit) --if true we\'re hovering in parameters + -- check if we\'re landed + local grounded = not self:IsUnitInAir(Unit) + -- Get what we have loaded + local unitname = Unit:GetName() + if self.Loaded_Cargo[unitname] and (grounded or hoverunload) then + local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo + -- looking for troops + local cargotable = loadedcargo.Cargo + for _,_cargo in pairs (cargotable) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if type ~= CTLD_CARGO.Enum.TROOPS and not cargo:WasDropped() then + -- unload crates + self:_GetCrates(Group, Unit, cargo, 1, true) + cargo:SetWasDropped(true) + cargo:SetHasMoved(true) + --local name cargo:GetName() + --local m = MESSAGE:New(string.format("Dropped Crate for %s!",name),10,"CTLD"):ToGroup(Group) + end + end + -- cleanup load list + local loaded = {} -- #CTLD.LoadedCargo + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} + for _,_cargo in pairs (cargotable) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + local size = cargo:GetCratesNeeded() + if type == CTLD_CARGO.Enum.TROOP then + table.insert(loaded.Cargo,_cargo) + loaded.Cratesloaded = loaded.Troopsloaded + size + end + end + self.Loaded_Cargo[unitname] = nil + self.Loaded_Cargo[unitname] = loaded + else + local m = MESSAGE:New("Nothing loaded or not hovering within parameters!",10,"CTLD"):ToGroup(Group) + end + return self +end + +--- Function to build nearby crates. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrappe.Unit#UNIT Unit +function CTLD:_BuildCrates(Group, Unit) + self:T(self.lid .. " _BuildCrates") + -- get nearby crates + local finddist = self.CrateDistance or 30 + local crates,number = self:_FindCratesNearby(Group,Unit, finddist) -- #table + local buildables = {} + local foundbuilds = false + local canbuild = false + if number > 0 then + -- get dropped crates + for _,_crate in pairs(crates) do + local Crate = _crate -- #CTLD_CARGO + if Crate:WasDropped() then + -- we can build these - maybe + local name = Crate:GetName() + local required = Crate:GetCratesNeeded() + local template = Crate:GetTemplates() + local ctype = Crate:GetType() + if not buildables[name] then + local object = {} -- #CTLD.Buildable + object.Name = name + object.Required = required + object.Found = 1 + object.Template = template + object.CanBuild = false + object.Type = ctype -- #CTLD_CARGO.Enum + buildables[name] = object + foundbuilds = true + else + buildables[name].Found = buildables[name].Found + 1 + if buildables[name].Found >= buildables[name].Required then + buildables[name].CanBuild = true + canbuild = true + end + foundbuilds = true + end + self:T({buildables = buildables}) + end -- end dropped + end -- end crate loop + -- ok let\'s list what we have + local report = REPORT:New("Checklist Buildable Crates") + report:Add("------------------------------------------------------------") + for _,_build in pairs(buildables) do + local build = _build -- Object table from above + local name = build.Name + local needed = build.Required + local found = build.Found + local txtok = "NO" + if build.CanBuild then + txtok = "YES" + end + self:T({name,needed,found,txtok}) + local text = string.format("Type: %s | Required %d | Found %d | Can Build %s", name, needed, found, txtok) + report:Add(text) + end -- end list buildables + if not foundbuilds then report:Add(" --- None Found ---") end + report:Add("------------------------------------------------------------") + local text = report:Text() + local m = MESSAGE:New(text,30,"CTLD",true):ToGroup(Group) + -- let\'s get going + if canbuild then + -- loop again + for _,_build in pairs(buildables) do + local build = _build -- #CTLD.Buildable + if build.CanBuild then + self:_CleanUpCrates(crates,build,number) + self:_BuildObjectFromCrates(Group,Unit,build) + end + end + end + else + local m = MESSAGE:New(string.format("No crates within %d meters!",finddist),15,"CTLD",true):ToGroup(Group) + end -- number > 0 + return self +end + +--- Function to actually SPAWN buildables in the mission. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Group#UNIT Unit +-- @param #CTLD.Buildable Build +function CTLD:_BuildObjectFromCrates(Group,Unit,Build) + self:T(self.lid .. " _BuildObjectFromCrates") + -- Spawn-a-crate-content + local position = Unit:GetCoordinate() or Group:GetCoordinate() + local unitname = Unit:GetName() or Group:GetName() + local name = Build.Name + local type = Build.Type -- #CTLD_CARGO.Enum + local canmove = false + if type == CTLD_CARGO.Enum.VEHICLE then canmove = true end + local temptable = Build.Template or {} + local zone = ZONE_GROUP:New(string.format("Unload zone-%s",unitname),Group,100) + local randomcoord = zone:GetRandomCoordinate(20,50):GetVec2() + for _,_template in pairs(temptable) do + self.TroopCounter = self.TroopCounter + 1 + local alias = string.format("%s-%d", _template, math.random(1,100000)) + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) + :InitRandomizeUnits(true,20,2) + :InitDelayOff() + :SpawnFromVec2(randomcoord) + if self.movetroopstowpzone and canmove then + self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) + end + self:__CratesBuild(1,Group,Unit,self.DroppedTroops[self.TroopCounter]) + end -- template loop + return self +end + +--- Function to move group to WP zone. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group The Group to move. +function CTLD:_MoveGroupToZone(Group) + self:T(self.lid .. " _MoveGroupToZone") + local groupname = Group:GetName() or "none" + local groupcoord = Group:GetCoordinate() + self:T(self.lid .. " _MoveGroupToZone for " .. groupname) + -- Get closest zone of type + local outcome, name, zone, distance = self:IsUnitInZone(Group,CTLD.CargoZoneType.MOVE) + self:T(string.format("Closest WP zone %s is %d meters",name,distance)) + if (distance <= self.movetroopsdistance) and zone then + -- yes, we can ;) + local groupname = Group:GetName() + self:T(string.format("Moving troops %s to zone %s, distance %d!",groupname,name,distance)) + local zonecoord = zone:GetRandomCoordinate(20,125) -- Core.Point#COORDINATE + local coordinate = zonecoord:GetVec2() + self:T({coordinate=coordinate}) + Group:SetAIOn() + Group:OptionAlarmStateAuto() + Group:OptionDisperseOnAttack(30) + Group:OptionROEOpenFirePossible() + Group:RouteToVec2(coordinate,5) + end + return self +end + +--- Housekeeping - Cleanup crates when build +-- @param #CTLD self +-- @param #table Crates Table of #CTLD_CARGO objects near the unit. +-- @param #CTLD.Buildable Build Table build object. +-- @param #number Number Number of objects in Crates (found) to limit search. +function CTLD:_CleanUpCrates(Crates,Build,Number) + self:T(self.lid .. " _CleanUpCrates") + -- clean up real world crates + local build = Build -- #CTLD.Buildable + self:T({Build = Build}) + local existingcrates = self.Spawned_Cargo -- #table of exising crates + local newexcrates = {} + -- get right number of crates to destroy + local numberdest = Build.Required + local nametype = Build.Name + local found = 0 + local rounds = Number + local destIDs = {} + + -- loop and find matching IDs in the set + for _,_crate in pairs(Crates) do + local nowcrate = _crate -- #CTLD_CARGO + local name = nowcrate:GetName() + self:T(string.format("Looking for Crate for %s", name)) + local thisID = nowcrate:GetID() + if name == nametype then -- matching crate type + table.insert(destIDs,thisID) + found = found + 1 + nowcrate:GetPositionable():Destroy() + nowcrate.Positionable = nil + self:T(string.format("%s Found %d Need %d", name, found, numberdest)) + end + if found == numberdest then break end -- got enough + end + self:T({destIDs}) + -- loop and remove from real world representation + for _,_crate in pairs(existingcrates) do + local excrate = _crate -- #CTLD_CARGO + local ID = excrate:GetID() + for _,_ID in pairs(destIDs) do + if ID ~= _ID then + table.insert(newexcrates,_crate) + end + end + end + + -- reset Spawned_Cargo + self.Spawned_Cargo = nil + self.Spawned_Cargo = newexcrates + return self +end + +--- Housekeeping - Function to refresh F10 menus. +-- @param #CTLD self +-- @return #CTLD self +function CTLD:_RefreshF10Menus() + self:T(self.lid .. " _RefreshF10Menus") + local PlayerSet = self.PilotGroups -- Core.Set#SET_GROUP + local PlayerTable = PlayerSet:GetSetObjects() -- #table of #GROUP objects + + -- rebuild units table + local _UnitList = {} + for _key, _group in pairs (PlayerTable) do + local _unit = _group:GetUnit(1) -- Asume that there is only one unit in the flight for players + if _unit then + if _unit:IsAlive() then + local unitName = _unit:GetName() + _UnitList[unitName] = unitName + end -- end isAlive + end -- end if _unit + end -- end for + self.CtldUnits = _UnitList + + -- build unit menus + + local menucount = 0 + local menus = {} + for _, _unitName in pairs(self.CtldUnits) do + if not self.MenusDone[_unitName] then + local _unit = UNIT:FindByName(_unitName) -- Wrapper.Unit#UNIT + if _unit then + local _group = _unit:GetGroup() -- Wrapper.Group#GROUP + if _group then + -- get chopper capabilities + local unittype = _unit:GetTypeName() + local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + local cantroops = capabilities.troops + local cancrates = capabilities.crates + -- top menu + local topmenu = MENU_GROUP:New(_group,"CTLD",nil) + local topcrates = MENU_GROUP:New(_group,"Manage Crates",topmenu) + local toptroops = MENU_GROUP:New(_group,"Manage Troops",topmenu) + local listmenu = MENU_GROUP_COMMAND:New(_group,"List boarded cargo",topmenu, self._ListCargo, self, _group, _unit) + local smokemenu = MENU_GROUP_COMMAND:New(_group,"Smoke zones nearby",topmenu, self.SmokeZoneNearBy, self, _unit, false) + local smokemenu = MENU_GROUP_COMMAND:New(_group,"Flare zones nearby",topmenu, self.SmokeZoneNearBy, self, _unit, true):Refresh() + -- sub menus + -- sub menu crates management + if cancrates then + local loadmenu = MENU_GROUP_COMMAND:New(_group,"Load crates",topcrates, self._LoadCratesNearby, self, _group, _unit) + local cratesmenu = MENU_GROUP:New(_group,"Get Crates",topcrates) + for _,_entry in pairs(self.Cargo_Crates) do + local entry = _entry -- #CTLD_CARGO + menucount = menucount + 1 + local menutext = string.format("Get crate for %s",entry.Name) + menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) + end + listmenu = MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates, self._ListCratesNearby, self, _group, _unit) + local unloadmenu = MENU_GROUP_COMMAND:New(_group,"Drop crates",topcrates, self._UnloadCrates, self, _group, _unit) + local buildmenu = MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates, self._BuildCrates, self, _group, _unit):Refresh() + end + -- sub menu troops management + if cantroops then + local troopsmenu = MENU_GROUP:New(_group,"Load troops",toptroops) + for _,_entry in pairs(self.Cargo_Troops) do + local entry = _entry -- #CTLD_CARGO + menucount = menucount + 1 + menus[menucount] = MENU_GROUP_COMMAND:New(_group,entry.Name,troopsmenu,self._LoadTroops, self, _group, _unit, entry) + end + local unloadmenu1 = MENU_GROUP_COMMAND:New(_group,"Drop troops",toptroops, self._UnloadTroops, self, _group, _unit):Refresh() + end + local rbcns = MENU_GROUP_COMMAND:New(_group,"List active zone beacons",topmenu, self._ListRadioBeacons, self, _group, _unit) + local hoverpars = MENU_GROUP_COMMAND:New(_group,"Show hover parameters",topmenu, self._ShowHoverParams, self, _group, _unit):Refresh() + self.MenusDone[_unitName] = true + end -- end group + end -- end unit + else -- menu build check + self:T(self.lid .. " Menus already done for this group!") + end -- end menu build check + end -- end for + return self + end + +--- User function - Add *generic* troop type loadable as cargo. This type will load directly into the heli without crates. +-- @param #CTLD self +-- @param #Name Name Unique name of this type of troop. E.g. "Anti-Air Small". +-- @param #Table Templates Table of #string names of late activated Wrapper.Group#GROUP making up this troop. +-- @param #CTLD_CARGO.Enum Type Type of cargo, here TROOPS - these will move to a nearby destination zone when dropped/build. +-- @param #number NoTroops Size of the group in number of Units across combined templates (for loading). +function CTLD:AddTroopsCargo(Name,Templates,Type,NoTroops) + self:T(self.lid .. " AddTroopsCargo") + self.CargoCounter = self.CargoCounter + 1 + -- Troops are directly loadable + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,true,NoTroops) + table.insert(self.Cargo_Troops,cargo) + return self +end + +--- User function - Add *generic* crate-type loadable as cargo. This type will create crates that need to be loaded, moved, dropped and built. +-- @param #CTLD self +-- @param #Name Name Unique name of this type of cargo. E.g. "Humvee". +-- @param #Table Templates Table of #string names of late activated Wrapper.Group#GROUP building this cargo. +-- @param #CTLD_CARGO.Enum Type Type of cargo. I.e. VEHICLE or FOB. VEHICLE will move to destination zones when dropped/build, FOB stays put. +-- @param #number NoCrates Number of crates needed to build this cargo. +function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates) + self:T(self.lid .. " AddCratesCargo") + self.CargoCounter = self.CargoCounter + 1 + -- Crates are not directly loadable + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,false,NoCrates) + table.insert(self.Cargo_Crates,cargo) + return self +end + +--- User function - Add a #CTLD.CargoZoneType zone for this CTLD instance. +-- @param #CTLD self +-- @param #CTLD.CargoZone Zone Zone #CTLD.CargoZone describing the zone. +function CTLD:AddZone(Zone) + self:T(self.lid .. " AddZone") + local zone = Zone -- #CTLD.CargoZone + if zone.type == CTLD.CargoZoneType.LOAD then + table.insert(self.pickupZones,zone) + self:T("Registered LOAD zone " .. zone.name) + elseif zone.type == CTLD.CargoZoneType.DROP then + table.insert(self.dropOffZones,zone) + self:T("Registered DROP zone " .. zone.name) + else + table.insert(self.wpZones,zone) + self:T("Registered MOVE zone " .. zone.name) + end + return self +end + +--- User function - Activate Name #CTLD.CargoZone.Type ZoneType for this CTLD instance. +-- @param #CTLD self +-- @param #string Name Name of the zone to change in the ME. +-- @param #CTLD.CargoZoneTyp ZoneType Type of zone this belongs to. +-- @param #boolean NewState (Optional) Set to true to activate, false to switch off. +function CTLD:ActivateZone(Name,ZoneType,NewState) + self:T(self.lid .. " AddZone") + local newstate = true + -- set optional in case we\'re deactivating + if not NewState or NewState == false then + newstate = false + end + -- get correct table + local zone = ZoneType -- #CTLD.CargoZone + local table = {} + if zone.type == CTLD.CargoZoneType.LOAD then + table = self.pickupZones + elseif zone.type == CTLD.CargoZoneType.DROP then + table = self.dropOffZones + else + table = self.wpZones + end + -- loop table + for _,_zone in pairs(table) do + local thiszone = _zone --#CTLD.CargoZone + if thiszone.name == Name then + thiszone.active = newstate + break + end + end + return self +end + +--- User function - Deactivate Name #CTLD.CargoZoneType ZoneType for this CTLD instance. +-- @param #CTLD self +-- @param #string Name Name of the zone to change in the ME. +-- @param #CTLD.CargoZoneTyp ZoneType Type of zone this belongs to. +function CTLD:DeactivateZone(Name,ZoneType) + self:T(self.lid .. " AddZone") + self:ActivateZone(Name,ZoneType,false) + return self +end + +--- Function to obtain a valid FM frequency. +-- @param #CTLD self +-- @param #string Name Name of zone. +-- @return #CTLD.ZoneBeacon Beacon Beacon table. +function CTLD:_GetFMBeacon(Name) + self:T(self.lid .. " _GetFMBeacon") + local beacon = {} -- #CTLD.ZoneBeacon + if #self.FreeFMFrequencies <= 1 then + self.FreeFMFrequencies = self.UsedFMFrequencies + self.UsedFMFrequencies = {} + end + --random + local FM = table.remove(self.FreeFMFrequencies, math.random(#self.FreeFMFrequencies)) + table.insert(self.UsedFMFrequencies, FM) + beacon.name = Name + beacon.frequency = FM / 1000000 + beacon.modulation = radio.modulation.FM + + return beacon +end + +--- Function to obtain a valid UHF frequency. +-- @param #CTLD self +-- @param #string Name Name of zone. +-- @return #CTLD.ZoneBeacon Beacon Beacon table. +function CTLD:_GetUHFBeacon(Name) + self:T(self.lid .. " _GetUHFBeacon") + local beacon = {} -- #CTLD.ZoneBeacon + if #self.FreeUHFFrequencies <= 1 then + self.FreeUHFFrequencies = self.UsedUHFFrequencies + self.UsedUHFFrequencies = {} + end + --random + local UHF = table.remove(self.FreeUHFFrequencies, math.random(#self.FreeUHFFrequencies)) + table.insert(self.UsedUHFFrequencies, UHF) + beacon.name = Name + beacon.frequency = UHF / 1000000 + beacon.modulation = radio.modulation.AM + + return beacon +end + +--- Function to obtain a valid VHF frequency. +-- @param #CTLD self +-- @param #string Name Name of zone. +-- @return #CTLD.ZoneBeacon Beacon Beacon table. +function CTLD:_GetVHFBeacon(Name) + self:T(self.lid .. " _GetVHFBeacon") + local beacon = {} -- #CTLD.ZoneBeacon + if #self.FreeVHFFrequencies <= 3 then + self.FreeVHFFrequencies = self.UsedVHFFrequencies + self.UsedVHFFrequencies = {} + end + --get random + local VHF = table.remove(self.FreeVHFFrequencies, math.random(#self.FreeVHFFrequencies)) + table.insert(self.UsedVHFFrequencies, VHF) + beacon.name = Name + beacon.frequency = VHF / 1000000 + beacon.modulation = radio.modulation.FM + return beacon +end + + +--- User function - Crates and adds a #CTLD.CargoZone zone for this CTLD instance. +-- Zones of type LOAD: Players load crates and troops here. +-- Zones of type DROP: Players can drop crates here. Note that troops can be unloaded anywhere. +-- Zone of type MOVE: Dropped troops and vehicles will start moving to the nearest zone of this type (also see options). +-- @param #CTLD self +-- @param #string Name Name of this zone, as in Mission Editor. +-- @param #string Type Type of this zone, #CTLD.CargoZoneType +-- @param #number Color Smoke/Flare color e.g. #SMOKECOLOR.Red +-- @param #string Active Is this zone currently active? +-- @param #string HasBeacon Does this zone have a beacon if it is active? +-- @return #CTLD self +function CTLD:AddCTLDZone(Name, Type, Color, Active, HasBeacon) + self:T(self.lid .. " AddCTLDZone") + + local ctldzone = {} -- #CTLD.CargoZone + ctldzone.active = Active or false + ctldzone.color = Color or SMOKECOLOR.Red + ctldzone.name = Name or "NONE" + ctldzone.type = Type or CTLD.CargoZoneType.MOVE -- #CTLD.CargoZoneType + ctldzone.hasbeacon = HasBeacon or false + + if HasBeacon then + ctldzone.fmbeacon = self:_GetFMBeacon(Name) + ctldzone.uhfbeacon = self:_GetUHFBeacon(Name) + ctldzone.vhfbeacon = self:_GetVHFBeacon(Name) + else + ctldzone.fmbeacon = nil + ctldzone.uhfbeacon = nil + ctldzone.vhfbeacon = nil + end + + self:AddZone(ctldzone) + return self +end + +--- Function to show list of radio beacons +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +function CTLD:_ListRadioBeacons(Group, Unit) + self:T(self.lid .. " _ListRadioBeacons") + local report = REPORT:New("Active Zone Beacons") + report:Add("------------------------------------------------------------") + local zones = {[1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones} + for i=1,3 do + for index,cargozone in pairs(zones[i]) do + -- Get Beacon object from zone + local czone = cargozone -- #CTLD.CargoZone + if czone.active and czone.hasbeacon then + local FMbeacon = czone.fmbeacon -- #CTLD.ZoneBeacon + local VHFbeacon = czone.vhfbeacon -- #CTLD.ZoneBeacon + local UHFbeacon = czone.uhfbeacon -- #CTLD.ZoneBeacon + local Name = czone.name + local FM = FMbeacon.frequency -- MHz + local VHF = VHFbeacon.frequency * 1000 -- KHz + local UHF = UHFbeacon.frequency -- MHz + report:AddIndent(string.format(" %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ", Name, FM, VHF, UHF),"|") + end + end + end + if report:GetCount() == 1 then + report:Add("--------- N O N E ------------") + end + report:Add("------------------------------------------------------------") + local m = MESSAGE:New(report:Text(),30,"CTLD",true):ToGroup(Group) + return self +end + +--- Add radio beacon to zone. Runs 30 secs. +-- @param #CTLD self +-- @param #string Name Name of zone. +-- @param #string Sound Name of soundfile. +-- @param #number Mhz Frequency in Mhz. +-- @param #number Modulation Modulation AM or FM. +function CTLD:_AddRadioBeacon(Name, Sound, Mhz, Modulation) + self:T(self.lid .. " _AddRadioBeacon") + local Zone = ZONE:FindByName(Name) + local Sound = Sound or "beacon.ogg" + if Zone then + local ZoneCoord = Zone:GetCoordinate() + local ZoneVec3 = ZoneCoord:GetVec3() + local Frequency = Mhz * 1000000 -- Freq in Hertz + local Sound = "l10n/DEFAULT/"..Sound + trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, Frequency, 1000) -- Beacon in MP only runs for 30secs straight + end + return self +end + +--- Function to refresh radio beacons +-- @param #CTLD self +function CTLD:_RefreshRadioBeacons() + self:I(self.lid .. " _RefreshRadioBeacons") + + local zones = {[1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones} + for i=1,3 do + for index,cargozone in pairs(zones[i]) do + -- Get Beacon object from zone + local czone = cargozone -- #CTLD.CargoZone + local Sound = self.RadioSound + if czone.active and czone.hasbeacon then + local FMbeacon = czone.fmbeacon -- #CTLD.ZoneBeacon + local VHFbeacon = czone.vhfbeacon -- #CTLD.ZoneBeacon + local UHFbeacon = czone.uhfbeacon -- #CTLD.ZoneBeacon + local Name = czone.name + local FM = FMbeacon.frequency -- MHz + local VHF = VHFbeacon.frequency -- KHz + local UHF = UHFbeacon.frequency -- MHz + self:_AddRadioBeacon(Name,Sound,FM,radio.modulation.FM) + self:_AddRadioBeacon(Name,Sound,VHF,radio.modulation.FM) + self:_AddRadioBeacon(Name,Sound,UHF,radio.modulation.AM) + end + end + end + return self +end + +--- function to see if a unit is in a specific zone type. +-- @param #CTLD self +-- @param Wrapper.Unit#UNIT Unit Unit +-- @param #CTLD.CargoZoneType Zonetype Zonetype +-- @return #boolean Outcome Is in zone or not +-- @return #string name Closest zone name +-- @return #string zone Closest Core.Zone#ZONE object +-- @return #number distance Distance to closest zone +function CTLD:IsUnitInZone(Unit,Zonetype) + self:T(self.lid .. " IsUnitInZone") + local unitname = Unit:GetName() + self:T(string.format("%s | Zone search for %s | Type %s",self.lid,unitname,Zonetype)) + local zonetable = {} + local outcome = false + if Zonetype == CTLD.CargoZoneType.LOAD then + zonetable = self.pickupZones -- #table + elseif Zonetype == CTLD.CargoZoneType.DROP then + zonetable = self.dropOffZones -- #table + else + zonetable = self.wpZones -- #table + end + --- now see if we\'re in + local zonecoord = nil + local colorret = nil + local maxdist = 1000000 -- 100km + local zoneret = nil + local zonenameret = nil + for _,_cargozone in pairs(zonetable) do + local czone = _cargozone -- #CTLD.CargoZone + local unitcoord = Unit:GetCoordinate() + local zonename = czone.name + local zone = ZONE:FindByName(zonename) + zonecoord = zone:GetCoordinate() + local active = czone.active + local color = czone.color + local zoneradius = zone:GetRadius() + local distance = self:_GetDistance(zonecoord,unitcoord) + self:T(string.format("Check distance: %d",distance)) + if distance <= zoneradius and active then + outcome = true + end + if maxdist > distance then + maxdist = distance + zoneret = zone + zonenameret = zonename + colorret = color + end + end + self:T({outcome, zonenameret, zoneret, maxdist}) + return outcome, zonenameret, zoneret, maxdist +end + +--- Userfunction - Start smoke in a zone close to the Unit. +-- @param #CTLD self +-- @param Wrapper.Unit#UNIT Unit The Unit. +-- @param #boolean Flare If true, flare instead. +function CTLD:SmokeZoneNearBy(Unit, Flare) + self:T(self.lid .. " SmokeZoneNearBy") + -- table of #CTLD.CargoZone table + local unitcoord = Unit:GetCoordinate() + local Group = Unit:GetGroup() + local smokedistance = self.smokedistance + local smoked = false + local zones = {[1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones} + for i=1,3 do + for index,cargozone in pairs(zones[i]) do + local CZone = cargozone --#CTLD.CargoZone + local zonename = CZone.name + local zone = ZONE:FindByName(zonename) + local zonecoord = zone:GetCoordinate() + local active = CZone.active + local color = CZone.color + local distance = self:_GetDistance(zonecoord,unitcoord) + if distance < smokedistance and active then + -- smoke zone since we\'re nearby + if not Flare then + zonecoord:Smoke(color or SMOKECOLOR.White) + else + if color == SMOKECOLOR.Blue then color = FLARECOLOR.White end + zonecoord:Flare(color or FLARECOLOR.White) + end + local txt = "smoking" + if Flare then txt = "flaring" end + local m = MESSAGE:New(string.format("Roger, %s zone %s!",txt, zonename),10,"CTLD"):ToGroup(Group) + smoked = true + end + end + end + if not smoked then + local distance = UTILS.MetersToNM(self.smkedistance) + local m = MESSAGE:New(string.format("Negative, need to be closer than %dnm to a zone!",distance),10,"CTLD"):ToGroup(Group) + end + return self +end + --- User - Function to add/adjust unittype capabilities. + -- @param #CTLD self + -- @param #string Unittype The unittype to adjust. If passed as Wrapper.Unit#UNIT, it will search for the unit in the mission. + -- @param #boolean Cancrates Unit can load crates. + -- @param #boolean Cantroops Unit can load troops. + -- @param #number Cratelimit Unit can carry number of crates. + -- @param #number Trooplimit Unit can carry number of troops. + function CTLD:UnitCapabilities(Unittype, Cancrates, Cantroops, Cratelimit, Trooplimit) + self:T(self.lid .. " UnitCapabilities") + local unittype = nil + local unit = nil + if type(Unittype) == "string" then + unittype = Unittype + elseif type(Unittype) == "table" then + unit = UNIT:FindByName(Unittype) -- Wrapper.Unit#UNIT + unittype = unit:GetTypeName() + else + return self + end + -- set capabilities + local capabilities = {} -- #CTLD.UnitCapabilities + capabilities.type = unittype + capabilities.crates = Cancrates or false + capabilities.troops = Cantroops or false + capabilities.cratelimit = Cratelimit or 0 + capabilities.trooplimit = Trooplimit or 0 + self.UnitTypes[unittype] = capabilities + return self + end + + --- Check if a unit is hovering *in parameters*. + -- @param #CTLD self + -- @param Wrapper.Unit#UNIT Unit + -- @return #boolean Outcome + function CTLD:IsCorrectHover(Unit) + self:T(self.lid .. " IsCorrectHover") + local outcome = false + -- see if we are in air and within parameters. + if self:IsUnitInAir(Unit) then + -- get speed and height + local uspeed = Unit:GetVelocityMPS() + local uheight = Unit:GetHeight() + local ucoord = Unit:GetCoordinate() + local gheight = ucoord:GetLandHeight() + local aheight = uheight - gheight -- height above ground + local maxh = self.maximumHoverHeight -- 15 + local minh = self.minimumHoverHeight -- 5 + local mspeed = 2 -- 2 m/s + self:T(string.format("%s Unit parameters: at %dm AGL with %dmps",self.lid,aheight,uspeed)) + if (uspeed <= maxh) and (aheight <= maxh) and (aheight >= minh) then + -- yep within parameters + outcome = true + end + end + return outcome + end + + --- List if a unit is hovering *in parameters*. + -- @param #CTLD self + -- @param Wrapper.Group#GROUP Group + -- @param Wrapper.Unit#UNIT Unit + function CTLD:_ShowHoverParams(Group,Unit) + local inhover = self:IsCorrectHover(Unit) + local htxt = "true" + if not inhover then htxt = "false" end + local text = string.format("Hover parameter (autoload):\n - Min height %dm \n - Max height %dm \n - Max speed 2mps \n - In parameter: %s", self.minimumHoverHeight, self.maximumHoverHeight, htxt) + local m = MESSAGE:New(text,10,"CTLD",false):ToGroup(Group) + return self + end + + --- Check if a unit is in a load zone and is hovering in parameters. + -- @param #CTLD self + -- @param Wrapper.Unit#UNIT Unit + -- @return #boolean Outcome + function CTLD:CanHoverLoad(Unit) + self:T(self.lid .. " CanHoverLoad") + local outcome = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) and self:IsCorrectHover(Unit) + return outcome + end + + --- Check if a unit is above ground. + -- @param #CTLD self + -- @param Wrapper.Unit#UNIT Unit + -- @return #boolean Outcome + function CTLD:IsUnitInAir(Unit) + -- get speed and height + local uheight = Unit:GetHeight() + local ucoord = Unit:GetCoordinate() + local gheight = ucoord:GetLandHeight() + local aheight = uheight - gheight -- height above ground + if aheight >= self.minimumHoverHeight then + return true + else + return false + end + end + + --- Autoload if we can do crates, have capacity free and are in a load zone. + -- @param #CTLD self + -- @param Wrapper.Unit#UNIT Unit + -- @return #CTLD self + function CTLD:AutoHoverLoad(Unit) + self:T(self.lid .. " AutoHoverLoad") + -- get capabilities and current load + local unittype = Unit:GetTypeName() + local unitname = Unit:GetName() + local Group = Unit:GetGroup() + local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + local cancrates = capabilities.crates -- #boolean + local cratelimit = capabilities.cratelimit -- #number + if cancrates then + -- get load + local numberonboard = 0 + local loaded = {} + if self.Loaded_Cargo[unitname] then + loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo + numberonboard = loaded.Cratesloaded or 0 + end + local load = cratelimit - numberonboard + local canload = self:CanHoverLoad(Unit) + if canload and load > 0 then + self:_LoadCratesNearby(Group,Unit) + end + end + return self + end + + --- Run through all pilots and see if we autoload. + -- @param #CTLD self + -- @return #CTLD self + function CTLD:CheckAutoHoverload() + if self.hoverautoloading then + for _,_pilot in pairs (self.CtldUnits) do + local Unit = UNIT:FindByName(_pilot) + self:AutoHoverLoad(Unit) + end + end + return self + end + +------------------------------------------------------------------- +-- FSM functions +------------------------------------------------------------------- + + --- FSM Function onafterStart. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @return #CTLD self + function CTLD:onafterStart(From, Event, To) + self:I({From, Event, To}) + if self.useprefix then + self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.prefixes):FilterCategoryHelicopter():FilterStart() + else + self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart() + end + -- Events + self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) + self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) + self:HandleEvent(EVENTS.PlayerLeaveUnit, self._EventHandler) + self:__Status(-5) + return self + end + + --- FSM Function onbeforeStatus. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @return #CTLD self + function CTLD:onbeforeStatus(From, Event, To) + self:T({From, Event, To}) + self:_RefreshF10Menus() + self:_RefreshRadioBeacons() + self:CheckAutoHoverload() + return self + end + + --- FSM Function onafterStatus. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @return #CTLD self + function CTLD:onafterStatus(From, Event, To) + self:I({From, Event, To}) + -- gather some stats + -- pilots + local pilots = 0 + for _,_pilot in pairs (self.CtldUnits) do + + pilots = pilots + 1 + end + + -- spawned cargo boxes curr in field + local boxes = 0 + for _,_pilot in pairs (self.Spawned_Cargo) do + boxes = boxes + 1 + end + + local cc = self.CargoCounter + local tc = self.TroopCounter + + if self.debug or self.verbose > 0 then + local text = string.format("%s Pilots %d | Live Crates %d |\nCargo Counter %d | Troop Counter %d", self.lid, pilots, boxes, cc, tc) + local m = MESSAGE:New(text,10,"CTLD"):ToAll() + end + self:__Status(-30) + return self + end + + --- FSM Function onafterStop. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @return #CTLD self + function CTLD:onafterStop(From, Event, To) + self:T({From, Event, To}) + self:UnhandleEvent(EVENTS.PlayerEnterAircraft) + self:UnhandleEvent(EVENTS.PlayerEnterUnit) + self:UnhandleEvent(EVENTS.PlayerLeaveUnit) + return self + end + + --- FSM Function onbeforeTroopsPickedUp. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #CTLD_CARGO Cargo Cargo crate. + -- @return #CTLD self + function CTLD:onbeforeTroopsPickedUp(From, Event, To, Group, Unit, Cargo) + self:I({From, Event, To}) + return self + end + + --- FSM Function onbeforeCratesPickedUp. + -- @param #CTLD self + -- @param #string From State . + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #CTLD_CARGO Cargo Cargo crate. + -- @return #CTLD self + function CTLD:onbeforeCratesPickedUp(From, Event, To, Group, Unit, Cargo) + self:I({From, Event, To}) + return self + end + + --- FSM Function onbeforeTroopsDeployed. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param Wrapper.Group#GROUP Troops Troops #GROUP Object. + -- @return #CTLD self + function CTLD:onbeforeTroopsDeployed(From, Event, To, Group, Unit, Troops) + self:I({From, Event, To}) + return self + end + + --- FSM Function onbeforeCratesDropped. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #table Cargotable Table of #CTLD_CARGO objects dropped. + -- @return #CTLD self + function CTLD:onbeforeCratesDropped(From, Event, To, Group, Unit, Cargotable) + self:I({From, Event, To}) + return self + end + + --- FSM Function onbeforeCratesBuild. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @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:I({From, Event, To}) + return self + end + +end -- end do +------------------------------------------------------------------- +-- End Ops.CTLD.lua +------------------------------------------------------------------- From ee503a378ee711e2009858db73559d50c833e321 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 22 Jun 2021 18:24:24 +0200 Subject: [PATCH 062/111] Update Modules.lua (#1557) added CTLD --- Moose Development/Moose/Modules.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index fef4fc982..7ad1b1829 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -85,6 +85,7 @@ __Moose.Include( 'Scripts/Moose/Ops/Squadron.lua' ) __Moose.Include( 'Scripts/Moose/Ops/AirWing.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Intelligence.lua' ) __Moose.Include( 'Scripts/Moose/Ops/CSAR.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/CTLD.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' ) From b84c0aba593eb366dcb4a67320f610a14f718ff7 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 22 Jun 2021 18:24:36 +0200 Subject: [PATCH 063/111] Update Moose.files (#1556) --- Moose Setup/Moose.files | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index e7bec2087..077e75168 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -81,6 +81,8 @@ Ops/NavyGroup.lua Ops/Squadron.lua Ops/AirWing.lua Ops/Intelligence.lua +Ops/CSAR.lua +Ops/CTLD.lua AI/AI_Balancer.lua AI/AI_Air.lua From 576281a61212c8440fb939bf38bea5c5a129b691 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 22 Jun 2021 18:24:46 +0200 Subject: [PATCH 064/111] Update CSAR.lua (#1554) Added options to limit number of downed pilots via Events (mission designers can still "inject" downed pilots): `self.limitmaxdownedpilots = true self.maxdownedpilots = 10` --- Moose Development/Moose/Ops/CSAR.lua | 62 ++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index ad6ae1a97..61c3c58bc 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -59,7 +59,7 @@ -- local my_csar = CSAR:New(coalition.side.BLUE,"Downed Pilot","Luftrettung") -- -- options -- my_csar.immortalcrew = true -- downed pilot spawn is immortal --- my_csar.invisiblevrew = false -- downed pilot spawn is visible +-- my_csar.invisiblecrew = false -- downed pilot spawn is visible -- -- start the FSM -- my_csar:__Start(5) -- @@ -86,6 +86,9 @@ -- self.useprefix = true -- Requires CSAR helicopter #GROUP names to have the prefix(es) defined below. -- self.csarPrefix = { "helicargo", "MEDEVAC"} -- #GROUP name prefixes used for useprefix=true - DO NOT use # in helicopter names in the Mission Editor! -- self.verbose = 0 -- set to > 1 for stats output for debugging. +-- -- (added 0.1.4) limit amount of downed pilots spawned by ejection events +-- self.limitmaxdownedpilots = true, +-- self.maxdownedpilots = 10, -- -- ## 2.1 Experimental Features -- @@ -188,6 +191,8 @@ CSAR = { smokecolor = 4, rescues = 0, rescuedpilots = 0, + limitmaxdownedpilots = true, + maxdownedpilots = 10, } --- Downed pilots info. @@ -235,7 +240,7 @@ CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.3r4" +CSAR.version="0.1.4r1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -350,6 +355,9 @@ function CSAR:New(Coalition, Template, Alias) self.mashprefix = {"MASH"} -- prefixes used to find MASHes self.bluemash = SET_GROUP:New():FilterCoalitions(self.coalition):FilterPrefixes(self.mashprefix):FilterOnce() -- currently only GROUP objects, maybe support STATICs also? self.autosmoke = false -- automatically smoke location when heli is near + -- added 0.1.4 + self.limitmaxdownedpilots = true + self.maxdownedpilots = 25 -- WARNING - here\'ll be dragons -- for this to work you need to de-sanitize your mission environment in \Scripts\MissionScripting.lua @@ -774,7 +782,13 @@ function CSAR:_EventHandler(EventData) if self:_DoubleEjection(_unitname) then return end - + + -- limit no of pilots in the field. + if self.limitmaxdownedpilots and self:_ReachedPilotLimit() then + return + end + + -- all checks passed, get going. local _freq = self:_GenerateADFFrequency() self:_AddCsar(_coalition, _unit:GetCountry(), _unit:GetCoordinate() , _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, false, 0) @@ -1776,6 +1790,36 @@ function CSAR:_RefreshRadioBeacons() end end +--- Helper function to count active downed pilots. +-- @param #CSAR self +-- @return #number Number of pilots in the field. +function CSAR:_CountActiveDownedPilots() + self:T(self.lid .. " _CountActiveDownedPilots") + local PilotsInFieldN = 0 + for _, _unitName in pairs(self.downedPilots) do + self:T({_unitName}) + if _unitName.name ~= nil then + PilotsInFieldN = PilotsInFieldN + 1 + end + end + return PilotsInFieldN +end + +--- Helper to decide if we're over max limit. +-- @param #CSAR self +-- @return #boolean True or false. +function CSAR:_ReachedPilotLimit() + self:T(self.lid .. " _ReachedPilotLimit") + local limit = self.maxdownedpilots + local islimited = self.limitmaxdownedpilots + local count = self:_CountActiveDownedPilots() + if islimited and (count >= limit) then + return true + else + return false + end +end + ------------------------------ --- FSM internal Functions --- ------------------------------ @@ -1844,13 +1888,7 @@ function CSAR:onafterStatus(From, Event, To) NumberOfSARPilots = NumberOfSARPilots + 1 end - local PilotsInFieldN = 0 - for _, _unitName in pairs(self.downedPilots) do - self:T({_unitName}) - if _unitName.name ~= nil then - PilotsInFieldN = PilotsInFieldN + 1 - end - end + local PilotsInFieldN = self:_CountActiveDownedPilots() local PilotsBoarded = 0 for _, _unitName in pairs(self.inTransitGroups) do @@ -1860,8 +1898,8 @@ function CSAR:onafterStatus(From, Event, To) end if self.verbose > 0 then - local text = string.format("%s Active SAR: %d | Downed Pilots in field: %d | Pilots boarded: %d | Landings: %d | Pilots rescued: %d", - self.lid,NumberOfSARPilots,PilotsInFieldN,PilotsBoarded,self.rescues,self.rescuedpilots) + local text = string.format("%s Active SAR: %d | Downed Pilots in field: %d (max %d) | Pilots boarded: %d | Landings: %d | Pilots rescued: %d", + self.lid,NumberOfSARPilots,PilotsInFieldN,self.maxdownedpilots,PilotsBoarded,self.rescues,self.rescuedpilots) self:T(text) if self.verbose < 2 then self:I(text) From 5021a1e1f3d1a833f2b97ed921b0ec2f0671ed71 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 22 Jun 2021 18:26:44 +0200 Subject: [PATCH 065/111] Update CTLD.lua --- Moose Development/Moose/Ops/CTLD.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 38640518f..4d34aabe8 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -22,7 +22,7 @@ -- @module Ops.CTLD -- @image OPS_CTLD.jpg --- Date: June 2021 +-- Date: 22 June 2021 do ------------------------------------------------------ @@ -382,7 +382,7 @@ do -- @field #CTLD CTLD = { ClassName = "CTLD", - verbose = 2, + verbose = 0, lid = "", coalition = 1, coalitiontxt = "blue", From 5123ab0720d5efb27dd93a0518fec501d47a40c8 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 23 Jun 2021 09:47:05 +0200 Subject: [PATCH 066/111] Update CTLD.lua (#1558) corrected fsm function name headlines in documentation --- Moose Development/Moose/Ops/CTLD.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 4d34aabe8..211932703 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -327,7 +327,7 @@ do -- ... your code here ... -- end -- --- ## 3.3 OnAfterTroopsTroopsDeployed +-- ## 3.3 OnAfterTroopsDeployed -- -- This function is called when a player has deployed troops into the field: -- @@ -335,7 +335,7 @@ do -- ... your code here ... -- end -- --- ## 3.4 OnAfterTroopsCratesDropped +-- ## 3.4 OnAfterCratesDropped -- -- This function is called when a player has deployed crates to a DROP zone: -- @@ -343,7 +343,7 @@ do -- ... your code here ... -- end -- --- ## 3.5 OnAfterTroopsCratesBuild +-- ## 3.5 OnAfterCratesBuild -- -- This function is called when a player has build a vehicle or FOB: -- From d4cdfcc48c6d05a79c7a48558631c751a7be6d48 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 23 Jun 2021 14:08:34 +0200 Subject: [PATCH 067/111] Corrected Mi-8MTV2 Unit Type Name --- Moose Development/Moose/Ops/CSAR.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 61c3c58bc..6c6245ef4 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -234,7 +234,7 @@ CSAR.AircraftType["SA342Minigun"] = 2 CSAR.AircraftType["SA342L"] = 4 CSAR.AircraftType["SA342M"] = 4 CSAR.AircraftType["UH-1H"] = 8 -CSAR.AircraftType["Mi-8MT"] = 12 +CSAR.AircraftType["Mi-8MTV2"] = 12 CSAR.AircraftType["Mi-24P"] = 8 CSAR.AircraftType["Mi-24V"] = 8 From 3b44aba34173931d4ecd1f47e09d0b6391d0a777 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 24 Jun 2021 08:34:14 +0200 Subject: [PATCH 068/111] Updated frequency test --- Moose Development/Moose/Ops/CSAR.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 6c6245ef4..1f9d22f64 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -240,7 +240,7 @@ CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.4r1" +CSAR.version="0.1.4r3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -1783,7 +1783,7 @@ function CSAR:_RefreshRadioBeacons() for _,_pilot in pairs (PilotTable) do local pilot = _pilot -- #CSAR.DownedPilot local group = pilot.group - local frequency = pilot.frequency + local frequency = pilot.frequency or 0 -- thanks to @Thrud if frequency and frequency > 0 then self:_AddBeaconToGroup(group,frequency) end From 2ff128f184b7a83b4834c6ee68c4003d8d696e40 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 25 Jun 2021 12:39:02 +0200 Subject: [PATCH 069/111] Added Docu for functions --- Moose Development/Moose/Ops/CSAR.lua | 116 +++++++++++++++------------ Moose Development/Moose/Ops/CTLD.lua | 93 ++++++++++----------- 2 files changed, 113 insertions(+), 96 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 1f9d22f64..7be6de481 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -25,7 +25,7 @@ -- Date: June 2021 ------------------------------------------------------------------------- ---- **CSAR** class, extends #Core.Base#BASE, #Core.Fsm#FSM +--- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM -- @type CSAR -- @field #string ClassName Name of the class. -- @field #number verbose Verbosity level. @@ -157,7 +157,7 @@ -- If missions designers want to spawn downed pilots into the field, e.g. at mission begin to give the helicopter guys works, they can do this like so: -- -- -- Create downed "Pilot Wagner" in #ZONE "CSAR_Start_1" at a random point for the blue coalition --- my_csar:_SpawnCsarAtZone( "CSAR_Start_1", coalition.side.BLUE, "Pilot Wagner", true ) +-- my_csar:SpawnCSARAtZone( "CSAR_Start_1", coalition.side.BLUE, "Pilot Wagner", true ) -- -- -- @field #CSAR @@ -240,7 +240,7 @@ CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.4r3" +CSAR.version="0.1.4r4" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -452,7 +452,7 @@ end --- Helper Functions --- ------------------------ ---- Function to insert downed pilot tracker object. +--- (Internal) Function to insert downed pilot tracker object. -- @param #CSAR self -- @param Wrapper.Group#GROUP Group The #GROUP object -- @param #string Groupname Name of the spawned group. @@ -490,7 +490,7 @@ function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Descript self.downedpilotcounter = self.downedpilotcounter+1 end ---- Count pilots on board. +--- (Internal) Count pilots on board. -- @param #CSAR self -- @param #string _heliName -- @return #number count @@ -505,7 +505,7 @@ function CSAR:_PilotsOnboard(_heliName) return count end ---- Function to check for dupe eject events. +--- (Internal) Function to check for dupe eject events. -- @param #CSAR self -- @param #string _unitname Name of unit. -- @return #boolean Outcome @@ -521,7 +521,7 @@ function CSAR:_DoubleEjection(_unitname) return false end ---- Spawn a downed pilot +--- (Internal) Spawn a downed pilot -- @param #CSAR self -- @param #number country Country for template. -- @param Core.Point#COORDINATE point Coordinate to spawn at. @@ -547,7 +547,7 @@ function CSAR:_SpawnPilotInField(country,point) return _spawnedGroup, alias -- Wrapper.Group#GROUP object end ---- Add options to a downed pilot +--- (Internal) Add options to a downed pilot -- @param #CSAR self -- @param Wrapper.Group#GROUP group Group to use. function CSAR:_AddSpecialOptions(group) @@ -581,7 +581,7 @@ function CSAR:_AddSpecialOptions(group) end ---- Function to spawn a CSAR object into the scene. +--- (Internal) Function to spawn a CSAR object into the scene. -- @param #CSAR self -- @param #number _coalition Coalition -- @param DCS#country.id _country Country ID @@ -634,7 +634,7 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla end ---- Function to add a CSAR object into the scene at a zone coordinate. For mission designers wanting to add e.g. PoWs to the scene. +--- (Internal) Function to add a CSAR object into the scene at a zone coordinate. For mission designers wanting to add e.g. PoWs to the scene. -- @param #CSAR self -- @param #string _zone Name of the zone. -- @param #number _coalition Coalition. @@ -672,8 +672,24 @@ function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _ self:_AddCsar(_coalition, _country, pos, "PoW", "Unknown", nil, freq, _nomessage, _description) end +--- Function to add a CSAR object into the scene at a zone coordinate. For mission designers wanting to add e.g. PoWs to the scene. +-- @param #CSAR self +-- @param #string Zone Name of the zone. +-- @param #number Coalition Coalition. +-- @param #string Description (optional) Description. +-- @param #boolean RandomPoint (optional) Random yes or no. +-- @param #boolean Nomessage (optional) If true, don\'t send a message to SAR. +-- @usage If missions designers want to spawn downed pilots into the field, e.g. at mission begin, to give the helicopter guys works, they can do this like so: +-- +-- -- Create downed "Pilot Wagner" in #ZONE "CSAR_Start_1" at a random point for the blue coalition +-- my_csar:SpawnCSARAtZone( "CSAR_Start_1", coalition.side.BLUE, "Pilot Wagner", true ) +function CSAR:SpawnCSARAtZone(Zone, Coalition, Description, RandomPoint, Nomessage) + self:_SpawnCsarAtZone(Zone, Coalition, Description, RandomPoint, Nomessage) + return self +end + -- TODO: Split in functions per Event type ---- Event handler. +--- (Internal) Event handler. -- @param #CSAR self function CSAR:_EventHandler(EventData) self:T(self.lid .. " _EventHandler") @@ -836,7 +852,7 @@ function CSAR:_EventHandler(EventData) end ---- Initialize the action for a pilot. +--- (Internal) Initialize the action for a pilot. -- @param #CSAR self -- @param Wrapper.Group#GROUP _downedGroup The group to rescue. -- @param #string _GroupName Name of the Group @@ -864,7 +880,7 @@ function CSAR:_InitSARForPilot(_downedGroup, _GroupName, _freq, _nomessage) self:__PilotDown(2,_downedGroup, _freqk, _leadername, _coordinatesText) end ---- Check if a name is in downed pilot table +--- (Internal) Check if a name is in downed pilot table -- @param #CSAR self -- @param #string name Name to search for. -- @return #boolean Outcome. @@ -883,7 +899,7 @@ function CSAR:_CheckNameInDownedPilots(name) return found, table end ---- Check if a name is in downed pilot table and remove it. +--- (Internal) Check if a name is in downed pilot table and remove it. -- @param #CSAR self -- @param #string name Name to search for. -- @param #boolean force Force removal. @@ -914,7 +930,7 @@ function CSAR:_RemoveNameFromDownedPilots(name,force) return found end ---- Check state of wounded group. +--- (Internal) Check state of wounded group. -- @param #CSAR self -- @param #string heliname heliname -- @param #string woundedgroupname woundedgroupname @@ -967,7 +983,7 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) end end ---- Function to pop a smoke at a wounded pilot\'s positions. +--- (Internal) Function to pop a smoke at a wounded pilot\'s positions. -- @param #CSAR self -- @param #string _woundedGroupName Name of the group. -- @param Wrapper.Group#GROUP _woundedLeader Object of the group. @@ -984,7 +1000,7 @@ function CSAR:_PopSmokeForGroup(_woundedGroupName, _woundedLeader) end end ---- Function to pickup the wounded pilot from the ground. +--- (Internal) Function to pickup the wounded pilot from the ground. -- @param #CSAR self -- @param Wrapper.Unit#UNIT _heliUnit Object of the group. -- @param #string _pilotName Name of the pilot. @@ -1035,7 +1051,7 @@ function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupNam return true end ---- Move group to destination. +--- (Internal) Move group to destination. -- @param #CSAR self -- @param Wrapper.Group#GROUP _leader -- @param Core.Point#COORDINATE _destination @@ -1048,7 +1064,7 @@ function CSAR:_OrderGroupToMoveToPoint(_leader, _destination) group:RouteToVec2(coordinate,5) end ---- Function to check if heli is close to group. +--- (Internal) Function to check if heli is close to group. -- @param #CSAR self -- @param #number _distance -- @param Wrapper.Unit#UNIT _heliUnit @@ -1179,7 +1195,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG end end ---- Check if group not KIA. +--- (Internal) Check if group not KIA. -- @param #CSAR self -- @param Wrapper.Group#GROUP _woundedGroup -- @param #string _woundedGroupName @@ -1209,7 +1225,7 @@ function CSAR:_CheckGroupNotKIA(_woundedGroup, _woundedGroupName, _heliUnit, _he return inTransit end ---- Monitor in-flight returning groups. +--- (Internal) Monitor in-flight returning groups. -- @param #CSAR self -- @param #string heliname Heli name -- @param #string groupname Group name @@ -1245,7 +1261,7 @@ function CSAR:_ScheduledSARFlight(heliname,groupname) self:__Returning(-5,heliname,_woundedGroupName) end ---- Mark pilot as rescued and remove from tables. +--- (Internal) Mark pilot as rescued and remove from tables. -- @param #CSAR self -- @param Wrapper.Unit#UNIT _heliUnit function CSAR:_RescuePilots(_heliUnit) @@ -1270,7 +1286,7 @@ function CSAR:_RescuePilots(_heliUnit) self:__Rescued(-1,_heliUnit,_heliName, PilotsSaved) end ---- Check and return Wrappe.Unit#UNIT based on the name if alive. +--- (Internal) Check and return Wrappe.Unit#UNIT based on the name if alive. -- @param #CSAR self -- @param #string _unitname Name of Unit -- @return #UNIT or nil @@ -1284,7 +1300,7 @@ function CSAR:_GetSARHeli(_unitName) end end ---- Display message to single Unit. +--- (Internal) Display message to single Unit. -- @param #CSAR self -- @param Wrapper.Unit#UNIT _unit Unit #UNIT to display to. -- @param #string _text Text of message. @@ -1308,7 +1324,7 @@ function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak) end end ---- Function to get string of a group\'s position. +--- (Internal) Function to get string of a group\'s position. -- @param #CSAR self -- @param Wrapper.Controllable#CONTROLLABLE _woundedGroup Group or Unit object. -- @return #string Coordinates as Text @@ -1334,7 +1350,7 @@ function CSAR:_GetPositionOfWounded(_woundedGroup) return _coordinatesText end ---- Display active SAR tasks to player. +--- (Internal) Display active SAR tasks to player. -- @param #CSAR self -- @param #string _unitName Unit to display to function CSAR:_DisplayActiveSAR(_unitName) @@ -1386,7 +1402,7 @@ function CSAR:_DisplayActiveSAR(_unitName) self:_DisplayMessageToSAR(_heli, _msg, self.messageTime*2) end ---- Find the closest downed pilot to a heli. +--- (Internal) Find the closest downed pilot to a heli. -- @param #CSAR self -- @param Wrapper.Unit#UNIT _heli Helicopter #UNIT -- @return #table Table of results @@ -1420,7 +1436,7 @@ function CSAR:_GetClosestDownedPilot(_heli) return { pilot = _closestGroup, distance = _shortestDistance, groupInfo = _closestGroupInfo } end ---- Fire a flare at the point of a downed pilot. +--- (Internal) Fire a flare at the point of a downed pilot. -- @param #CSAR self -- @param #string _unitName Name of the unit. function CSAR:_SignalFlare(_unitName) @@ -1455,7 +1471,7 @@ function CSAR:_SignalFlare(_unitName) end end ---- Display info to all SAR groups. +--- (Internal) Display info to all SAR groups. -- @param #CSAR self -- @param #string _message Message to display. -- @param #number _side Coalition of message. @@ -1472,7 +1488,7 @@ function CSAR:_DisplayToAllSAR(_message, _side, _messagetime) end end ----Request smoke at closest downed pilot. +---(Internal) Request smoke at closest downed pilot. --@param #CSAR self --@param #string _unitName Name of the helicopter function CSAR:_Reqsmoke( _unitName ) @@ -1504,7 +1520,7 @@ function CSAR:_Reqsmoke( _unitName ) end end ---- Determine distance to closest MASH. +--- (Internal) Determine distance to closest MASH. -- @param #CSAR self -- @param Wrapper.Unit#UNIT _heli Helicopter #UNIT -- @retunr @@ -1559,7 +1575,7 @@ function CSAR:_GetClosestMASH(_heli) end end ---- Display onboarded rescued pilots. +--- (Internal) Display onboarded rescued pilots. -- @param #CSAR self -- @param #string _unitName Name of the chopper function CSAR:_CheckOnboard(_unitName) @@ -1581,7 +1597,7 @@ function CSAR:_CheckOnboard(_unitName) end end ---- Populate F10 menu for CSAR players. +--- (Internal) Populate F10 menu for CSAR players. -- @param #CSAR self function CSAR:_AddMedevacMenuItem() self:T(self.lid .. " _AddMedevacMenuItem") @@ -1624,7 +1640,7 @@ function CSAR:_AddMedevacMenuItem() return end ---- Return distance in meters between two coordinates. +--- (Internal) Return distance in meters between two coordinates. -- @param #CSAR self -- @param Core.Point#COORDINATE _point1 Coordinate one -- @param Core.Point#COORDINATE _point2 Coordinate two @@ -1639,7 +1655,7 @@ function CSAR:_GetDistance(_point1, _point2) end end ---- Populate table with available beacon frequencies. +--- (Internal) Populate table with available beacon frequencies. -- @param #CSAR self function CSAR:_GenerateVHFrequencies() self:T(self.lid .. " _GenerateVHFrequencies") @@ -1710,7 +1726,7 @@ function CSAR:_GenerateVHFrequencies() self.FreeVHFFrequencies = FreeVHFFrequencies end ---- Pop frequency from prepopulated table. +--- (Internal) Pop frequency from prepopulated table. -- @param #CSAR self -- @return #number frequency function CSAR:_GenerateADFFrequency() @@ -1724,7 +1740,7 @@ function CSAR:_GenerateADFFrequency() return _vhf end ---- Function to determine clockwise direction for flares. +--- (Internal) Function to determine clockwise direction for flares. -- @param #CSAR self -- @param Wrapper.Unit#UNIT _heli The Helicopter -- @param Wrapper.Group#GROUP _group The downed Group @@ -1748,7 +1764,7 @@ function CSAR:_GetClockDirection(_heli, _group) return clock end ---- Function to add beacon to downed pilot. +--- (Internal) Function to add beacon to downed pilot. -- @param #CSAR self -- @param Wrapper.Group#GROUP _group Group #GROUP object. -- @param #number _freq Frequency to use @@ -1774,7 +1790,7 @@ function CSAR:_AddBeaconToGroup(_group, _freq) end end ---- Helper function to (re-)add beacon to downed pilot. +--- (Internal) Helper function to (re-)add beacon to downed pilot. -- @param #CSAR self -- @param #table _args Arguments function CSAR:_RefreshRadioBeacons() @@ -1790,7 +1806,7 @@ function CSAR:_RefreshRadioBeacons() end end ---- Helper function to count active downed pilots. +--- (Internal) Helper function to count active downed pilots. -- @param #CSAR self -- @return #number Number of pilots in the field. function CSAR:_CountActiveDownedPilots() @@ -1805,7 +1821,7 @@ function CSAR:_CountActiveDownedPilots() return PilotsInFieldN end ---- Helper to decide if we're over max limit. +--- (Internal) Helper to decide if we're over max limit. -- @param #CSAR self -- @return #boolean True or false. function CSAR:_ReachedPilotLimit() @@ -1824,7 +1840,7 @@ end --- FSM internal Functions --- ------------------------------ ---- Function called after Start() event. +--- (Internal) Function called after Start() event. -- @param #CSAR self. -- @param #string From From state. -- @param #string Event Event triggered. @@ -1850,7 +1866,7 @@ function CSAR:onafterStart(From, Event, To) return self end ---- Function called before Status() event. +--- (Internal) Function called before Status() event. -- @param #CSAR self. -- @param #string From From state. -- @param #string Event Event triggered. @@ -1875,7 +1891,7 @@ function CSAR:onbeforeStatus(From, Event, To) return self end ---- Function called after Status() event. +--- (Internal) Function called after Status() event. -- @param #CSAR self. -- @param #string From From state. -- @param #string Event Event triggered. @@ -1912,7 +1928,7 @@ function CSAR:onafterStatus(From, Event, To) return self end ---- Function called after Stop() event. +--- (Internal) Function called after Stop() event. -- @param #CSAR self. -- @param #string From From state. -- @param #string Event Event triggered. @@ -1930,7 +1946,7 @@ function CSAR:onafterStop(From, Event, To) return self end ---- Function called before Approach() event. +--- (Internal) Function called before Approach() event. -- @param #CSAR self. -- @param #string From From state. -- @param #string Event Event triggered. @@ -1943,7 +1959,7 @@ function CSAR:onbeforeApproach(From, Event, To, Heliname, Woundedgroupname) return self end ---- Function called before Boarded() event. +--- (Internal) Function called before Boarded() event. -- @param #CSAR self. -- @param #string From From state. -- @param #string Event Event triggered. @@ -1956,7 +1972,7 @@ function CSAR:onbeforeBoarded(From, Event, To, Heliname, Woundedgroupname) return self end ---- Function called before Returning() event. +--- (Internal) Function called before Returning() event. -- @param #CSAR self. -- @param #string From From state. -- @param #string Event Event triggered. @@ -1969,7 +1985,7 @@ function CSAR:onbeforeReturning(From, Event, To, Heliname, Woundedgroupname) return self end ---- Function called before Rescued() event. +--- (Internal) Function called before Rescued() event. -- @param #CSAR self. -- @param #string From From state. -- @param #string Event Event triggered. @@ -1984,7 +2000,7 @@ function CSAR:onbeforeRescued(From, Event, To, HeliUnit, HeliName, PilotsSaved) return self end ---- Function called before PilotDown() event. +--- (Internal) Function called before PilotDown() event. -- @param #CSAR self. -- @param #string From From state. -- @param #string Event Event triggered. diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 211932703..6c62d5046 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -8,7 +8,7 @@ -- -- ## Missions: -- --- ### [CTLD - Combat Troop & Logistics Deployment](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/) +-- ### [CTLD - Combat Troop & Logistics Deployment](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20CTLD) -- -- === -- @@ -180,7 +180,7 @@ end do ------------------------------------------------------------------------- ---- **CTLD** class, extends #Core.Base#BASE, #Core.Fsm#FSM +--- **CTLD** class, extends Core.Base#BASE, Core.Fsm#FSM -- @type CTLD -- @field #string ClassName Name of the class. -- @field #number verbose Verbosity level. @@ -489,7 +489,7 @@ CTLD.SkipFrequencies = { --- CTLD class version. -- @field #string version -CTLD.version="0.1.1b1" +CTLD.version="0.1.1b2" --- Instantiate a new CTLD. -- @param #CTLD self @@ -703,7 +703,7 @@ end -- Helper and User Functions ------------------------------------------------------------------- ---- Function to generate valid UHF Frequencies +--- (Internal) Function to generate valid UHF Frequencies -- @param #CTLD self function CTLD:_GenerateUHFrequencies() self:T(self.lid .. " _GenerateUHFrequencies") @@ -718,7 +718,7 @@ function CTLD:_GenerateUHFrequencies() return self end ---- Function to generate valid FM Frequencies +--- (Internal) Function to generate valid FM Frequencies -- @param #CTLD sel function CTLD:_GenerateFMFrequencies() self:T(self.lid .. " _GenerateFMrequencies") @@ -742,7 +742,7 @@ function CTLD:_GenerateFMFrequencies() return self end ---- Populate table with available VHF beacon frequencies. +--- (Internal) Populate table with available VHF beacon frequencies. -- @param #CTLD self function CTLD:_GenerateVHFrequencies() self:T(self.lid .. " _GenerateVHFrequencies") @@ -806,7 +806,7 @@ function CTLD:_GenerateVHFrequencies() return self end ---- Function to generate valid laser codes. +--- (Internal) Function to generate valid laser codes. -- @param #CTLD self function CTLD:_GenerateLaserCodes() self:T(self.lid .. " _GenerateLaserCodes") @@ -828,7 +828,7 @@ function CTLD:_GenerateLaserCodes() end end ---- Helper function to generate laser codes. +--- (Internal) Helper function to generate laser codes. -- @param #CTLD self -- @param #number _number -- @param #number _numberToFind @@ -846,7 +846,7 @@ function CTLD:_ContainsDigit(_number, _numberToFind) return false end ---- Event handler function +--- (Internal) Event handler function -- @param #CTLD self -- @param Core.Event#EVENTDATA EventData function CTLD:_EventHandler(EventData) @@ -873,7 +873,7 @@ function CTLD:_EventHandler(EventData) return self end ---- Function to load troops into a heli. +--- (Internal) Function to load troops into a heli. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit @@ -930,7 +930,7 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) return self end ---- Function to spawn crates in front of the heli. +--- (Internal) Function to spawn crates in front of the heli. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit @@ -1011,7 +1011,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) return self end ---- Function to find and list nearby crates. +--- (Internal) Function to find and list nearby crates. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit @@ -1045,7 +1045,7 @@ function CTLD:_ListCratesNearby( _group, _unit) return self end ---- Return distance in meters between two coordinates. +--- (Internal) Return distance in meters between two coordinates. -- @param #CTLD self -- @param Core.Point#COORDINATE _point1 Coordinate one -- @param Core.Point#COORDINATE _point2 Coordinate two @@ -1060,7 +1060,7 @@ function CTLD:_GetDistance(_point1, _point2) end end ---- Function to find and return nearby crates. +--- (Internal) Function to find and return nearby crates. -- @param #CTLD self -- @param Wrapper.Group#GROUP _group Group -- @param Wrapper.Unit#UNIT _unit Unit @@ -1094,7 +1094,7 @@ function CTLD:_FindCratesNearby( _group, _unit, _dist) return found, index end ---- Function to get and load nearby crates. +--- (Internal) Function to get and load nearby crates. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit @@ -1194,7 +1194,7 @@ function CTLD:_LoadCratesNearby(Group, Unit) return self end ---- Function to list loaded cargo. +--- (Internal) Function to list loaded cargo. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit @@ -1249,7 +1249,7 @@ function CTLD:_ListCargo(Group, Unit) return self end ---- Function to unload troops from heli. +--- (Internal) Function to unload troops from heli. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit @@ -1326,7 +1326,7 @@ function CTLD:_UnloadTroops(Group, Unit) return self end ---- Function to unload crates from heli. +--- (Internal) Function to unload crates from heli. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrappe.Unit#UNIT Unit @@ -1384,7 +1384,7 @@ function CTLD:_UnloadCrates(Group, Unit) return self end ---- Function to build nearby crates. +--- (Internal) Function to build nearby crates. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrappe.Unit#UNIT Unit @@ -1464,7 +1464,7 @@ function CTLD:_BuildCrates(Group, Unit) return self end ---- Function to actually SPAWN buildables in the mission. +--- (Internal) Function to actually SPAWN buildables in the mission. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Group#UNIT Unit @@ -1496,7 +1496,7 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build) return self end ---- Function to move group to WP zone. +--- (Internal) Function to move group to WP zone. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group The Group to move. function CTLD:_MoveGroupToZone(Group) @@ -1523,7 +1523,7 @@ function CTLD:_MoveGroupToZone(Group) return self end ---- Housekeeping - Cleanup crates when build +--- (Internal) Housekeeping - Cleanup crates when build -- @param #CTLD self -- @param #table Crates Table of #CTLD_CARGO objects near the unit. -- @param #CTLD.Buildable Build Table build object. @@ -1575,7 +1575,7 @@ function CTLD:_CleanUpCrates(Crates,Build,Number) return self end ---- Housekeeping - Function to refresh F10 menus. +--- (Internal) Housekeeping - Function to refresh F10 menus. -- @param #CTLD self -- @return #CTLD self function CTLD:_RefreshF10Menus() @@ -1747,7 +1747,7 @@ function CTLD:DeactivateZone(Name,ZoneType) return self end ---- Function to obtain a valid FM frequency. +--- (Internal) Function to obtain a valid FM frequency. -- @param #CTLD self -- @param #string Name Name of zone. -- @return #CTLD.ZoneBeacon Beacon Beacon table. @@ -1768,7 +1768,7 @@ function CTLD:_GetFMBeacon(Name) return beacon end ---- Function to obtain a valid UHF frequency. +--- (Internal) Function to obtain a valid UHF frequency. -- @param #CTLD self -- @param #string Name Name of zone. -- @return #CTLD.ZoneBeacon Beacon Beacon table. @@ -1789,7 +1789,7 @@ function CTLD:_GetUHFBeacon(Name) return beacon end ---- Function to obtain a valid VHF frequency. +--- (Internal) Function to obtain a valid VHF frequency. -- @param #CTLD self -- @param #string Name Name of zone. -- @return #CTLD.ZoneBeacon Beacon Beacon table. @@ -1845,7 +1845,7 @@ function CTLD:AddCTLDZone(Name, Type, Color, Active, HasBeacon) return self end ---- Function to show list of radio beacons +--- (Internal) Function to show list of radio beacons -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit @@ -1878,7 +1878,7 @@ function CTLD:_ListRadioBeacons(Group, Unit) return self end ---- Add radio beacon to zone. Runs 30 secs. +--- (Internal) Add radio beacon to zone. Runs 30 secs. -- @param #CTLD self -- @param #string Name Name of zone. -- @param #string Sound Name of soundfile. @@ -1898,7 +1898,7 @@ function CTLD:_AddRadioBeacon(Name, Sound, Mhz, Modulation) return self end ---- Function to refresh radio beacons +--- (Internal) Function to refresh radio beacons -- @param #CTLD self function CTLD:_RefreshRadioBeacons() self:I(self.lid .. " _RefreshRadioBeacons") @@ -1926,7 +1926,7 @@ function CTLD:_RefreshRadioBeacons() return self end ---- function to see if a unit is in a specific zone type. +--- (Internal) Function to see if a unit is in a specific zone type. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit Unit -- @param #CTLD.CargoZoneType Zonetype Zonetype @@ -1978,7 +1978,7 @@ function CTLD:IsUnitInZone(Unit,Zonetype) return outcome, zonenameret, zoneret, maxdist end ---- Userfunction - Start smoke in a zone close to the Unit. +--- User function - Start smoke in a zone close to the Unit. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit The Unit. -- @param #boolean Flare If true, flare instead. @@ -2020,6 +2020,7 @@ function CTLD:SmokeZoneNearBy(Unit, Flare) end return self end + --- User - Function to add/adjust unittype capabilities. -- @param #CTLD self -- @param #string Unittype The unittype to adjust. If passed as Wrapper.Unit#UNIT, it will search for the unit in the mission. @@ -2050,7 +2051,7 @@ end return self end - --- Check if a unit is hovering *in parameters*. + --- (Internal) Check if a unit is hovering *in parameters*. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit -- @return #boolean Outcome @@ -2077,7 +2078,7 @@ end return outcome end - --- List if a unit is hovering *in parameters*. + --- (Internal) List if a unit is hovering *in parameters*. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit @@ -2090,7 +2091,7 @@ end return self end - --- Check if a unit is in a load zone and is hovering in parameters. + --- (Internal) Check if a unit is in a load zone and is hovering in parameters. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit -- @return #boolean Outcome @@ -2100,7 +2101,7 @@ end return outcome end - --- Check if a unit is above ground. + --- (Internal) Check if a unit is above ground. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit -- @return #boolean Outcome @@ -2117,7 +2118,7 @@ end end end - --- Autoload if we can do crates, have capacity free and are in a load zone. + --- (Internal) Autoload if we can do crates, have capacity free and are in a load zone. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit -- @return #CTLD self @@ -2147,7 +2148,7 @@ end return self end - --- Run through all pilots and see if we autoload. + --- (Internal) Run through all pilots and see if we autoload. -- @param #CTLD self -- @return #CTLD self function CTLD:CheckAutoHoverload() @@ -2164,7 +2165,7 @@ end -- FSM functions ------------------------------------------------------------------- - --- FSM Function onafterStart. + --- (Internal) FSM Function onafterStart. -- @param #CTLD self -- @param #string From State. -- @param #string Event Trigger. @@ -2185,7 +2186,7 @@ end return self end - --- FSM Function onbeforeStatus. + --- (Internal) FSM Function onbeforeStatus. -- @param #CTLD self -- @param #string From State. -- @param #string Event Trigger. @@ -2199,7 +2200,7 @@ end return self end - --- FSM Function onafterStatus. + --- (Internal) FSM Function onafterStatus. -- @param #CTLD self -- @param #string From State. -- @param #string Event Trigger. @@ -2232,7 +2233,7 @@ end return self end - --- FSM Function onafterStop. + --- (Internal) FSM Function onafterStop. -- @param #CTLD self -- @param #string From State. -- @param #string Event Trigger. @@ -2246,7 +2247,7 @@ end return self end - --- FSM Function onbeforeTroopsPickedUp. + --- (Internal) FSM Function onbeforeTroopsPickedUp. -- @param #CTLD self -- @param #string From State. -- @param #string Event Trigger. @@ -2260,7 +2261,7 @@ end return self end - --- FSM Function onbeforeCratesPickedUp. + --- (Internal) FSM Function onbeforeCratesPickedUp. -- @param #CTLD self -- @param #string From State . -- @param #string Event Trigger. @@ -2274,7 +2275,7 @@ end return self end - --- FSM Function onbeforeTroopsDeployed. + --- (Internal) FSM Function onbeforeTroopsDeployed. -- @param #CTLD self -- @param #string From State. -- @param #string Event Trigger. @@ -2288,7 +2289,7 @@ end return self end - --- FSM Function onbeforeCratesDropped. + --- (Internal) FSM Function onbeforeCratesDropped. -- @param #CTLD self -- @param #string From State. -- @param #string Event Trigger. @@ -2302,7 +2303,7 @@ end return self end - --- FSM Function onbeforeCratesBuild. + --- (Internal) FSM Function onbeforeCratesBuild. -- @param #CTLD self -- @param #string From State. -- @param #string Event Trigger. From 3f5e322948ee7a4b1567482d62b41a63f5458d1e Mon Sep 17 00:00:00 2001 From: Celso Dantas Date: Sat, 26 Jun 2021 07:33:51 -0400 Subject: [PATCH 070/111] Update Airboss debug msg with BRC/Final heading (#1559) Including the BRC and Final Heading on the debug message is useful for squadrons relying on that msg to know what is the end heading of the carrier. The carrier tends to turns a few times before ending up on the final heading as it adjust into its track. Thus, having the BRC/Final Heading on the last message "Starting aircraft recovery Case %d ops." is useful. --- Moose Development/Moose/Ops/Airboss.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index a6924682d..f2ad2df53 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -15526,8 +15526,12 @@ function AIRBOSS:_MarshalCallRecoveryStart(case) -- Debug output. local text=string.format("Starting aircraft recovery Case %d ops.", case) - if case>1 then - text=text..string.format(" Marshal radial %03d°.", radial) + if case==1 then + text=text..string.format(" BRC %03d°.", self:GetBRC()) + elseif case==2 then + text=text..string.format(" Marshal radial %03d°. BRC %03d°.", radial, self:GetBRC()) + elseif case==3 then + text=text..string.format(" Marshal radial %03d°. Final heading %03d°.", radial, self:GetFinalBearing(false)) end self:T(self.lid..text) From 5a022a2246a76a6b8186f540ec5c29ef06d376e0 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 29 Jun 2021 08:50:46 +0200 Subject: [PATCH 071/111] Messages going to SAR flights only --- Moose Development/Moose/Ops/CSAR.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 7be6de481..07b8e103e 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -240,7 +240,7 @@ CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.4r4" +CSAR.version="0.1.5r1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -601,7 +601,8 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point) local _typeName = _typeName or "PoW" if not noMessage then - local m = MESSAGE:New("MAYDAY MAYDAY! " .. _typeName .. " is down. ",10,"INFO"):ToCoalition(self.coalition) + self:_DisplayToAllSAR("MAYDAY MAYDAY! " .. _typeName .. " is down. ", self.coalition, 10) + --local m = MESSAGE:New("MAYDAY MAYDAY! " .. _typeName .. " is down. ",10,"INFO"):ToCoalition(self.coalition) end if not _freq then @@ -759,8 +760,9 @@ function CSAR:_EventHandler(EventData) if self.takenOff[_event.IniUnitName] == true or _group:IsAirborne() then if self:_DoubleEjection(_unitname) then return - end - local m = MESSAGE:New("MAYDAY MAYDAY! " .. _unit:GetTypeName() .. " shot down. No Chute!",10,"Info"):ToCoalition(self.coalition) + end + self:_DisplayToAllSAR("MAYDAY MAYDAY! " .. _unit:GetTypeName() .. " shot down. No Chute!", self.coalition, 10) + --local m = MESSAGE:New("MAYDAY MAYDAY! " .. _unit:GetTypeName() .. " shot down. No Chute!",10,"Info"):ToCoalition(self.coalition) else self:T(self.lid .. " Pilot has not taken off, ignore") end From 3289ad281794f15089caf43d2ff3a7f0aa4f93e9 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 29 Jun 2021 17:49:42 +0200 Subject: [PATCH 072/111] Added basic support for Hercules mod --- Moose Development/Moose/Ops/CTLD.lua | 173 ++++++++++++++++++++++++--- 1 file changed, 156 insertions(+), 17 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 6c62d5046..4adaa0c24 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -22,7 +22,7 @@ -- @module Ops.CTLD -- @image OPS_CTLD.jpg --- Date: 22 June 2021 +-- Date: July 2021 do ------------------------------------------------------ @@ -379,6 +379,27 @@ do -- -- Lists hover parameters and indicates if these are curently fulfilled. Also @see options on hover heights. -- +-- ## 5. Support for Hercules mod by Anubis +-- +-- Basic support for the Hercules mod By Anubis has been build into CTLD. Currently this does **not** cover objects and troops which can +-- be loaded from the Rearm/Refuel menu, i.e. you can drop them into the field, but you cannot use them in functions scripted with this class. +-- +-- local my_ctld = CTLD:New(coalition.side.BLUE,{"Helicargo", "Hercules"},"Lufttransportbrigade I") +-- +-- Enable these options for Hercules support: +-- +-- my_ctld.enableHercules = true +-- my_ctld.HercMinAngels = 155 -- for troop/cargo drop via chute in meters, ca 470 ft +-- my_ctld.HercMaxAngels = 2000 -- for troop/cargo drop via chute in meters, ca 6000 ft +-- +-- Also, the following options need to be set to `true`: +-- +-- my_ctld.useprefix = true -- this is true by default +-- +-- Standard transport capabilities as per the real Hercules are: +-- +-- ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64}, -- 19t cargo, 64 paratroopers +-- -- @field #CTLD CTLD = { ClassName = "CTLD", @@ -415,6 +436,7 @@ CTLD = { -- DONE: Troops running to WP Zone -- DONE: Zone Radio Beacons -- DONE: Stats Running +-- DONE: Added support for Hercules ------------------------------ --- Radio Beacons @@ -468,6 +490,7 @@ CTLD.UnitTypes = { ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0}, ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8}, ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8}, + ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64}, -- 19t cargo, 64 paratroopers } --- Updated and sorted known NDB beacons (in kHz!) from the available maps @@ -590,7 +613,8 @@ function CTLD:New(Coalition, Prefixes, Alias) -- setup self.CrateDistance = 30 -- list/load crates in this radius - self.prefixes = Prefixes or {"cargoheli"} + self.prefixes = Prefixes or {"Cargoheli"} + --self.I({prefixes = self.prefixes}) self.useprefix = true self.maximumHoverHeight = 15 @@ -602,6 +626,11 @@ function CTLD:New(Coalition, Prefixes, Alias) self.movetroopstowpzone = true self.movetroopsdistance = 5000 + -- added support Hercules Mod + self.enableHercules = true + self.HercMinAngels = 165 -- for troop/cargo drop via chute + self.HercMaxAngels = 2000 -- for troop/cargo drop via chute + for i=1,100 do math.random() end @@ -863,7 +892,12 @@ function CTLD:_EventHandler(EventData) local _group = event.IniGroup if _unit:IsHelicopter() or _group:IsHelicopter() then self:_RefreshF10Menus() - end + end + -- Herc support + self:I(_unit:GetTypeName()) + if _unit:GetTypeName() == "Hercules" and self.enableHercules then + self:_RefreshF10Menus() + end return elseif event.id == EVENTS.PlayerLeaveUnit then -- remove from pilot table @@ -965,6 +999,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) return self end -- spawn crates in front of helicopter + local IsHerc = self:IsHercules(Unit) -- Herc local cargotype = Cargo -- #CTLD_CARGO local number = number or cargotype:GetCratesNeeded() --#number local cratesneeded = cargotype:GetCratesNeeded() --#number @@ -980,8 +1015,14 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) for i=1,number do local cratealias = string.format("%s-%d", cratetemplate, math.random(1,100000)) local cratedistance = i*4 + 6 + if IsHerc then + -- wider radius + cratedistance = i*4 + 12 + end local rheading = math.floor(math.random(90,270) * heading + 1 / 360) - local rheading = rheading + 180 -- mirror + if not IsHerc then + rheading = rheading + 180 -- mirror for Helis + end if rheading > 360 then rheading = rheading - 360 end -- catch > 360 local cratecoord = position:Translate(cratedistance,rheading) local cratevec2 = cratecoord:GetVec2() @@ -1249,6 +1290,18 @@ function CTLD:_ListCargo(Group, Unit) return self end +--- (Internal) Function to check if a unit is a Hercules C-130. +-- @param #CTLD self +-- @param Wrapper.Unit#UNIT Unit +-- @return #boolean Outcome +function CTLD:IsHercules(Unit) + if Unit:GetTypeName() == "Hercules" then + return true + else + return false + end +end + --- (Internal) Function to unload troops from heli. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group @@ -1263,6 +1316,11 @@ function CTLD:_UnloadTroops(Group, Unit) end -- check for hover unload local hoverunload = self:IsCorrectHover(Unit) --if true we\'re hovering in parameters + local IsHerc = self:IsHercules(Unit) + if IsHerc then + -- no hover but airdrop here + hoverunload = self:IsCorrectFlightParameters(Unit) + end -- check if we\'re landed local grounded = not self:IsUnitInAir(Unit) -- Get what we have loaded @@ -1280,8 +1338,14 @@ function CTLD:_UnloadTroops(Group, Unit) local name = cargo:GetName() or "none" local temptable = cargo:GetTemplates() or {} local position = Group:GetCoordinate() - local zone = ZONE_GROUP:New(string.format("Unload zone-%s",unitname),Group,100) - local randomcoord = zone:GetRandomCoordinate(10,30):GetVec2() + local zoneradius = 100 -- drop zone radius + local factor = 1 + if IsHerc then + factor = cargo:GetCratesNeeded() or 1 -- spread a bit more if airdropping + zoneradius = Unit:GetVelocityMPS() or 100 + end + local zone = ZONE_GROUP:New(string.format("Unload zone-%s",unitname),Group,zoneradius*factor) + local randomcoord = zone:GetRandomCoordinate(10,30*factor):GetVec2() for _,_template in pairs(temptable) do self.TroopCounter = self.TroopCounter + 1 local alias = string.format("%s-%d", _template, math.random(1,100000)) @@ -1321,7 +1385,11 @@ function CTLD:_UnloadTroops(Group, Unit) self.Loaded_Cargo[unitname] = nil self.Loaded_Cargo[unitname] = loaded else - local m = MESSAGE:New("Nothing loaded or not hovering within parameters!",10,"CTLD"):ToGroup(Group) + if IsHerc then + local m = MESSAGE:New("Nothing loaded or not within airdrop parameters!",10,"CTLD"):ToGroup(Group) + else + local m = MESSAGE:New("Nothing loaded or not hovering within parameters!",10,"CTLD"):ToGroup(Group) + end end return self end @@ -1342,6 +1410,11 @@ function CTLD:_UnloadCrates(Group, Unit) end -- check for hover unload local hoverunload = self:IsCorrectHover(Unit) --if true we\'re hovering in parameters + local IsHerc = self:IsHercules(Unit) + if IsHerc then + -- no hover but airdrop here + hoverunload = self:IsCorrectFlightParameters(Unit) + end -- check if we\'re landed local grounded = not self:IsUnitInAir(Unit) -- Get what we have loaded @@ -1379,7 +1452,11 @@ function CTLD:_UnloadCrates(Group, Unit) self.Loaded_Cargo[unitname] = nil self.Loaded_Cargo[unitname] = loaded else - local m = MESSAGE:New("Nothing loaded or not hovering within parameters!",10,"CTLD"):ToGroup(Group) + if IsHerc then + local m = MESSAGE:New("Nothing loaded or not within airdrop parameters!",10,"CTLD"):ToGroup(Group) + else + local m = MESSAGE:New("Nothing loaded or not hovering within parameters!",10,"CTLD"):ToGroup(Group) + end end return self end @@ -1579,10 +1656,10 @@ end -- @param #CTLD self -- @return #CTLD self function CTLD:_RefreshF10Menus() - self:T(self.lid .. " _RefreshF10Menus") + self:I(self.lid .. " _RefreshF10Menus") local PlayerSet = self.PilotGroups -- Core.Set#SET_GROUP local PlayerTable = PlayerSet:GetSetObjects() -- #table of #GROUP objects - + --self:I({PlayerTable=PlayerTable}) -- rebuild units table local _UnitList = {} for _key, _group in pairs (PlayerTable) do @@ -1644,7 +1721,11 @@ function CTLD:_RefreshF10Menus() local unloadmenu1 = MENU_GROUP_COMMAND:New(_group,"Drop troops",toptroops, self._UnloadTroops, self, _group, _unit):Refresh() end local rbcns = MENU_GROUP_COMMAND:New(_group,"List active zone beacons",topmenu, self._ListRadioBeacons, self, _group, _unit) - local hoverpars = MENU_GROUP_COMMAND:New(_group,"Show hover parameters",topmenu, self._ShowHoverParams, self, _group, _unit):Refresh() + if unittype == "Hercules" then + local hoverpars = MENU_GROUP_COMMAND:New(_group,"Show flight parameters",topmenu, self._ShowFlightParams, self, _group, _unit):Refresh() + else + local hoverpars = MENU_GROUP_COMMAND:New(_group,"Show hover parameters",topmenu, self._ShowHoverParams, self, _group, _unit):Refresh() + end self.MenusDone[_unitName] = true end -- end group end -- end unit @@ -1901,7 +1982,7 @@ end --- (Internal) Function to refresh radio beacons -- @param #CTLD self function CTLD:_RefreshRadioBeacons() - self:I(self.lid .. " _RefreshRadioBeacons") + self:T(self.lid .. " _RefreshRadioBeacons") local zones = {[1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones} for i=1,3 do @@ -2070,7 +2151,35 @@ end local minh = self.minimumHoverHeight -- 5 local mspeed = 2 -- 2 m/s self:T(string.format("%s Unit parameters: at %dm AGL with %dmps",self.lid,aheight,uspeed)) - if (uspeed <= maxh) and (aheight <= maxh) and (aheight >= minh) then + if (uspeed <= mspeed) and (aheight <= maxh) and (aheight >= minh) then + -- yep within parameters + outcome = true + end + end + return outcome + end + + --- (Internal) Check if a Hercules is flying *in parameters* for air drops. + -- @param #CTLD self + -- @param Wrapper.Unit#UNIT Unit + -- @return #boolean Outcome + function CTLD:IsCorrectFlightParameters(Unit) + self:T(self.lid .. " IsCorrectFlightParameters") + local outcome = false + -- see if we are in air and within parameters. + if self:IsUnitInAir(Unit) then + -- get speed and height + local uspeed = Unit:GetVelocityMPS() + local uheight = Unit:GetHeight() + local ucoord = Unit:GetCoordinate() + local gheight = ucoord:GetLandHeight() + local aheight = uheight - gheight -- height above ground + local maxh = self.HercMinAngels-- 1500m + local minh = self.HercMaxAngels -- 5000m + local mspeed = 2 -- 2 m/s + -- TODO:Add speed test for Herc, should not be above 280kph/150kn + self:T(string.format("%s Unit parameters: at %dm AGL with %dmps",self.lid,aheight,uspeed)) + if (aheight <= maxh) and (aheight >= minh) then -- yep within parameters outcome = true end @@ -2086,17 +2195,34 @@ end local inhover = self:IsCorrectHover(Unit) local htxt = "true" if not inhover then htxt = "false" end - local text = string.format("Hover parameter (autoload):\n - Min height %dm \n - Max height %dm \n - Max speed 2mps \n - In parameter: %s", self.minimumHoverHeight, self.maximumHoverHeight, htxt) + local text = string.format("Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 2mps \n - In parameter: %s", self.minimumHoverHeight, self.maximumHoverHeight, htxt) local m = MESSAGE:New(text,10,"CTLD",false):ToGroup(Group) return self end + --- (Internal) List if a Herc unit is flying *in parameters*. + -- @param #CTLD self + -- @param Wrapper.Group#GROUP Group + -- @param Wrapper.Unit#UNIT Unit + function CTLD:_ShowFlightParams(Group,Unit) + local inhover = self:IsCorrectFlightParameters(Unit) + local htxt = "true" + if not inhover then htxt = "false" end + local minheight = UTILS.MetersToFeet(self.HercMinAngels) + local maxheight = UTILS.MetersToFeet(self.HercMaxAngels) + local text = string.format("Flight parameters (airdrop):\n - Min height %dft \n - Max height %dft \n - In parameter: %s", minheight, maxheight, htxt) + local m = MESSAGE:New(text,10,"CTLD",false):ToGroup(Group) + return self + end + + --- (Internal) Check if a unit is in a load zone and is hovering in parameters. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit -- @return #boolean Outcome function CTLD:CanHoverLoad(Unit) self:T(self.lid .. " CanHoverLoad") + if self:IsHercules(Unit) then return false end local outcome = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) and self:IsCorrectHover(Unit) return outcome end @@ -2107,11 +2233,15 @@ end -- @return #boolean Outcome function CTLD:IsUnitInAir(Unit) -- get speed and height + local minheight = self.minimumHoverHeight + if self.enableHercules and Unit:GetTypeName() == "Hercules" then + minheight = 5.1 -- herc is 5m AGL on the ground + end local uheight = Unit:GetHeight() local ucoord = Unit:GetCoordinate() local gheight = ucoord:GetLandHeight() local aheight = uheight - gheight -- height above ground - if aheight >= self.minimumHoverHeight then + if aheight >= minheight then return true else return false @@ -2174,8 +2304,17 @@ end function CTLD:onafterStart(From, Event, To) self:I({From, Event, To}) if self.useprefix then - self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.prefixes):FilterCategoryHelicopter():FilterStart() + local prefix = self.prefixes + self:I({prefix=prefix}) + if self.enableHercules then + --self:I("CTLD with prefixes and Hercules") + self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterStart() + else + --self:I("CTLD with prefixes NO Hercules") + self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterCategoryHelicopter():FilterStart() + end else + --self:I("CTLD NO prefixes NO Hercules") self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart() end -- Events @@ -2320,4 +2459,4 @@ end end -- end do ------------------------------------------------------------------- -- End Ops.CTLD.lua -------------------------------------------------------------------- +------------------------------------------------------------------- \ No newline at end of file From dca626bbcbabf1edf7e9bbd622b6e700416dd70e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 30 Jun 2021 17:59:43 +0200 Subject: [PATCH 073/111] CTLD - added option to suppress messaging, added event for `OnAfterTroopsRTB` CSAR - minor bugfix --- Moose Development/Moose/Ops/CSAR.lua | 9 +- Moose Development/Moose/Ops/CTLD.lua | 224 ++++++++++++++++++--------- 2 files changed, 152 insertions(+), 81 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 07b8e103e..4a9344a54 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -187,7 +187,7 @@ CSAR = { useprefix = true, -- Use the Prefixed defined below, Requires Unit have the Prefix defined below csarPrefix = {}, template = nil, - bluemash = {}, + mash = {}, smokecolor = 4, rescues = 0, rescuedpilots = 0, @@ -240,13 +240,14 @@ CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.5r1" +CSAR.version="0.1.5r2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- DONE: SRS Integration (to be tested) +-- TODO: Maybe - add option to smoke/flare closest MASH ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -353,7 +354,7 @@ function CSAR:New(Coalition, Template, Alias) self.csarPrefix = { "helicargo", "MEDEVAC"} -- prefixes used for useprefix=true - DON\'T use # in names! self.template = Template or "generic" -- template for downed pilot self.mashprefix = {"MASH"} -- prefixes used to find MASHes - self.bluemash = SET_GROUP:New():FilterCoalitions(self.coalition):FilterPrefixes(self.mashprefix):FilterOnce() -- currently only GROUP objects, maybe support STATICs also? + self.mash = SET_GROUP:New():FilterCoalitions(self.coalition):FilterPrefixes(self.mashprefix):FilterOnce() -- currently only GROUP objects, maybe support STATICs also? self.autosmoke = false -- automatically smoke location when heli is near -- added 0.1.4 self.limitmaxdownedpilots = true @@ -1528,7 +1529,7 @@ end -- @retunr function CSAR:_GetClosestMASH(_heli) self:T(self.lid .. " _GetClosestMASH") - local _mashset = self.bluemash -- Core.Set#SET_GROUP + local _mashset = self.mash -- Core.Set#SET_GROUP local _mashes = _mashset:GetSetObjects() -- #table local _shortestDistance = -1 local _distance = 0 diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 4adaa0c24..78edc5a75 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -270,6 +270,7 @@ do -- my_ctld.movetroopstowpzone = true -- Troops and vehicles will move to the nearest MOVE zone... -- my_ctld.movetroopsdistance = 5000 -- .. but only if this far away (in meters) -- my_ctld.smokedistance = 2000 -- Only smoke or flare zones if requesting player unit is this far away (in meters) +-- my_ctld.suppressmessages = false -- Set to true if you want to script your own messages. -- -- ## 2.1 User functions -- @@ -512,7 +513,7 @@ CTLD.SkipFrequencies = { --- CTLD class version. -- @field #string version -CTLD.version="0.1.1b2" +CTLD.version="0.1.2b1" --- Instantiate a new CTLD. -- @param #CTLD self @@ -521,7 +522,6 @@ CTLD.version="0.1.1b2" -- @param #string Alias Alias of this CTLD for logging. -- @return #CTLD self function CTLD:New(Coalition, Prefixes, Alias) - -- TODO: CTLD Marker -- Inherit everything from FSM class. local self=BASE:Inherit(self, FSM:New()) -- #CTLD @@ -568,14 +568,15 @@ function CTLD:New(Coalition, Prefixes, Alias) -- Add FSM transitions. -- From State --> Event --> To State - self:AddTransition("Stopped", "Start", "Running") -- Start FSM. - self:AddTransition("*", "Status", "*") -- CTLD status update. + self:AddTransition("Stopped", "Start", "Running") -- Start FSM. + self:AddTransition("*", "Status", "*") -- CTLD status update. self:AddTransition("*", "TroopsPickedUp", "*") -- CTLD pickup event. self:AddTransition("*", "CratesPickedUp", "*") -- CTLD pickup event. - self:AddTransition("*", "TroopsDeployed", "*") -- CTLD deploy event. + self:AddTransition("*", "TroopsDeployed", "*") -- CTLD deploy event. + self:AddTransition("*", "TroopsRTB", "*") -- CTLD deploy event. self:AddTransition("*", "CratesDropped", "*") -- CTLD deploy event. self:AddTransition("*", "CratesBuild", "*") -- CTLD build event. - self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. + self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. -- tables self.PilotGroups ={} @@ -627,10 +628,13 @@ function CTLD:New(Coalition, Prefixes, Alias) self.movetroopsdistance = 5000 -- added support Hercules Mod - self.enableHercules = true + self.enableHercules = false self.HercMinAngels = 165 -- for troop/cargo drop via chute self.HercMaxAngels = 2000 -- for troop/cargo drop via chute + -- message suppression + self.suppressmessages = false + for i=1,100 do math.random() end @@ -678,7 +682,7 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param #string To State. -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. - -- @param #CTLD_CARGO Cargo Cargo crate. + -- @param #CTLD_CARGO Cargo Cargo troops. -- @return #CTLD self --- FSM Function OnAfterCratesPickedUp. @@ -725,6 +729,15 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB build. -- @return #CTLD self + --- FSM Function OnAfterTroopsRTB. + -- @function [parent=#CTLD] OnAfterTroopsRTB + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + return self end @@ -894,7 +907,7 @@ function CTLD:_EventHandler(EventData) self:_RefreshF10Menus() end -- Herc support - self:I(_unit:GetTypeName()) + --self:T_unit:GetTypeName()) if _unit:GetTypeName() == "Hercules" and self.enableHercules then self:_RefreshF10Menus() end @@ -907,6 +920,20 @@ function CTLD:_EventHandler(EventData) return self end +--- (Internal) Function to message a group. +-- @param #CTLD self +-- @param #string Text The text to display. +-- @param #number Time Number of seconds to display the message. +-- @param #boolean Clearscreen Clear screen or not. +-- @param Wrapper.Group#GROUP Group The group receiving the message. +function CTLD:_SendMessage(Text, Time, Clearscreen, Group) + self:T(self.lid .. " _SendMessage") + if not self.suppressmessages then + local m = MESSAGE:New(Text,Time,"CTLD",Clearscreen):ToGroup(Group) + end + return self +end + --- (Internal) Function to load troops into a heli. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group @@ -920,10 +947,11 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) -- check if we are in LOAD zone local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) if not inzone then - local m = MESSAGE:New("You are not close enough to a logistics zone!",15,"CTLD"):ToGroup(Group) + self:_SendMessage("You are not close enough to a logistics zone!", 10, false, Group) if not self.debug then return self end elseif not grounded and not hoverload then - local m = MESSAGE:New("You need to land or hover in position to load!",15,"CTLD"):ToGroup(Group) + self:_SendMessage("You need to land or hover in position to load!", 10, false, Group) + --local m = MESSAGE:New("You need to land or hover in position to load!",15,"CTLD"):ToGroup(Group) if not self.debug then return self end end -- load troops into heli @@ -932,7 +960,7 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) local unitname = unit:GetName() local cargotype = Cargotype -- #CTLD_CARGO local cratename = cargotype:GetName() -- #string - self:T(self.lid .. string.format("Troops %s requested", cratename)) + --self:Tself.lid .. string.format("Troops %s requested", cratename)) -- see if this heli can load troops local unittype = unit:GetTypeName() local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities @@ -952,13 +980,15 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) loaded.Cargo = {} end if troopsize + numberonboard > trooplimit then - local m = MESSAGE:New("Sorry, we\'re crammed already!",10,"CTLD",true):ToGroup(group) + self:_SendMessage("Sorry, we\'re crammed already!", 10, false, Group) + --local m = MESSAGE:New("Sorry, we\'re crammed already!",10,"CTLD",true):ToGroup(group) return else loaded.Troopsloaded = loaded.Troopsloaded + troopsize table.insert(loaded.Cargo,Cargotype) self.Loaded_Cargo[unitname] = loaded - local m = MESSAGE:New("Troops boarded!",10,"CTLD",true):ToGroup(group) + self:_SendMessage("Troops boarded!", 10, false, Group) + --local m = MESSAGE:New("Troops boarded!",10,"CTLD",true):ToGroup(group) self:__TroopsPickedUp(1,Group, Unit, Cargotype) end return self @@ -974,7 +1004,7 @@ end function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) self:T(self.lid .. " _GetCrates") local cgoname = Cargo:GetName() - self:T({cgoname, number, drop}) + --self:T{cgoname, number, drop}) -- check if we are in LOAD zone local inzone = true @@ -985,7 +1015,8 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) end if not inzone then - local m = MESSAGE:New("You are not close enough to a logistics zone!",15,"CTLD"):ToGroup(Group) + self:_SendMessage("You are not close enough to a logistics zone!", 10, false, Group) + --local m = MESSAGE:New("You are not close enough to a logistics zone!",15,"CTLD"):ToGroup(Group) if not self.debug then return self end end @@ -995,7 +1026,8 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) local loaddist = self.CrateDistance or 30 local nearcrates, numbernearby = self:_FindCratesNearby(Group,Unit,loaddist) if numbernearby >= canloadcratesno and not drop then - local m = MESSAGE:New("There are enough crates nearby already! Take care of those first!",15,"CTLD"):ToGroup(Group) + self:_SendMessage("There are enough crates nearby already! Take care of those first!", 10, false, Group) + --local m = MESSAGE:New("There are enough crates nearby already! Take care of those first!",15,"CTLD"):ToGroup(Group) return self end -- spawn crates in front of helicopter @@ -1004,7 +1036,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) local number = number or cargotype:GetCratesNeeded() --#number local cratesneeded = cargotype:GetCratesNeeded() --#number local cratename = cargotype:GetName() - self:T(self.lid .. string.format("Crate %s requested", cratename)) + --self:Tself.lid .. string.format("Crate %s requested", cratename)) local cratetemplate = "Container"-- #string -- get position and heading of heli local position = Unit:GetCoordinate() @@ -1047,8 +1079,9 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) if drop then text = string.format("Crates for %s have been dropped!",cratename) self:__CratesDropped(1, Group, Unit, droppedcargo) - end - local m = MESSAGE:New(text,15,"CTLD",true):ToGroup(Group) + end + self:_SendMessage(text, 10, false, Group) + --local m = MESSAGE:New(text,15,"CTLD",true):ToGroup(Group) return self end @@ -1079,9 +1112,11 @@ function CTLD:_ListCratesNearby( _group, _unit) text:Add("--------- N O N E ------------") end text:Add("------------------------------------------------------------") - local m = MESSAGE:New(text:Text(),15,"CTLD",true):ToGroup(_group) + self:_SendMessage(text:Text(), 30, true, _group) + --local m = MESSAGE:New(text:Text(),15,"CTLD",true):ToGroup(_group) else - local m = MESSAGE:New(string.format("No (loadable) crates within %d meters!",finddist),15,"CTLD",true):ToGroup(_group) + self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist), 10, false, _group) + --local m = MESSAGE:New(string.format("No (loadable) crates within %d meters!",finddist),15,"CTLD",true):ToGroup(_group) end return self end @@ -1129,7 +1164,7 @@ function CTLD:_FindCratesNearby( _group, _unit, _dist) end end end - self:T(string.format("Found crates = %d",index)) + --self:Tstring.format("Found crates = %d",index)) -- table.sort(found) --self:T({found}) return found, index @@ -1160,11 +1195,14 @@ function CTLD:_LoadCratesNearby(Group, Unit) -- --> hover or land if not forcedhover ----------------------------------------- if not cancrates then - local m = MESSAGE:New("Sorry this chopper cannot carry crates!",10,"CTLD"):ToGroup(Group) + self:_SendMessage("Sorry this chopper cannot carry crates!", 10, false, Group) + --local m = MESSAGE:New("Sorry this chopper cannot carry crates!",10,"CTLD"):ToGroup(Group) elseif self.forcehoverload and not canhoverload then - local m = MESSAGE:New("Hover over the crates to pick them up!",10,"CTLD"):ToGroup(Group) + self:_SendMessage("Hover over the crates to pick them up!", 10, false, Group) + --local m = MESSAGE:New("Hover over the crates to pick them up!",10,"CTLD"):ToGroup(Group) elseif not grounded and not canhoverload then - local m = MESSAGE:New("Land or hover over the crates to pick them up!",10,"CTLD"):ToGroup(Group) + self:_SendMessage("Land or hover over the crates to pick them up!", 10, false, Group) + --local m = MESSAGE:New("Land or hover over the crates to pick them up!",10,"CTLD"):ToGroup(Group) else -- have we loaded stuff already? local numberonboard = 0 @@ -1182,7 +1220,8 @@ function CTLD:_LoadCratesNearby(Group, Unit) local finddist = self.CrateDistance or 30 local nearcrates,number = self:_FindCratesNearby(Group,Unit,finddist) -- #table if number == 0 or numberonboard == cratelimit then - local m = MESSAGE:New("Sorry no loadable crates nearby or fully loaded!",10,"CTLD"):ToGroup(Group) + self:_SendMessage("Sorry no loadable crates nearby or fully loaded!", 10, false, Group) + --local m = MESSAGE:New("Sorry no loadable crates nearby or fully loaded!",10,"CTLD"):ToGroup(Group) return -- exit else -- go through crates and load @@ -1210,7 +1249,8 @@ function CTLD:_LoadCratesNearby(Group, Unit) -- destroy crate crate:GetPositionable():Destroy() crate.Positionable = nil - local m = MESSAGE:New(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()),10,"CTLD"):ToGroup(Group) + self:_SendMessage(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()), 10, false, Group) + --local m = MESSAGE:New(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()),10,"CTLD"):ToGroup(Group) self:__CratesPickedUp(1, Group, Unit, crate) end --if loaded.Cratesloaded == cratelimit then break end @@ -1283,9 +1323,11 @@ function CTLD:_ListCargo(Group, Unit) end report:Add("------------------------------------------------------------") local text = report:Text() - local m = MESSAGE:New(text,30,"CTLD",true):ToGroup(Group) + self:_SendMessage(text, 30, true, Group) + --local m = MESSAGE:New(text,30,"CTLD",true):ToGroup(Group) else - local m = MESSAGE:New(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d",trooplimit,cratelimit),10,"CTLD"):ToGroup(Group) + self:_SendMessage(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d",trooplimit,cratelimit), 10, false, Group) + --local m = MESSAGE:New(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d",trooplimit,cratelimit),10,"CTLD"):ToGroup(Group) end return self end @@ -1358,12 +1400,15 @@ function CTLD:_UnloadTroops(Group, Unit) end end -- template loop cargo:SetWasDropped(true) - local m = MESSAGE:New(string.format("Dropped Troops %s into action!",name),10,"CTLD"):ToGroup(Group) - self:__TroopsDeployed(1, Group, Unit, name, self.DroppedTroops[self.TroopCounter]) + self:_SendMessage(string.format("Dropped Troops %s into action!",name), 10, false, Group) + --local m = MESSAGE:New(string.format("Dropped Troops %s into action!",name),10,"CTLD"):ToGroup(Group) + self:__TroopsDeployed(1, Group, Unit, self.DroppedTroops[self.TroopCounter]) end -- if type end end -- cargotable loop else -- droppingatbase - local m = MESSAGE:New("Troops have returned to base!",15,"CTLD"):ToGroup(Group) + self:_SendMessage("Troops have returned to base!", 10, false, Group) + --local m = MESSAGE:New("Troops have returned to base!",15,"CTLD"):ToGroup(Group) + self:__TroopsRTB(1, Group, Unit) end -- cleanup load list local loaded = {} -- #CTLD.LoadedCargo @@ -1386,9 +1431,11 @@ function CTLD:_UnloadTroops(Group, Unit) self.Loaded_Cargo[unitname] = loaded else if IsHerc then - local m = MESSAGE:New("Nothing loaded or not within airdrop parameters!",10,"CTLD"):ToGroup(Group) + self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) + --local m = MESSAGE:New("Nothing loaded or not within airdrop parameters!",10,"CTLD"):ToGroup(Group) else - local m = MESSAGE:New("Nothing loaded or not hovering within parameters!",10,"CTLD"):ToGroup(Group) + self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) + --local m = MESSAGE:New("Nothing loaded or not hovering within parameters!",10,"CTLD"):ToGroup(Group) end end return self @@ -1403,7 +1450,8 @@ function CTLD:_UnloadCrates(Group, Unit) -- check if we are in DROP zone local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) if not inzone then - local m = MESSAGE:New("You are not close enough to a drop zone!",15,"CTLD"):ToGroup(Group) + self:_SendMessage("You are not close enough to a drop zone!", 10, false, Group) + --local m = MESSAGE:New("You are not close enough to a drop zone!",15,"CTLD"):ToGroup(Group) if not self.debug then return self end @@ -1453,9 +1501,11 @@ function CTLD:_UnloadCrates(Group, Unit) self.Loaded_Cargo[unitname] = loaded else if IsHerc then - local m = MESSAGE:New("Nothing loaded or not within airdrop parameters!",10,"CTLD"):ToGroup(Group) + self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) + --local m = MESSAGE:New("Nothing loaded or not within airdrop parameters!",10,"CTLD"):ToGroup(Group) else - local m = MESSAGE:New("Nothing loaded or not hovering within parameters!",10,"CTLD"):ToGroup(Group) + self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) + --local m = MESSAGE:New("Nothing loaded or not hovering within parameters!",10,"CTLD"):ToGroup(Group) end end return self @@ -1501,7 +1551,7 @@ function CTLD:_BuildCrates(Group, Unit) end foundbuilds = true end - self:T({buildables = buildables}) + --self:T{buildables = buildables}) end -- end dropped end -- end crate loop -- ok let\'s list what we have @@ -1516,14 +1566,15 @@ function CTLD:_BuildCrates(Group, Unit) if build.CanBuild then txtok = "YES" end - self:T({name,needed,found,txtok}) + --self:T{name,needed,found,txtok}) local text = string.format("Type: %s | Required %d | Found %d | Can Build %s", name, needed, found, txtok) report:Add(text) end -- end list buildables if not foundbuilds then report:Add(" --- None Found ---") end report:Add("------------------------------------------------------------") local text = report:Text() - local m = MESSAGE:New(text,30,"CTLD",true):ToGroup(Group) + self:_SendMessage(text, 30, true, Group) + --local m = MESSAGE:New(text,30,"CTLD",true):ToGroup(Group) -- let\'s get going if canbuild then -- loop again @@ -1536,7 +1587,8 @@ function CTLD:_BuildCrates(Group, Unit) end end else - local m = MESSAGE:New(string.format("No crates within %d meters!",finddist),15,"CTLD",true):ToGroup(Group) + self:_SendMessage(string.format("No crates within %d meters!",finddist), 10, false, Group) + --local m = MESSAGE:New(string.format("No crates within %d meters!",finddist),15,"CTLD",true):ToGroup(Group) end -- number > 0 return self end @@ -1580,17 +1632,17 @@ function CTLD:_MoveGroupToZone(Group) self:T(self.lid .. " _MoveGroupToZone") local groupname = Group:GetName() or "none" local groupcoord = Group:GetCoordinate() - self:T(self.lid .. " _MoveGroupToZone for " .. groupname) + --self:Tself.lid .. " _MoveGroupToZone for " .. groupname) -- Get closest zone of type local outcome, name, zone, distance = self:IsUnitInZone(Group,CTLD.CargoZoneType.MOVE) - self:T(string.format("Closest WP zone %s is %d meters",name,distance)) + --self:Tstring.format("Closest WP zone %s is %d meters",name,distance)) if (distance <= self.movetroopsdistance) and zone then -- yes, we can ;) local groupname = Group:GetName() - self:T(string.format("Moving troops %s to zone %s, distance %d!",groupname,name,distance)) + --self:Tstring.format("Moving troops %s to zone %s, distance %d!",groupname,name,distance)) local zonecoord = zone:GetRandomCoordinate(20,125) -- Core.Point#COORDINATE local coordinate = zonecoord:GetVec2() - self:T({coordinate=coordinate}) + --self:T{coordinate=coordinate}) Group:SetAIOn() Group:OptionAlarmStateAuto() Group:OptionDisperseOnAttack(30) @@ -1609,7 +1661,7 @@ function CTLD:_CleanUpCrates(Crates,Build,Number) self:T(self.lid .. " _CleanUpCrates") -- clean up real world crates local build = Build -- #CTLD.Buildable - self:T({Build = Build}) + --self:T{Build = Build}) local existingcrates = self.Spawned_Cargo -- #table of exising crates local newexcrates = {} -- get right number of crates to destroy @@ -1623,18 +1675,18 @@ function CTLD:_CleanUpCrates(Crates,Build,Number) for _,_crate in pairs(Crates) do local nowcrate = _crate -- #CTLD_CARGO local name = nowcrate:GetName() - self:T(string.format("Looking for Crate for %s", name)) + --self:Tstring.format("Looking for Crate for %s", name)) local thisID = nowcrate:GetID() if name == nametype then -- matching crate type table.insert(destIDs,thisID) found = found + 1 nowcrate:GetPositionable():Destroy() nowcrate.Positionable = nil - self:T(string.format("%s Found %d Need %d", name, found, numberdest)) + --self:Tstring.format("%s Found %d Need %d", name, found, numberdest)) end if found == numberdest then break end -- got enough end - self:T({destIDs}) + --self:T{destIDs}) -- loop and remove from real world representation for _,_crate in pairs(existingcrates) do local excrate = _crate -- #CTLD_CARGO @@ -1656,10 +1708,10 @@ end -- @param #CTLD self -- @return #CTLD self function CTLD:_RefreshF10Menus() - self:I(self.lid .. " _RefreshF10Menus") + self:T(self.lid .. " _RefreshF10Menus") local PlayerSet = self.PilotGroups -- Core.Set#SET_GROUP local PlayerTable = PlayerSet:GetSetObjects() -- #table of #GROUP objects - --self:I({PlayerTable=PlayerTable}) + --self:T({PlayerTable=PlayerTable}) -- rebuild units table local _UnitList = {} for _key, _group in pairs (PlayerTable) do @@ -1774,13 +1826,13 @@ function CTLD:AddZone(Zone) local zone = Zone -- #CTLD.CargoZone if zone.type == CTLD.CargoZoneType.LOAD then table.insert(self.pickupZones,zone) - self:T("Registered LOAD zone " .. zone.name) + --self:T"Registered LOAD zone " .. zone.name) elseif zone.type == CTLD.CargoZoneType.DROP then table.insert(self.dropOffZones,zone) - self:T("Registered DROP zone " .. zone.name) + --self:T"Registered DROP zone " .. zone.name) else table.insert(self.wpZones,zone) - self:T("Registered MOVE zone " .. zone.name) + --self:T"Registered MOVE zone " .. zone.name) end return self end @@ -1955,7 +2007,8 @@ function CTLD:_ListRadioBeacons(Group, Unit) report:Add("--------- N O N E ------------") end report:Add("------------------------------------------------------------") - local m = MESSAGE:New(report:Text(),30,"CTLD",true):ToGroup(Group) + self:_SendMessage(report:Text(), 30, true, Group) + --local m = MESSAGE:New(report:Text(),30,"CTLD",true):ToGroup(Group) return self end @@ -2018,7 +2071,7 @@ end function CTLD:IsUnitInZone(Unit,Zonetype) self:T(self.lid .. " IsUnitInZone") local unitname = Unit:GetName() - self:T(string.format("%s | Zone search for %s | Type %s",self.lid,unitname,Zonetype)) + --self:Tstring.format("%s | Zone search for %s | Type %s",self.lid,unitname,Zonetype)) local zonetable = {} local outcome = false if Zonetype == CTLD.CargoZoneType.LOAD then @@ -2044,7 +2097,7 @@ function CTLD:IsUnitInZone(Unit,Zonetype) local color = czone.color local zoneradius = zone:GetRadius() local distance = self:_GetDistance(zonecoord,unitcoord) - self:T(string.format("Check distance: %d",distance)) + --self:Tstring.format("Check distance: %d",distance)) if distance <= zoneradius and active then outcome = true end @@ -2055,7 +2108,7 @@ function CTLD:IsUnitInZone(Unit,Zonetype) colorret = color end end - self:T({outcome, zonenameret, zoneret, maxdist}) + --self:T{outcome, zonenameret, zoneret, maxdist}) return outcome, zonenameret, zoneret, maxdist end @@ -2090,14 +2143,16 @@ function CTLD:SmokeZoneNearBy(Unit, Flare) end local txt = "smoking" if Flare then txt = "flaring" end - local m = MESSAGE:New(string.format("Roger, %s zone %s!",txt, zonename),10,"CTLD"):ToGroup(Group) + self:_SendMessage(string.format("Roger, %s zone %s!",txt, zonename), 10, false, Group) + --local m = MESSAGE:New(string.format("Roger, %s zone %s!",txt, zonename),10,"CTLD"):ToGroup(Group) smoked = true end end end if not smoked then - local distance = UTILS.MetersToNM(self.smkedistance) - local m = MESSAGE:New(string.format("Negative, need to be closer than %dnm to a zone!",distance),10,"CTLD"):ToGroup(Group) + local distance = UTILS.MetersToNM(self.smokedistance) + self:_SendMessage(string.format("Negative, need to be closer than %dnm to a zone!",distance), 10, false, Group) + --local m = MESSAGE:New(string.format("Negative, need to be closer than %dnm to a zone!",distance),10,"CTLD"):ToGroup(Group) end return self end @@ -2150,7 +2205,7 @@ end local maxh = self.maximumHoverHeight -- 15 local minh = self.minimumHoverHeight -- 5 local mspeed = 2 -- 2 m/s - self:T(string.format("%s Unit parameters: at %dm AGL with %dmps",self.lid,aheight,uspeed)) + --self:Tstring.format("%s Unit parameters: at %dm AGL with %dmps",self.lid,aheight,uspeed)) if (uspeed <= mspeed) and (aheight <= maxh) and (aheight >= minh) then -- yep within parameters outcome = true @@ -2178,7 +2233,7 @@ end local minh = self.HercMaxAngels -- 5000m local mspeed = 2 -- 2 m/s -- TODO:Add speed test for Herc, should not be above 280kph/150kn - self:T(string.format("%s Unit parameters: at %dm AGL with %dmps",self.lid,aheight,uspeed)) + --self:Tstring.format("%s Unit parameters: at %dm AGL with %dmps",self.lid,aheight,uspeed)) if (aheight <= maxh) and (aheight >= minh) then -- yep within parameters outcome = true @@ -2196,7 +2251,8 @@ end local htxt = "true" if not inhover then htxt = "false" end local text = string.format("Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 2mps \n - In parameter: %s", self.minimumHoverHeight, self.maximumHoverHeight, htxt) - local m = MESSAGE:New(text,10,"CTLD",false):ToGroup(Group) + self:_SendMessage(text, 10, false, Group) + --local m = MESSAGE:New(text,10,"CTLD",false):ToGroup(Group) return self end @@ -2211,7 +2267,8 @@ end local minheight = UTILS.MetersToFeet(self.HercMinAngels) local maxheight = UTILS.MetersToFeet(self.HercMaxAngels) local text = string.format("Flight parameters (airdrop):\n - Min height %dft \n - Max height %dft \n - In parameter: %s", minheight, maxheight, htxt) - local m = MESSAGE:New(text,10,"CTLD",false):ToGroup(Group) + self:_SendMessage(text, 10, false, Group) + --local m = MESSAGE:New(text,15,"CTLD",false):ToGroup(Group) return self end @@ -2302,19 +2359,19 @@ end -- @param #string To State. -- @return #CTLD self function CTLD:onafterStart(From, Event, To) - self:I({From, Event, To}) + self:T({From, Event, To}) if self.useprefix then local prefix = self.prefixes - self:I({prefix=prefix}) + --self:T{prefix=prefix}) if self.enableHercules then - --self:I("CTLD with prefixes and Hercules") + --self:T("CTLD with prefixes and Hercules") self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterStart() else - --self:I("CTLD with prefixes NO Hercules") + --self:T("CTLD with prefixes NO Hercules") self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterCategoryHelicopter():FilterStart() end else - --self:I("CTLD NO prefixes NO Hercules") + --self:T("CTLD NO prefixes NO Hercules") self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart() end -- Events @@ -2346,7 +2403,7 @@ end -- @param #string To State. -- @return #CTLD self function CTLD:onafterStatus(From, Event, To) - self:I({From, Event, To}) + self:T({From, Event, To}) -- gather some stats -- pilots local pilots = 0 @@ -2396,7 +2453,7 @@ end -- @param #CTLD_CARGO Cargo Cargo crate. -- @return #CTLD self function CTLD:onbeforeTroopsPickedUp(From, Event, To, Group, Unit, Cargo) - self:I({From, Event, To}) + self:T({From, Event, To}) return self end @@ -2410,7 +2467,7 @@ end -- @param #CTLD_CARGO Cargo Cargo crate. -- @return #CTLD self function CTLD:onbeforeCratesPickedUp(From, Event, To, Group, Unit, Cargo) - self:I({From, Event, To}) + self:T({From, Event, To}) return self end @@ -2424,7 +2481,7 @@ end -- @param Wrapper.Group#GROUP Troops Troops #GROUP Object. -- @return #CTLD self function CTLD:onbeforeTroopsDeployed(From, Event, To, Group, Unit, Troops) - self:I({From, Event, To}) + self:T({From, Event, To}) return self end @@ -2438,7 +2495,7 @@ end -- @param #table Cargotable Table of #CTLD_CARGO objects dropped. -- @return #CTLD self function CTLD:onbeforeCratesDropped(From, Event, To, Group, Unit, Cargotable) - self:I({From, Event, To}) + self:T({From, Event, To}) return self end @@ -2452,7 +2509,20 @@ 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:I({From, Event, To}) + self:T({From, Event, To}) + return self + end + + --- (Internal) FSM Function onbeforeTroopsRTB. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @return #CTLD self + function CTLD:onbeforeTroopsRTB(From, Event, To, Group, Unit) + self:T({From, Event, To}) return self end From 76a53ab1543c70c89026bf73a4f0571ed65b01bb Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 1 Jul 2021 09:07:01 +0200 Subject: [PATCH 074/111] Added extra checks for Beacon refresh --- Moose Development/Moose/Ops/CSAR.lua | 86 +++++++++++++++++----------- 1 file changed, 54 insertions(+), 32 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 4a9344a54..a42384db2 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -240,7 +240,7 @@ CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.5r2" +CSAR.version="0.1.5r3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -489,6 +489,7 @@ function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Descript self.downedPilots = PilotTable -- Increase counter self.downedpilotcounter = self.downedpilotcounter+1 + return self end --- (Internal) Count pilots on board. @@ -579,7 +580,7 @@ function CSAR:_AddSpecialOptions(group) group:OptionAlarmStateGreen() group:OptionROEHoldFire() - + return self end --- (Internal) Function to spawn a CSAR object into the scene. @@ -634,6 +635,7 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla self:_InitSARForPilot(_spawnedGroup, _GroupName, _freq, noMessage) + return self end --- (Internal) Function to add a CSAR object into the scene at a zone coordinate. For mission designers wanting to add e.g. PoWs to the scene. @@ -672,6 +674,8 @@ function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _ end self:_AddCsar(_coalition, _country, pos, "PoW", "Unknown", nil, freq, _nomessage, _description) + + return self end --- Function to add a CSAR object into the scene at a zone coordinate. For mission designers wanting to add e.g. PoWs to the scene. @@ -852,7 +856,7 @@ function CSAR:_EventHandler(EventData) return true end - + return self end --- (Internal) Initialize the action for a pilot. @@ -881,6 +885,8 @@ function CSAR:_InitSARForPilot(_downedGroup, _GroupName, _freq, _nomessage) -- trigger FSM event self:__PilotDown(2,_downedGroup, _freqk, _leadername, _coordinatesText) + + return self end --- (Internal) Check if a name is in downed pilot table @@ -984,6 +990,7 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) self:T("...Downed Pilot KIA?!") self:_RemoveNameFromDownedPilots(_downedpilot.name) end + return self end --- (Internal) Function to pop a smoke at a wounded pilot\'s positions. @@ -1001,6 +1008,7 @@ function CSAR:_PopSmokeForGroup(_woundedGroupName, _woundedLeader) _smokecoord:Smoke(_smokecolor) self.smokeMarkers[_woundedGroupName] = timer.getTime() + 300 -- next smoke time end + return self end --- (Internal) Function to pickup the wounded pilot from the ground. @@ -1065,6 +1073,7 @@ function CSAR:_OrderGroupToMoveToPoint(_leader, _destination) group:SetAIOn() group:RouteToVec2(coordinate,5) + return self end --- (Internal) Function to check if heli is close to group. @@ -1235,33 +1244,34 @@ end function CSAR:_ScheduledSARFlight(heliname,groupname) self:T(self.lid .. " _ScheduledSARFlight") self:T({heliname,groupname}) - local _heliUnit = self:_GetSARHeli(heliname) - local _woundedGroupName = groupname + local _heliUnit = self:_GetSARHeli(heliname) + local _woundedGroupName = groupname - if (_heliUnit == nil) then - --helicopter crashed? - self.inTransitGroups[heliname] = nil - return - end + if (_heliUnit == nil) then + --helicopter crashed? + self.inTransitGroups[heliname] = nil + return + end - if self.inTransitGroups[heliname] == nil or self.inTransitGroups[heliname][_woundedGroupName] == nil then - -- Groups already rescued - return - end + if self.inTransitGroups[heliname] == nil or self.inTransitGroups[heliname][_woundedGroupName] == nil then + -- Groups already rescued + return + end - local _dist = self:_GetClosestMASH(_heliUnit) + local _dist = self:_GetClosestMASH(_heliUnit) - if _dist == -1 then - return - end + if _dist == -1 then + return + end - if _dist < 200 and _heliUnit:InAir() == false then - self:_RescuePilots(_heliUnit) - return - end + if _dist < 200 and _heliUnit:InAir() == false then + self:_RescuePilots(_heliUnit) + return + end - --queue up - self:__Returning(-5,heliname,_woundedGroupName) + --queue up + self:__Returning(-5,heliname,_woundedGroupName) + return self end --- (Internal) Mark pilot as rescued and remove from tables. @@ -1287,6 +1297,7 @@ function CSAR:_RescuePilots(_heliUnit) self:_DisplayMessageToSAR(_heliUnit, _txt, self.messageTime) -- trigger event self:__Rescued(-1,_heliUnit,_heliName, PilotsSaved) + return self end --- (Internal) Check and return Wrappe.Unit#UNIT based on the name if alive. @@ -1325,6 +1336,7 @@ function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak) local msrs = MSRS:New(path,channel,modulation) msrs:PlaySoundText(srstext, 2) end + return self end --- (Internal) Function to get string of a group\'s position. @@ -1403,6 +1415,7 @@ function CSAR:_DisplayActiveSAR(_unitName) end self:_DisplayMessageToSAR(_heli, _msg, self.messageTime*2) + return self end --- (Internal) Find the closest downed pilot to a heli. @@ -1472,6 +1485,7 @@ function CSAR:_SignalFlare(_unitName) end self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",disttext), self.messageTime) end + return self end --- (Internal) Display info to all SAR groups. @@ -1489,6 +1503,7 @@ function CSAR:_DisplayToAllSAR(_message, _side, _messagetime) end end end + return self end ---(Internal) Request smoke at closest downed pilot. @@ -1521,6 +1536,7 @@ function CSAR:_Reqsmoke( _unitName ) end self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",disttext), self.messageTime) end + return self end --- (Internal) Determine distance to closest MASH. @@ -1598,6 +1614,7 @@ function CSAR:_CheckOnboard(_unitName) end self:_DisplayMessageToSAR(_unit, _text, self.messageTime*2) end + return self end --- (Internal) Populate F10 menu for CSAR players. @@ -1640,7 +1657,7 @@ function CSAR:_AddMedevacMenuItem() end end end - return + return self end --- (Internal) Return distance in meters between two coordinates. @@ -1727,6 +1744,7 @@ function CSAR:_GenerateVHFrequencies() _start = _start + 50000 end self.FreeVHFFrequencies = FreeVHFFrequencies + return self end --- (Internal) Pop frequency from prepopulated table. @@ -1791,6 +1809,7 @@ function CSAR:_AddBeaconToGroup(_group, _freq) local Sound = "l10n/DEFAULT/"..self.radioSound trigger.action.radioTransmission(Sound, _radioUnit:GetPositionVec3(), 0, false, Frequency, 1000) -- Beacon in MP only runs for exactly 30secs straight end + return self end --- (Internal) Helper function to (re-)add beacon to downed pilot. @@ -1798,15 +1817,18 @@ end -- @param #table _args Arguments function CSAR:_RefreshRadioBeacons() self:T(self.lid .. " _RefreshRadioBeacons") - local PilotTable = self.downedPilots - for _,_pilot in pairs (PilotTable) do - local pilot = _pilot -- #CSAR.DownedPilot - local group = pilot.group - local frequency = pilot.frequency or 0 -- thanks to @Thrud - if frequency and frequency > 0 then - self:_AddBeaconToGroup(group,frequency) + if self:_CountActiveDownedPilots() > 0 then + local PilotTable = self.downedPilots + for _,_pilot in pairs (PilotTable) do + local pilot = _pilot -- #CSAR.DownedPilot + local group = pilot.group + local frequency = pilot.frequency or 0.0 -- thanks to @Thrud + if group:IsAlive() and frequency > 0.0 then + self:_AddBeaconToGroup(group,frequency) + end end end + return self end --- (Internal) Helper function to count active downed pilots. From c80cebb82485a1f7f8ee79f8e2faaaad9f50b45f Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 1 Jul 2021 23:54:24 +0200 Subject: [PATCH 075/111] WAREHOUSE, AIRWING & SQUADRON - Added function for hot start to SQUADRON - Addressed bug #1560 --- Moose Development/Moose/Core/Point.lua | 8 +- .../Moose/Functional/Warehouse.lua | 45 +++-- Moose Development/Moose/Ops/AirWing.lua | 170 ++++++++---------- Moose Development/Moose/Ops/Squadron.lua | 39 +++- 4 files changed, 148 insertions(+), 114 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 061d61ed5..354b584c1 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -1607,8 +1607,12 @@ do -- COORDINATE roadtype="railroads" end local x,y = land.getClosestPointOnRoads(roadtype, self.x, self.z) - local vec2={ x = x, y = y } - return COORDINATE:NewFromVec2(vec2) + local coord=nil + if x and y then + local vec2={ x = x, y = y } + coord=COORDINATE:NewFromVec2(vec2) + end + return coord end diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 0d4f074c4..1d4e76d2e 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -5790,21 +5790,32 @@ end -- @param #WAREHOUSE.Queueitem request Request belonging to this asset. Needed for the name/alias. -- @param #table parking Parking data for this asset. -- @param #boolean uncontrolled Spawn aircraft in uncontrolled state. --- @param #boolean hotstart Spawn aircraft with engines already on. Default is a cold start with engines off. -- @return Wrapper.Group#GROUP The spawned group or nil if the group could not be spawned. -function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrolled, hotstart) +function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrolled) if asset and asset.category==Group.Category.AIRPLANE or asset.category==Group.Category.HELICOPTER then -- Prepare the spawn template. local template=self:_SpawnAssetPrepareTemplate(asset, alias) + -- Cold start (default). + local _type=COORDINATE.WaypointType.TakeOffParking + local _action=COORDINATE.WaypointAction.FromParkingArea + + -- Hot start. + if asset.takeoffType and asset.takeoffType==COORDINATE.WaypointType.TakeOffParkingHot then + _type=COORDINATE.WaypointType.TakeOffParkingHot + _action=COORDINATE.WaypointAction.FromParkingAreaHot + uncontrolled=false + end + + -- Set route points. if request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then -- Get flight path if the group goes to another warehouse by itself. if request.toself then - local wp=self.airbase:GetCoordinate():WaypointAir("RADIO", COORDINATE.WaypointType.TakeOffParking, COORDINATE.WaypointAction.FromParkingArea, 0, false, self.airbase, {}, "Parking") + local wp=self.airbase:GetCoordinate():WaypointAir("RADIO", _type, _action, 0, false, self.airbase, {}, "Parking") template.route.points={wp} else template.route.points=self:_GetFlightplan(asset, self.airbase, request.warehouse.airbase) @@ -5812,18 +5823,8 @@ function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrol else - -- Cold start (default). - local _type=COORDINATE.WaypointType.TakeOffParking - local _action=COORDINATE.WaypointAction.FromParkingArea - - -- Hot start. - if hotstart then - _type=COORDINATE.WaypointType.TakeOffParkingHot - _action=COORDINATE.WaypointAction.FromParkingAreaHot - end - -- First route point is the warehouse airbase. - template.route.points[1]=self.airbase:GetCoordinate():WaypointAir("BARO",_type,_action, 0, true, self.airbase, nil, "Spawnpoint") + template.route.points[1]=self.airbase:GetCoordinate():WaypointAir("BARO", _type, _action, 0, true, self.airbase, nil, "Spawnpoint") end @@ -9040,9 +9041,23 @@ function WAREHOUSE:_GetFlightplan(asset, departure, destination) local wp={} local c={} + -- Cold start (default). + local _type=COORDINATE.WaypointType.TakeOffParking + local _action=COORDINATE.WaypointAction.FromParkingArea + + -- Hot start. + if asset.takeoffType and asset.takeoffType==COORDINATE.WaypointType.TakeOffParkingHot then + env.info("FF hot") + _type=COORDINATE.WaypointType.TakeOffParkingHot + _action=COORDINATE.WaypointAction.FromParkingAreaHot + else + env.info("FF cold") + end + + --- Departure/Take-off c[#c+1]=Pdeparture - wp[#wp+1]=Pdeparture:WaypointAir("RADIO", COORDINATE.WaypointType.TakeOffParking, COORDINATE.WaypointAction.FromParkingArea, VxClimb*3.6, true, departure, nil, "Departure") + wp[#wp+1]=Pdeparture:WaypointAir("RADIO", _type, _action, VxClimb*3.6, true, departure, nil, "Departure") --- Begin of Cruise local Pcruise=Pdeparture:Translate(d_climb, heading) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 202eb631d..4493264c9 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -32,6 +32,7 @@ -- @field #table pointsCAP Table of CAP points. -- @field #table pointsTANKER Table of Tanker points. -- @field #table pointsAWACS Table of AWACS points. +-- @field #boolean markpoints Display markers on the F10 map. -- @field Ops.WingCommander#WINGCOMMANDER wingcommander The wing commander responsible for this airwing. -- -- @field Ops.RescueHelo#RESCUEHELO rescuehelo The rescue helo. @@ -153,7 +154,7 @@ AIRWING = { --- AIRWING class version. -- @field #string version -AIRWING.version="0.5.1" +AIRWING.version="0.5.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -210,7 +211,7 @@ function AIRWING:New(warehousename, airwingname) self.nflightsTANKERprobe=0 self.nflightsRecoveryTanker=0 self.nflightsRescueHelo=0 - self.markpoints = false + self.markpoints=false ------------------------ --- Pseudo Functions --- @@ -1551,6 +1552,9 @@ function AIRWING:onafterNewAsset(From, Event, To, asset, assignment) asset.nunits=squad.ngrouping end + + -- Set takeoff type. + asset.takeoffType=squad.takeoffType -- Create callsign and modex (needs to be after grouping). squad:GetCallsign(asset) @@ -1616,84 +1620,86 @@ function AIRWING:onafterAssetSpawned(From, Event, To, group, asset, request) -- Call parent warehouse function first. self:GetParent(self).onafterAssetSpawned(self, From, Event, To, group, asset, request) - - -- Create a flight group. - local flightgroup=self:_CreateFlightGroup(asset) - - - --- - -- Asset - --- - - -- Set asset flightgroup. - asset.flightgroup=flightgroup - - -- Not requested any more. - asset.requested=nil - - -- Did not return yet. - asset.Treturned=nil - - --- - -- Squadron - --- -- Get the SQUADRON of the asset. local squadron=self:GetSquadronOfAsset(asset) - -- Get TACAN channel. - local Tacan=squadron:FetchTacan() - if Tacan then - asset.tacan=Tacan - end - - -- Set radio frequency and modulation - local radioFreq, radioModu=squadron:GetRadio() - if radioFreq then - flightgroup:SwitchRadio(radioFreq, radioModu) - end + -- Check if we have a squadron or if this was some other request. + if squadron then + + -- Create a flight group. + local flightgroup=self:_CreateFlightGroup(asset) + + --- + -- Asset + --- - if squadron.fuellow then - flightgroup:SetFuelLowThreshold(squadron.fuellow) - end - - if squadron.fuellowRefuel then - flightgroup:SetFuelLowRefuel(squadron.fuellowRefuel) - end - - --- - -- Mission - --- - - -- Get Mission (if any). - local mission=self:GetMissionByID(request.assignment) - - -- Add mission to flightgroup queue. - if mission then + -- Set asset flightgroup. + asset.flightgroup=flightgroup + + -- Not requested any more. + asset.requested=nil + + -- Did not return yet. + asset.Treturned=nil + --- + -- Squadron + --- + + -- Get TACAN channel. + local Tacan=squadron:FetchTacan() if Tacan then - mission:SetTACAN(Tacan, Morse, UnitName, Band) + asset.tacan=Tacan + end + + -- Set radio frequency and modulation + local radioFreq, radioModu=squadron:GetRadio() + if radioFreq then + flightgroup:SwitchRadio(radioFreq, radioModu) end - -- Add mission to flightgroup queue. - asset.flightgroup:AddMission(mission) - - -- Trigger event. - self:FlightOnMission(flightgroup, mission) - - else - - if Tacan then - flightgroup:SwitchTACAN(Tacan, Morse, UnitName, Band) + if squadron.fuellow then + flightgroup:SetFuelLowThreshold(squadron.fuellow) end - - end - + if squadron.fuellowRefuel then + flightgroup:SetFuelLowRefuel(squadron.fuellowRefuel) + end - -- Add group to the detection set of the WINGCOMMANDER. - if self.wingcommander and self.wingcommander.chief then - self.wingcommander.chief.detectionset:AddGroup(asset.flightgroup.group) + --- + -- Mission + --- + + -- Get Mission (if any). + local mission=self:GetMissionByID(request.assignment) + + -- Add mission to flightgroup queue. + if mission then + + if Tacan then + mission:SetTACAN(Tacan, Morse, UnitName, Band) + end + + -- Add mission to flightgroup queue. + asset.flightgroup:AddMission(mission) + + -- Trigger event. + self:FlightOnMission(flightgroup, mission) + + else + + if Tacan then + flightgroup:SwitchTACAN(Tacan, Morse, UnitName, Band) + end + + end + + -- Add group to the detection set of the WINGCOMMANDER. + if self.wingcommander and self.wingcommander.chief then + self.wingcommander.chief.detectionset:AddGroup(asset.flightgroup.group) + end + end end @@ -1822,34 +1828,6 @@ function AIRWING:_CreateFlightGroup(asset) -- Set home base. flightgroup.homebase=self.airbase - --[[ - - --- Check if out of missiles. For A2A missions ==> RTB. - function flightgroup:OnAfterOutOfMissiles() - local airwing=flightgroup:GetAirWing() - - end - - --- Check if out of missiles. For A2G missions ==> RTB. But need to check A2G missiles, rockets as well. - function flightgroup:OnAfterOutOfBombs() - local airwing=flightgroup:GetAirWing() - - end - - --- Mission started. - function flightgroup:OnAfterMissionStart(From, Event, To, Mission) - local airwing=flightgroup:GetAirWing() - - end - - --- Flight is DEAD. - function flightgroup:OnAfterFlightDead(From, Event, To) - local airwing=flightgroup:GetAirWing() - - end - - ]] - return flightgroup end diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index 5ed71fba7..c0f53d960 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -45,6 +45,7 @@ -- @field #table tacanChannel List of TACAN channels available to the squadron. -- @field #number radioFreq Radio frequency in MHz the squad uses. -- @field #number radioModu Radio modulation the squad uses. +-- @field #number takeoffType Take of type. -- @extends Core.Fsm#FSM --- *It is unbelievable what a squadron of twelve aircraft did to tip the balance.* -- Adolf Galland @@ -87,12 +88,13 @@ SQUADRON = { --- SQUADRON class version. -- @field #string version -SQUADRON.version="0.5.0" +SQUADRON.version="0.5.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO: Parking spots for squadrons? -- DONE: Engage radius. -- DONE: Modex. -- DONE: Call signs. @@ -282,6 +284,41 @@ function SQUADRON:SetGrouping(nunits) return self end + +--- Set takeoff type. All assets of this squadron will be spawned with cold (default) or hot engines. +-- Spawning on runways is not supported. +-- @param #SQUADRON self +-- @param #string TakeoffType Take off type: "Cold" (default) or "Hot" with engines on. +-- @return #SQUADRON self +function SQUADRON:SetTakeoffType(TakeoffType) + TakeoffType=TakeoffType or "Cold" + if TakeoffType:lower()=="hot" then + self.takeoffType=COORDINATE.WaypointType.TakeOffParkingHot + elseif TakeoffType:lower()=="cold" then + self.takeoffType=COORDINATE.WaypointType.TakeOffParking + else + self.takeoffType=COORDINATE.WaypointType.TakeOffParking + end + return self +end + +--- Set takeoff type cold (default). +-- @param #SQUADRON self +-- @return #SQUADRON self +function SQUADRON:SetTakeoffCold() + self:SetTakeoffType("Cold") + return self +end + +--- Set takeoff type hot. +-- @param #SQUADRON self +-- @return #SQUADRON self +function SQUADRON:SetTakeoffHot() + self:SetTakeoffType("Hot") + return self +end + + --- Set mission types this squadron is able to perform. -- @param #SQUADRON self -- @param #table MissionTypes Table of mission types. Can also be passed as a #string if only one type. From 353d6dfec0ed1d574945c537ca6c3e7b87e9f3ea Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 2 Jul 2021 08:51:51 +0200 Subject: [PATCH 076/111] Update Airbase.lua - Corrected Mariana Islands airbase names - Fixed bug in Syria airbase name `["Thalah"]="Tha'lah"` --- Moose Development/Moose/Wrapper/Airbase.lua | 49 +++++++++++---------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 615cf6870..e63218ac5 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -73,7 +73,7 @@ AIRBASE = { --- Enumeration to identify the airbases in the Caucasus region. -- --- These are all airbases of Caucasus: +-- Airbases of the Caucasus map: -- -- * AIRBASE.Caucasus.Gelendzhik -- * AIRBASE.Caucasus.Krasnodar_Pashkovsky @@ -122,7 +122,7 @@ AIRBASE.Caucasus = { ["Beslan"] = "Beslan", } ---- These are all airbases of Nevada: +--- Airbases of the Nevada map: -- -- * AIRBASE.Nevada.Creech_AFB -- * AIRBASE.Nevada.Groom_Lake_AFB @@ -141,6 +141,7 @@ AIRBASE.Caucasus = { -- * AIRBASE.Nevada.Pahute_Mesa_Airstrip -- * AIRBASE.Nevada.Tonopah_Airport -- * AIRBASE.Nevada.Tonopah_Test_Range_Airfield +-- -- @field Nevada AIRBASE.Nevada = { ["Creech_AFB"] = "Creech AFB", @@ -162,7 +163,7 @@ AIRBASE.Nevada = { ["Tonopah_Test_Range_Airfield"] = "Tonopah Test Range Airfield", } ---- These are all airbases of Normandy: +--- Airbases of the Normandy map: -- -- * AIRBASE.Normandy.Saint_Pierre_du_Mont -- * AIRBASE.Normandy.Lignerolles @@ -195,6 +196,7 @@ AIRBASE.Nevada = { -- * AIRBASE.Normandy.Funtington -- * AIRBASE.Normandy.Tangmere -- * AIRBASE.Normandy.Ford_AF +-- -- @field Normandy AIRBASE.Normandy = { ["Saint_Pierre_du_Mont"] = "Saint Pierre du Mont", @@ -237,7 +239,7 @@ AIRBASE.Normandy = { ["Conches"] = "Conches", } ---- These are all airbases of the Persion Gulf Map: +--- Airbases of the Persion Gulf Map: -- -- * AIRBASE.PersianGulf.Abu_Dhabi_International_Airport -- * AIRBASE.PersianGulf.Abu_Musa_Island_Airport @@ -268,6 +270,7 @@ AIRBASE.Normandy = { -- * AIRBASE.PersianGulf.Sirri_Island -- * AIRBASE.PersianGulf.Tunb_Island_AFB -- * AIRBASE.PersianGulf.Tunb_Kochak +-- -- @field PersianGulf AIRBASE.PersianGulf = { ["Abu_Dhabi_International_Airport"] = "Abu Dhabi Intl", @@ -301,7 +304,7 @@ AIRBASE.PersianGulf = { ["Tunb_Kochak"] = "Tunb Kochak", } ---- These are all airbases of the The Channel Map: +--- Airbases of The Channel Map: -- -- * AIRBASE.TheChannel.Abbeville_Drucat -- * AIRBASE.TheChannel.Merville_Calonne @@ -326,7 +329,7 @@ AIRBASE.TheChannel = { ["High_Halden"] = "High Halden", } ---- Airbases of Syria +--- Airbases of the Syria map: -- -- * AIRBASE.Syria.Kuweires -- * AIRBASE.Syria.Marj_Ruhayyil @@ -348,7 +351,7 @@ AIRBASE.TheChannel = { -- * AIRBASE.Syria.Pinarbashi -- * AIRBASE.Syria.Paphos -- * AIRBASE.Syria.Kingsfield --- * AIRBASE.Syria.Tha'lah +-- * AIRBASE.Syria.Thalah -- * AIRBASE.Syria.Haifa -- * AIRBASE.Syria.Khalkhalah -- * AIRBASE.Syria.Megiddo @@ -404,7 +407,7 @@ AIRBASE.Syria={ ["Pinarbashi"]="Pinarbashi", ["Paphos"]="Paphos", ["Kingsfield"]="Kingsfield", - ["Tha'lah"]="Tha'lah", + ["Thalah"]="Tha'lah", ["Haifa"]="Haifa", ["Khalkhalah"]="Khalkhalah", ["Megiddo"]="Megiddo", @@ -416,7 +419,6 @@ AIRBASE.Syria={ ["Akrotiri"]="Akrotiri", ["Naqoura"]="Naqoura", ["Gaziantep"]="Gaziantep", - ["CVN_71"]="CVN-71", ["Sayqal"]="Sayqal", ["Tiyas"]="Tiyas", ["Shayrat"]="Shayrat", @@ -441,22 +443,23 @@ AIRBASE.Syria={ ---- Airbases of the Mariana Islands map. +--- Airbases of the Mariana Islands map: -- --- * AIRBASE.MarianaIslands.Rota_International_Airport --- * AIRBASE.MarianaIslands.Andersen --- * AIRBASE.MarianaIslands.Northwest_Field --- * AIRBASE.MarianaIslands.Antonio_B_Won_Pat_International_Airport --- * AIRBASE.MarianaIslands.Saipan_International_Airport --- * AIRBASE.MarianaIslands.Tinian_International_Airport --- @field MarianaIslands +-- * AIRBASE.MarianaIslands.Rota_Intl +-- * AIRBASE.MarianaIslands.Andersen_AFB +-- * AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl +-- * AIRBASE.MarianaIslands.Saipan_Intl +-- * AIRBASE.MarianaIslands.Tinian_Intl +-- * AIRBASE.MarianaIslands.Olf_Orote +-- +--@field MarianaIslands AIRBASE.MarianaIslands={ - ["Rota_International_Airport"]="Rota International Airport", - ["Andersen"]="Andersen", - ["Northwest_Field"]="Northwest_Field", - ["Antonio_B_Won_Pat_International_Airport"]="Antonio B. Won Pat International Airport", - ["Saipan_International_Airport"]="Saipan International Airport", - ["Tinian_International_Airport"]="Tinian International Airport", + ["Rota_Intl"]="Rota Intl", + ["Andersen_AFB"]="Andersen AFB", + ["Antonio_B_Won_Pat_Intl"]="Antonio B. Won Pat Intl", + ["Saipan_Intl"]="Saipan Intl", + ["Tinian_Intl"]="Tinian Intl", + ["Olf_Orote"]="Olf Orote", } From 12555a6ff1e6e8fa45af8d27f16a06ef98cbb3b4 Mon Sep 17 00:00:00 2001 From: "wob3155@posteo.de" Date: Fri, 2 Jul 2021 08:56:28 +0200 Subject: [PATCH 077/111] Reactivate the :destroy() event on clients with scoring below thresmark --- Moose Development/Moose/Functional/Scoring.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Moose Development/Moose/Functional/Scoring.lua b/Moose Development/Moose/Functional/Scoring.lua index b5c8da81b..b92c7e789 100644 --- a/Moose Development/Moose/Functional/Scoring.lua +++ b/Moose Development/Moose/Functional/Scoring.lua @@ -667,8 +667,6 @@ function SCORING:_AddPlayerFromUnit( UnitData ) self.Players[PlayerName].ThreatLevel = UnitThreatLevel self.Players[PlayerName].ThreatType = UnitThreatType - -- TODO: DCS bug concerning Units with skill level client don't get destroyed in multi player. This logic is deactivated until this bug gets fixed. - --[[ if self.Players[PlayerName].Penalty > self.Fratricide * 0.50 then if self.Players[PlayerName].PenaltyWarning < 1 then MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "': WARNING! If you continue to commit FRATRICIDE and have a PENALTY score higher than " .. self.Fratricide .. ", you will be COURT MARTIALED and DISMISSED from this mission! \nYour total penalty is: " .. self.Players[PlayerName].Penalty, @@ -684,8 +682,6 @@ function SCORING:_AddPlayerFromUnit( UnitData ) ):ToAll() UnitData:GetGroup():Destroy() end - --]] - end end From 299e08f53d713482e251fd538c04a5b6b3d8c782 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 2 Jul 2021 17:52:52 +0200 Subject: [PATCH 078/111] additional checks to ensure only human players, extra checks in the logic, some logic errors corrected. --- Moose Development/Moose/Ops/CSAR.lua | 40 +++++++----- Moose Development/Moose/Ops/CTLD.lua | 96 ++++++++++++++++++---------- 2 files changed, 86 insertions(+), 50 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index a42384db2..ef181a759 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -18,11 +18,11 @@ -- -- === -- --- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original) +-- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original), Thanks to: Shadowze, Cammel (testing) -- @module Ops.CSAR -- @image OPS_CSAR.jpg --- Date: June 2021 +-- Date: July 2021 ------------------------------------------------------------------------- --- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM @@ -240,7 +240,7 @@ CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.5r3" +CSAR.version="0.1.6r1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -290,7 +290,7 @@ function CSAR:New(Coalition, Template, Alias) self.alias="Red Cross" if self.coalition then if self.coalition==coalition.side.RED then - self.alias="СпаÑение" + self.alias="�¡�¿�°Ñ��µ�½�¸�µ" elseif self.coalition==coalition.side.BLUE then self.alias="CSAR" end @@ -527,15 +527,18 @@ end -- @param #CSAR self -- @param #number country Country for template. -- @param Core.Point#COORDINATE point Coordinate to spawn at. +-- @param #number frequency Frequency of the pilot's beacon -- @return Wrapper.Group#GROUP group The #GROUP object. -- @return #string alias The alias name. -function CSAR:_SpawnPilotInField(country,point) - self:T({country,point}) +function CSAR:_SpawnPilotInField(country,point,frequency) + self:T({country,point,frequency}) + local freq = frequency or 1000 + local freq = freq / 1000 -- kHz for i=1,10 do math.random(i,10000) end local template = self.template - local alias = string.format("Downed Pilot-%d",math.random(1,10000)) + local alias = string.format("Pilot %.2fkHz-%d", freq, math.random(1,10000)) local coalition = self.coalition local pilotcacontrol = self.allowDownedPilotCAcontrol -- Switch AI on/oof - is this really correct for CA? local _spawnedGroup = SPAWN @@ -545,7 +548,7 @@ function CSAR:_SpawnPilotInField(country,point) :InitAIOnOff(pilotcacontrol) :InitDelayOff() :SpawnFromCoordinate(point) - + return _spawnedGroup, alias -- Wrapper.Group#GROUP object end @@ -599,19 +602,21 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla self:T({_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description}) local template = self.template - - local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point) + + if not _freq then + _freq = self:_GenerateADFFrequency() + if not _freq then _freq = "333250" end --noob catch + end + + local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point,_freq) + local _typeName = _typeName or "PoW" + if not noMessage then self:_DisplayToAllSAR("MAYDAY MAYDAY! " .. _typeName .. " is down. ", self.coalition, 10) --local m = MESSAGE:New("MAYDAY MAYDAY! " .. _typeName .. " is down. ",10,"INFO"):ToCoalition(self.coalition) end - if not _freq then - _freq = self:_GenerateADFFrequency() - if not _freq then _freq = "333.25" end --noob catch - end - if _freq then self:_AddBeaconToGroup(_spawnedGroup, _freq) end @@ -1631,7 +1636,7 @@ function CSAR:_AddMedevacMenuItem() for _key, _group in pairs (_allHeliGroups) do local _unit = _group:GetUnit(1) -- Asume that there is only one unit in the flight for players if _unit then - if _unit:IsAlive() then + if _unit:IsAlive() and _unit:IsPlayer() then local unitName = _unit:GetName() _UnitList[unitName] = unitName end -- end isAlive @@ -1820,10 +1825,11 @@ function CSAR:_RefreshRadioBeacons() if self:_CountActiveDownedPilots() > 0 then local PilotTable = self.downedPilots for _,_pilot in pairs (PilotTable) do + self:T({_pilot}) local pilot = _pilot -- #CSAR.DownedPilot local group = pilot.group local frequency = pilot.frequency or 0.0 -- thanks to @Thrud - if group:IsAlive() and frequency > 0.0 then + if group and group:IsAlive() and frequency > 0.0 then self:_AddBeaconToGroup(group,frequency) end end diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 78edc5a75..9808d16da 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -18,7 +18,7 @@ -- -- === -- --- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original) +-- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original), Thanks to: Shadowze, Cammel (testing) -- @module Ops.CTLD -- @image OPS_CTLD.jpg @@ -260,7 +260,7 @@ do -- -- The following options are available (with their defaults). Only set the ones you want changed: -- --- my_ctld.useprefix = true -- Adjust *before* starting CTLD. If set to false, *all* choppers of the coalition side will be enabled for CTLD. +-- my_ctld.useprefix = true -- (DO NOT SWITCH THIS OFF UNLESS YOU KNOW WHAT YOU ARE DOING!) Adjust **before** starting CTLD. If set to false, *all* choppers of the coalition side will be enabled for CTLD. -- my_ctld.CrateDistance = 30 -- List and Load crates in this radius only. -- my_ctld.maximumHoverHeight = 15 -- Hover max this high to load. -- my_ctld.minimumHoverHeight = 4 -- Hover min this low to load. @@ -316,7 +316,7 @@ do -- -- This function is called when a player has loaded Troops: -- --- function CTLD:OnAfterTroopsPickedUp(From, Event, To, Group, Unit, Cargo) +-- function my_ctld:OnAfterTroopsPickedUp(From, Event, To, Group, Unit, Cargo) -- ... your code here ... -- end -- @@ -324,7 +324,7 @@ do -- -- This function is called when a player has picked up crates: -- --- function CTLD:OnAfterCratesPickedUp(From, Event, To, Group, Unit, Cargo) +-- function my_ctld:OnAfterCratesPickedUp(From, Event, To, Group, Unit, Cargo) -- ... your code here ... -- end -- @@ -332,7 +332,7 @@ do -- -- This function is called when a player has deployed troops into the field: -- --- function CTLD:OnAfterTroopsDeployed(From, Event, To, Group, Unit, Troops) +-- function my_ctld:OnAfterTroopsDeployed(From, Event, To, Group, Unit, Troops) -- ... your code here ... -- end -- @@ -340,7 +340,7 @@ do -- -- This function is called when a player has deployed crates to a DROP zone: -- --- function CTLD:OnAfterCratesDropped(From, Event, To, Group, Unit, Cargotable) +-- function my_ctld:OnAfterCratesDropped(From, Event, To, Group, Unit, Cargotable) -- ... your code here ... -- end -- @@ -348,7 +348,7 @@ do -- -- This function is called when a player has build a vehicle or FOB: -- --- function CTLD:OnAfterCratesBuild(From, Event, To, Group, Unit, Vehicle) +-- function my_ctld:OnAfterCratesBuild(From, Event, To, Group, Unit, Vehicle) -- ... your code here ... -- end -- @@ -395,7 +395,7 @@ do -- -- Also, the following options need to be set to `true`: -- --- my_ctld.useprefix = true -- this is true by default +-- my_ctld.useprefix = true -- this is true by default and MUST BE ON. -- -- Standard transport capabilities as per the real Hercules are: -- @@ -513,7 +513,7 @@ CTLD.SkipFrequencies = { --- CTLD class version. -- @field #string version -CTLD.version="0.1.2b1" +CTLD.version="0.1.3r1" --- Instantiate a new CTLD. -- @param #CTLD self @@ -745,6 +745,28 @@ end -- Helper and User Functions ------------------------------------------------------------------- +--- (Internal) Function to get capabilities of a chopper +-- @param #CTLD self +-- @param Wrapper.Unit#UNIT Unit The unit +-- @return #table Capabilities Table of caps +function CTLD:_GetUnitCapabilities(Unit) + self:T(self.lid .. " _GetUnitCapabilities") + local _unit = Unit -- Wrapper.Unit#UNIT + local unittype = _unit:GetTypeName() + local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + if not capabilities or capabilities == {} then + -- e.g. ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0}, + capabilities = {} + capabilities.troops = false + capabilities.crates = false + capabilities.cratelimit = 0 + capabilities.trooplimit = 0 + capabilities.type = "generic" + end + return capabilities +end + + --- (Internal) Function to generate valid UHF Frequencies -- @param #CTLD self function CTLD:_GenerateUHFrequencies() @@ -963,7 +985,8 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) --self:Tself.lid .. string.format("Troops %s requested", cratename)) -- see if this heli can load troops local unittype = unit:GetTypeName() - local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + --local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + local capabilities = self:_GetUnitCapabilities(Unit) local cantroops = capabilities.troops -- #boolean local trooplimit = capabilities.trooplimit -- #number local troopsize = cargotype:GetCratesNeeded() -- #number @@ -1006,12 +1029,12 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) local cgoname = Cargo:GetName() --self:T{cgoname, number, drop}) -- check if we are in LOAD zone - local inzone = true - - if drop then - local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) + local inzone = false + local drop = drop or false + if not drop then + inzone = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) else - local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) + inzone = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) end if not inzone then @@ -1021,7 +1044,8 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) end -- avoid crate spam - local capabilities = self.UnitTypes[Unit:GetTypeName()] -- #CTLD.UnitCapabilities + local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities + --local capabilities = self.UnitTypes[Unit:GetTypeName()] -- #CTLD.UnitCapabilities local canloadcratesno = capabilities.cratelimit local loaddist = self.CrateDistance or 30 local nearcrates, numbernearby = self:_FindCratesNearby(Group,Unit,loaddist) @@ -1183,7 +1207,8 @@ function CTLD:_LoadCratesNearby(Group, Unit) local unitname = unit:GetName() -- see if this heli can load crates local unittype = unit:GetTypeName() - local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + local capabilities = self:_GetUnitCapabilities(Unit) + --local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities local cancrates = capabilities.crates -- #boolean local cratelimit = capabilities.cratelimit -- #number local grounded = not self:IsUnitInAir(Unit) @@ -1284,9 +1309,10 @@ function CTLD:_ListCargo(Group, Unit) self:T(self.lid .. " _ListCargo") local unitname = Unit:GetName() local unittype = Unit:GetTypeName() - local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities + --local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities local trooplimit = capabilities.trooplimit -- #boolean - local cratelimit = capabilities.cratelimit -- #numbe + local cratelimit = capabilities.cratelimit -- #number local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo if self.Loaded_Cargo[unitname] then local no_troops = loadedcargo.Troopsloaded or 0 @@ -1715,11 +1741,13 @@ function CTLD:_RefreshF10Menus() -- rebuild units table local _UnitList = {} for _key, _group in pairs (PlayerTable) do - local _unit = _group:GetUnit(1) -- Asume that there is only one unit in the flight for players + local _unit = _group:GetUnit(1) -- Wrapper.Unit#UNIT Asume that there is only one unit in the flight for players if _unit then - if _unit:IsAlive() then - local unitName = _unit:GetName() - _UnitList[unitName] = unitName + if _unit:IsAlive() and _unit:IsPlayer() then + if _unit:IsHelicopter() or (_unit:GetTypeName() == "Hercules" and self.enableHercules) then --ensure no stupid unit entries here + local unitName = _unit:GetName() + _UnitList[unitName] = unitName + end end -- end isAlive end -- end if _unit end -- end for @@ -1737,7 +1765,8 @@ function CTLD:_RefreshF10Menus() if _group then -- get chopper capabilities local unittype = _unit:GetTypeName() - local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + local capabilities = self:_GetUnitCapabilities(_unit) -- #CTLD.UnitCapabilities + --local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities local cantroops = capabilities.troops local cancrates = capabilities.crates -- top menu @@ -1790,8 +1819,8 @@ function CTLD:_RefreshF10Menus() --- User function - Add *generic* troop type loadable as cargo. This type will load directly into the heli without crates. -- @param #CTLD self --- @param #Name Name Unique name of this type of troop. E.g. "Anti-Air Small". --- @param #Table Templates Table of #string names of late activated Wrapper.Group#GROUP making up this troop. +-- @param #string Name Unique name of this type of troop. E.g. "Anti-Air Small". +-- @param #table Templates Table of #string names of late activated Wrapper.Group#GROUP making up this troop. -- @param #CTLD_CARGO.Enum Type Type of cargo, here TROOPS - these will move to a nearby destination zone when dropped/build. -- @param #number NoTroops Size of the group in number of Units across combined templates (for loading). function CTLD:AddTroopsCargo(Name,Templates,Type,NoTroops) @@ -1805,8 +1834,8 @@ end --- User function - Add *generic* crate-type loadable as cargo. This type will create crates that need to be loaded, moved, dropped and built. -- @param #CTLD self --- @param #Name Name Unique name of this type of cargo. E.g. "Humvee". --- @param #Table Templates Table of #string names of late activated Wrapper.Group#GROUP building this cargo. +-- @param #string Name Unique name of this type of cargo. E.g. "Humvee". +-- @param #table Templates Table of #string names of late activated Wrapper.Group#GROUP building this cargo. -- @param #CTLD_CARGO.Enum Type Type of cargo. I.e. VEHICLE or FOB. VEHICLE will move to destination zones when dropped/build, FOB stays put. -- @param #number NoCrates Number of crates needed to build this cargo. function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates) @@ -2315,7 +2344,8 @@ end local unittype = Unit:GetTypeName() local unitname = Unit:GetName() local Group = Unit:GetGroup() - local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities + --local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities local cancrates = capabilities.crates -- #boolean local cratelimit = capabilities.cratelimit -- #number if cancrates then @@ -2360,7 +2390,7 @@ end -- @return #CTLD self function CTLD:onafterStart(From, Event, To) self:T({From, Event, To}) - if self.useprefix then + if self.useprefix or self.enableHercules then local prefix = self.prefixes --self:T{prefix=prefix}) if self.enableHercules then @@ -2368,11 +2398,11 @@ end self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterStart() else --self:T("CTLD with prefixes NO Hercules") - self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterCategoryHelicopter():FilterStart() + self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterCategories("helicopter"):FilterStart() end else --self:T("CTLD NO prefixes NO Hercules") - self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart() + self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategories("helicopter"):FilterStart() end -- Events self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) @@ -2529,4 +2559,4 @@ end end -- end do ------------------------------------------------------------------- -- End Ops.CTLD.lua -------------------------------------------------------------------- \ No newline at end of file +------------------------------------------------------------------- From 39c46dcab0803d8464140a5e25d2565827d08cc3 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 2 Jul 2021 20:15:12 +0200 Subject: [PATCH 079/111] Changed frequency logic, some documentation changes --- Moose Development/Moose/Ops/CSAR.lua | 8 ++++---- Moose Development/Moose/Ops/CTLD.lua | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index ef181a759..39e6f0654 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -290,7 +290,7 @@ function CSAR:New(Coalition, Template, Alias) self.alias="Red Cross" if self.coalition then if self.coalition==coalition.side.RED then - self.alias="�¡�¿�°Ñ��µ�½�¸�µ" + self.alias="IFRC" elseif self.coalition==coalition.side.BLUE then self.alias="CSAR" end @@ -538,7 +538,7 @@ function CSAR:_SpawnPilotInField(country,point,frequency) math.random(i,10000) end local template = self.template - local alias = string.format("Pilot %.2fkHz-%d", freq, math.random(1,10000)) + local alias = string.format("Pilot %.2fkHz-%d", freq, math.random(1,99)) local coalition = self.coalition local pilotcacontrol = self.allowDownedPilotCAcontrol -- Switch AI on/oof - is this really correct for CA? local _spawnedGroup = SPAWN @@ -1828,8 +1828,8 @@ function CSAR:_RefreshRadioBeacons() self:T({_pilot}) local pilot = _pilot -- #CSAR.DownedPilot local group = pilot.group - local frequency = pilot.frequency or 0.0 -- thanks to @Thrud - if group and group:IsAlive() and frequency > 0.0 then + local frequency = pilot.frequency or 0 -- thanks to @Thrud + if group and group:IsAlive() and frequency > 0 then self:_AddBeaconToGroup(group,frequency) end end diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 9808d16da..6ae0e4137 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -300,12 +300,12 @@ do -- Activate a zone: -- -- -- Activate zone called Name of type #CTLD.CargoZoneType ZoneType: --- my_ctld:ActivateZone(Name,ZoneType) +-- my_ctld:ActivateZone(Name,CTLD.CargoZoneType.MOVE) -- -- Deactivate a zone: -- -- -- Deactivate zone called Name of type #CTLD.CargoZoneType ZoneType: --- my_ctld:DeactivateZone(Name,ZoneType) +-- my_ctld:DeactivateZone(Name,CTLD.CargoZoneType.DROP) -- -- ## 3. Events -- From 9591c6217525b43b8f7dc37c3fbe06726a4ed935 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 2 Jul 2021 20:22:43 +0200 Subject: [PATCH 080/111] corrected autovalue for frequency - thanks to shadowze --- Moose Development/Moose/Ops/CSAR.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 39e6f0654..15318de07 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -605,7 +605,7 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla if not _freq then _freq = self:_GenerateADFFrequency() - if not _freq then _freq = "333250" end --noob catch + if not _freq then _freq = 333250 end --noob catch end local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point,_freq) @@ -818,7 +818,7 @@ function CSAR:_EventHandler(EventData) -- all checks passed, get going. local _freq = self:_GenerateADFFrequency() - self:_AddCsar(_coalition, _unit:GetCountry(), _unit:GetCoordinate() , _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, false, 0) + self:_AddCsar(_coalition, _unit:GetCountry(), _unit:GetCoordinate() , _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, false, "none") return true From e14d655447d2d1a2b39028ac578c2f9abe2f6472 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 3 Jul 2021 09:30:04 +0200 Subject: [PATCH 081/111] Changed default fallback freq to 333.00Khz --- Moose Development/Moose/Ops/CSAR.lua | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 15318de07..2422b8c14 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -240,7 +240,7 @@ CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.6r1" +CSAR.version="0.1.6r2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -359,12 +359,14 @@ function CSAR:New(Coalition, Template, Alias) -- added 0.1.4 self.limitmaxdownedpilots = true self.maxdownedpilots = 25 - + -- generate Frequencies + self:_GenerateVHFrequencies() + -- WARNING - here\'ll be dragons -- for this to work you need to de-sanitize your mission environment in \Scripts\MissionScripting.lua -- needs SRS => 1.9.6 to work (works on the *server* side) self.useSRS = false -- Use FF\'s SRS integration - self.SRSPath = "E:\\Program Files\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your server(!) + self.SRSPath = "E:\\Progra~1\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your server(!) self.SRSchannel = 300 -- radio channel self.SRSModulation = radio.modulation.AM -- modulation @@ -605,7 +607,7 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla if not _freq then _freq = self:_GenerateADFFrequency() - if not _freq then _freq = 333250 end --noob catch + if not _freq then _freq = 333000 end --noob catch end local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point,_freq) @@ -1886,7 +1888,6 @@ function CSAR:onafterStart(From, Event, To) self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) self:HandleEvent(EVENTS.PilotDead, self._EventHandler) - self:_GenerateVHFrequencies() if self.useprefix then local prefixes = self.csarPrefix or {} self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefixes):FilterCategoryHelicopter():FilterStart() From a7e07af24f1e2bb0abf0b518009ad4cddb19d246 Mon Sep 17 00:00:00 2001 From: Justin Lovell Date: Sat, 3 Jul 2021 23:24:04 +1000 Subject: [PATCH 082/111] Correcting typo for debug message Small PR --- Moose Development/Moose/Wrapper/Marker.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Marker.lua b/Moose Development/Moose/Wrapper/Marker.lua index 88a868501..098e0d4ba 100644 --- a/Moose Development/Moose/Wrapper/Marker.lua +++ b/Moose Development/Moose/Wrapper/Marker.lua @@ -646,7 +646,7 @@ function MARKER:OnEventMarkRemoved(EventData) local MarkID=EventData.MarkID - self:T3(self.lid..string.format("Captured event MarkAdded for Mark ID=%s", tostring(MarkID))) + self:T3(self.lid..string.format("Captured event MarkRemoved for Mark ID=%s", tostring(MarkID))) if MarkID==self.mid then From a14bca1059fa47770eeacb5bd66b276ef6b1626b Mon Sep 17 00:00:00 2001 From: Justin Lovell Date: Sat, 3 Jul 2021 23:30:45 +1000 Subject: [PATCH 083/111] Synchronize Text with Wrapper State Bug - text is not synchronized with the wrapper state, hence the `GetText()` will be incorrect. Method `TextChanged` does not exist, resulting `nil` reference errors when the players update markers. Current implementation of `MARKER:OnEventMarkChange(EventData)` is not implemented the same as its siblings of `OnEventMarkRemoved` and `OnEventMarkAdded`. The siblings would move the FSM accordingly -- aligned implementation --- Moose Development/Moose/Wrapper/Marker.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Marker.lua b/Moose Development/Moose/Wrapper/Marker.lua index 88a868501..1027e030a 100644 --- a/Moose Development/Moose/Wrapper/Marker.lua +++ b/Moose Development/Moose/Wrapper/Marker.lua @@ -673,9 +673,9 @@ function MARKER:OnEventMarkChange(EventData) if MarkID==self.mid then - self:Changed(EventData) + self.text=tostring(EventData.MarkText) - self:TextChanged(tostring(EventData.MarkText)) + self:Changed(EventData) end From 4ba52212a9fc7480180258952a156b26e956d820 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 4 Jul 2021 18:04:08 +0200 Subject: [PATCH 084/111] ATIS - addede rainy presets 1-3 CTLD - avoid "preloading" when pilot leaves/crashes and rejoins CSAR - added option for autosmokedistance --- Moose Development/Moose/Ops/CTLD.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 6ae0e4137..9f9885068 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -513,7 +513,7 @@ CTLD.SkipFrequencies = { --- CTLD class version. -- @field #string version -CTLD.version="0.1.3r1" +CTLD.version="0.1.4r1" --- Instantiate a new CTLD. -- @param #CTLD self @@ -926,6 +926,8 @@ function CTLD:_EventHandler(EventData) local _unit = event.IniUnit local _group = event.IniGroup if _unit:IsHelicopter() or _group:IsHelicopter() then + local unitname = event.IniUnitName or "none" + self.Loaded_Cargo[unitname] = nil self:_RefreshF10Menus() end -- Herc support @@ -938,6 +940,7 @@ function CTLD:_EventHandler(EventData) -- remove from pilot table local unitname = event.IniUnitName or "none" self.CtldUnits[unitname] = nil + self.Loaded_Cargo[unitname] = nil end return self end @@ -2559,4 +2562,4 @@ end end -- end do ------------------------------------------------------------------- -- End Ops.CTLD.lua -------------------------------------------------------------------- +------------------------------------------------------------------- \ No newline at end of file From 48aa841adddf1bdd2924a519263ad853f50f688e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 4 Jul 2021 18:04:27 +0200 Subject: [PATCH 085/111] ATIS - addede rainy presets 1-3 CTLD - avoid "preloading" when pilot leaves/crashes and rejoins CSAR - added option for autosmokedistance --- Moose Development/Moose/Ops/ATIS.lua | 24 ++++++++++++++++++++++++ Moose Development/Moose/Ops/CSAR.lua | 8 +++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index e8b475195..f865f98be 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -1617,6 +1617,30 @@ function ATIS:onafterBroadcast(From, Event, To) -- Scattered 4 clouddens=4 elseif cloudspreset:find("RainyPreset") then + -- Overcast + Rain + clouddens=9 + if temperature>5 then + precepitation=1 -- rain + else + precepitation=3 -- snow + end + elseif cloudspreset:find("RainyPreset1") then + -- Overcast + Rain + clouddens=9 + if temperature>5 then + precepitation=1 -- rain + else + precepitation=3 -- snow + end + elseif cloudspreset:find("RainyPreset2") then + -- Overcast + Rain + clouddens=9 + if temperature>5 then + precepitation=1 -- rain + else + precepitation=3 -- snow + end + elseif cloudspreset:find("RainyPreset3") then -- Overcast + Rain clouddens=9 if temperature>5 then diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 2422b8c14..7c3b47850 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -70,6 +70,7 @@ -- self.allowDownedPilotCAcontrol = false -- Set to false if you don\'t want to allow control by Combined Arms. -- self.allowFARPRescue = true -- allows pilots to be rescued by landing at a FARP or Airbase. Else MASH only! -- self.autosmoke = false -- automatically smoke a downed pilot\'s location when a heli is near. +-- self.autosmokedistance = 1000 -- distance for autosmoke -- self.coordtype = 1 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. -- self.csarOncrash = false -- (WIP) If set to true, will generate a downed pilot when a plane crashes as well. -- self.enableForAI = false -- set to false to disable AI pilots from being rescued. @@ -240,7 +241,7 @@ CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.6r2" +CSAR.version="0.1.7r2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -356,6 +357,7 @@ function CSAR:New(Coalition, Template, Alias) self.mashprefix = {"MASH"} -- prefixes used to find MASHes self.mash = SET_GROUP:New():FilterCoalitions(self.coalition):FilterPrefixes(self.mashprefix):FilterOnce() -- currently only GROUP objects, maybe support STATICs also? self.autosmoke = false -- automatically smoke location when heli is near + self.autosmokedistance = 1000 -- distance for autosmoke -- added 0.1.4 self.limitmaxdownedpilots = true self.maxdownedpilots = 25 @@ -987,7 +989,7 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) _downedpilot.timestamp = timer.getAbsTime() self:__Approach(-5,heliname,woundedgroupname) end - else + elseif _distance >= 3000 and _distance < 5000 then self.heliVisibleMessage[_lookupKeyHeli] = nil --reschedule as units aren\'t dead yet , schedule for a bit slower though as we\'re far away _downedpilot.timestamp = timer.getAbsTime() @@ -1103,7 +1105,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG local _reset = true - if (self.autosmoke == true) and (_distance < 500) then + if (self.autosmoke == true) and (_distance < self.autosmokedistance) then self:_PopSmokeForGroup(_woundedGroupName, _woundedLeader) end From a3d8a48b92b80293c97d2374188893e6aece2575 Mon Sep 17 00:00:00 2001 From: Julien B Date: Mon, 5 Jul 2021 13:09:34 +0200 Subject: [PATCH 086/111] Add ATC Ground Mariana Islands support Added ATC_GROUND_MARIANAISLANDS Supported optional airbase ZONE_POLYGON boundaries (like initially intented) when there is provided, fallback remain a ZONE from the center of the airfield. Some airports are bigger than the default radius area and need custom boundaries to work properly on the edge of it. --- .../Moose/Functional/ATC_Ground.lua | 313 +++++++++++++++++- 1 file changed, 312 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/ATC_Ground.lua b/Moose Development/Moose/Functional/ATC_Ground.lua index 455f3eaf5..e5d17ea14 100644 --- a/Moose Development/Moose/Functional/ATC_Ground.lua +++ b/Moose Development/Moose/Functional/ATC_Ground.lua @@ -59,7 +59,13 @@ function ATC_GROUND:New( Airbases, AirbaseList ) for AirbaseID, Airbase in pairs( self.Airbases ) do - Airbase.ZoneBoundary = _DATABASE:FindAirbase( AirbaseID ):GetZone() + -- Specified ZoneBoundary is used if setted or Airbase radius by default + if Airbase.ZoneBoundary then + Airbase.ZoneBoundary = ZONE_POLYGON_BASE:New( "Boundary " .. AirbaseID, Airbase.ZoneBoundary ) + else + Airbase.ZoneBoundary = _DATABASE:FindAirbase( AirbaseID ):GetZone() + end + Airbase.ZoneRunways = {} for PointsRunwayID, PointsRunway in pairs( Airbase.PointsRunways ) do Airbase.ZoneRunways[PointsRunwayID] = ZONE_POLYGON_BASE:New( "Runway " .. PointsRunwayID, PointsRunway ) @@ -3263,5 +3269,310 @@ function ATC_GROUND_PERSIANGULF:Start( RepeatScanSeconds ) end + --- @type ATC_GROUND_MARIANAISLANDS +-- @extends #ATC_GROUND + +--- # ATC\_GROUND\_MARIANA, extends @{#ATC_GROUND} +-- +-- The ATC\_GROUND\_MARIANA class monitors the speed of the airplanes at the airbase during taxi. +-- The pilots may not drive faster than the maximum speed for the airbase, or they will be despawned. +-- +-- --- +-- +-- ![Banner Image](..\Presentations\ATC_GROUND\Dia1.JPG) +-- +-- --- +-- +-- The default maximum speed for the airbases at Persian Gulf is **50 km/h**. Warnings are given if this speed limit is trespassed. +-- Players will be immediately kicked when driving faster than **150 km/h** on the taxi way. +-- +-- The ATC\_GROUND\_MARIANA class monitors the speed of the airplanes at the airbase during taxi. +-- The pilots may not drive faster than the maximum speed for the airbase, or they will be despawned. +-- +-- The pilot will receive 3 times a warning during speeding. After the 3rd warning, if the pilot is still driving +-- faster than the maximum allowed speed, the pilot will be kicked. +-- +-- Different airbases have different maximum speeds, according safety regulations. +-- +-- # Airbases monitored +-- +-- The following airbases are monitored at the Mariana Island region. +-- Use the @{Wrapper.Airbase#AIRBASE.MarianaIslands} enumeration to select the airbases to be monitored. +-- +-- * AIRBASE.MarianaIslands.Rota_Intl +-- * AIRBASE.MarianaIslands.Andersen_AFB +-- * AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl +-- * AIRBASE.MarianaIslands.Saipan_Intl +-- * AIRBASE.MarianaIslands.Tinian_Intl +-- * AIRBASE.MarianaIslands.Olf_Orote +-- +-- # Installation +-- +-- ## In Single Player Missions +-- +-- ATC\_GROUND is fully functional in single player. +-- +-- ## In Multi Player Missions +-- +-- ATC\_GROUND is functional in multi player, however ... +-- +-- Due to a bug in DCS since release 1.5, the despawning of clients are not anymore working in multi player. +-- To **work around this problem**, a much better solution has been made, using the **slot blocker** script designed +-- by Ciribob. +-- +-- With the help of __Ciribob__, this script has been extended to also kick client players while in flight. +-- ATC\_GROUND is communicating with this modified script to kick players! +-- +-- Install the file **SimpleSlotBlockGameGUI.lua** on the server, following the installation instructions described by Ciribob. +-- +-- [Simple Slot Blocker from Ciribob & FlightControl](https://github.com/ciribob/DCS-SimpleSlotBlock) +-- +-- # Script it! +-- +-- ## 1. ATC_GROUND_MARIANAISLANDS Constructor +-- +-- Creates a new ATC_GROUND_MARIANAISLANDS object that will monitor pilots taxiing behaviour. +-- +-- -- This creates a new ATC_GROUND_MARIANAISLANDS object. +-- +-- -- Monitor for these clients the airbases. +-- AirbasePoliceCaucasus = ATC_GROUND_MARIANAISLANDS:New() +-- +-- ATC_Ground = ATC_GROUND_MARIANAISLANDS:New( +-- { AIRBASE.MarianaIslands.Andersen_AFB, +-- AIRBASE.MarianaIslands.Saipan_Intl +-- } +-- ) +-- +-- +-- ## 2. Set various options +-- +-- There are various methods that you can use to tweak the behaviour of the ATC\_GROUND classes. +-- +-- ### 2.1 Speed limit at an airbase. +-- +-- * @{#ATC_GROUND.SetKickSpeed}(): Set the speed limit allowed at an airbase in meters per second. +-- * @{#ATC_GROUND.SetKickSpeedKmph}(): Set the speed limit allowed at an airbase in kilometers per hour. +-- * @{#ATC_GROUND.SetKickSpeedMiph}(): Set the speed limit allowed at an airbase in miles per hour. +-- +-- ### 2.2 Prevent Takeoff at an airbase. Players will be kicked immediately. +-- +-- * @{#ATC_GROUND.SetMaximumKickSpeed}(): Set the maximum speed allowed at an airbase in meters per second. +-- * @{#ATC_GROUND.SetMaximumKickSpeedKmph}(): Set the maximum speed allowed at an airbase in kilometers per hour. +-- * @{#ATC_GROUND.SetMaximumKickSpeedMiph}(): Set the maximum speed allowed at an airbase in miles per hour. +-- +---- @field #ATC_GROUND_MARIANAISLANDS +ATC_GROUND_MARIANAISLANDS = { + ClassName = "ATC_GROUND_MARIANAISLANDS", + Airbases = { + + [AIRBASE.MarianaIslands.Andersen_AFB] = { + ZoneBoundary = { + [1]={["y"]=16534.138036037,["x"]=11357.42159178,}, + [2]={["y"]=16193.406442738,["x"]=12080.012957533,}, + [3]={["y"]=13846.966851869,["x"]=12017.348398727,}, + [4]={["y"]=13085.815989171,["x"]=11686.317876875,}, + [5]={["y"]=13157.991797443,["x"]=11307.826209991,}, + [6]={["y"]=12055.725179065,["x"]=10795.955695916,}, + [7]={["y"]=12762.455491112,["x"]=8890.9830441032,}, + [8]={["y"]=15955.829493693,["x"]=10333.527220132,}, + [9]={["y"]=16537.500532414,["x"]=11302.009499603,}, + }, + PointsRunways = { + [1]={ + [1]={["y"]=12586.683049611,["x"]=10224.374497932,}, + [2]={["y"]=16191.720475696,["x"]=11791.299100017,}, + [3]={["y"]=16126.93956642,["x"]=11938.855615591,}, + [4]={["y"]=12520.758127164,["x"]=10385.177131701,}, + [5]={["y"]=12584.654720512,["x"]=10227.416991581,}, + }, + [2]={ + [1]={["y"]=12663.030391743,["x"]=9661.9623015306,}, + [2]={["y"]=16478.347303358,["x"]=11328.665745976,}, + [3]={["y"]=16405.4731048,["x"]=11479.11570429,}, + [4]={["y"]=12597.277684174,["x"]=9817.9733769647,}, + [5]={["y"]=12661.894752524,["x"]=9674.4462086962,}, + }, + }, + }, + [AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl] = { + ZoneBoundary = { + [1]={["y"]=2288.5182403943,["x"]=1469.0170841716,}, + [2]={["y"]=1126.2025877996,["x"]=1174.37135631,}, + [3]={["y"]=-2015.6461924287,["x"]=-484.62000718931,}, + [4]={["y"]=-2102.1292389114,["x"]=-988.03393750566,}, + [5]={["y"]=476.03853524366,["x"]=-1220.1783269883,}, + [6]={["y"]=2059.2220058047,["x"]=78.889693514402,}, + [7]={["y"]=1898.1396965104,["x"]=705.67531284795,}, + [8]={["y"]=2760.1768681934,["x"]=1026.0681119777,}, + [9]={["y"]=2317.2278959994,["x"]=1460.8143254273,}, + }, + PointsRunways = { + [1]={ + [1]={["y"]=-1872.6620108821,["x"]=-924.3572605835,}, + [2]={["y"]=1763.4754603305,["x"]=735.35988877983,}, + [3]={["y"]=1700.6941677961,["x"]=866.32615476157,}, + [4]={["y"]=-1934.0078007732,["x"]=-779.8149298453,}, + [5]={["y"]=-1875.0113982627,["x"]=-914.95971106094,}, + }, + [2]={ + [1]={["y"]=-1512.9403660377,["x"]=-1005.5903386188,}, + [2]={["y"]=1577.9055714735,["x"]=413.22750176368,}, + [3]={["y"]=1523.1182807849,["x"]=543.89726442232,}, + [4]={["y"]=-1572.5102998047,["x"]=-867.04004322806,}, + [5]={["y"]=-1514.2790162347,["x"]=-1003.5823633233,}, + }, + }, + }, + [AIRBASE.MarianaIslands.Rota_Intl] = { + ZoneBoundary = { + [1]={["y"]=47237.615412849,["x"]=76048.890408862,}, + [2]={["y"]=49938.030053628,["x"]=75921.721582932,}, + [3]={["y"]=49931.24873272,["x"]=75735.184004851,}, + [4]={["y"]=49295.999227075,["x"]=75754.716414519,}, + [5]={["y"]=49286.963307515,["x"]=75510.037806569,}, + [6]={["y"]=48774.280745707,["x"]=75513.331990155,}, + [7]={["y"]=48785.021396773,["x"]=75795.691662161,}, + [8]={["y"]=47232.749278491,["x"]=75839.239059146,}, + [9]={["y"]=47236.687866223,["x"]=76042.706764692,}, + }, + PointsRunways = { + [1]={ + [1]={["y"]=49741.295228062,["x"]=75901.50955922,}, + [2]={["y"]=49739.033213305,["x"]=75768.333440425,}, + [3]={["y"]=47448.460520408,["x"]=75857.400271466,}, + [4]={["y"]=47452.270177742,["x"]=75999.965448133,}, + [5]={["y"]=49738.502011054,["x"]=75905.338915708,}, + }, + }, + }, + [AIRBASE.MarianaIslands.Saipan_Intl] = { + ZoneBoundary = { + [1]={["y"]=100489.08491445,["x"]=179799.05158855,}, + [2]={["y"]=100869.73415313,["x"]=179948.98719903,}, + [3]={["y"]=101364.78967515,["x"]=180831.98517043,}, + [4]={["y"]=101563.85713359,["x"]=180885.21496237,}, + [5]={["y"]=101733.92591034,["x"]=180457.73296886,}, + [6]={["y"]=103340.30228775,["x"]=180990.08362622,}, + [7]={["y"]=103459.55080438,["x"]=180453.77747027,}, + [8]={["y"]=100406.63048095,["x"]=179266.60983762,}, + [9]={["y"]=100225.55027532,["x"]=179423.9380961,}, + [10]={["y"]=100477.48558937,["x"]=179791.9827288,}, + }, + PointsRunways = { + [1]={ + [1]={["y"]=103170.38882002,["x"]=180654.56630524,}, + [2]={["y"]=103235.37868835,["x"]=180497.25368418,}, + [3]={["y"]=100564.72969504,["x"]=179435.41443498,}, + [4]={["y"]=100509.30718722,["x"]=179584.65394733,}, + [5]={["y"]=103163.53918905,["x"]=180651.82645285,}, + }, + [2]={ + [1]={["y"]=103048.83223261,["x"]=180819.94107128,}, + [2]={["y"]=103087.60579257,["x"]=180720.06315265,}, + [3]={["y"]=101037.52694966,["x"]=179899.50061624,}, + [4]={["y"]=100994.61708907,["x"]=180009.33151758,}, + [5]={["y"]=103043.26643227,["x"]=180820.40488798,}, + }, + }, + }, + [AIRBASE.MarianaIslands.Tinian_Intl] = { + ZoneBoundary = { + [1]={["y"]=88393.477575413,["x"]=166704.16076438,}, + [2]={["y"]=91581.732441809,["x"]=167402.54409276,}, + [3]={["y"]=91533.451647402,["x"]=166826.23670062,}, + [4]={["y"]=90827.604136952,["x"]=166699.75590414,}, + [5]={["y"]=90894.853975623,["x"]=166375.37836304,}, + [6]={["y"]=89995.027922869,["x"]=166224.92495935,}, + [7]={["y"]=88937.62899352,["x"]=166244.48573911,}, + [8]={["y"]=88408.916178231,["x"]=166480.39896864,}, + [9]={["y"]=88387.745481732,["x"]=166685.82715656,}, + }, + PointsRunways = { + [1]={ + [1]={["y"]=91329.480937912,["x"]=167204.44064529,}, + [2]={["y"]=91363.95475433,["x"]=167038.15603429,}, + [3]={["y"]=88585.849307337,["x"]=166520.3807647,}, + [4]={["y"]=88554.422227212,["x"]=166686.49505251,}, + [5]={["y"]=91318.8152578,["x"]=167203.31794212,}, + }, + }, + }, + + }, +} + +--- Creates a new ATC_GROUND_MARIANAISLANDS object. +-- @param #ATC_GROUND_MARIANAISLANDS self +-- @param AirbaseNames A list {} of airbase names (Use AIRBASE.MarianaIslands enumerator). +-- @return #ATC_GROUND_MARIANAISLANDS self +function ATC_GROUND_MARIANAISLANDS:New( AirbaseNames ) + + -- Inherits from BASE + local self = BASE:Inherit( self, ATC_GROUND:New( self.Airbases, AirbaseNames ) ) + + self:SetKickSpeedKmph( 50 ) + self:SetMaximumKickSpeedKmph( 150 ) + +-- -- Andersen +-- local AndersenBoundary = GROUP:FindByName( "Andersen Boundary" ) +-- self.Airbases[AIRBASE.MarianaIslands.Andersen_AFB].ZoneBoundary = ZONE_POLYGON:New( "Andersen Boundary", AndersenBoundary ):SmokeZone(SMOKECOLOR.White):Flush() +-- +-- local AndersenRunway1 = GROUP:FindByName( "Andersen Runway 1" ) +-- self.Airbases[AIRBASE.MarianaIslands.Andersen_AFB].ZoneRunways[1] = ZONE_POLYGON:New( "Andersen Runway 1", AndersenRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() +-- +-- local AndersenRunway2 = GROUP:FindByName( "Andersen Runway 2" ) +-- self.Airbases[AIRBASE.MarianaIslands.Andersen_AFB].ZoneRunways[2] = ZONE_POLYGON:New( "Andersen Runway 2", AndersenRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() +-- +-- +-- -- Antonio_B_Won_Pat_International_Airport +-- local AntonioBoundary = GROUP:FindByName( "Antonio Boundary" ) +-- self.Airbases[AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl].ZoneBoundary = ZONE_POLYGON:New( "Antonio Boundary", AntonioBoundary ):SmokeZone(SMOKECOLOR.White):Flush() +-- +-- local AntonioRunway1 = GROUP:FindByName( "Antonio Runway 1" ) +-- self.Airbases[AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl].ZoneRunways[1] = ZONE_POLYGON:New( "Antonio Runway 1", AntonioRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() +-- +-- local AntonioRunway2 = GROUP:FindByName( "Antonio Runway 2" ) +-- self.Airbases[AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl].ZoneRunways[2] = ZONE_POLYGON:New( "Antonio Runway 2", AntonioRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() +-- +-- +-- -- Rota_International_Airport +-- local RotaBoundary = GROUP:FindByName( "Rota Boundary" ) +-- self.Airbases[AIRBASE.MarianaIslands.Rota_Intl].ZoneBoundary = ZONE_POLYGON:New( "Rota Boundary", RotaBoundary ):SmokeZone(SMOKECOLOR.White):Flush() +-- +-- local RotaRunway1 = GROUP:FindByName( "Rota Runway 1" ) +-- self.Airbases[AIRBASE.MarianaIslands.Rota_Intl].ZoneRunways[1] = ZONE_POLYGON:New( "Rota Runway 1", RotaRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() +-- +-- +-- -- Saipan_International_Airport +-- local SaipanBoundary = GROUP:FindByName( "Saipan Boundary" ) +-- self.Airbases[AIRBASE.MarianaIslands.Saipan_Intl].ZoneBoundary = ZONE_POLYGON:New( "Saipan Boundary", SaipanBoundary ):SmokeZone(SMOKECOLOR.White):Flush() +-- +-- local SaipanRunway1 = GROUP:FindByName( "Saipan Runway 1" ) +-- self.Airbases[AIRBASE.MarianaIslands.Saipan_Intl].ZoneRunways[1] = ZONE_POLYGON:New( "Saipan Runway 1", SaipanRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() +-- +-- local SaipanRunway2 = GROUP:FindByName( "Saipan Runway 2" ) +-- self.Airbases[AIRBASE.MarianaIslands.Saipan_Intl].ZoneRunways[2] = ZONE_POLYGON:New( "Saipan Runway 2", SaipanRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() +-- +-- +-- -- Tinian_International_Airport +-- local TinianBoundary = GROUP:FindByName( "Tinian Boundary" ) +-- self.Airbases[AIRBASE.MarianaIslands.Tinian_Intl].ZoneBoundary = ZONE_POLYGON:New( "Tinian Boundary", TinianBoundary ):SmokeZone(SMOKECOLOR.White):Flush() +-- +-- local TinianRunway1 = GROUP:FindByName( "Tinian Runway 1" ) +-- self.Airbases[AIRBASE.MarianaIslands.Tinian_Intl].ZoneRunways[1] = ZONE_POLYGON:New( "Tinian Runway 1", TinianRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + + return self +end + + +--- Start SCHEDULER for ATC_GROUND_MARIANAISLANDS object. +-- @param #ATC_GROUND_MARIANAISLANDS self +-- @param RepeatScanSeconds Time in second for defining occurency of alerts. +-- @return nothing +function ATC_GROUND_MARIANAISLANDS:Start( RepeatScanSeconds ) + RepeatScanSeconds = RepeatScanSeconds or 0.05 + self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, 2, RepeatScanSeconds ) +end From b7bc3cfbcf7d558580d70bdcadc7c2bcbc8ecdcf Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 5 Jul 2021 18:54:29 +0200 Subject: [PATCH 087/111] Spawn crates in a wider radius. Also build, when only one crate is required. --- Moose Development/Moose/Ops/CTLD.lua | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 9f9885068..1192de3a5 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -513,7 +513,7 @@ CTLD.SkipFrequencies = { --- CTLD class version. -- @field #string version -CTLD.version="0.1.4r1" +CTLD.version="0.1.4r2" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1073,11 +1073,14 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) -- loop crates needed for i=1,number do local cratealias = string.format("%s-%d", cratetemplate, math.random(1,100000)) - local cratedistance = i*4 + 6 + local cratedistance = i*4 + 8 if IsHerc then -- wider radius cratedistance = i*4 + 12 end + for i=1,50 do + math.random(90,270) + end local rheading = math.floor(math.random(90,270) * heading + 1 / 360) if not IsHerc then rheading = rheading + 180 -- mirror for Helis @@ -1574,11 +1577,11 @@ function CTLD:_BuildCrates(Group, Unit) foundbuilds = true else buildables[name].Found = buildables[name].Found + 1 - if buildables[name].Found >= buildables[name].Required then + foundbuilds = true + end + if buildables[name].Found >= buildables[name].Required then buildables[name].CanBuild = true canbuild = true - end - foundbuilds = true end --self:T{buildables = buildables}) end -- end dropped @@ -1638,7 +1641,7 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build) if type == CTLD_CARGO.Enum.VEHICLE then canmove = true end local temptable = Build.Template or {} local zone = ZONE_GROUP:New(string.format("Unload zone-%s",unitname),Group,100) - local randomcoord = zone:GetRandomCoordinate(20,50):GetVec2() + local randomcoord = zone:GetRandomCoordinate(35):GetVec2() for _,_template in pairs(temptable) do self.TroopCounter = self.TroopCounter + 1 local alias = string.format("%s-%d", _template, math.random(1,100000)) From 5d3ea57d4dae037dfd628aa7e1ec10d3f1ac9bca Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 6 Jul 2021 20:24:35 +0200 Subject: [PATCH 088/111] CSAR give scripted spawned pilots a name. CTLD corrected that generic troops could be spawned only once. --- Moose Development/Moose/Ops/CSAR.lua | 4 ++-- Moose Development/Moose/Ops/CTLD.lua | 19 +++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 7c3b47850..8fd660487 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -663,7 +663,7 @@ function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _ return end - local _description = _description or "none" + local _description = _description or "Unknown" local pos = {} if _randomPoint then @@ -682,7 +682,7 @@ function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _ _country = country.id.UN_PEACEKEEPERS end - self:_AddCsar(_coalition, _country, pos, "PoW", "Unknown", nil, freq, _nomessage, _description) + self:_AddCsar(_coalition, _country, pos, "PoW", _description, nil, freq, _nomessage, _description) return self end diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 1192de3a5..ae11f996c 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -55,10 +55,10 @@ CTLD_CARGO = { -- @type CTLD_CARGO.Enum -- @field #string Type Type of Cargo. CTLD_CARGO.Enum = { - VEHICLE = "Vehicle", -- #string vehicles - TROOPS = "Troops", -- #string troops - FOB = "FOB", -- #string FOB - CRATE = "CRATE", -- #string crate + ["VEHICLE"] = "Vehicle", -- #string vehicles + ["TROOPS"] = "Troops", -- #string troops + ["FOB"] = "FOB", -- #string FOB + ["CRATE"] = "Crate", -- #string crate } --- Function to create new CTLD_CARGO object. @@ -513,7 +513,7 @@ CTLD.SkipFrequencies = { --- CTLD class version. -- @field #string version -CTLD.version="0.1.4r2" +CTLD.version="0.1.3r2" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1010,8 +1010,11 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) --local m = MESSAGE:New("Sorry, we\'re crammed already!",10,"CTLD",true):ToGroup(group) return else + self.CargoCounter = self.CargoCounter + 1 + local loadcargotype = CTLD_CARGO:New(self.CargoCounter, Cargotype.Name, Cargotype.Templates, CTLD_CARGO.Enum.TROOPS, true, true, Cargotype.CratesNeeded) + self:T({cargotype=loadcargotype}) loaded.Troopsloaded = loaded.Troopsloaded + troopsize - table.insert(loaded.Cargo,Cargotype) + table.insert(loaded.Cargo,loadcargotype) self.Loaded_Cargo[unitname] = loaded self:_SendMessage("Troops boarded!", 10, false, Group) --local m = MESSAGE:New("Troops boarded!",10,"CTLD",true):ToGroup(group) @@ -1583,7 +1586,7 @@ function CTLD:_BuildCrates(Group, Unit) buildables[name].CanBuild = true canbuild = true end - --self:T{buildables = buildables}) + self:T({buildables = buildables}) end -- end dropped end -- end crate loop -- ok let\'s list what we have @@ -2565,4 +2568,4 @@ end end -- end do ------------------------------------------------------------------- -- End Ops.CTLD.lua -------------------------------------------------------------------- \ No newline at end of file +------------------------------------------------------------------- From 65bd7909e105a9ff4011b86f6aa870dadbd5b5a9 Mon Sep 17 00:00:00 2001 From: cammel tech <47948096+cammeltech@users.noreply.github.com> Date: Tue, 6 Jul 2021 20:26:20 +0200 Subject: [PATCH 089/111] Example for CTLD > SCORING added (#1566) * Example added Example for the connection to the SCORING Class. * kleiner Fehler eingeschlichen --- Moose Development/Moose/Ops/CTLD.lua | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index ae11f996c..8c5f7f076 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -351,6 +351,24 @@ do -- function my_ctld:OnAfterCratesBuild(From, Event, To, Group, Unit, Vehicle) -- ... your code here ... -- end + -- +-- ## 3.6 A simple SCORING example: +-- +-- To award player with points, using the SCORING Class (SCORING: my_Scoring, CTLD: CTLD_Cargotransport) +-- +-- function CTLD_Cargotransport:OnAfterCratesDropped(From, Event, To, Group, Unit, Cargotable) +-- local points = 10 +-- local PlayerName = Unit:GetPlayerName() +-- my_scoring:_AddPlayerFromUnit( Unit ) +-- my_scoring:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for transporting cargo crates!", PlayerName, points), points) +-- end +-- +-- function CTLD_Cargotransport:OnAfterCratesBuild(From, Event, To, Group, Unit, Vehicle) +-- local points = 5 +-- local PlayerName = Unit:GetPlayerName() +-- my_scoring:_AddPlayerFromUnit( Unit ) +-- my_scoring:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for the construction of Units!", PlayerName, points), points) +-- end -- -- ## 4. F10 Menu structure -- From 89308f7d06804f84492366467052e4ebe3822b81 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 6 Jul 2021 21:49:51 +0200 Subject: [PATCH 090/111] Update Airbase.lua - Fixed Andersen --- Moose Development/Moose/Wrapper/Airbase.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index e63218ac5..223d459b6 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -1420,7 +1420,7 @@ function AIRBASE:GetRunwayData(magvar, mark) name==AIRBASE.PersianGulf.Dubai_Intl or name==AIRBASE.PersianGulf.Shiraz_International_Airport or name==AIRBASE.PersianGulf.Kish_International_Airport or - name==AIRBASE.MarianaIslands.Andersen then + name==AIRBASE.MarianaIslands.Andersen_AFB then -- 1-->4, 2-->3, 3-->2, 4-->1 exception=1 From 97668e5413aaa6323938fa85ff823ee7179b1a86 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 6 Jul 2021 21:56:15 +0200 Subject: [PATCH 091/111] Update FlightGroup.lua - Fixed flight of airwing is going to tanker even if fuellowrefuel switch is false --- Moose Development/Moose/Ops/FlightGroup.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index a71619474..a9e42d16e 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2757,8 +2757,9 @@ function FLIGHTGROUP:onafterFuelLow(From, Event, To) -- Get closest tanker from airwing that can refuel this flight. local tanker=self.airwing:GetTankerForFlight(self) - if tanker then + if tanker and self.fuellowrefuel then + -- Debug message. self:I(self.lid..string.format("Send to refuel at tanker %s", tanker.flightgroup:GetName())) -- Get a coordinate towards the tanker. From 4a1df3d5cc4da70c57a4e15da0b97ed234a946d4 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 10 Jul 2021 16:56:03 +0200 Subject: [PATCH 092/111] -- -- (added 0.1.8) - allow to set far/near distance for approach and optionally pilot must open doors -- self.approachdist_far = 5000 -- switch do 10 sec interval approach mode, meters -- self.approachdist_near = 3000 -- switch to 5 sec interval approach mode, meters -- self.pilotmustopendoors = false -- switch to true to enable check of open doors --- Moose Development/Moose/Ops/CSAR.lua | 93 +++++++++++++++++++++++----- 1 file changed, 79 insertions(+), 14 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 8fd660487..0a3d14c64 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -87,17 +87,21 @@ -- self.useprefix = true -- Requires CSAR helicopter #GROUP names to have the prefix(es) defined below. -- self.csarPrefix = { "helicargo", "MEDEVAC"} -- #GROUP name prefixes used for useprefix=true - DO NOT use # in helicopter names in the Mission Editor! -- self.verbose = 0 -- set to > 1 for stats output for debugging. --- -- (added 0.1.4) limit amount of downed pilots spawned by ejection events --- self.limitmaxdownedpilots = true, --- self.maxdownedpilots = 10, +-- -- (added 0.1.4) limit amount of downed pilots spawned by **ejection** events +-- self.limitmaxdownedpilots = true +-- self.maxdownedpilots = 10 +-- -- (added 0.1.8) - allow to set far/near distance for approach and optionally pilot must open doors +-- self.approachdist_far = 5000 -- switch do 10 sec interval approach mode, meters +-- self.approachdist_near = 3000 -- switch to 5 sec interval approach mode, meters +-- self.pilotmustopendoors = false -- switch to true to enable check of open doors -- -- ## 2.1 Experimental Features -- --- "WARNING - Here\'ll be dragons! +-- WARNING - Here\'ll be dragons! -- DANGER - For this to work you need to de-sanitize your mission environment (all three entries) in \Scripts\MissionScripting.lua --- Needs SRS => 1.9.6 to work (works on the *server* side of SRS)" +-- Needs SRS => 1.9.6 to work (works on the **server** side of SRS) -- self.useSRS = false -- Set true to use FF\'s SRS integration --- self.SRSPath = "E:\\Program Files\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your SRS installation -- server(!) +-- self.SRSPath = "E:\\Progra~1\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your SRS installation -- server(!) -- self.SRSchannel = 300 -- radio channel -- self.SRSModulation = radio.modulation.AM -- modulation -- @@ -241,7 +245,7 @@ CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.7r2" +CSAR.version="0.1.8r1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -363,6 +367,10 @@ function CSAR:New(Coalition, Template, Alias) self.maxdownedpilots = 25 -- generate Frequencies self:_GenerateVHFrequencies() + -- added 0.1.8 + self.approachdist_far = 5000 -- switch do 10 sec interval approach mode, meters + self.approachdist_near = 3000 -- switch to 5 sec interval approach mode, meters + self.pilotmustopendoors = false -- switch to true to enable check on open doors -- WARNING - here\'ll be dragons -- for this to work you need to de-sanitize your mission environment in \Scripts\MissionScripting.lua @@ -983,13 +991,13 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) local _heliCoord = _heliUnit:GetCoordinate() local _leaderCoord = _woundedGroup:GetCoordinate() local _distance = self:_GetDistance(_heliCoord,_leaderCoord) - if _distance < 3000 and _distance > 0 then + if _distance < self.approachdist_near and _distance > 0 then if self:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName) == true then -- we\'re close, reschedule _downedpilot.timestamp = timer.getAbsTime() self:__Approach(-5,heliname,woundedgroupname) end - elseif _distance >= 3000 and _distance < 5000 then + elseif _distance >= self.approachdist_near and _distance < self.approachdist_far then self.heliVisibleMessage[_lookupKeyHeli] = nil --reschedule as units aren\'t dead yet , schedule for a bit slower though as we\'re far away _downedpilot.timestamp = timer.getAbsTime() @@ -1085,6 +1093,48 @@ 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. +-- @return #boolean outcome The outcome. +function CSAR:_IsLoadingDoorOpen( unit_name ) + self:T(self.lid .. " _IsLoadingDoorOpen") + local ret_val = false + local unit = Unit.getByName(unit_name) + if unit ~= nil then + local type_name = unit:getTypeName() + + if type_name == "Mi-8MT" and unit:getDrawArgumentValue(86) == 1 or unit:getDrawArgumentValue(250) == 1 then + self:I(unit_name .. " Cargo doors are open or cargo door not present") + ret_val = true + end + + if type_name == "Mi-24P" and unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(86) == 1 then + self:I(unit_name .. " a side door is open") + ret_val = true + end + + if type_name == "UH-1H" and unit:getDrawArgumentValue(43) == 1 or unit:getDrawArgumentValue(44) == 1 then + self:I(unit_name .. " a side door is open ") + ret_val = true + end + + if string.find(type_name, "SA342" ) and unit:getDrawArgumentValue(34) == 1 or unit:getDrawArgumentValue(38) == 1 then + self:I(unit_name .. " front door(s) are open") + ret_val = true + end + + if ret_val == false then + self:I(unit_name .. " all doors are closed") + end + return ret_val + + end -- nil + + return false +end + --- (Internal) Function to check if heli is close to group. -- @param #CSAR self -- @param #number _distance @@ -1148,15 +1198,25 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG self.landedStatus[_lookupKeyHeli] = _time end if _time <= 0 or _distance < self.loadDistance then - self.landedStatus[_lookupKeyHeli] = nil - self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) - return false + if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then + self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in, bugger!", self.messageTime, true) + return true + else + self.landedStatus[_lookupKeyHeli] = nil + self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) + return false + end end end else if (_distance < self.loadDistance) then - self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) - return false + if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then + self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in, honk!", self.messageTime, true) + return true + else + self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) + return false + end end end else @@ -1191,9 +1251,14 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG if _time > 0 then self:_DisplayMessageToSAR(_heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you\'re too far away!", self.messageTime, true) else + if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then + self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in, noob!", self.messageTime, true) + return true + else self.hoverStatus[_lookupKeyHeli] = nil self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) return false + end end _reset = false else From a69865b8c95a8f342260f67adc26990cfa51cc38 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 10 Jul 2021 16:56:31 +0200 Subject: [PATCH 093/111] -- -- (added 0.1.8) - allow to set far/near distance for approach and optionally pilot must open doors -- self.approachdist_far = 5000 -- switch do 10 sec interval approach mode, meters -- self.approachdist_near = 3000 -- switch to 5 sec interval approach mode, meters -- self.pilotmustopendoors = false -- switch to true to enable check of open doors --- Moose Development/Moose/Ops/CTLD.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 8c5f7f076..4c9adf68d 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -456,6 +456,8 @@ CTLD = { -- DONE: Zone Radio Beacons -- DONE: Stats Running -- DONE: Added support for Hercules +-- TODO: Possibly - either/or loading crates and troops +-- TODO: Limit of troops, crates buildable? ------------------------------ --- Radio Beacons From 3d6b053eb4ad68af438299bbe4d86e40ae191bbd Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 11 Jul 2021 18:31:09 +0200 Subject: [PATCH 094/111] Added function to check if a loading door on a heli is open --- Moose Development/Moose/Utilities/Utils.lua | 40 +++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 29ac7f8c2..ad8bb6291 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1567,3 +1567,43 @@ function UTILS.ShuffleTable(t) end return TempTable end + +--- (Helicopter) Check if one loading door is open. +--@param #string unit_name Unit name to be checked +--@return #boolean Outcome - true if a (loading door) is open, false if not, nil if none exists. +function UTILS.IsLoadingDoorOpen( unit_name ) + + local ret_val = false + local unit = Unit.getByName(unit_name) + if unit ~= nil then + local type_name = unit:getTypeName() + + if type_name == "Mi-8MT" and unit:getDrawArgumentValue(86) == 1 or unit:getDrawArgumentValue(250) == 1 then + BASE:T(unit_name .. " Cargo doors are open or cargo door not present") + ret_val = true + end + + if type_name == "Mi-24P" and unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(86) == 1 then + BASE:T(unit_name .. " a side door is open") + ret_val = true + end + + if type_name == "UH-1H" and unit:getDrawArgumentValue(43) == 1 or unit:getDrawArgumentValue(44) == 1 then + BASE:T(unit_name .. " a side door is open ") + ret_val = true + end + + if string.find(type_name, "SA342" ) and unit:getDrawArgumentValue(34) == 1 or unit:getDrawArgumentValue(38) == 1 then + BASE:T(unit_name .. " front door(s) are open") + ret_val = true + end + + if ret_val == false then + BASE:T(unit_name .. " all doors are closed") + end + return ret_val + + end -- nil + + return nil +end \ No newline at end of file From 52e2ac7174f11310ac74f193cf179bb7a48200f9 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 11 Jul 2021 18:31:17 +0200 Subject: [PATCH 095/111] Added documentation --- Moose Development/Moose/Ops/CSAR.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 0a3d14c64..d243475b0 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -1377,7 +1377,7 @@ end --- (Internal) Check and return Wrappe.Unit#UNIT based on the name if alive. -- @param #CSAR self -- @param #string _unitname Name of Unit --- @return #UNIT or nil +-- @return Wrapper.Unit#UNIT The unit or nil function CSAR:_GetSARHeli(_unitName) self:T(self.lid .. " _GetSARHeli") local unit = UNIT:FindByName(_unitName) From c0f4eef896ecd28d95c0cb5577de0bc21955375d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 12 Jul 2021 18:10:12 +0200 Subject: [PATCH 096/111] Slightly changed versions of Mantis, Sead and Shorad as the Emissions On/Off stuff ED introduced doesn't really work well. --- Moose Development/Moose/Functional/Mantis.lua | 6 +- Moose Development/Moose/Functional/Sead.lua | 278 ++++++++---------- Moose Development/Moose/Functional/Shorad.lua | 247 +++++++++------- 3 files changed, 254 insertions(+), 277 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 23faabb3f..bb6fbe45e 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -191,7 +191,7 @@ MANTIS = { ShoradLink = false, ShoradTime = 600, ShoradActDistance = 15000, - UseEmOnOff = true, + UseEmOnOff = false, } ----------------------------------------------------------------------- @@ -208,7 +208,7 @@ do --@param #string coaltion Coalition side of your setup, e.g. "blue", "red" or "neutral" --@param #boolean dynamic Use constant (true) filtering or just filter once (false, default) (optional) --@param #string awacs Group name of your Awacs (optional) - --@param #boolean EmOnOff Make MANTIS switch Emissions on and off instead of changing the alarm state between RED and GREEN (optional, deault true) + --@param #boolean EmOnOff Make MANTIS switch Emissions on and off instead of changing the alarm state between RED and GREEN --@return #MANTIS self --@usage Start up your MANTIS with a basic setting -- @@ -267,6 +267,8 @@ do if EmOnOff then if EmOnOff == false then self.UseEmOnOff = false + else + self.UseEmOnOff = true end end diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index 8eb98d4f4..ec568b627 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -1,26 +1,26 @@ --- **Functional** -- Make SAM sites execute evasive and defensive behaviour when being fired upon. --- +-- -- === --- +-- -- ## Features: --- +-- -- * When SAM sites are being fired upon, the SAMs will take evasive action will reposition themselves when possible. -- * When SAM sites are being fired upon, the SAMs will take defensive action by shutting down their radars. --- +-- -- === --- +-- -- ## Missions: --- +-- -- [SEV - SEAD Evasion](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SEV%20-%20SEAD%20Evasion) --- +-- -- === --- +-- -- ### Authors: **FlightControl**, **applevangelist** --- --- Last Update: April 2021 --- +-- +-- Last Update: Feb 2021 +-- -- === --- +-- -- @module Functional.Sead -- @image SEAD.JPG @@ -28,49 +28,32 @@ -- @extends Core.Base#BASE --- Make SAM sites execute evasive and defensive behaviour when being fired upon. --- +-- -- This class is very easy to use. Just setup a SEAD object by using @{#SEAD.New}() and SAMs will evade and take defensive action when being fired upon. --- +-- -- # Constructor: --- +-- -- Use the @{#SEAD.New}() constructor to create a new SEAD object. --- +-- -- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } ) --- +-- -- @field #SEAD SEAD = { - ClassName = "SEAD", - TargetSkill = { - Average = { Evade = 30, DelayOn = { 40, 60 } } , - Good = { Evade = 20, DelayOn = { 30, 50 } } , - High = { Evade = 15, DelayOn = { 20, 40 } } , - Excellent = { Evade = 10, DelayOn = { 10, 30 } } - }, - SEADGroupPrefixes = {}, - SuppressedGroups = {}, - EngagementRange = 75 -- default 75% engagement range Feature Request #1355 + ClassName = "SEAD", + TargetSkill = { + Average = { Evade = 30, DelayOn = { 40, 60 } } , + Good = { Evade = 20, DelayOn = { 30, 50 } } , + High = { Evade = 15, DelayOn = { 20, 40 } } , + Excellent = { Evade = 10, DelayOn = { 10, 30 } } + }, + SEADGroupPrefixes = {}, + SuppressedGroups = {}, + EngagementRange = 75 -- default 75% engagement range Feature Request #1355 } - -- TODO Complete list? --- Missile enumerators -- @field Harms SEAD.Harms = { - --[[ - ["X58"] = "weapons.missiles.X_58", --Kh-58X anti-radiation missiles fired - ["Kh25"] = "weapons.missiles.Kh25MP_PRGS1VP", --Kh-25MP anti-radiation missiles fired - ["X25"] = "weapons.missiles.X_25MP", --Kh-25MPU anti-radiation missiles fired - ["X28"] = "weapons.missiles.X_28", --Kh-28 anti-radiation missiles fired - ["X31"] = "weapons.missiles.X_31P", --Kh-31P anti-radiation missiles fired - ["AGM45A"] = "weapons.missiles.AGM_45A", --AGM-45A anti-radiation missiles fired - ["AGM45"] = "weapons.missiles.AGM_45", --AGM-45B anti-radiation missiles fired - ["AGM88"] = "weapons.missiles.AGM_88", --AGM-88C anti-radiation missiles fired - ["AGM122"] = "weapons.missiles.AGM_122", --AGM-122 Sidearm anti-radiation missiles fired - ["LD10"] = "weapons.missiles.LD-10", --LD-10 anti-radiation missiles fired - ["ALARM"] = "weapons.missiles.ALARM", --ALARM anti-radiation missiles fired - ["AGM84E"] = "weapons.missiles.AGM_84E", --AGM84 anti-radiation missiles fired - ["AGM84A"] = "weapons.missiles.AGM_84A", --AGM84 anti-radiation missiles fired - ["AGM84H"] = "weapons.missiles.AGM_84H", --AGM84 anti-radiation missiles fired - --]] ["AGM_88"] = "AGM_88", ["AGM_45"] = "AGM_45", ["AGM_122"] = "AGM_122", @@ -84,7 +67,7 @@ SEAD = { ["X_31"] = "X_31", ["Kh25"] = "Kh25", } - + --- Creates the main object which is handling defensive actions for SA sites or moving SA vehicles. -- When an anti radiation missile is fired (KH-58, KH-31P, KH-31A, KH-25MPU, HARM missiles), the SA will shut down their radars and will take evasive actions... -- Chances are big that the missile will miss. @@ -97,20 +80,20 @@ SEAD = { -- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } ) function SEAD:New( SEADGroupPrefixes ) - local self = BASE:Inherit( self, BASE:New() ) - self:F( SEADGroupPrefixes ) - - if type( SEADGroupPrefixes ) == 'table' then - for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do - self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix - end - else - self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes - end - - self:HandleEvent( EVENTS.Shot ) - self:I("*** SEAD - Started Version 0.2.7") - return self + local self = BASE:Inherit( self, BASE:New() ) + self:F( SEADGroupPrefixes ) + + if type( SEADGroupPrefixes ) == 'table' then + for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do + self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix + end + else + self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes + end + + self:HandleEvent( EVENTS.Shot, self.HandleEventShot ) + self:I("*** SEAD - Started Version 0.2.8") + return self end --- Update the active SEAD Set @@ -120,7 +103,7 @@ end function SEAD:UpdateSet( SEADGroupPrefixes ) self:F( SEADGroupPrefixes ) - + if type( SEADGroupPrefixes ) == 'table' then for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix @@ -164,112 +147,83 @@ end -- @see SEAD -- @param #SEAD -- @param Core.Event#EVENTDATA EventData -function SEAD:OnEventShot( EventData ) - self:T( { EventData } ) +function SEAD:HandleEventShot( EventData ) + self:T( { EventData } ) - local SEADUnit = EventData.IniDCSUnit - local SEADUnitName = EventData.IniDCSUnitName - local SEADWeapon = EventData.Weapon -- Identify the weapon fired - local SEADWeaponName = EventData.WeaponName -- return weapon type + local SEADUnit = EventData.IniDCSUnit + local SEADUnitName = EventData.IniDCSUnitName + local SEADWeapon = EventData.Weapon -- Identify the weapon fired + local SEADWeaponName = EventData.WeaponName -- return weapon type - self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName) - self:T({ SEADWeapon }) - - --[[check for SEAD missiles - if SEADWeaponName == "weapons.missiles.X_58" --Kh-58U anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.Kh25MP_PRGS1VP" --Kh-25MP anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.X_25MP" --Kh-25MPU anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.X_28" --Kh-28 anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.X_31P" --Kh-31P anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.AGM_45A" --AGM-45A anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.AGM_45" --AGM-45B anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.AGM_88" --AGM-88C anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.AGM_122" --AGM-122 Sidearm anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.LD-10" --LD-10 anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.ALARM" --ALARM anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.AGM_84E" --AGM84 anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.AGM_84A" --AGM84 anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.AGM_84H" --AGM84 anti-radiation missiles fired - --]] + self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName) + self:T({ SEADWeapon }) + if self:_CheckHarms(SEADWeaponName) then local _targetskill = "Random" local _targetMimgroupName = "none" - local _evade = math.random (1,100) -- random number for chance of evading action - local _targetMim = EventData.Weapon:getTarget() -- Identify target - local _targetUnit = UNIT:Find(_targetMim) -- Unit name by DCS Object - if _targetUnit and _targetUnit:IsAlive() then - local _targetMimgroup = _targetUnit:GetGroup() - local _targetMimgroupName = _targetMimgroup:GetName() -- group name - --local _targetskill = _DATABASE.Templates.Units[_targetUnit].Template.skill - self:T( self.SEADGroupPrefixes ) - self:T( _targetMimgroupName ) - end - -- see if we are shot at - local SEADGroupFound = false - for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do - if string.find( _targetMimgroupName, SEADGroupPrefix, 1, true ) then - SEADGroupFound = true - self:T( '*** SEAD - Group Found' ) - break - end - end - if SEADGroupFound == true then -- yes we are being attacked - if _targetskill == "Random" then -- when skill is random, choose a skill - local Skills = { "Average", "Good", "High", "Excellent" } - _targetskill = Skills[ math.random(1,4) ] - end - self:T( _targetskill ) - if self.TargetSkill[_targetskill] then - if (_evade > self.TargetSkill[_targetskill].Evade) then + local _evade = math.random (1,100) -- random number for chance of evading action + local _targetMim = EventData.Weapon:getTarget() -- Identify target + local _targetUnit = UNIT:Find(_targetMim) -- Unit name by DCS Object + if _targetUnit and _targetUnit:IsAlive() then + local _targetMimgroup = _targetUnit:GetGroup() + local _targetMimgroupName = _targetMimgroup:GetName() -- group name + --local _targetskill = _DATABASE.Templates.Units[_targetUnit].Template.skill + self:T( self.SEADGroupPrefixes ) + self:T( _targetMimgroupName ) + end + -- see if we are shot at + local SEADGroupFound = false + for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do + if string.find( _targetMimgroupName, SEADGroupPrefix, 1, true ) then + SEADGroupFound = true + self:T( '*** SEAD - Group Found' ) + break + end + end + if SEADGroupFound == true then -- yes we are being attacked + if _targetskill == "Random" then -- when skill is random, choose a skill + local Skills = { "Average", "Good", "High", "Excellent" } + _targetskill = Skills[ math.random(1,4) ] + end + self:T( _targetskill ) + if self.TargetSkill[_targetskill] then + if (_evade > self.TargetSkill[_targetskill].Evade) then + + self:T( string.format("*** SEAD - Evading, target skill " ..string.format(_targetskill)) ) + + local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) + local _targetMimcont= _targetMimgroup:getController() + + routines.groupRandomDistSelf(_targetMimgroup,300,'Diamond',250,20) -- move randomly + + --tracker ID table to switch groups off and on again + local id = { + groupName = _targetMimgroup, + ctrl = _targetMimcont + } - self:T( string.format("*** SEAD - Evading, target skill " ..string.format(_targetskill)) ) - - local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) - local _targetMimcont= _targetMimgroup:getController() - - routines.groupRandomDistSelf(_targetMimgroup,300,'Diamond',250,20) -- move randomly - - --tracker ID table to switch groups off and on again - local id = { - groupName = _targetMimgroup, - ctrl = _targetMimcont - } - - local function SuppressionEnd(id) --switch group back on - local range = self.EngagementRange -- Feature Request #1355 - self:T(string.format("*** SEAD - Engagement Range is %d", range)) - id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED) - --id.groupName:enableEmission(true) - id.ctrl:setOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,range) --Feature Request #1355 - self.SuppressedGroups[id.groupName] = nil --delete group id from table when done - end - -- randomize switch-on time - local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2]) - local SuppressionEndTime = timer.getTime() + delay - --create entry - if self.SuppressedGroups[id.groupName] == nil then --no timer entry for this group yet - self.SuppressedGroups[id.groupName] = { - SuppressionEndTime = delay - } - Controller.setOption(_targetMimcont, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN) - --_targetMimgroup:enableEmission(false) - timer.scheduleFunction(SuppressionEnd, id, SuppressionEndTime) --Schedule the SuppressionEnd() function - end - end - end - end - end + local function SuppressionEnd(id) --switch group back on + local range = self.EngagementRange -- Feature Request #1355 + self:T(string.format("*** SEAD - Engagement Range is %d", range)) + id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED) + --id.groupName:enableEmission(true) + id.ctrl:setOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,range) --Feature Request #1355 + self.SuppressedGroups[id.groupName] = nil --delete group id from table when done + end + -- randomize switch-on time + local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2]) + local SuppressionEndTime = timer.getTime() + delay + --create entry + if self.SuppressedGroups[id.groupName] == nil then --no timer entry for this group yet + self.SuppressedGroups[id.groupName] = { + SuppressionEndTime = delay + } + Controller.setOption(_targetMimcont, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN) + --_targetMimgroup:enableEmission(false) + timer.scheduleFunction(SuppressionEnd, id, SuppressionEndTime) --Schedule the SuppressionEnd() function + end + end + end + end + end end diff --git a/Moose Development/Moose/Functional/Shorad.lua b/Moose Development/Moose/Functional/Shorad.lua index 1dbff94f4..36ca8ecdf 100644 --- a/Moose Development/Moose/Functional/Shorad.lua +++ b/Moose Development/Moose/Functional/Shorad.lua @@ -1,24 +1,24 @@ --- **Functional** -- Short Range Air Defense System --- +-- -- === --- +-- -- **SHORAD** - Short Range Air Defense System -- Controls a network of short range air/missile defense groups. --- +-- -- === --- +-- -- ## Missions: -- -- ### [SHORAD - Short Range Air Defense](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SRD%20-%20SHORAD%20Defense) --- +-- -- === --- --- ### Author : **applevangelist** --- +-- +-- ### Author : **applevangelist ** +-- -- @module Functional.Shorad -- @image Functional.Shorad.jpg -- --- Date: May 2021 +-- Date: July 2021 ------------------------------------------------------------------------- --- **SHORAD** class, extends Core.Base#BASE @@ -26,7 +26,7 @@ -- @field #string ClassName -- @field #string name Name of this Shorad -- @field #boolean debug Set the debug state --- @field #string Prefixes String to be used to build the @{#Core.Set#SET_GROUP} +-- @field #string Prefixes String to be used to build the @{#Core.Set#SET_GROUP} -- @field #number Radius Shorad defense radius in meters -- @field Core.Set#SET_GROUP Groupset The set of Shorad groups -- @field Core.Set#SET_GROUP Samset The set of SAM groups to defend @@ -41,10 +41,10 @@ -- @field #boolean UseEmOnOff Decide if we are using Emission on/off (default) or AlarmState red/green. -- @extends Core.Base#BASE ---- *Good friends are worth defending.* Mr Tushman, Wonder (the Movie) --- +--- *Good friends are worth defending.* Mr Tushman, Wonder (the Movie) +-- -- Simple Class for a more intelligent Short Range Air Defense System --- +-- -- #SHORAD -- Moose derived missile intercepting short range defense system. -- Protects a network of SAM sites. Uses events to switch on the defense groups closest to the enemy. @@ -52,26 +52,26 @@ -- -- ## Usage -- --- Set up a #SET_GROUP for the SAM sites to be protected: --- --- `local SamSet = SET_GROUP:New():FilterPrefixes("Red SAM"):FilterCoalitions("red"):FilterStart()` --- +-- Set up a #SET_GROUP for the SAM sites to be protected: +-- +-- `local SamSet = SET_GROUP:New():FilterPrefixes("Red SAM"):FilterCoalitions("red"):FilterStart()` +-- -- By default, SHORAD will defense against both HARMs and AG-Missiles with short to medium range. The default defense probability is 70-90%. --- When a missile is detected, SHORAD will activate defense groups in the given radius around the target for 10 minutes. It will *not* react to friendly fire. --- +-- When a missile is detected, SHORAD will activate defense groups in the given radius around the target for 10 minutes. It will *not* react to friendly fire. +-- -- ### Start a new SHORAD system, parameters are: +-- +-- * Name: Name of this SHORAD. +-- * ShoradPrefix: Filter for the Shorad #SET_GROUP. +-- * Samset: The #SET_GROUP of SAM sites to defend. +-- * Radius: Defense radius in meters. +-- * ActiveTimer: Determines how many seconds the systems stay on red alert after wake-up call. +-- * Coalition: Coalition, i.e. "blue", "red", or "neutral".* +-- +-- `myshorad = SHORAD:New("RedShorad", "Red SHORAD", SamSet, 25000, 600, "red")` -- --- * Name: Name of this SHORAD. --- * ShoradPrefix: Filter for the Shorad #SET_GROUP. --- * Samset: The #SET_GROUP of SAM sites to defend. --- * Radius: Defense radius in meters. --- * ActiveTimer: Determines how many seconds the systems stay on red alert after wake-up call. --- * Coalition: Coalition, i.e. "blue", "red", or "neutral".* --- --- `myshorad = SHORAD:New("RedShorad", "Red SHORAD", SamSet, 25000, 600, "red")` --- --- ## Customize options --- +-- ## Customize options +-- -- * SHORAD:SwitchDebug(debug) -- * SHORAD:SwitchHARMDefense(onoff) -- * SHORAD:SwitchAGMDefense(onoff) @@ -94,9 +94,9 @@ SHORAD = { lid = "", DefendHarms = true, DefendMavs = true, - DefenseLowProb = 75, + DefenseLowProb = 70, DefenseHighProb = 90, - UseEmOnOff = false, + UseEmOnOff = false, } ----------------------------------------------------------------------- @@ -108,22 +108,6 @@ do --- Missile enumerators -- @field Harms SHORAD.Harms = { - --[[ - ["X58"] = "weapons.missiles.X_58", --Kh-58X anti-radiation missiles fired - ["Kh25"] = "weapons.missiles.Kh25MP_PRGS1VP", --Kh-25MP anti-radiation missiles fired - ["X25"] = "weapons.missiles.X_25MP", --Kh-25MPU anti-radiation missiles fired - ["X28"] = "weapons.missiles.X_28", --Kh-28 anti-radiation missiles fired - ["X31"] = "weapons.missiles.X_31P", --Kh-31P anti-radiation missiles fired - ["AGM45A"] = "weapons.missiles.AGM_45A", --AGM-45A anti-radiation missiles fired - ["AGM45"] = "weapons.missiles.AGM_45", --AGM-45B anti-radiation missiles fired - ["AGM88"] = "weapons.missiles.AGM_88", --AGM-88C anti-radiation missiles fired - ["AGM122"] = "weapons.missiles.AGM_122", --AGM-122 Sidearm anti-radiation missiles fired - ["LD10"] = "weapons.missiles.LD-10", --LD-10 anti-radiation missiles fired - ["ALARM"] = "weapons.missiles.ALARM", --ALARM anti-radiation missiles fired - ["AGM84E"] = "weapons.missiles.AGM_84E", --AGM84 anti-radiation missiles fired - ["AGM84A"] = "weapons.missiles.AGM_84A", --AGM84 anti-radiation missiles fired - ["AGM84H"] = "weapons.missiles.AGM_84H", --AGM84 anti-radiation missiles fired - --]] ["AGM_88"] = "AGM_88", ["AGM_45"] = "AGM_45", ["AGM_122"] = "AGM_122", @@ -137,7 +121,7 @@ do ["X_31"] = "X_31", ["Kh25"] = "Kh25", } - + --- TODO complete list? -- @field Mavs SHORAD.Mavs = { @@ -148,7 +132,7 @@ do ["Kh31"] = "Kh31", ["Kh66"] = "Kh66", } - + --- Instantiates a new SHORAD object -- @param #SHORAD self -- @param #string Name Name of this SHORAD @@ -157,10 +141,12 @@ do -- @param #number Radius Defense radius in meters, used to switch on groups -- @param #number ActiveTimer Determines how many seconds the systems stay on red alert after wake-up call -- @param #string Coalition Coalition, i.e. "blue", "red", or "neutral" - function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition) + -- @param #boolean UseEmOnOff Use Emissions On/Off rather than Alarm State Red/Green (default: use Emissions switch) + -- @retunr #SHORAD self + function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition, UseEmOnOff) local self = BASE:Inherit( self, BASE:New() ) - self:T({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition}) - + self:I({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition}) + local GroupSet = SET_GROUP:New():FilterPrefixes(ShoradPrefix):FilterCoalitions(Coalition):FilterCategoryGround():FilterStart() self.name = Name or "MyShorad" @@ -171,81 +157,101 @@ do self.ActiveTimer = ActiveTimer or 600 self.ActiveGroups = {} self.Groupset = GroupSet - self:HandleEvent( EVENTS.Shot ) self.DefendHarms = true self.DefendMavs = true self.DefenseLowProb = 70 -- probability to detect a missile shot, low margin self.DefenseHighProb = 90 -- probability to detect a missile shot, high margin - self.UseEmOnOff = true -- Decide if we are using Emission on/off (default) or AlarmState red/green - self:I("*** SHORAD - Started Version 0.2.5") + self.UseEmOnOff = UseEmOnOff or false -- Decide if we are using Emission on/off (default) or AlarmState red/green + self:I("*** SHORAD - Started Version 0.2.8") -- Set the string id for output to DCS.log file. self.lid=string.format("SHORAD %s | ", self.name) self:_InitState() + self:HandleEvent(EVENTS.Shot, self.HandleEventShot) return self end - + --- Initially set all groups to alarm state GREEN -- @param #SHORAD self function SHORAD:_InitState() + self:I(self.lid .. " _InitState") local table = {} local set = self.Groupset - self:T({set = set}) + self:I({set = set}) local aliveset = set:GetAliveSet() --#table for _,_group in pairs (aliveset) do if self.UseEmOnOff then --_group:SetAIOff() _group:EnableEmission(false) + _group:OptionAlarmStateRed() --Wrapper.Group#GROUP else _group:OptionAlarmStateGreen() --Wrapper.Group#GROUP end + _group:OptionDisperseOnAttack(30) end -- gather entropy - for i=1,10 do + for i=1,100 do math.random() end + return self end - - --- Switch debug state + + --- Switch debug state on -- @param #SHORAD self -- @param #boolean debug Switch debug on (true) or off (false) - function SHORAD:SwitchDebug(debug) - self:T( { debug } ) - local onoff = debug or false - if debug then - self.debug = true - --tracing - BASE:TraceOn() - BASE:TraceClass("SHORAD") + function SHORAD:SwitchDebug(onoff) + self:I( { onoff } ) + if onoff then + self:SwitchDebugOn() else - self.debug = false - BASE:TraceOff() + self.SwitchDebugOff() end + return self end - + + --- Switch debug state on + -- @param #SHORAD self + function SHORAD:SwitchDebugOn() + self.debug = true + --tracing + BASE:TraceOn() + BASE:TraceClass("SHORAD") + return self + end + + --- Switch debug state off + -- @param #SHORAD self + function SHORAD:SwitchDebugOff() + self.debug = false + BASE:TraceOff() + return self + end + --- Switch defense for HARMs -- @param #SHORAD self -- @param #boolean onoff function SHORAD:SwitchHARMDefense(onoff) - self:T( { onoff } ) + self:I( { onoff } ) local onoff = onoff or true self.DefendHarms = onoff + return self end - + --- Switch defense for AGMs -- @param #SHORAD self -- @param #boolean onoff function SHORAD:SwitchAGMDefense(onoff) - self:T( { onoff } ) + self:I( { onoff } ) local onoff = onoff or true self.DefendMavs = onoff + return self end - + --- Set defense probability limits -- @param #SHORAD self -- @param #number low Minimum detection limit, integer 1-100 -- @param #number high Maximum detection limit integer 1-100 function SHORAD:SetDefenseLimits(low,high) - self:T( { low, high } ) + self:I( { low, high } ) local low = low or 70 local high = high or 90 if (low < 0) or (low > 100) or (low > high) then @@ -256,43 +262,51 @@ do end self.DefenseLowProb = low self.DefenseHighProb = high + return self end - + --- Set the number of seconds a SHORAD site will stay active -- @param #SHORAD self -- @param #number seconds Number of seconds systems stay active function SHORAD:SetActiveTimer(seconds) + self:I(self.lid .. " SetActiveTimer") local timer = seconds or 600 if timer < 0 then timer = 600 end self.ActiveTimer = timer + return self end --- Set the number of meters for the SHORAD defense zone -- @param #SHORAD self - -- @param #number meters Radius of the defense search zone in meters. #SHORADs in this range around a targeted group will go active + -- @param #number meters Radius of the defense search zone in meters. #SHORADs in this range around a targeted group will go active function SHORAD:SetDefenseRadius(meters) + self:I(self.lid .. " SetDefenseRadius") local radius = meters or 20000 if radius < 0 then radius = 20000 end self.Radius = radius + return self end - + --- Set using Emission on/off instead of changing alarm state -- @param #SHORAD self -- @param #boolean switch Decide if we are changing alarm state or AI state function SHORAD:SetUsingEmOnOff(switch) + self:I(self.lid .. " SetUsingEmOnOff") self.UseEmOnOff = switch or false + return self end - + --- Check if a HARM was fired -- @param #SHORAD self -- @param #string WeaponName -- @return #boolean Returns true for a match function SHORAD:_CheckHarms(WeaponName) - self:T( { WeaponName } ) + self:I(self.lid .. " _CheckHarms") + self:I( { WeaponName } ) local hit = false if self.DefendHarms then for _,_name in pairs (SHORAD.Harms) do @@ -301,13 +315,14 @@ do end return hit end - + --- Check if an AGM was fired -- @param #SHORAD self -- @param #string WeaponName -- @return #boolean Returns true for a match function SHORAD:_CheckMavs(WeaponName) - self:T( { WeaponName } ) + self:I(self.lid .. " _CheckMavs") + self:I( { WeaponName } ) local hit = false if self.DefendMavs then for _,_name in pairs (SHORAD.Mavs) do @@ -316,34 +331,36 @@ do end return hit end - + --- Check the coalition of the attacker -- @param #SHORAD self -- @param #string Coalition name -- @return #boolean Returns false for a match function SHORAD:_CheckCoalition(Coalition) + self:I(self.lid .. " _CheckCoalition") local owncoalition = self.Coalition local othercoalition = "" - if Coalition == 0 then + if Coalition == 0 then othercoalition = "neutral" elseif Coalition == 1 then othercoalition = "red" else othercoalition = "blue" end - self:T({owncoalition = owncoalition, othercoalition = othercoalition}) + self:I({owncoalition = owncoalition, othercoalition = othercoalition}) if owncoalition ~= othercoalition then return true else return false end end - + --- Check if the missile is aimed at a SHORAD -- @param #SHORAD self -- @param #string TargetGroupName Name of the target group -- @return #boolean Returns true for a match, else false function SHORAD:_CheckShotAtShorad(TargetGroupName) + self:I(self.lid .. " _CheckShotAtShorad") local tgtgrp = TargetGroupName local shorad = self.Groupset local shoradset = shorad:GetAliveSet() --#table @@ -352,17 +369,18 @@ do local groupname = _groups:GetName() if string.find(groupname, tgtgrp, 1) then returnname = true - _groups:RelocateGroundRandomInRadius(7,100,false,false) -- be a bit evasive + --_groups:RelocateGroundRandomInRadius(7,100,false,false) -- be a bit evasive end end - return returnname + return returnname end - + --- Check if the missile is aimed at a SAM site -- @param #SHORAD self -- @param #string TargetGroupName Name of the target group -- @return #boolean Returns true for a match, else false function SHORAD:_CheckShotAtSams(TargetGroupName) + self:I(self.lid .. " _CheckShotAtSams") local tgtgrp = TargetGroupName local shorad = self.Samset --local shoradset = shorad:GetAliveSet() --#table @@ -376,11 +394,12 @@ do end return returnname end - + --- Calculate if the missile shot is detected -- @param #SHORAD self -- @return #boolean Returns true for a detection, else false function SHORAD:_ShotIsDetected() + self:I(self.lid .. " _ShotIsDetected") local IsDetected = false local DetectionProb = math.random(self.DefenseLowProb, self.DefenseHighProb) -- reference value local ActualDetection = math.random(1,100) -- value for this shot @@ -389,15 +408,15 @@ do end return IsDetected end - + --- Wake up #SHORADs in a zone with diameter Radius for ActiveTimer seconds -- @param #SHORAD self -- @param #string TargetGroup Name of the target group used to build the #ZONE -- @param #number Radius Radius of the #ZONE -- @param #number ActiveTimer Number of seconds to stay active -- @param #number TargetCat (optional) Category, i.e. Object.Category.UNIT or Object.Category.STATIC - -- @usage Use this function to integrate with other systems, example - -- + -- @usage Use this function to integrate with other systems, example + -- -- local SamSet = SET_GROUP:New():FilterPrefixes("Blue SAM"):FilterCoalitions("blue"):FilterStart() -- myshorad = SHORAD:New("BlueShorad", "Blue SHORAD", SamSet, 22000, 600, "blue") -- myshorad:SwitchDebug(true) @@ -405,7 +424,8 @@ do -- mymantis:AddShorad(myshorad,720) -- mymantis:Start() function SHORAD:WakeUpShorad(TargetGroup, Radius, ActiveTimer, TargetCat) - self:T({TargetGroup, Radius, ActiveTimer, TargetCat}) + self:I(self.lid .. " WakeUpShorad") + self:I({TargetGroup, Radius, ActiveTimer, TargetCat}) local targetcat = TargetCat or Object.Category.UNIT local targetgroup = TargetGroup local targetvec2 = nil @@ -432,17 +452,17 @@ do group:OptionAlarmStateGreen() end local text = string.format("Sleeping SHORAD %s", group:GetName()) - self:T(text) + self:I(text) local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) end -- go through set and find the one(s) to activate for _,_group in pairs (shoradset) do if _group:IsAnyInZone(targetzone) then local text = string.format("Waking up SHORAD %s", _group:GetName()) - self:T(text) + self:I(text) local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) if self.UseEmOnOff then - _group:SetAIOn() + --_group:SetAIOn() _group:EnableEmission(true) end _group:OptionAlarmStateRed() @@ -454,14 +474,15 @@ do end end end + return self end - + --- Main function - work on the EventData -- @param #SHORAD self -- @param Core.Event#EVENTDATA EventData The event details table data set - function SHORAD:OnEventShot( EventData ) - self:T( { EventData } ) - + function SHORAD:HandleEventShot( EventData ) + self:I( { EventData } ) + self:I(self.lid .. " HandleEventShot") --local ShootingUnit = EventData.IniDCSUnit --local ShootingUnitName = EventData.IniDCSUnitName local ShootingWeapon = EventData.Weapon -- Identify the weapon fired @@ -473,24 +494,24 @@ do local IsDetected = self:_ShotIsDetected() -- convert to text local DetectedText = "false" - if IsDetected then + if IsDetected then DetectedText = "true" end local text = string.format("%s Missile Launched = %s | Detected probability state is %s", self.lid, ShootingWeaponName, DetectedText) - self:T( text ) + self:I( text ) local m = MESSAGE:New(text,10,"Info"):ToAllIf(self.debug) -- if (self:_CheckHarms(ShootingWeaponName) or self:_CheckMavs(ShootingWeaponName)) and IsDetected then -- get target data local targetdata = EventData.Weapon:getTarget() -- Identify target local targetcat = targetdata:getCategory() -- Identify category - self:T(string.format("Target Category (3=STATIC, 1=UNIT)= %s",tostring(targetcat))) + self:I(string.format("Target Category (3=STATIC, 1=UNIT)= %s",tostring(targetcat))) local targetunit = nil if targetcat == Object.Category.UNIT then -- UNIT targetunit = UNIT:Find(targetdata) elseif targetcat == Object.Category.STATIC then -- STATIC targetunit = STATIC:Find(targetdata) - end + end --local targetunitname = Unit.getName(targetdata) -- Unit name if targetunit and targetunit:IsAlive() then local targetunitname = targetunit:GetName() @@ -505,23 +526,23 @@ do targetgroupname = targetunitname end local text = string.format("%s Missile Target = %s", self.lid, tostring(targetgroupname)) - self:T( text ) + self:I( text ) local m = MESSAGE:New(text,10,"Info"):ToAllIf(self.debug) - -- check if we or a SAM site are the target + -- check if we or a SAM site are the target --local TargetGroup = EventData.TgtGroup -- Wrapper.Group#GROUP local shotatus = self:_CheckShotAtShorad(targetgroupname) --#boolean local shotatsams = self:_CheckShotAtSams(targetgroupname) --#boolean -- if being shot at, find closest SHORADs to activate if shotatsams or shotatus then - self:T({shotatsams=shotatsams,shotatus=shotatus}) + self:I({shotatsams=shotatsams,shotatus=shotatus}) self:WakeUpShorad(targetgroupname, self.Radius, self.ActiveTimer, targetcat) end - end + end end end - end + end -- end ----------------------------------------------------------------------- -- SHORAD end ------------------------------------------------------------------------ +----------------------------------------------------------------------- \ No newline at end of file From 86fedbfaae4235c3e0b231c8df0d17e282e71d6f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 12 Jul 2021 18:16:22 +0200 Subject: [PATCH 097/111] Updated noise level --- Moose Development/Moose/Functional/Mantis.lua | 4 +- Moose Development/Moose/Functional/Sead.lua | 2 +- Moose Development/Moose/Functional/Shorad.lua | 58 +++++++++---------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index bb6fbe45e..8ff110925 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -20,7 +20,7 @@ -- @module Functional.Mantis -- @image Functional.Mantis.jpg --- Date: Apr 2021 +-- Date: July 2021 ------------------------------------------------------------------------- --- **MANTIS** class, extends #Core.Base#BASE @@ -310,7 +310,7 @@ do end -- @field #string version - self.version="0.4.1" + self.version="0.4.2" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) return self diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index ec568b627..c99e1f241 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -17,7 +17,7 @@ -- -- ### Authors: **FlightControl**, **applevangelist** -- --- Last Update: Feb 2021 +-- Last Update: July 2021 -- -- === -- diff --git a/Moose Development/Moose/Functional/Shorad.lua b/Moose Development/Moose/Functional/Shorad.lua index 36ca8ecdf..958877ec8 100644 --- a/Moose Development/Moose/Functional/Shorad.lua +++ b/Moose Development/Moose/Functional/Shorad.lua @@ -145,7 +145,7 @@ do -- @retunr #SHORAD self function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition, UseEmOnOff) local self = BASE:Inherit( self, BASE:New() ) - self:I({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition}) + self:T({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition}) local GroupSet = SET_GROUP:New():FilterPrefixes(ShoradPrefix):FilterCoalitions(Coalition):FilterCategoryGround():FilterStart() @@ -173,10 +173,10 @@ do --- Initially set all groups to alarm state GREEN -- @param #SHORAD self function SHORAD:_InitState() - self:I(self.lid .. " _InitState") + self:T(self.lid .. " _InitState") local table = {} local set = self.Groupset - self:I({set = set}) + self:T({set = set}) local aliveset = set:GetAliveSet() --#table for _,_group in pairs (aliveset) do if self.UseEmOnOff then @@ -199,7 +199,7 @@ do -- @param #SHORAD self -- @param #boolean debug Switch debug on (true) or off (false) function SHORAD:SwitchDebug(onoff) - self:I( { onoff } ) + self:T( { onoff } ) if onoff then self:SwitchDebugOn() else @@ -230,7 +230,7 @@ do -- @param #SHORAD self -- @param #boolean onoff function SHORAD:SwitchHARMDefense(onoff) - self:I( { onoff } ) + self:T( { onoff } ) local onoff = onoff or true self.DefendHarms = onoff return self @@ -240,7 +240,7 @@ do -- @param #SHORAD self -- @param #boolean onoff function SHORAD:SwitchAGMDefense(onoff) - self:I( { onoff } ) + self:T( { onoff } ) local onoff = onoff or true self.DefendMavs = onoff return self @@ -251,7 +251,7 @@ do -- @param #number low Minimum detection limit, integer 1-100 -- @param #number high Maximum detection limit integer 1-100 function SHORAD:SetDefenseLimits(low,high) - self:I( { low, high } ) + self:T( { low, high } ) local low = low or 70 local high = high or 90 if (low < 0) or (low > 100) or (low > high) then @@ -269,7 +269,7 @@ do -- @param #SHORAD self -- @param #number seconds Number of seconds systems stay active function SHORAD:SetActiveTimer(seconds) - self:I(self.lid .. " SetActiveTimer") + self:T(self.lid .. " SetActiveTimer") local timer = seconds or 600 if timer < 0 then timer = 600 @@ -282,7 +282,7 @@ do -- @param #SHORAD self -- @param #number meters Radius of the defense search zone in meters. #SHORADs in this range around a targeted group will go active function SHORAD:SetDefenseRadius(meters) - self:I(self.lid .. " SetDefenseRadius") + self:T(self.lid .. " SetDefenseRadius") local radius = meters or 20000 if radius < 0 then radius = 20000 @@ -295,7 +295,7 @@ do -- @param #SHORAD self -- @param #boolean switch Decide if we are changing alarm state or AI state function SHORAD:SetUsingEmOnOff(switch) - self:I(self.lid .. " SetUsingEmOnOff") + self:T(self.lid .. " SetUsingEmOnOff") self.UseEmOnOff = switch or false return self end @@ -305,8 +305,8 @@ do -- @param #string WeaponName -- @return #boolean Returns true for a match function SHORAD:_CheckHarms(WeaponName) - self:I(self.lid .. " _CheckHarms") - self:I( { WeaponName } ) + self:T(self.lid .. " _CheckHarms") + self:T( { WeaponName } ) local hit = false if self.DefendHarms then for _,_name in pairs (SHORAD.Harms) do @@ -321,8 +321,8 @@ do -- @param #string WeaponName -- @return #boolean Returns true for a match function SHORAD:_CheckMavs(WeaponName) - self:I(self.lid .. " _CheckMavs") - self:I( { WeaponName } ) + self:T(self.lid .. " _CheckMavs") + self:T( { WeaponName } ) local hit = false if self.DefendMavs then for _,_name in pairs (SHORAD.Mavs) do @@ -337,7 +337,7 @@ do -- @param #string Coalition name -- @return #boolean Returns false for a match function SHORAD:_CheckCoalition(Coalition) - self:I(self.lid .. " _CheckCoalition") + self:T(self.lid .. " _CheckCoalition") local owncoalition = self.Coalition local othercoalition = "" if Coalition == 0 then @@ -347,7 +347,7 @@ do else othercoalition = "blue" end - self:I({owncoalition = owncoalition, othercoalition = othercoalition}) + self:T({owncoalition = owncoalition, othercoalition = othercoalition}) if owncoalition ~= othercoalition then return true else @@ -360,7 +360,7 @@ do -- @param #string TargetGroupName Name of the target group -- @return #boolean Returns true for a match, else false function SHORAD:_CheckShotAtShorad(TargetGroupName) - self:I(self.lid .. " _CheckShotAtShorad") + self:T(self.lid .. " _CheckShotAtShorad") local tgtgrp = TargetGroupName local shorad = self.Groupset local shoradset = shorad:GetAliveSet() --#table @@ -380,7 +380,7 @@ do -- @param #string TargetGroupName Name of the target group -- @return #boolean Returns true for a match, else false function SHORAD:_CheckShotAtSams(TargetGroupName) - self:I(self.lid .. " _CheckShotAtSams") + self:T(self.lid .. " _CheckShotAtSams") local tgtgrp = TargetGroupName local shorad = self.Samset --local shoradset = shorad:GetAliveSet() --#table @@ -399,7 +399,7 @@ do -- @param #SHORAD self -- @return #boolean Returns true for a detection, else false function SHORAD:_ShotIsDetected() - self:I(self.lid .. " _ShotIsDetected") + self:T(self.lid .. " _ShotIsDetected") local IsDetected = false local DetectionProb = math.random(self.DefenseLowProb, self.DefenseHighProb) -- reference value local ActualDetection = math.random(1,100) -- value for this shot @@ -424,8 +424,8 @@ do -- mymantis:AddShorad(myshorad,720) -- mymantis:Start() function SHORAD:WakeUpShorad(TargetGroup, Radius, ActiveTimer, TargetCat) - self:I(self.lid .. " WakeUpShorad") - self:I({TargetGroup, Radius, ActiveTimer, TargetCat}) + self:T(self.lid .. " WakeUpShorad") + self:T({TargetGroup, Radius, ActiveTimer, TargetCat}) local targetcat = TargetCat or Object.Category.UNIT local targetgroup = TargetGroup local targetvec2 = nil @@ -452,14 +452,14 @@ do group:OptionAlarmStateGreen() end local text = string.format("Sleeping SHORAD %s", group:GetName()) - self:I(text) + self:T(text) local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) end -- go through set and find the one(s) to activate for _,_group in pairs (shoradset) do if _group:IsAnyInZone(targetzone) then local text = string.format("Waking up SHORAD %s", _group:GetName()) - self:I(text) + self:T(text) local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) if self.UseEmOnOff then --_group:SetAIOn() @@ -481,8 +481,8 @@ do -- @param #SHORAD self -- @param Core.Event#EVENTDATA EventData The event details table data set function SHORAD:HandleEventShot( EventData ) - self:I( { EventData } ) - self:I(self.lid .. " HandleEventShot") + self:T( { EventData } ) + self:T(self.lid .. " HandleEventShot") --local ShootingUnit = EventData.IniDCSUnit --local ShootingUnitName = EventData.IniDCSUnitName local ShootingWeapon = EventData.Weapon -- Identify the weapon fired @@ -498,14 +498,14 @@ do DetectedText = "true" end local text = string.format("%s Missile Launched = %s | Detected probability state is %s", self.lid, ShootingWeaponName, DetectedText) - self:I( text ) + self:T( text ) local m = MESSAGE:New(text,10,"Info"):ToAllIf(self.debug) -- if (self:_CheckHarms(ShootingWeaponName) or self:_CheckMavs(ShootingWeaponName)) and IsDetected then -- get target data local targetdata = EventData.Weapon:getTarget() -- Identify target local targetcat = targetdata:getCategory() -- Identify category - self:I(string.format("Target Category (3=STATIC, 1=UNIT)= %s",tostring(targetcat))) + self:T(string.format("Target Category (3=STATIC, 1=UNIT)= %s",tostring(targetcat))) local targetunit = nil if targetcat == Object.Category.UNIT then -- UNIT targetunit = UNIT:Find(targetdata) @@ -526,7 +526,7 @@ do targetgroupname = targetunitname end local text = string.format("%s Missile Target = %s", self.lid, tostring(targetgroupname)) - self:I( text ) + self:T( text ) local m = MESSAGE:New(text,10,"Info"):ToAllIf(self.debug) -- check if we or a SAM site are the target --local TargetGroup = EventData.TgtGroup -- Wrapper.Group#GROUP @@ -534,7 +534,7 @@ do local shotatsams = self:_CheckShotAtSams(targetgroupname) --#boolean -- if being shot at, find closest SHORADs to activate if shotatsams or shotatus then - self:I({shotatsams=shotatsams,shotatus=shotatus}) + self:T({shotatsams=shotatsams,shotatus=shotatus}) self:WakeUpShorad(targetgroupname, self.Radius, self.ActiveTimer, targetcat) end end From e33de03522d7bdb4bfb4f2dc6306477bac159f33 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 12 Jul 2021 19:17:39 +0200 Subject: [PATCH 098/111] CSAR - honor settings NM vs KM CTLD - documentation corrections UTILS - added functions to generate beacon frequency tables FM,VHF,UHF and valid laser codes for JTACs --- Moose Development/Moose/Ops/CSAR.lua | 31 ++--- Moose Development/Moose/Ops/CTLD.lua | 12 +- Moose Development/Moose/Utilities/Utils.lua | 145 ++++++++++++++++++++ 3 files changed, 161 insertions(+), 27 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index d243475b0..aa2015393 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -1428,12 +1428,8 @@ function CSAR:_GetPositionOfWounded(_woundedGroup) _coordinatesText = _coordinate:ToStringLLDMS() elseif self.coordtype == 2 then -- MGRS _coordinatesText = _coordinate:ToStringMGRS() - elseif self.coordtype == 3 then -- Bullseye Imperial - local Settings = _SETTINGS:SetImperial() - _coordinatesText = _coordinate:ToStringBULLS(self.coalition,Settings) - else -- Bullseye Metric --(medevac.coordtype == 4) - local Settings = _SETTINGS:SetMetric() - _coordinatesText = _coordinate:ToStringBULLS(self.coalition,Settings) + else -- Bullseye Metric --(medevac.coordtype == 4 or 3) + _coordinatesText = _coordinate:ToStringBULLS(self.coalition) end end return _coordinatesText @@ -1467,12 +1463,11 @@ function CSAR:_DisplayActiveSAR(_unitName) local _woundcoord = _woundedGroup:GetCoordinate() local _distance = self:_GetDistance(_helicoord, _woundcoord) self:T({_distance = _distance}) - -- change distance to miles if self.coordtype < 4 local distancetext = "" - if self.coordtype < 4 then - distancetext = string.format("%.3fnm",UTILS.MetersToNM(_distance)) + if _SETTINGS:IsImperial() then + distancetext = string.format("%.1fnm",UTILS.MetersToNM(_distance)) else - distancetext = string.format("%.3fkm", _distance/1000.0) + distancetext = string.format("%.1fkm", _distance/1000.0) end table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %s ", _value.desc, _coordinatesText, _value.frequency / 1000, distancetext) }) end @@ -1542,10 +1537,10 @@ function CSAR:_SignalFlare(_unitName) local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) local _distance = 0 - if self.coordtype < 4 then - _distance = string.format("%.3fnm",UTILS.MetersToNM(_closest.distance)) + if _SETTINGS:IsImperial() then + _distance = string.format("%.1fnm",UTILS.MetersToNM(_closest.distance)) else - _distance = string.format("%.3fkm",_closest.distance) + _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) self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true) @@ -1554,7 +1549,7 @@ function CSAR:_SignalFlare(_unitName) _coord:FlareRed(_clockDir) else local disttext = "4.3nm" - if self.coordtype == 4 then + if _SETTINGS:IsMetric() then disttext = "8km" end self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",disttext), self.messageTime) @@ -1593,10 +1588,10 @@ function CSAR:_Reqsmoke( _unitName ) if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < 8000.0 then local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) local _distance = 0 - if self.coordtype < 4 then - _distance = string.format("%.3fnm",UTILS.MetersToNM(_closest.distance)) + if _SETTINGS:IsImperial() then + _distance = string.format("%.1fnm",UTILS.MetersToNM(_closest.distance)) else - _distance = string.format("%.3fkm",_closest.distance) + _distance = string.format("%.1fkm",_closest.distance) end local _msg = string.format("%s - Popping signal smoke at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance) self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true) @@ -1605,7 +1600,7 @@ function CSAR:_Reqsmoke( _unitName ) _coord:Smoke(color) else local disttext = "4.3nm" - if self.coordtype == 4 then + if _SETTINGS:IsMetric() then disttext = "8km" end self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",disttext), self.messageTime) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 4c9adf68d..f910323d7 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -81,8 +81,8 @@ CTLD_CARGO = { self.Name = Name or "none" -- #string self.Templates = Templates or {} -- #table self.CargoType = Sorte or "type" -- #CTLD_CARGO.Enum - self.HasBeenMoved = HasBeenMoved or false -- #booolean - self.LoadDirectly = LoadDirectly or false -- #booolean + self.HasBeenMoved = HasBeenMoved or false -- #boolean + self.LoadDirectly = LoadDirectly or false -- #boolean self.CratesNeeded = CratesNeeded or 0 -- #number self.Positionable = Positionable or nil -- Wrapper.Positionable#POSITIONABLE self.HasBeenDropped = Dropped or false --#boolean @@ -803,16 +803,10 @@ function CTLD:_GenerateUHFrequencies() end --- (Internal) Function to generate valid FM Frequencies --- @param #CTLD sel +-- @param #CTLD self function CTLD:_GenerateFMFrequencies() self:T(self.lid .. " _GenerateFMrequencies") self.FreeFMFrequencies = {} - local _start = 220000000 - - while _start < 399000000 do - - _start = _start + 500000 - end for _first = 3, 7 do for _second = 0, 5 do diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index ad8bb6291..609ace372 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1606,4 +1606,149 @@ function UTILS.IsLoadingDoorOpen( unit_name ) end -- nil return nil +end + +--- Function to generate valid FM frequencies in mHz for radio beacons (FM). +-- @return #table Table of frequencies. +function UTILS.GenerateFMFrequencies() + local FreeFMFrequencies = {} + for _first = 3, 7 do + for _second = 0, 5 do + for _third = 0, 9 do + local _frequency = ((100 * _first) + (10 * _second) + _third) * 100000 --extra 0 because we didnt bother with 4th digit + table.insert(FreeFMFrequencies, _frequency) + end + end + end + return FreeFMFrequencies +end + +--- Function to generate valid VHF frequencies in kHz for radio beacons (FM). +-- @return #table VHFrequencies +function UTILS.GenerateVHFrequencies() + + -- known and sorted map-wise NDBs in kHz + local _skipFrequencies = { + 214,274,291.5,295,297.5, + 300.5,304,307,309.5,311,312,312.5,316, + 320,324,328,329,330,336,337, + 342,343,348,351,352,353,358, + 363,365,368,372.5,374, + 380,381,384,389,395,396, + 414,420,430,432,435,440,450,455,462,470,485, + 507,515,520,525,528,540,550,560,570,577,580, + 602,625,641,662,670,680,682,690, + 705,720,722,730,735,740,745,750,770,795, + 822,830,862,866, + 905,907,920,935,942,950,995, + 1000,1025,1030,1050,1065,1116,1175,1182,1210 + } + + local FreeVHFFrequencies = {} + + -- first range + local _start = 200000 + while _start < 400000 do + + -- skip existing NDB frequencies# + local _found = false + for _, value in pairs(_skipFrequencies) do + if value * 1000 == _start then + _found = true + break + end + end + if _found == false then + table.insert(FreeVHFFrequencies, _start) + end + _start = _start + 10000 + end + + -- second range + _start = 400000 + while _start < 850000 do + -- skip existing NDB frequencies + local _found = false + for _, value in pairs(_skipFrequencies) do + if value * 1000 == _start then + _found = true + break + end + end + if _found == false then + table.insert(FreeVHFFrequencies, _start) + end + _start = _start + 10000 + end + + -- third range + _start = 850000 + while _start <= 999000 do -- adjusted for Gazelle + -- skip existing NDB frequencies + local _found = false + for _, value in pairs(_skipFrequencies) do + if value * 1000 == _start then + _found = true + break + end + end + if _found == false then + table.insert(FreeVHFFrequencies, _start) + end + _start = _start + 50000 + end + + return FreeVHFFrequencies +end + +--- Function to generate valid UHF Frequencies in mHz (AM). +-- @return #table UHF Frequencies +function UTILS.GenerateUHFrequencies() + + local FreeUHFFrequencies = {} + local _start = 220000000 + + while _start < 399000000 do + table.insert(FreeUHFFrequencies, _start) + _start = _start + 500000 + end + + return FreeUHFFrequencies +end + +--- Function to generate valid laser codes for JTAC. +-- @return #table Laser Codes. +function UTILS.GenerateLaserCodes() + local jtacGeneratedLaserCodes = {} + + -- helper function + local function ContainsDigit(_number, _numberToFind) + local _thisNumber = _number + local _thisDigit = 0 + while _thisNumber ~= 0 do + _thisDigit = _thisNumber % 10 + _thisNumber = math.floor(_thisNumber / 10) + if _thisDigit == _numberToFind then + return true + end + end + return false + end + + -- generate list of laser codes + local _code = 1111 + local _count = 1 + while _code < 1777 and _count < 30 do + while true do + _code = _code + 1 + if not self:_ContainsDigit(_code, 8) + and not ContainsDigit(_code, 9) + and not ContainsDigit(_code, 0) then + table.insert(jtacGeneratedLaserCodes, _code) + break + end + end + _count = _count + 1 + end + return jtacGeneratedLaserCodes end \ No newline at end of file From 433d1bbf57c7e2a384f472f11d3e6883c7598539 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 13 Jul 2021 17:50:44 +0200 Subject: [PATCH 099/111] MANTIS - Change logic to FSM, added functions CSAR - advanced options to name injected AI downed pilots CTLD - added Herc speed check --- Moose Development/Moose/Functional/Mantis.lua | 657 ++++++++++++------ Moose Development/Moose/Ops/CSAR.lua | 24 +- Moose Development/Moose/Ops/CTLD.lua | 36 +- 3 files changed, 493 insertions(+), 224 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 8ff110925..10f0bf1a6 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -191,7 +191,17 @@ MANTIS = { ShoradLink = false, ShoradTime = 600, ShoradActDistance = 15000, - UseEmOnOff = false, + UseEmOnOff = false, + TimeStamp = 0, + state2flag = false, +} + +--- Advanced state enumerator +-- @type MANTIS.AdvancedState +MANTIS.AdvancedState = { + GREEN = 0, + AMBER = 1, + RED = 2, } ----------------------------------------------------------------------- @@ -263,7 +273,10 @@ do self.ShoradLink = false self.ShoradTime = 600 self.ShoradActDistance = 15000 - -- TODO: add emissions on/off when available .... in 2 weeks + self.TimeStamp = timer.getAbsTime() + self.relointerval = math.random(1800,3600) -- random between 30 and 60 mins + self.state2flag = false + if EmOnOff then if EmOnOff == false then self.UseEmOnOff = false @@ -279,7 +292,7 @@ do end -- Inherit everything from BASE class. - local self = BASE:Inherit(self, BASE:New()) -- #MANTIS + local self = BASE:Inherit(self, FSM:New()) -- #MANTIS -- Set the string id for output to DCS.log file. self.lid=string.format("MANTIS %s | ", self.name) @@ -310,27 +323,122 @@ do end -- @field #string version - self.version="0.4.2" + self.version="0.5.1" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) - return self - end + --- FSM Functions --- + + -- Start State. + self:SetStartState("Stopped") + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("Stopped", "Start", "Running") -- Start FSM. + self:AddTransition("*", "Status", "*") -- MANTIS status update. + self:AddTransition("*", "Relocating", "*") -- MANTIS HQ and EWR are relocating. + self:AddTransition("*", "GreenState", "*") -- MANTIS A SAM switching to GREEN state. + self:AddTransition("*", "RedState", "*") -- MANTIS A SAM switching to RED state. + self:AddTransition("*", "AdvStateChange", "*") -- MANTIS advanced mode state change. + self:AddTransition("*", "ShoradActivated", "*") -- MANTIS woke up a connected SHORAD. + self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. + + ------------------------ + --- Pseudo Functions --- + ------------------------ + + --- Triggers the FSM event "Start". Starts the MANTIS. Initializes parameters and starts event handlers. + -- @function [parent=#MANTIS] Start + -- @param #MANTIS self + + --- Triggers the FSM event "Start" after a delay. Starts the MANTIS. Initializes parameters and starts event handlers. + -- @function [parent=#MANTIS] __Start + -- @param #MANTIS self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Stop". Stops the MANTIS and all its event handlers. + -- @param #MANTIS self + + --- Triggers the FSM event "Stop" after a delay. Stops the MANTIS and all its event handlers. + -- @function [parent=#MANTIS] __Stop + -- @param #MANTIS self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Status". + -- @function [parent=#MANTIS] Status + -- @param #MANTIS self + + --- Triggers the FSM event "Status" after a delay. + -- @function [parent=#MANTIS] __Status + -- @param #MANTIS self + -- @param #number delay Delay in seconds. + + --- On After "Relocating" event. HQ and/or EWR moved. + -- @function [parent=#MANTIS] OnAfterRelocating + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @return #MANTIS self + + --- On After "GreenState" event. A SAM group was switched to GREEN alert. + -- @function [parent=#MANTIS] OnAfterGreenState + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param Wrapper.Group#GROUP Group The GROUP object whose state was changed + -- @return #MANTIS self + + --- On After "RedState" event. A SAM group was switched to RED alert. + -- @function [parent=#MANTIS] OnAfterRedState + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param Wrapper.Group#GROUP Group The GROUP object whose state was changed + -- @return #MANTIS self + + --- On After "AdvStateChange" event. Advanced state changed, influencing detection speed. + -- @function [parent=#MANTIS] OnAfterAdvStateChange + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param #number Oldstate Old state - 0 = green, 1 = amber, 2 = red + -- @param #number Newstate New state - 0 = green, 1 = amber, 2 = red + -- @param #number Interval Calculated detection interval based on state and advanced feature setting + -- @return #MANTIS self + + --- On After "ShoradActivated" event. Mantis has activated a SHORAD. + -- @function [parent=#MANTIS] OnAfterShoradActivated + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param #string Name Name of the GROUP which SHORAD shall protect + -- @param #number Radius Radius around the named group to find SHORAD groups + -- @param #number Ontime Seconds the SHORAD will stay active + + return self + end ----------------------------------------------------------------------- -- MANTIS helper functions ----------------------------------------------------------------------- - --- [internal] Function to get the self.SAM_Table + --- [Internal] Function to get the self.SAM_Table -- @param #MANTIS self -- @return #table table function MANTIS:_GetSAMTable() + self:T(self.lid .. "GetSAMTable") return self.SAM_Table end - --- [internal] Function to set the self.SAM_Table + --- [Internal] Function to set the self.SAM_Table -- @param #MANTIS self -- @return #MANTIS self function MANTIS:_SetSAMTable(table) + self:T(self.lid .. "SetSAMTable") self.SAM_Table = table return self end @@ -339,41 +447,50 @@ do -- @param #MANTIS self -- @param #number radius Radius upon which detected objects will be grouped function MANTIS:SetEWRGrouping(radius) + self:T(self.lid .. "SetEWRGrouping") local radius = radius or 5000 self.grouping = radius + return self end --- Function to set the detection radius of the EWR in meters -- @param #MANTIS self -- @param #number radius Radius of the EWR detection zone function MANTIS:SetEWRRange(radius) + self:T(self.lid .. "SetEWRRange") local radius = radius or 80000 self.acceptrange = radius + return self end --- Function to set switch-on/off zone for the SAM sites in meters -- @param #MANTIS self -- @param #number radius Radius of the firing zone function MANTIS:SetSAMRadius(radius) + self:T(self.lid .. "SetSAMRadius") local radius = radius or 25000 self.checkradius = radius + return self end --- Function to set SAM firing engage range, 0-100 percent, e.g. 75 -- @param #MANTIS self -- @param #number range Percent of the max fire range function MANTIS:SetSAMRange(range) + self:T(self.lid .. "SetSAMRange") local range = range or 75 if range < 0 or range > 100 then range = 75 end self.engagerange = range + return self end --- Function to set a new SAM firing engage range, use this method to adjust range while running MANTIS, e.g. for different setups day and night -- @param #MANTIS self -- @param #number range Percent of the max fire range function MANTIS:SetNewSAMRangeWhileRunning(range) + self:T(self.lid .. "SetNewSAMRangeWhileRunning") local range = range or 75 if range < 0 or range > 100 then range = 75 @@ -381,20 +498,32 @@ do self.engagerange = range self:_RefreshSAMTable() self.mysead.EngagementRange = range + return self end --- Function to set switch-on/off the debug state -- @param #MANTIS self -- @param #boolean onoff Set true to switch on function MANTIS:Debug(onoff) + self:T(self.lid .. "SetDebug") local onoff = onoff or false self.debug = onoff + if onoff then + -- Debug trace. + BASE:TraceOn() + BASE:TraceClass("MANTIS") + BASE:TraceLevel(1) + else + BASE:TraceOff() + end + return self end --- Function to get the HQ object for further use -- @param #MANTIS self -- @return Wrapper.GROUP#GROUP The HQ #GROUP object or *nil* if it doesn't exist function MANTIS:GetCommandCenter() + self:T(self.lid .. "GetCommandCenter") if self.HQ_CC then return self.HQ_CC else @@ -406,26 +535,31 @@ do -- @param #MANTIS self -- @param #string prefix Name of the AWACS group in the mission editor function MANTIS:SetAwacs(prefix) + self:T(self.lid .. "SetAwacs") if prefix ~= nil then if type(prefix) == "string" then self.AWACS_Prefix = prefix self.advAwacs = true end end + return self end --- Function to set AWACS detection range. Defaults to 250.000m (250km) - use **before** starting your Mantis! -- @param #MANTIS self -- @param #number range Detection range of the AWACS group function MANTIS:SetAwacsRange(range) - local range = range or 250000 - self.awacsrange = range + self:T(self.lid .. "SetAwacsRange") + local range = range or 250000 + self.awacsrange = range + return self end --- Function to set the HQ object for further use -- @param #MANTIS self -- @param Wrapper.GROUP#GROUP group The #GROUP object to be set as HQ function MANTIS:SetCommandCenter(group) + self:T(self.lid .. "SetCommandCenter") local group = group or nil if group ~= nil then if type(group) == "string" then @@ -436,14 +570,17 @@ do self.HQ_Template_CC = group:GetName() end end + return self end --- Function to set the detection interval -- @param #MANTIS self -- @param #number interval The interval in seconds function MANTIS:SetDetectInterval(interval) + self:T(self.lid .. "SetDetectInterval") local interval = interval or 30 self.detectinterval = interval + return self end --- Function to set Advanded Mode @@ -453,7 +590,8 @@ do -- @usage Advanced mode will *decrease* reactivity of MANTIS, if HQ and/or EWR network dies. Set SAMs to RED state if both are dead. Requires usage of an **HQ** object and the **dynamic** option. -- E.g. `mymantis:SetAdvancedMode(true, 90)` function MANTIS:SetAdvancedMode(onoff, ratio) - self:F({onoff, ratio}) + self:T(self.lid .. "SetAdvancedMode") + self:T({onoff, ratio}) local onoff = onoff or false local ratio = ratio or 100 if (type(self.HQ_Template_CC) == "string") and onoff and self.dynamic then @@ -461,53 +599,58 @@ do self.advanced = true self.adv_state = 0 self.Adv_EWR_Group = SET_GROUP:New():FilterPrefixes(self.EWR_Templates_Prefix):FilterCoalitions(self.Coalition):FilterStart() - env.info(string.format("***** Starting Advanced Mode MANTIS Version %s *****", self.version)) + self:I(string.format("***** Starting Advanced Mode MANTIS Version %s *****", self.version)) else local text = self.lid.." Advanced Mode requires a HQ and dynamic to be set. Revisit your MANTIS:New() statement to add both." local m= MESSAGE:New(text,10,"MANTIS",true):ToAll() - BASE:E(text) + self:E(text) end + return self end --- Set using Emissions on/off instead of changing alarm state -- @param #MANTIS self -- @param #boolean switch Decide if we are changing alarm state or Emission state function MANTIS:SetUsingEmOnOff(switch) + self:T(self.lid .. "SetUsingEmOnOff") self.UseEmOnOff = switch or false + return self end --- [Internal] Function to check if HQ is alive -- @param #MANTIS self -- @return #boolean True if HQ is alive, else false function MANTIS:_CheckHQState() + self:T(self.lid .. "CheckHQState") local text = self.lid.." Checking HQ State" - self:T(text) local m= MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then env.info(text) end + if self.verbose then self:I(text) end -- start check if self.advanced then local hq = self.HQ_Template_CC local hqgrp = GROUP:FindByName(hq) if hqgrp then if hqgrp:IsAlive() then -- ok we're on, hq exists and as alive - env.info(self.lid.." HQ is alive!") + self:T(self.lid.." HQ is alive!") return true else - env.info(self.lid.." HQ is dead!") + self:T(self.lid.." HQ is dead!") return false end end - end + end + return self end --- [Internal] Function to check if EWR is (at least partially) alive -- @param #MANTIS self -- @return #boolean True if EWR is alive, else false function MANTIS:_CheckEWRState() + self:T(self.lid .. "CheckEWRState") local text = self.lid.." Checking EWR State" - self:F(text) + self:T(text) local m= MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then env.info(text) end + if self.verbose then self:I(text) end -- start check if self.advanced then local EWR_Group = self.Adv_EWR_Group @@ -521,24 +664,26 @@ do end end end - env.info(self.lid..string.format(" No of EWR alive is %d", nalive)) + self:T(self.lid..string.format(" No of EWR alive is %d", nalive)) if nalive > 0 then return true else return false end - end + end + return self end --- [Internal] Function to determine state of the advanced mode -- @param #MANTIS self -- @return #number Newly calculated interval -- @return #number Previous state for tracking 0, 1, or 2 - function MANTIS:_CheckAdvState() - local text = self.lid.." Checking Advanced State" - self:F(text) + function MANTIS:_CalcAdvState() + self:T(self.lid .. "CalcAdvState") + local text = self.lid.." Calculating Advanced State" + self:T(text) local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then env.info(text) end + if self.verbose then self:I(text) end -- start check local currstate = self.adv_state -- save curr state for comparison later local EWR_State = self:_CheckEWRState() @@ -557,9 +702,9 @@ do ratio = ratio * self.adv_state -- e.g 0.8*2 = 1.6 local newinterval = interval + (interval * ratio) -- e.g. 30+(30*1.6) = 78 local text = self.lid..string.format(" Calculated OldState/NewState/Interval: %d / %d / %d", currstate, self.adv_state, newinterval) - self:F(text) + self:T(text) local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then env.info(text) end + if self.verbose then self:I(text) end return newinterval, currstate end @@ -568,7 +713,8 @@ do -- @param #boolean hq If true, will relocate HQ object -- @param #boolean ewr If true, will relocate EWR objects function MANTIS:SetAutoRelocate(hq, ewr) - self:F({hq, ewr}) + self:T(self.lid .. "SetAutoRelocate") + self:T({hq, ewr}) local hqrel = hq or false local ewrel = ewr or false if hqrel or ewrel then @@ -576,23 +722,25 @@ do self.autorelocateunits = { HQ = hqrel, EWR = ewrel } self:T({self.autorelocate, self.autorelocateunits}) end + return self end --- [Internal] Function to execute the relocation -- @param #MANTIS self function MANTIS:_RelocateGroups() - self:T(self.lid.." Relocating Groups") + self:T(self.lid .. "RelocateGroups") local text = self.lid.." Relocating Groups" local m= MESSAGE:New(text,10,"MANTIS",true):ToAllIf(self.debug) - if self.verbose then env.info(text) end + if self.verbose then self:I(text) end if self.autorelocate then -- relocate HQ - if self.autorelocateunits.HQ and self.HQ_CC then --only relocate if HQ exists + local HQGroup = self.HQ_CC + if self.autorelocateunits.HQ and self.HQ_CC and HQGroup:IsAlive() then --only relocate if HQ exists local _hqgrp = self.HQ_CC self:T(self.lid.." Relocating HQ") local text = self.lid.." Relocating HQ" - local m= MESSAGE:New(text,10,"MANTIS"):ToAll() - _hqgrp:RelocateGroundRandomInRadius(20,500,true,true) + --local m= MESSAGE:New(text,10,"MANTIS"):ToAll() + _hqgrp:RelocateGroundRandomInRadius(20,500,true,true) end --relocate EWR -- TODO: maybe dependent on AlarmState? Observed: SA11 SR only relocates if no objects in reach @@ -601,26 +749,27 @@ do local EWR_GRP = SET_GROUP:New():FilterPrefixes(self.EWR_Templates_Prefix):FilterCoalitions(self.Coalition):FilterOnce() local EWR_Grps = EWR_GRP.Set --table of objects in SET_GROUP for _,_grp in pairs (EWR_Grps) do - if _grp:IsGround() then + if _grp:IsAlive() and _grp:IsGround() then self:T(self.lid.." Relocating EWR ".._grp:GetName()) local text = self.lid.." Relocating EWR ".._grp:GetName() local m= MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then env.info(text) end + if self.verbose then self:I(text) end _grp:RelocateGroundRandomInRadius(20,500,true,true) end end end end + return self end - --- (Internal) Function to check if any object is in the given SAM zone + --- [Internal] Function to check if any object is in the given SAM zone -- @param #MANTIS self -- @param #table dectset Table of coordinates of detected items - -- @param samcoordinate Core.Point#COORDINATE Coordinate object. + -- @param Core.Point#COORDINATE samcoordinate Coordinate object. -- @return #boolean True if in any zone, else false -- @return #number Distance Target distance in meters or zero when no object is in zone function MANTIS:CheckObjectInZone(dectset, samcoordinate) - self:F(self.lid.."CheckObjectInZone Called") + self:T(self.lid.."CheckObjectInZone") -- check if non of the coordinate is in the given defense zone local radius = self.checkradius local set = dectset @@ -632,7 +781,7 @@ do local targetdistance = samcoordinate:DistanceFromPointVec2(coord) local text = string.format("Checking SAM at % s - Distance %d m - Target %s", samstring, targetdistance, dectstring) local m = MESSAGE:New(text,10,"Check"):ToAllIf(self.debug) - if self.verbose then env.info(self.lid..text) end + if self.verbose then self:I(self.lid..text) end -- end output to cross-check if targetdistance <= radius then return true, targetdistance @@ -641,11 +790,11 @@ do return false, 0 end - --- (Internal) Function to start the detection via EWR groups + --- [Internal] Function to start the detection via EWR groups -- @param #MANTIS self -- @return Functional.Detection #DETECTION_AREAS The running detection set function MANTIS:StartDetection() - self:F(self.lid.."Starting Detection") + self:T(self.lid.."Starting Detection") -- start detection local groupset = self.EWR_Group @@ -653,14 +802,14 @@ do local acceptrange = self.acceptrange or 80000 local interval = self.detectinterval or 60 - --@param Functional.Detection #DETECTION_AREAS _MANTISdetection [internal] The MANTIS detection object - _MANTISdetection = DETECTION_AREAS:New( groupset, grouping ) --[internal] Grouping detected objects to 5000m zones - _MANTISdetection:FilterCategories({ Unit.Category.AIRPLANE, Unit.Category.HELICOPTER }) - _MANTISdetection:SetAcceptRange(acceptrange) - _MANTISdetection:SetRefreshTimeInterval(interval) - _MANTISdetection:Start() + --@param Functional.Detection #DETECTION_AREAS _MANTISdetection [Internal] The MANTIS detection object + local MANTISdetection = DETECTION_AREAS:New( groupset, grouping ) --[Internal] Grouping detected objects to 5000m zones + MANTISdetection:FilterCategories({ Unit.Category.AIRPLANE, Unit.Category.HELICOPTER }) + MANTISdetection:SetAcceptRange(acceptrange) + MANTISdetection:SetRefreshTimeInterval(interval) + MANTISdetection:Start() - function _MANTISdetection:OnAfterDetectedItem(From,Event,To,DetectedItem) + function MANTISdetection:OnAfterDetectedItem(From,Event,To,DetectedItem) --BASE:I( { From, Event, To, DetectedItem }) local debug = false if DetectedItem.IsDetected and debug then @@ -669,14 +818,14 @@ do local m = MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) end end - return _MANTISdetection + return MANTISdetection end - --- (Internal) Function to start the detection via AWACS if defined as separate + --- [Internal] Function to start the detection via AWACS if defined as separate -- @param #MANTIS self -- @return Functional.Detection #DETECTION_AREAS The running detection set function MANTIS:StartAwacsDetection() - self:F(self.lid.."Starting Awacs Detection") + self:T(self.lid.."Starting Awacs Detection") -- start detection local group = self.AWACS_Prefix @@ -685,14 +834,14 @@ do --local acceptrange = self.acceptrange or 80000 local interval = self.detectinterval or 60 - --@param Functional.Detection #DETECTION_AREAS _MANTISdetection [internal] The MANTIS detection object - _MANTISAwacs = DETECTION_AREAS:New( groupset, grouping ) --[internal] Grouping detected objects to 5000m zones - _MANTISAwacs:FilterCategories({ Unit.Category.AIRPLANE, Unit.Category.HELICOPTER }) - _MANTISAwacs:SetAcceptRange(self.awacsrange) --250km - _MANTISAwacs:SetRefreshTimeInterval(interval) - _MANTISAwacs:Start() + --@param Functional.Detection #DETECTION_AREAS _MANTISdetection [Internal] The MANTIS detection object + local MANTISAwacs = DETECTION_AREAS:New( groupset, grouping ) --[Internal] Grouping detected objects to 5000m zones + MANTISAwacs:FilterCategories({ Unit.Category.AIRPLANE, Unit.Category.HELICOPTER }) + MANTISAwacs:SetAcceptRange(self.awacsrange) --250km + MANTISAwacs:SetRefreshTimeInterval(interval) + MANTISAwacs:Start() - function _MANTISAwacs:OnAfterDetectedItem(From,Event,To,DetectedItem) + function MANTISAwacs:OnAfterDetectedItem(From,Event,To,DetectedItem) --BASE:I( { From, Event, To, DetectedItem }) local debug = false if DetectedItem.IsDetected and debug then @@ -701,15 +850,15 @@ do local m = MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) end end - return _MANTISAwacs + return MANTISAwacs end - --- (Internal) Function to set the SAM start state + --- [Internal] Function to set the SAM start state -- @param #MANTIS self -- @return #MANTIS self function MANTIS:SetSAMStartState() -- DONE: if using dynamic filtering, update SAM_Table and the (active) SEAD groups, pull req #1405/#1406 - self:F(self.lid.."Setting SAM Start States") + self:T(self.lid.."Setting SAM Start States") -- get SAM Group local SAM_SET = self.SAM_Group local SAM_Grps = SAM_SET.Set --table of objects @@ -742,11 +891,11 @@ do return self end - --- (Internal) Function to update SAM table and SEAD state + --- [Internal] Function to update SAM table and SEAD state -- @param #MANTIS self -- @return #MANTIS self function MANTIS:_RefreshSAMTable() - self:F(self.lid.."Setting SAM Start States") + self:T(self.lid.."RefreshSAMTable") -- Requires SEAD 0.2.2 or better -- get SAM Group local SAM_SET = self.SAM_Group @@ -779,6 +928,7 @@ do -- @param Functional.Shorad#SHORAD Shorad The #SHORAD object -- @param #number Shoradtime Number of seconds #SHORAD stays active post wake-up function MANTIS:AddShorad(Shorad,Shoradtime) + self:T(self.lid.."AddShorad") local Shorad = Shorad or nil local ShoradTime = Shoradtime or 600 local ShoradLink = true @@ -787,184 +937,279 @@ do self.Shorad = Shorad --#SHORAD self.ShoradTime = Shoradtime -- #number end + return self end --- Function to unlink #MANTIS from a #SHORAD installation -- @param #MANTIS self function MANTIS:RemoveShorad() + self:T(self.lid.."RemoveShorad") self.ShoradLink = false + return self end ----------------------------------------------------------------------- -- MANTIS main functions ----------------------------------------------------------------------- - --- Function to set the SAM start state + --- [Internal] Check detection function + -- @param #MANTIS self + -- @param Functional.Detection#DETECTION_AREAS detection Detection object + -- @return #MANTIS self + function MANTIS:_Check(detection) + self:T(self.lid .. "Check") + --get detected set + local detset = detection:GetDetectedItemCoordinates() + self:T("Check:", {detset}) + -- randomly update SAM Table + local rand = math.random(1,100) + if rand > 65 then -- 1/3 of cases + self:_RefreshSAMTable() + end + -- switch SAMs on/off if (n)one of the detected groups is inside their reach + local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates + for _,_data in pairs (samset) do + local samcoordinate = _data[2] + local name = _data[1] + local samgroup = GROUP:FindByName(name) + local IsInZone, Distance = self:CheckObjectInZone(detset, samcoordinate) + if IsInZone then --check any target in zone + if samgroup:IsAlive() then + -- switch on SAM + if self.UseEmOnOff then + -- TODO: add emissions on/off + --samgroup:SetAIOn() + samgroup:EnableEmission(true) + end + samgroup:OptionAlarmStateRed() + self:__RedState(1,samgroup) + -- link in to SHORAD if available + -- DONE: Test integration fully + if self.ShoradLink and Distance < self.ShoradActDistance then -- don't give SHORAD position away too early + local Shorad = self.Shorad + local radius = self.checkradius + local ontime = self.ShoradTime + Shorad:WakeUpShorad(name, radius, ontime) + self:__ShoradActivated(1,name, radius, ontime) + end + -- debug output + local text = string.format("SAM %s switched to alarm state RED!", name) + local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) + if self.verbose then self:I(self.lid..text) end + end --end alive + else + if samgroup:IsAlive() then + -- switch off SAM + if self.UseEmOnOff then + -- TODO: add emissions on/off + samgroup:EnableEmission(false) + self:__GreenState(1,samgroup) + --samgroup:SetAIOff() + else + samgroup:OptionAlarmStateGreen() + self:__GreenState(1,samgroup) + end + --samgroup:OptionROEWeaponFree() + --samgroup:SetAIOn() + local text = string.format("SAM %s switched to alarm state GREEN!", name) + local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) + if self.verbose then self:I(self.lid..text) end + end --end alive + end --end check + end --for for loop + return self + end + + --- [Internal] Relocation relay function -- @param #MANTIS self -- @return #MANTIS self - function MANTIS:Start() - self:F(self.lid.."Starting MANTIS") - self:SetSAMStartState() - self.Detection = self:StartDetection() - if self.advAwacs then - self.AWACS_Detection = self:StartAwacsDetection() - end - -- detection function - local function check(detection) - --get detected set - local detset = detection:GetDetectedItemCoordinates() - self:F("Check:", {detset}) - -- randomly update SAM Table - local rand = math.random(1,100) - if rand > 65 then -- 1/3 of cases - self:_RefreshSAMTable() - end - -- switch SAMs on/off if (n)one of the detected groups is inside their reach - local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates - for _,_data in pairs (samset) do - local samcoordinate = _data[2] - local name = _data[1] - local samgroup = GROUP:FindByName(name) - local IsInZone, Distance = self:CheckObjectInZone(detset, samcoordinate) - if IsInZone then --check any target in zone + function MANTIS:_Relocate() + self:T(self.lid .. "Relocate") + self:_RelocateGroups() + return self + end + + --- [Internal] Check advanced state + -- @param #MANTIS self + -- @return #MANTIS self + function MANTIS:_CheckAdvState() + self:T(self.lid .. "CheckAdvSate") + local interval, oldstate = self:_CalcAdvState() + local newstate = self.adv_state + if newstate ~= oldstate then + -- deal with new state + self:__AdvStateChange(1,oldstate,newstate,interval) + if newstate == 2 then + -- switch alarm state RED + self.state2flag = true + local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates + for _,_data in pairs (samset) do + local name = _data[1] + local samgroup = GROUP:FindByName(name) if samgroup:IsAlive() then - -- switch on SAM if self.UseEmOnOff then -- TODO: add emissions on/off --samgroup:SetAIOn() samgroup:EnableEmission(true) end samgroup:OptionAlarmStateRed() - -- link in to SHORAD if available - -- DONE: Test integration fully - if self.ShoradLink and Distance < self.ShoradActDistance then -- don't give SHORAD position away too early - local Shorad = self.Shorad - local radius = self.checkradius - local ontime = self.ShoradTime - Shorad:WakeUpShorad(name, radius, ontime) - end - -- debug output - local text = string.format("SAM %s switched to alarm state RED!", name) - local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then env.info(self.lid..text) end - end --end alive - else - if samgroup:IsAlive() then - -- switch off SAM - if self.UseEmOnOff then - -- TODO: add emissions on/off - samgroup:EnableEmission(false) - --samgroup:SetAIOff() - else - samgroup:OptionAlarmStateGreen() - end - --samgroup:OptionROEWeaponFree() - --samgroup:SetAIOn() - local text = string.format("SAM %s switched to alarm state GREEN!", name) - local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then env.info(self.lid..text) end - end --end alive - end --end check - end --for for loop - end --end function - -- relocation relay function - local function relocate() - self:_RelocateGroups() - end - -- check advanced state - local function checkadvstate() - local interval, oldstate = self:_CheckAdvState() - local newstate = self.adv_state - if newstate ~= oldstate then - -- deal with new state - if newstate == 2 then - -- switch alarm state RED - if self.MantisTimer.isrunning then - self.MantisTimer:Stop() - self.MantisTimer.isrunning = false - end -- stop Awacs timer - if self.MantisATimer.isrunning then - self.MantisATimer:Stop() - self.MantisATimer.isrunning = false - end -- stop timer - local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates - for _,_data in pairs (samset) do - local name = _data[1] - local samgroup = GROUP:FindByName(name) - if samgroup:IsAlive() then - if self.UseEmOnOff then - -- TODO: add emissions on/off - --samgroup:SetAIOn() - samgroup:EnableEmission(true) - end - samgroup:OptionAlarmStateRed() - end -- end alive - end -- end for loop - elseif newstate <= 1 then - -- change MantisTimer to slow down or speed up - if self.MantisTimer.isrunning then - self.MantisTimer:Stop() - self.MantisTimer.isrunning = false - end - if self.MantisATimer.isrunning then - self.MantisATimer:Stop() - self.MantisATimer.isrunning = false - end - self.MantisTimer = TIMER:New(check,self.Detection) - self.MantisTimer:Start(5,interval,nil) - self.MantisTimer.isrunning = true - if self.advAwacs then - self.MantisATimer = TIMER:New(check,self.AWACS_Detection) - self.MantisATimer:Start(15,interval,nil) - self.MantisATimer.isrunning = true - end - end - end -- end newstate vs oldstate - end - -- timers to run the system - local interval = self.detectinterval - self.MantisTimer = TIMER:New(check,self.Detection) - self.MantisTimer:Start(5,interval,nil) - self.MantisTimer.isrunning = true - -- Awacs timer + end -- end alive + end -- end for loop + elseif newstate <= 1 then + -- change MantisTimer to slow down or speed up + self.detectinterval = interval + self.state2flag = false + end + end -- end newstate vs oldstate + return self + end + + --- [Internal] Function to set start state + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @return #MANTIS self + function MANTIS:onafterStart(From, Event, To) + self:T({From, Event, To}) + self:T(self.lid.."Starting MANTIS") + self:SetSAMStartState() + self.Detection = self:StartDetection() if self.advAwacs then - self.MantisATimer = TIMER:New(check,self.AWACS_Detection) - self.MantisATimer:Start(15,interval,nil) - self.MantisATimer.isrunning = true - end - -- timer to relocate HQ and EWR - if self.autorelocate then - local relointerval = math.random(1800,3600) -- random between 30 and 60 mins - self.MantisReloTimer = TIMER:New(relocate) - self.MantisReloTimer:Start(relointerval,relointerval,nil) - end - -- timer for advanced state check - if self.advanced then - self.MantisAdvTimer = TIMER:New(checkadvstate) - self.MantisAdvTimer:Start(30,interval*5,nil) + self.AWACS_Detection = self:StartAwacsDetection() end + self:__Status(self.detectinterval) return self end - --- Function to stop MANTIS + --- [Internal] Before status function for MANTIS -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State -- @return #MANTIS self - function MANTIS:Stop() - if self.MantisTimer.isrunning then - self.MantisTimer:Stop() + function MANTIS:onbeforeStatus(From, Event, To) + self:T({From, Event, To}) + -- check detection + if not self.state2flag then + self:_Check(self.Detection) end - if self.MantisATimer.isrunning then - self.MantisATimer:Stop() + + -- check Awacs + if self.advAwacs and not self.state2flag then + self:_Check(self.AWACS_Detection) end + + -- relocate HQ and EWR if self.autorelocate then - self.MantisReloTimer:Stop() + local relointerval = self.relointerval + local thistime = timer.getAbsTime() + local timepassed = thistime - self.TimeStamp + + local halfintv = math.floor(timepassed / relointerval) + + --self:T({timepassed=timepassed, halfintv=halfintv}) + + if halfintv >= 1 then + self.TimeStamp = timer.getAbsTime() + self:_Relocate() + self:__Relocating(1) + end end + + -- timer for advanced state check if self.advanced then - self.MantisAdvTimer:Stop() + self:_CheckAdvState() end - return self + + return self + end + + --- [Internal] Status function for MANTIS + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @return #MANTIS self + function MANTIS:onafterStatus(From,Event,To) + self:T({From, Event, To}) + local interval = self.detectinterval * -1 + self:__Status(interval) + return self end + --- [Internal] Function to stop MANTIS + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @return #MANTIS self + function MANTIS:onafterStop(From, Event, To) + self:T({From, Event, To}) + return self + end + + --- [Internal] Function triggered by Event Relocating + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @return #MANTIS self + function MANTIS:onafterRelocating(From, Event, To) + self:T({From, Event, To}) + return self + end + + --- [Internal] Function triggered by Event GreenState + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param Wrapper.Group#GROUP Group The GROUP object whose state was changed + -- @return #MANTIS self + function MANTIS:onafterGreenState(From, Event, To, Group) + self:T({From, Event, To, Group}) + return self + end + + --- [Internal] Function triggered by Event RedState + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param Wrapper.Group#GROUP Group The GROUP object whose state was changed + -- @return #MANTIS self + function MANTIS:onafterRedState(From, Event, To, Group) + self:T({From, Event, To, Group}) + return self + end + + --- [Internal] Function triggered by Event AdvStateChange + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param #number Oldstate Old state - 0 = green, 1 = amber, 2 = red + -- @param #number Newstate New state - 0 = green, 1 = amber, 2 = red + -- @param #number Interval Calculated detection interval based on state and advanced feature setting + -- @return #MANTIS self + function MANTIS:onafterAdvStateChange(From, Event, To, Oldstate, Newstate, Interval) + self:T({From, Event, To, Oldstate, Newstate, Interval}) + return self + end + + --- [Internal] Function triggered by Event ShoradActivated + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param #string Name Name of the GROUP which SHORAD shall protect + -- @param #number Radius Radius around the named group to find SHORAD groups + -- @param #number Ontime Seconds the SHORAD will stay active + function MANTIS:onafterShoradActivated(From, Event, To, Name, Radius, Ontime) + self:T({From, Event, To, Name, Radius, Ontime}) + return self + end end ----------------------------------------------------------------------- -- MANTIS end diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index aa2015393..c3dda1dff 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -662,7 +662,9 @@ end -- @param #string _description (optional) Description. -- @param #boolean _randomPoint (optional) Random yes or no. -- @param #boolean _nomessage (optional) If true, don\'t send a message to SAR. -function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _nomessage) +-- @param #string unitname (optional) Name of the lost unit. +-- @param #string typename (optional) Type of plane. +function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _nomessage, unitname, typename) self:T(self.lid .. " _SpawnCsarAtZone") local freq = self:_GenerateADFFrequency() local _triggerZone = ZONE:New(_zone) -- trigger to use as reference position @@ -671,7 +673,9 @@ function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _ return end - local _description = _description or "Unknown" + local _description = _description or "PoW" + local unitname = unitname or "Old Rusty" + local typename = typename or "Phantom II" local pos = {} if _randomPoint then @@ -690,7 +694,7 @@ function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _ _country = country.id.UN_PEACEKEEPERS end - self:_AddCsar(_coalition, _country, pos, "PoW", _description, nil, freq, _nomessage, _description) + self:_AddCsar(_coalition, _country, pos, typename, unitname, _description, freq, _nomessage, _description) return self end @@ -702,12 +706,14 @@ end -- @param #string Description (optional) Description. -- @param #boolean RandomPoint (optional) Random yes or no. -- @param #boolean Nomessage (optional) If true, don\'t send a message to SAR. +-- @param #string unitname (optional) Name of the lost unit. +-- @param #string typename (optional) Type of plane. -- @usage If missions designers want to spawn downed pilots into the field, e.g. at mission begin, to give the helicopter guys works, they can do this like so: -- -- -- Create downed "Pilot Wagner" in #ZONE "CSAR_Start_1" at a random point for the blue coalition --- my_csar:SpawnCSARAtZone( "CSAR_Start_1", coalition.side.BLUE, "Pilot Wagner", true ) -function CSAR:SpawnCSARAtZone(Zone, Coalition, Description, RandomPoint, Nomessage) - self:_SpawnCsarAtZone(Zone, Coalition, Description, RandomPoint, Nomessage) +-- my_csar:SpawnCSARAtZone( "CSAR_Start_1", coalition.side.BLUE, "Wagner", true, false, "Charly-1-1", "F5E" ) +function CSAR:SpawnCSARAtZone(Zone, Coalition, Description, RandomPoint, Nomessage, Unitname, Typename) + self:_SpawnCsarAtZone(Zone, Coalition, Description, RandomPoint, Nomessage, Unitname, Typename) return self end @@ -1199,7 +1205,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG end if _time <= 0 or _distance < self.loadDistance then if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then - self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in, bugger!", self.messageTime, true) + self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true) return true else self.landedStatus[_lookupKeyHeli] = nil @@ -1211,7 +1217,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG else if (_distance < self.loadDistance) then if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then - self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in, honk!", self.messageTime, true) + self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true) return true else self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) @@ -1252,7 +1258,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG self:_DisplayMessageToSAR(_heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you\'re too far away!", self.messageTime, true) else if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then - self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in, noob!", self.messageTime, true) + self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true) return true else self.hoverStatus[_lookupKeyHeli] = nil diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index f910323d7..8db791730 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -410,6 +410,7 @@ do -- my_ctld.enableHercules = true -- my_ctld.HercMinAngels = 155 -- for troop/cargo drop via chute in meters, ca 470 ft -- my_ctld.HercMaxAngels = 2000 -- for troop/cargo drop via chute in meters, ca 6000 ft +-- my_ctld.HercMaxSpeed = 77 -- 77mps or 270 kph or 150 kn -- -- Also, the following options need to be set to `true`: -- @@ -478,7 +479,7 @@ CTLD = { -- @field #table vhfbeacon Beacon info as #CTLD.ZoneBeacon --- Zone Type Info. --- @type CTLD. +-- @type CTLD.CargoZoneType CTLD.CargoZoneType = { LOAD = "load", DROP = "drop", @@ -651,6 +652,7 @@ function CTLD:New(Coalition, Prefixes, Alias) self.enableHercules = false self.HercMinAngels = 165 -- for troop/cargo drop via chute self.HercMaxAngels = 2000 -- for troop/cargo drop via chute + self.HercMaxSpeed = 77 -- 280 kph or 150kn eq 77 mps -- message suppression self.suppressmessages = false @@ -2283,10 +2285,12 @@ end local aheight = uheight - gheight -- height above ground local maxh = self.HercMinAngels-- 1500m local minh = self.HercMaxAngels -- 5000m - local mspeed = 2 -- 2 m/s - -- TODO:Add speed test for Herc, should not be above 280kph/150kn - --self:Tstring.format("%s Unit parameters: at %dm AGL with %dmps",self.lid,aheight,uspeed)) - if (aheight <= maxh) and (aheight >= minh) then + local maxspeed = self.HercMaxSpeed -- 77 mps + -- TODO: TEST - Speed test for Herc, should not be above 280kph/150kn + local kmspeed = uspeed * 3.6 + local knspeed = kmspeed / 1.86 + self:T(string.format("%s Unit parameters: at %dm AGL with %dmps | %dkph | %dkn",self.lid,aheight,uspeed,kmspeed,knspeed)) + if (aheight <= maxh) and (aheight >= minh) and (uspeed <= maxspeed) then -- yep within parameters outcome = true end @@ -2302,7 +2306,14 @@ end local inhover = self:IsCorrectHover(Unit) local htxt = "true" if not inhover then htxt = "false" end - local text = string.format("Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 2mps \n - In parameter: %s", self.minimumHoverHeight, self.maximumHoverHeight, htxt) + local text = "" + if _SETTINGS:IsMetric() then + text = string.format("Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 2mps \n - In parameter: %s", self.minimumHoverHeight, self.maximumHoverHeight, htxt) + else + local minheight = UTILS.MetersToFeet(self.minimumHoverHeight) + local maxheight = UTILS.MetersToFeet(self.maximumHoverHeight) + text = string.format("Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 6fts \n - In parameter: %s", minheight, maxheight, htxt) + end self:_SendMessage(text, 10, false, Group) --local m = MESSAGE:New(text,10,"CTLD",false):ToGroup(Group) return self @@ -2316,9 +2327,16 @@ end local inhover = self:IsCorrectFlightParameters(Unit) local htxt = "true" if not inhover then htxt = "false" end - local minheight = UTILS.MetersToFeet(self.HercMinAngels) - local maxheight = UTILS.MetersToFeet(self.HercMaxAngels) - local text = string.format("Flight parameters (airdrop):\n - Min height %dft \n - Max height %dft \n - In parameter: %s", minheight, maxheight, htxt) + local text = "" + if _SETTINGS:IsImperial() then + local minheight = UTILS.MetersToFeet(self.HercMinAngels) + local maxheight = UTILS.MetersToFeet(self.HercMaxAngels) + text = string.format("Flight parameters (airdrop):\n - Min height %dft \n - Max height %dft \n - In parameter: %s", minheight, maxheight, htxt) + else + local minheight = self.HercMinAngels + local maxheight = self.HercMaxAngels + text = string.format("Flight parameters (airdrop):\n - Min height %dm \n - Max height %dm \n - In parameter: %s", minheight, maxheight, htxt) + end self:_SendMessage(text, 10, false, Group) --local m = MESSAGE:New(text,15,"CTLD",false):ToGroup(Group) return self From 1ac40684defbc1f6a65b21a575017bc74c64a10b Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 14 Jul 2021 08:39:54 +0200 Subject: [PATCH 100/111] Added option to force description on injected pilots for scripting --- Moose Development/Moose/Ops/CSAR.lua | 39 ++++++++++++++-------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index c3dda1dff..49a357e34 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -609,7 +609,8 @@ end -- @param #number _freq Frequency -- @param #boolean noMessage -- @param #string _description Description -function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description ) +-- @param #boolean forcedesc Use the description only for the pilot track entry +function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description, forcedesc ) self:T(self.lid .. " _AddCsar") self:T({_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description}) @@ -622,11 +623,10 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point,_freq) - local _typeName = _typeName or "PoW" + local _typeName = _typeName or "Pilot" if not noMessage then self:_DisplayToAllSAR("MAYDAY MAYDAY! " .. _typeName .. " is down. ", self.coalition, 10) - --local m = MESSAGE:New("MAYDAY MAYDAY! " .. _typeName .. " is down. ",10,"INFO"):ToCoalition(self.coalition) end if _freq then @@ -635,15 +635,14 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla self:_AddSpecialOptions(_spawnedGroup) - local _text = " " - if _playerName ~= nil then - _text = "Pilot " .. _playerName .. " of " .. _unitName .. " - " .. _typeName - elseif _typeName ~= nil then - _text = "AI Pilot of " .. _unitName .. " - " .. _typeName - else - _text = _description - end - + local _text = _description + if not forcedesc then + if _playerName ~= nil then + _text = "Pilot " .. _playerName + elseif _unitName ~= nil then + _text = "AI Pilot of " .. _unitName + end + end self:T({_spawnedGroup, _alias}) local _GroupName = _spawnedGroup:GetName() or _alias @@ -664,7 +663,8 @@ end -- @param #boolean _nomessage (optional) If true, don\'t send a message to SAR. -- @param #string unitname (optional) Name of the lost unit. -- @param #string typename (optional) Type of plane. -function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _nomessage, unitname, typename) +-- @param #boolean forcedesc (optional) Force to use the description passed only for the pilot track entry. Use to have fully custom names. +function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _nomessage, unitname, typename, forcedesc) self:T(self.lid .. " _SpawnCsarAtZone") local freq = self:_GenerateADFFrequency() local _triggerZone = ZONE:New(_zone) -- trigger to use as reference position @@ -694,7 +694,7 @@ function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _ _country = country.id.UN_PEACEKEEPERS end - self:_AddCsar(_coalition, _country, pos, typename, unitname, _description, freq, _nomessage, _description) + self:_AddCsar(_coalition, _country, pos, typename, unitname, _description, freq, _nomessage, _description, forcedesc) return self end @@ -706,14 +706,15 @@ end -- @param #string Description (optional) Description. -- @param #boolean RandomPoint (optional) Random yes or no. -- @param #boolean Nomessage (optional) If true, don\'t send a message to SAR. --- @param #string unitname (optional) Name of the lost unit. --- @param #string typename (optional) Type of plane. --- @usage If missions designers want to spawn downed pilots into the field, e.g. at mission begin, to give the helicopter guys works, they can do this like so: +-- @param #string Unitname (optional) Name of the lost unit. +-- @param #string Typename (optional) Type of plane. +-- @param #boolean Forcedesc (optional) Force to use the **description passed only** for the pilot track entry. Use to have fully custom names. +-- @usage If missions designers want to spawn downed pilots into the field, e.g. at mission begin, to give the helicopter guys work, they can do this like so: -- -- -- Create downed "Pilot Wagner" in #ZONE "CSAR_Start_1" at a random point for the blue coalition -- my_csar:SpawnCSARAtZone( "CSAR_Start_1", coalition.side.BLUE, "Wagner", true, false, "Charly-1-1", "F5E" ) -function CSAR:SpawnCSARAtZone(Zone, Coalition, Description, RandomPoint, Nomessage, Unitname, Typename) - self:_SpawnCsarAtZone(Zone, Coalition, Description, RandomPoint, Nomessage, Unitname, Typename) +function CSAR:SpawnCSARAtZone(Zone, Coalition, Description, RandomPoint, Nomessage, Unitname, Typename, Forcedesc) + self:_SpawnCsarAtZone(Zone, Coalition, Description, RandomPoint, Nomessage, Unitname, Typename, Forcedesc) return self end From 93a8086ff6b3bccefb48efd7ed68ef177dd6f81a Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 14 Jul 2021 15:37:57 +0200 Subject: [PATCH 101/111] Update Mantis.lua (#1569) Added state tracker so that Red/Green Events only get triggered when a state actually changes. --- Moose Development/Moose/Functional/Mantis.lua | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 10f0bf1a6..1186378e1 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -194,6 +194,7 @@ MANTIS = { UseEmOnOff = false, TimeStamp = 0, state2flag = false, + SamStateTracker = {}, } --- Advanced state enumerator @@ -276,6 +277,7 @@ do self.TimeStamp = timer.getAbsTime() self.relointerval = math.random(1800,3600) -- random between 30 and 60 mins self.state2flag = false + self.SamStateTracker = {} -- table to hold alert states, so we don't trigger state changes twice in adv mode if EmOnOff then if EmOnOff == false then @@ -323,7 +325,7 @@ do end -- @field #string version - self.version="0.5.1" + self.version="0.5.2" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) --- FSM Functions --- @@ -867,7 +869,7 @@ do local engagerange = self.engagerange -- firing range in % of max --cycle through groups and set alarm state etc for _i,_group in pairs (SAM_Grps) do - local group = _group + local group = _group -- Wrapper.Group#GROUP -- TODO: add emissions on/off if self.UseEmOnOff then group:EnableEmission(false) @@ -876,11 +878,12 @@ do group:OptionAlarmStateGreen() -- AI off end group:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,engagerange) --default engagement will be 75% of firing range - if group:IsGround() then + if group:IsGround() and group:IsAlive() then local grpname = group:GetName() local grpcoord = group:GetCoordinate() table.insert( SAM_Tbl, {grpname, grpcoord}) table.insert( SEAD_Grps, grpname ) + self.SamStateTracker[grpname] = "GREEN" end end self.SAM_Table = SAM_Tbl @@ -907,7 +910,7 @@ do for _i,_group in pairs (SAM_Grps) do local group = _group group:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,engagerange) --engagement will be 75% of firing range - if group:IsGround() then + if group:IsGround() and group:IsAlive() then local grpname = group:GetName() local grpcoord = group:GetCoordinate() table.insert( SAM_Tbl, {grpname, grpcoord}) -- make the table lighter, as I don't really use the zone here @@ -982,7 +985,10 @@ do samgroup:EnableEmission(true) end samgroup:OptionAlarmStateRed() - self:__RedState(1,samgroup) + if self.SamStateTracker[name] ~= "RED" then + self:__RedState(1,samgroup) + self.SamStateTracker[name] = "RED" + end -- link in to SHORAD if available -- DONE: Test integration fully if self.ShoradLink and Distance < self.ShoradActDistance then -- don't give SHORAD position away too early @@ -1001,16 +1007,13 @@ do if samgroup:IsAlive() then -- switch off SAM if self.UseEmOnOff then - -- TODO: add emissions on/off samgroup:EnableEmission(false) - self:__GreenState(1,samgroup) - --samgroup:SetAIOff() - else - samgroup:OptionAlarmStateGreen() - self:__GreenState(1,samgroup) end - --samgroup:OptionROEWeaponFree() - --samgroup:SetAIOn() + samgroup:OptionAlarmStateGreen() + if self.SamStateTracker[name] ~= "GREEN" then + self:__GreenState(1,samgroup) + self.SamStateTracker[name] = "GREEN" + end local text = string.format("SAM %s switched to alarm state GREEN!", name) local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) if self.verbose then self:I(self.lid..text) end From efe41a5e21a1047aa8f47868d12b431f45e834ed Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 15 Jul 2021 17:16:25 +0200 Subject: [PATCH 102/111] CSAR and CTLD - use Frequency generation moved to UTILS CTLD - added option to drop crates anywhere MANTIS - added state tracker to call Green/Red state change events only once UTILS - added Marianas NDBs to Frequency generation --- Moose Development/Moose/Functional/Mantis.lua | 7 ++ Moose Development/Moose/Ops/CSAR.lua | 34 ++++++--- Moose Development/Moose/Ops/CTLD.lua | 76 ++++++++++++------- Moose Development/Moose/Utilities/Utils.lua | 4 +- 4 files changed, 80 insertions(+), 41 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 1186378e1..3b185043c 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -1136,6 +1136,13 @@ do -- @return #MANTIS self function MANTIS:onafterStatus(From,Event,To) self:T({From, Event, To}) + -- Display some states + if self.debug then + self:I(self.lid .. "Status Report") + for _name,_state in pairs(self.SamStateTracker) do + self:I(string.format("Site %s\tStatus %s",_name,_state)) + end + end local interval = self.detectinterval * -1 self:__Status(interval) return self diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 49a357e34..ecd048504 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -214,6 +214,8 @@ CSAR = { -- @field #number timestamp Timestamp for approach process --- Updated and sorted list of known NDB beacons (in kHz!) from the available maps. + +--[[ Moved to Utils -- @field #CSAR.SkipFrequencies CSAR.SkipFrequencies = { 214,274,291.5,295,297.5, @@ -229,7 +231,8 @@ CSAR.SkipFrequencies = { 905,907,920,935,942,950,995, 1000,1025,1030,1050,1065,1116,1175,1182,1210 } - +--]] + --- All slot / Limit settings -- @type CSAR.AircraftType -- @field #string typename Unit type name. @@ -245,7 +248,7 @@ CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.8r1" +CSAR.version="0.1.8r2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -872,7 +875,11 @@ function CSAR:_EventHandler(EventData) end if _place:GetCoalition() == self.coalition or _place:GetCoalition() == coalition.side.NEUTRAL then - self:_RescuePilots(_unit) + if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_event.IniUnitName) then + self:_DisplayMessageToSAR(_unit, "Open the door to let me out!", self.messageTime, true) + else + self:_RescuePilots(_unit) + end else self:T(string.format("Airfield %d, Unit %d", _place:GetCoalition(), _unit:GetCoalition())) end @@ -1113,27 +1120,27 @@ function CSAR:_IsLoadingDoorOpen( unit_name ) local type_name = unit:getTypeName() if type_name == "Mi-8MT" and unit:getDrawArgumentValue(86) == 1 or unit:getDrawArgumentValue(250) == 1 then - self:I(unit_name .. " Cargo doors are open or cargo door not present") + self:T(unit_name .. " Cargo doors are open or cargo door not present") ret_val = true end if type_name == "Mi-24P" and unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(86) == 1 then - self:I(unit_name .. " a side door is open") + self:T(unit_name .. " a side door is open") ret_val = true end if type_name == "UH-1H" and unit:getDrawArgumentValue(43) == 1 or unit:getDrawArgumentValue(44) == 1 then - self:I(unit_name .. " a side door is open ") + self:T(unit_name .. " a side door is open ") ret_val = true end if string.find(type_name, "SA342" ) and unit:getDrawArgumentValue(34) == 1 or unit:getDrawArgumentValue(38) == 1 then - self:I(unit_name .. " front door(s) are open") + self:T(unit_name .. " front door(s) are open") ret_val = true end if ret_val == false then - self:I(unit_name .. " all doors are closed") + self:T(unit_name .. " all doors are closed") end return ret_val @@ -1346,8 +1353,12 @@ function CSAR:_ScheduledSARFlight(heliname,groupname) end if _dist < 200 and _heliUnit:InAir() == false then + if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(heliname) then + self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me out!", self.messageTime, true) + else self:_RescuePilots(_heliUnit) return + end end --queue up @@ -1755,11 +1766,13 @@ end -- @param #CSAR self function CSAR:_GenerateVHFrequencies() self:T(self.lid .. " _GenerateVHFrequencies") - local _skipFrequencies = self.SkipFrequencies + --local _skipFrequencies = self.SkipFrequencies local FreeVHFFrequencies = {} - local UsedVHFFrequencies = {} + --local UsedVHFFrequencies = {} + FreeVHFFrequencies = UTILS.GenerateVHFrequencies() + --[[ moved to UTILS -- first range local _start = 200000 while _start < 400000 do @@ -1819,6 +1832,7 @@ function CSAR:_GenerateVHFrequencies() _start = _start + 50000 end + --]] self.FreeVHFFrequencies = FreeVHFFrequencies return self end diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 8db791730..f4363342f 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -262,6 +262,7 @@ do -- -- my_ctld.useprefix = true -- (DO NOT SWITCH THIS OFF UNLESS YOU KNOW WHAT YOU ARE DOING!) Adjust **before** starting CTLD. If set to false, *all* choppers of the coalition side will be enabled for CTLD. -- my_ctld.CrateDistance = 30 -- List and Load crates in this radius only. +-- my_ctld.dropcratesanywhere = false -- Option to allow crates to be dropped anywhere. -- my_ctld.maximumHoverHeight = 15 -- Hover max this high to load. -- my_ctld.minimumHoverHeight = 4 -- Hover min this low to load. -- my_ctld.forcehoverload = true -- Crates (not: troops) can only be loaded while hovering. @@ -516,6 +517,8 @@ CTLD.UnitTypes = { } --- Updated and sorted known NDB beacons (in kHz!) from the available maps + +--[[ -- Now in UTILS -- @field #CTLD.SkipFrequencies CTLD.SkipFrequencies = { 214,274,291.5,295,297.5, @@ -531,10 +534,11 @@ CTLD.SkipFrequencies = { 905,907,920,935,942,950,995, 1000,1025,1030,1050,1065,1116,1175,1182,1210 } - +--]] + --- CTLD class version. -- @field #string version -CTLD.version="0.1.3r2" +CTLD.version="0.1.4r1" --- Instantiate a new CTLD. -- @param #CTLD self @@ -643,6 +647,7 @@ function CTLD:New(Coalition, Prefixes, Alias) self.minimumHoverHeight = 4 self.forcehoverload = true self.hoverautoloading = true + self.dropcratesanywhere = false -- #1570 self.smokedistance = 2000 self.movetroopstowpzone = true @@ -794,13 +799,15 @@ end function CTLD:_GenerateUHFrequencies() self:T(self.lid .. " _GenerateUHFrequencies") self.FreeUHFFrequencies = {} + self.FreeUHFFrequencies = UTILS.GenerateUHFrequencies() + --[[ local _start = 220000000 while _start < 399000000 do table.insert(self.FreeUHFFrequencies, _start) _start = _start + 500000 end - + --]] return self end @@ -809,7 +816,8 @@ end function CTLD:_GenerateFMFrequencies() self:T(self.lid .. " _GenerateFMrequencies") self.FreeFMFrequencies = {} - + self.FreeFMFrequencies = UTILS.GenerateFMFrequencies() + --[[ for _first = 3, 7 do for _second = 0, 5 do for _third = 0, 9 do @@ -818,7 +826,7 @@ function CTLD:_GenerateFMFrequencies() end end end - + --]] return self end @@ -826,6 +834,13 @@ end -- @param #CTLD self function CTLD:_GenerateVHFrequencies() self:T(self.lid .. " _GenerateVHFrequencies") + + self.FreeVHFFrequencies = {} + self.UsedVHFFrequencies = {} + + self.FreeVHFFrequencies = UTILS.GenerateVHFrequencies() + + --[[ local _skipFrequencies = self.SkipFrequencies self.FreeVHFFrequencies = {} @@ -882,10 +897,11 @@ function CTLD:_GenerateVHFrequencies() end _start = _start + 50000 end - + --]] return self end +--[[ -- unused --- (Internal) Function to generate valid laser codes. -- @param #CTLD self function CTLD:_GenerateLaserCodes() @@ -926,6 +942,8 @@ function CTLD:_ContainsDigit(_number, _numberToFind) return false end +--]] + --- (Internal) Event handler function -- @param #CTLD self -- @param Core.Event#EVENTDATA EventData @@ -1056,24 +1074,26 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) if not drop then inzone = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) else - inzone = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) + if self.dropcratesanywhere then -- #1570 + inzone = true + else + inzone = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) + end end if not inzone then self:_SendMessage("You are not close enough to a logistics zone!", 10, false, Group) - --local m = MESSAGE:New("You are not close enough to a logistics zone!",15,"CTLD"):ToGroup(Group) if not self.debug then return self end end - + -- avoid crate spam local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities - --local capabilities = self.UnitTypes[Unit:GetTypeName()] -- #CTLD.UnitCapabilities local canloadcratesno = capabilities.cratelimit local loaddist = self.CrateDistance or 30 local nearcrates, numbernearby = self:_FindCratesNearby(Group,Unit,loaddist) if numbernearby >= canloadcratesno and not drop then self:_SendMessage("There are enough crates nearby already! Take care of those first!", 10, false, Group) - --local m = MESSAGE:New("There are enough crates nearby already! Take care of those first!",15,"CTLD"):ToGroup(Group) + return self end -- spawn crates in front of helicopter @@ -1082,7 +1102,6 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) local number = number or cargotype:GetCratesNeeded() --#number local cratesneeded = cargotype:GetCratesNeeded() --#number local cratename = cargotype:GetName() - --self:Tself.lid .. string.format("Crate %s requested", cratename)) local cratetemplate = "Container"-- #string -- get position and heading of heli local position = Unit:GetCoordinate() @@ -1130,7 +1149,6 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) self:__CratesDropped(1, Group, Unit, droppedcargo) end self:_SendMessage(text, 10, false, Group) - --local m = MESSAGE:New(text,15,"CTLD",true):ToGroup(Group) return self end @@ -1149,7 +1167,6 @@ function CTLD:_ListCratesNearby( _group, _unit) for _,_entry in pairs (crates) do local entry = _entry -- #CTLD_CARGO local name = entry:GetName() --#string - -- TODO Meaningful sorting/aggregation local dropped = entry:WasDropped() if dropped then text:Add(string.format("Dropped crate for %s",name)) @@ -1158,14 +1175,12 @@ function CTLD:_ListCratesNearby( _group, _unit) end end if text:GetCount() == 1 then - text:Add("--------- N O N E ------------") + text:Add(" N O N E") end text:Add("------------------------------------------------------------") self:_SendMessage(text:Text(), 30, true, _group) - --local m = MESSAGE:New(text:Text(),15,"CTLD",true):ToGroup(_group) else self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist), 10, false, _group) - --local m = MESSAGE:New(string.format("No (loadable) crates within %d meters!",finddist),15,"CTLD",true):ToGroup(_group) end return self end @@ -1347,7 +1362,7 @@ function CTLD:_ListCargo(Group, Unit) report:Add("------------------------------------------------------------") report:Add(string.format("Troops: %d(%d), Crates: %d(%d)",no_troops,trooplimit,no_crates,cratelimit)) report:Add("------------------------------------------------------------") - report:Add("-- TROOPS --") + report:Add(" -- TROOPS --") for _,_cargo in pairs(cargotable) do local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum @@ -1356,10 +1371,10 @@ function CTLD:_ListCargo(Group, Unit) end end if report:GetCount() == 4 then - report:Add("--------- N O N E ------------") + report:Add(" N O N E") end report:Add("------------------------------------------------------------") - report:Add("-- CRATES --") + report:Add(" -- CRATES --") local cratecount = 0 for _,_cargo in pairs(cargotable) do local cargo = _cargo -- #CTLD_CARGO @@ -1370,7 +1385,7 @@ function CTLD:_ListCargo(Group, Unit) end end if cratecount == 0 then - report:Add("--------- N O N E ------------") + report:Add(" N O N E") end report:Add("------------------------------------------------------------") local text = report:Text() @@ -1498,13 +1513,16 @@ end -- @param Wrappe.Unit#UNIT Unit function CTLD:_UnloadCrates(Group, Unit) self:T(self.lid .. " _UnloadCrates") - -- check if we are in DROP zone - local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) - if not inzone then - self:_SendMessage("You are not close enough to a drop zone!", 10, false, Group) - --local m = MESSAGE:New("You are not close enough to a drop zone!",15,"CTLD"):ToGroup(Group) - if not self.debug then - return self + + if not self.dropcratesanywhere then -- #1570 + -- check if we are in DROP zone + local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) + if not inzone then + self:_SendMessage("You are not close enough to a drop zone!", 10, false, Group) + --local m = MESSAGE:New("You are not close enough to a drop zone!",15,"CTLD"):ToGroup(Group) + if not self.debug then + return self + end end end -- check for hover unload @@ -2058,7 +2076,7 @@ function CTLD:_ListRadioBeacons(Group, Unit) end end if report:GetCount() == 1 then - report:Add("--------- N O N E ------------") + report:Add(" N O N E") end report:Add("------------------------------------------------------------") self:_SendMessage(report:Text(), 30, true, Group) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 609ace372..87198a2d7 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1631,10 +1631,10 @@ function UTILS.GenerateVHFrequencies() local _skipFrequencies = { 214,274,291.5,295,297.5, 300.5,304,307,309.5,311,312,312.5,316, - 320,324,328,329,330,336,337, + 320,324,328,329,330,332,336,337, 342,343,348,351,352,353,358, 363,365,368,372.5,374, - 380,381,384,389,395,396, + 380,381,384,385,389,395,396, 414,420,430,432,435,440,450,455,462,470,485, 507,515,520,525,528,540,550,560,570,577,580, 602,625,641,662,670,680,682,690, From 25e118f3dc7a1925b64b408a00332295d2550af7 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 17 Jul 2021 15:48:18 +0200 Subject: [PATCH 103/111] Added correct Player Mi-8MT unitname CSAR - logic change to detect dead pilots, if they are not set to immortal. Added FSM event "KIA" --- Moose Development/Moose/Ops/CSAR.lua | 182 +++++++++------------------ Moose Development/Moose/Ops/CTLD.lua | 22 +--- 2 files changed, 58 insertions(+), 146 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index ecd048504..df419a5b9 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -1,3 +1,4 @@ + --- **Ops** -- Combat Search and Rescue. -- -- === @@ -212,7 +213,8 @@ CSAR = { -- @field #string player Player name if applicable. -- @field Wrapper.Group#GROUP group Spawned group object. -- @field #number timestamp Timestamp for approach process - +-- @field #boolean alive Group is alive or dead/rescued +-- --- Updated and sorted list of known NDB beacons (in kHz!) from the available maps. --[[ Moved to Utils @@ -242,13 +244,14 @@ CSAR.AircraftType["SA342Minigun"] = 2 CSAR.AircraftType["SA342L"] = 4 CSAR.AircraftType["SA342M"] = 4 CSAR.AircraftType["UH-1H"] = 8 -CSAR.AircraftType["Mi-8MTV2"] = 12 +CSAR.AircraftType["Mi-8MTV2"] = 12 +CSAR.AircraftType["Mi-8MT"] = 12 CSAR.AircraftType["Mi-24P"] = 8 CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.8r2" +CSAR.version="0.1.8r3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -320,6 +323,7 @@ function CSAR:New(Coalition, Template, Alias) self:AddTransition("*", "Boarded", "*") -- Pilot boarded. self:AddTransition("*", "Returning", "*") -- CSAR able to return to base. self:AddTransition("*", "Rescued", "*") -- Pilot at MASH. + self:AddTransition("*", "KIA", "*") -- Pilot killed in action. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. -- tables, mainly for tracking actions @@ -461,6 +465,14 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string HeliName Name of the helicopter group. -- @param #number PilotsSaved Number of the saved pilots on board when landing. + --- On After "KIA" event. Pilot is dead. + -- @function [parent=#CSAR] OnAfterKIA + -- @param #CSAR self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string Pilotname Name of the pilot KIA. + return self end @@ -494,6 +506,7 @@ function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Descript DownedPilot.typename = Typename or "" DownedPilot.group = Group DownedPilot.timestamp = 0 + DownedPilot.alive = true -- Add Pilot local PilotTable = self.downedPilots @@ -930,7 +943,7 @@ function CSAR:_CheckNameInDownedPilots(name) local found = false local table = nil for _,_pilot in pairs(PilotTable) do - if _pilot.name == name then + if _pilot.name == name and _pilot.alive == true then found = true table = _pilot break @@ -947,25 +960,10 @@ end function CSAR:_RemoveNameFromDownedPilots(name,force) local PilotTable = self.downedPilots --#CSAR.DownedPilot local found = false - for _,_pilot in pairs(PilotTable) do + for _index,_pilot in pairs(PilotTable) do if _pilot.name == name then - local group = _pilot.group -- Wrapper.Group#GROUP - if group then - if (not group:IsAlive()) or ( force == true) then -- don\'t delete groups which still exist - found = true - _pilot.desc = nil - _pilot.frequency = nil - _pilot.index = nil - _pilot.name = nil - _pilot.originalUnit = nil - _pilot.player = nil - _pilot.side = nil - _pilot.typename = nil - _pilot.group = nil - _pilot.timestamp = nil - end + self.downedPilots[_index].alive = false end - end end return found end @@ -988,7 +986,7 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) end local _woundedGroup = _downedpilot.group - if _woundedGroup ~= nil then + if _woundedGroup ~= nil and _woundedGroup:IsAlive() then local _heliUnit = self:_GetSARHeli(_heliName) -- Wrapper.Unit#UNIT local _lookupKeyHeli = _heliName .. "_" .. _woundedGroupName --lookup key for message state tracking @@ -1001,7 +999,6 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) return end - --if self:_CheckGroupNotKIA(_woundedGroup, _woundedGroupName, _heliUnit, _heliName) then local _heliCoord = _heliUnit:GetCoordinate() local _leaderCoord = _woundedGroup:GetCoordinate() local _distance = self:_GetDistance(_heliCoord,_leaderCoord) @@ -1019,7 +1016,10 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) end else self:T("...Downed Pilot KIA?!") - self:_RemoveNameFromDownedPilots(_downedpilot.name) + if not _downedpilot.alive then + --self:__KIA(1,_downedpilot.name) + self:_RemoveNameFromDownedPilots(_downedpilot.name, true) + end end return self end @@ -1295,36 +1295,6 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG end end ---- (Internal) Check if group not KIA. --- @param #CSAR self --- @param Wrapper.Group#GROUP _woundedGroup --- @param #string _woundedGroupName --- @param Wrapper.Unit#UNIT _heliUnit --- @param #string _heliName --- @return #boolean Outcome -function CSAR:_CheckGroupNotKIA(_woundedGroup, _woundedGroupName, _heliUnit, _heliName) - self:T(self.lid .. " _CheckGroupNotKIA") - -- check if unit has died or been picked up - local inTransit = false - if _woundedGroup and _heliUnit then - for _currentHeli, _groups in pairs(self.inTransitGroups) do - if _groups[_woundedGroupName] then - inTransit = true - self:_DisplayToAllSAR(string.format("%s has been picked up by %s", _woundedGroupName, _currentHeli), self.coalition, self.messageTime) - break - end -- end name check - end -- end loop - if not inTransit then - -- KIA - self:_DisplayToAllSAR(string.format("%s is KIA ", _woundedGroupName), self.coalition, self.messageTime) - end - --stops the message being displayed again - self:_RemoveNameFromDownedPilots(_woundedGroupName) - end - --continue - return inTransit -end - --- (Internal) Monitor in-flight returning groups. -- @param #CSAR self -- @param #string heliname Heli name @@ -1475,7 +1445,7 @@ function CSAR:_DisplayActiveSAR(_unitName) self:T({Table=_value}) --local _woundedGroup = GROUP:FindByName(_groupName) local _woundedGroup = _value.group - if _woundedGroup then + if _woundedGroup and _value.alive then local _coordinatesText = self:_GetPositionOfWounded(_woundedGroup) local _helicoord = _heli:GetCoordinate() local _woundcoord = _woundedGroup:GetCoordinate() @@ -1769,70 +1739,7 @@ function CSAR:_GenerateVHFrequencies() --local _skipFrequencies = self.SkipFrequencies local FreeVHFFrequencies = {} - --local UsedVHFFrequencies = {} FreeVHFFrequencies = UTILS.GenerateVHFrequencies() - - --[[ moved to UTILS - -- first range - local _start = 200000 - while _start < 400000 do - - -- skip existing NDB frequencies - local _found = false - for _, value in pairs(_skipFrequencies) do - if value * 1000 == _start then - _found = true - break - end - end - - if _found == false then - table.insert(FreeVHFFrequencies, _start) - end - - _start = _start + 10000 - end - - -- second range - _start = 400000 - while _start < 850000 do - - -- skip existing NDB frequencies - local _found = false - for _, value in pairs(_skipFrequencies) do - if value * 1000 == _start then - _found = true - break - end - end - - if _found == false then - table.insert(FreeVHFFrequencies, _start) - end - - _start = _start + 10000 - end - - -- third range - _start = 850000 - while _start <= 999000 do -- updated for Gazelle - - -- skip existing NDB frequencies - local _found = false - for _, value in pairs(_skipFrequencies) do - if value * 1000 == _start then - _found = true - break - end - end - - if _found == false then - table.insert(FreeVHFFrequencies, _start) - end - - _start = _start + 50000 - end - --]] self.FreeVHFFrequencies = FreeVHFFrequencies return self end @@ -1929,8 +1836,8 @@ function CSAR:_CountActiveDownedPilots() self:T(self.lid .. " _CountActiveDownedPilots") local PilotsInFieldN = 0 for _, _unitName in pairs(self.downedPilots) do - self:T({_unitName}) - if _unitName.name ~= nil then + self:T({_unitName.desc}) + if _unitName.alive == true then PilotsInFieldN = PilotsInFieldN + 1 end end @@ -1981,6 +1888,26 @@ function CSAR:onafterStart(From, Event, To) return self end +--- (Internal) Function called before Status() event. +-- @param #CSAR self +function CSAR:_CheckDownedPilotTable() + local pilots = self.downedPilots + for _,_entry in pairs (pilots) do + self:T("Checking for " .. _entry.name) + self:T({entry=_entry}) + local group = _entry.group + if not group:IsAlive() then + self:T("Group is dead") + if _entry.alive == true then + self:T("Switching .alive to false") + self:__KIA(1,_entry.desc) + self:_RemoveNameFromDownedPilots(_entry.name,true) + end + end + end + return self +end + --- (Internal) Function called before Status() event. -- @param #CSAR self. -- @param #string From From state. @@ -1991,15 +1918,18 @@ function CSAR:onbeforeStatus(From, Event, To) -- housekeeping self:_AddMedevacMenuItem() self:_RefreshRadioBeacons() + self:_CheckDownedPilotTable() for _,_sar in pairs (self.csarUnits) do local PilotTable = self.downedPilots for _,_entry in pairs (PilotTable) do - local entry = _entry -- #CSAR.DownedPilot - local name = entry.name - local timestamp = entry.timestamp or 0 - local now = timer.getAbsTime() - if now - timestamp > 17 then -- only check if we\'re not in approach mode, which is iterations of 5 and 10. - self:_CheckWoundedGroupStatus(_sar,name) + if _entry.alive then + local entry = _entry -- #CSAR.DownedPilot + local name = entry.name + local timestamp = entry.timestamp or 0 + local now = timer.getAbsTime() + if now - timestamp > 17 then -- only check if we\'re not in approach mode, which is iterations of 5 and 10. + self:_CheckWoundedGroupStatus(_sar,name) + end end end end diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index f4363342f..d65a0deba 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -510,32 +510,13 @@ CTLD.UnitTypes = { ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2}, ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, ["Mi-8MTV2"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12}, + ["Mi-8MT"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12}, ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0}, ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8}, ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8}, ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64}, -- 19t cargo, 64 paratroopers } ---- Updated and sorted known NDB beacons (in kHz!) from the available maps - ---[[ -- Now in UTILS --- @field #CTLD.SkipFrequencies -CTLD.SkipFrequencies = { - 214,274,291.5,295,297.5, - 300.5,304,307,309.5,311,312,312.5,316, - 320,324,328,329,330,336,337, - 342,343,348,351,352,353,358, - 363,365,368,372.5,374, - 380,381,384,389,395,396, - 414,420,430,432,435,440,450,455,462,470,485, - 507,515,520,525,528,540,550,560,570,577,580,602,625,641,662,670,680,682,690, - 705,720,722,730,735,740,745,750,770,795, - 822,830,862,866, - 905,907,920,935,942,950,995, - 1000,1025,1030,1050,1065,1116,1175,1182,1210 - } ---]] - --- CTLD class version. -- @field #string version CTLD.version="0.1.4r1" @@ -2449,6 +2430,7 @@ end -- @return #CTLD self function CTLD:onafterStart(From, Event, To) self:T({From, Event, To}) + self:I(self.lid .. "Started.") if self.useprefix or self.enableHercules then local prefix = self.prefixes --self:T{prefix=prefix}) From 09785ef451fecb771bd6415d22be4385db042ca7 Mon Sep 17 00:00:00 2001 From: bbirchnz Date: Sun, 18 Jul 2021 21:01:45 +1000 Subject: [PATCH 104/111] - ctld: add troop extract (#1574) --- Moose Development/Moose/Ops/CTLD.lua | 118 +++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index d65a0deba..6fa3ff5f9 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -577,6 +577,7 @@ function CTLD:New(Coalition, Prefixes, Alias) self:AddTransition("Stopped", "Start", "Running") -- Start FSM. self:AddTransition("*", "Status", "*") -- CTLD status update. self:AddTransition("*", "TroopsPickedUp", "*") -- CTLD pickup event. + self:AddTransition("*", "TroopsExtracted", "*") -- CTLD extract event. self:AddTransition("*", "CratesPickedUp", "*") -- CTLD pickup event. self:AddTransition("*", "TroopsDeployed", "*") -- CTLD deploy event. self:AddTransition("*", "TroopsRTB", "*") -- CTLD deploy event. @@ -693,6 +694,17 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param #CTLD_CARGO Cargo Cargo troops. -- @return #CTLD self + --- FSM Function OnAfterTroopsExtracted. + -- @function [parent=#CTLD] OnAfterTroopsExtracted + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #CTLD_CARGO Cargo Cargo troops. + -- @return #CTLD self + --- FSM Function OnAfterCratesPickedUp. -- @function [parent=#CTLD] OnAfterCratesPickedUp -- @param #CTLD self @@ -1038,6 +1050,96 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) return self end + --- (Internal) Function to extract (load from the field) troops into a heli. + -- @param #CTLD self + -- @param Wrapper.Group#GROUP Group + -- @param Wrapper.Unit#UNIT Unit + -- @param #CTLD_CARGO Cargotype + function CTLD:_ExtractTroops(Group, Unit) + self:T(self.lid .. " _ExtractTroops") + -- landed or hovering over load zone? + local grounded = not self:IsUnitInAir(Unit) + local hoverload = self:CanHoverLoad(Unit) + + if not grounded and not hoverload then + self:_SendMessage("You need to land or hover in position to load!", 10, false, Group) + if not self.debug then return self end + end + -- load troops into heli + local unit = Unit -- Wrapper.Unit#UNIT + local unitname = unit:GetName() + -- see if this heli can load troops + local unittype = unit:GetTypeName() + local capabilities = self:_GetUnitCapabilities(Unit) + local cantroops = capabilities.troops -- #boolean + local trooplimit = capabilities.trooplimit -- #number + local unitcoord = unit:GetCoordinate() + + -- find nearest group of deployed troops + local nearestGroup = nil + local nearestGroupIndex = -1 + local nearestDistance = 10000000 + for k,v in pairs(self.DroppedTroops) do + local distance = self:_GetDistance(v:GetCoordinate(),unitcoord) + if distance < nearestDistance then + nearestGroup = v + nearestGroupIndex = k + nearestDistance = distance + end + end + + if nearestGroup == nil or nearestDistance > self.CrateDistance then + self:_SendMessage("No units close enough to extract!", 10, false, Group) + return self + end + -- find matching cargo type + local groupType = string.match(nearestGroup:GetName(), "(.+)-(.+)$") + local Cargotype = nil + for k,v in pairs(self.Cargo_Troops) do + if v.Name == groupType then + Cargotype = v + break + end + end + + if Cargotype == nil then + self:_SendMessage("Can't find a matching cargo type for " .. groupType, 10, false, Group) + return self + end + + local troopsize = Cargotype:GetCratesNeeded() -- #number + -- have we loaded stuff already? + local numberonboard = 0 + local loaded = {} + if self.Loaded_Cargo[unitname] then + loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo + numberonboard = loaded.Troopsloaded or 0 + else + loaded = {} -- #CTLD.LoadedCargo + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} + end + if troopsize + numberonboard > trooplimit then + self:_SendMessage("Sorry, we\'re crammed already!", 10, false, Group) + return + else + self.CargoCounter = self.CargoCounter + 1 + local loadcargotype = CTLD_CARGO:New(self.CargoCounter, Cargotype.Name, Cargotype.Templates, CTLD_CARGO.Enum.TROOPS, true, true, Cargotype.CratesNeeded) + self:T({cargotype=loadcargotype}) + loaded.Troopsloaded = loaded.Troopsloaded + troopsize + table.insert(loaded.Cargo,loadcargotype) + self.Loaded_Cargo[unitname] = loaded + self:_SendMessage("Troops boarded!", 10, false, Group) + self:__TroopsExtracted(1,Group, Unit, nearestGroup) + + -- clean up: + table.remove(self.DroppedTroops, nearestGroupIndex) + nearestGroup:Destroy() + end + return self + end + --- (Internal) Function to spawn crates in front of the heli. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group @@ -1824,6 +1926,7 @@ function CTLD:_RefreshF10Menus() menus[menucount] = MENU_GROUP_COMMAND:New(_group,entry.Name,troopsmenu,self._LoadTroops, self, _group, _unit, entry) end local unloadmenu1 = MENU_GROUP_COMMAND:New(_group,"Drop troops",toptroops, self._UnloadTroops, self, _group, _unit):Refresh() + local extractMenu1 = MENU_GROUP_COMMAND:New(_group, "Extract troops", toptroops, self._ExtractTroops, self, _group, _unit):Refresh() end local rbcns = MENU_GROUP_COMMAND:New(_group,"List active zone beacons",topmenu, self._ListRadioBeacons, self, _group, _unit) if unittype == "Hercules" then @@ -2542,6 +2645,21 @@ end return self end + --- (Internal) FSM Function onbeforeTroopsExtracted. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param Wrapper.Group#GROUP Troops Troops #GROUP Object. + -- @return #CTLD self + function CTLD:onbeforeTroopsExtracted(From, Event, To, Group, Unit, Troops) + self:T({From, Event, To}) + return self + end + + --- (Internal) FSM Function onbeforeTroopsDeployed. -- @param #CTLD self -- @param #string From State. From 96d1d3cb660b786c6ea8e73ada70c933a600d6a5 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 18 Jul 2021 14:53:00 +0200 Subject: [PATCH 105/111] Light code cleanup, added docu for troop extract --- Moose Development/Moose/Ops/CTLD.lua | 238 ++++----------------------- 1 file changed, 30 insertions(+), 208 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 6fa3ff5f9..3488ee961 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -290,7 +290,7 @@ do -- ["SA342M"] = {type="SA342M", crates=false, troops=true, cratelimit = 0, trooplimit = 4}, -- ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2}, -- ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, --- ["Mi-8MTV2"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12}, +-- ["Mi-8MT"] = {type="Mi-8MT", crates=true, troops=true, cratelimit = 2, trooplimit = 12}, -- ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0}, -- ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, -- ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, @@ -336,8 +336,16 @@ do -- function my_ctld:OnAfterTroopsDeployed(From, Event, To, Group, Unit, Troops) -- ... your code here ... -- end +-- +-- ## 3.4 OnAfterTroopsExtracted -- --- ## 3.4 OnAfterCratesDropped +-- This function is called when a player has re-boarded already deployed troops from the field: +-- +-- function my_ctld:OnAfterTroopsExtracted(From, Event, To, Group, Unit, Troops) +-- ... your code here ... +-- end +-- +-- ## 3.5 OnAfterCratesDropped -- -- This function is called when a player has deployed crates to a DROP zone: -- @@ -345,7 +353,7 @@ do -- ... your code here ... -- end -- --- ## 3.5 OnAfterCratesBuild +-- ## 3.6 OnAfterCratesBuild -- -- This function is called when a player has build a vehicle or FOB: -- @@ -353,7 +361,7 @@ do -- ... your code here ... -- end -- --- ## 3.6 A simple SCORING example: +-- ## 3.7 A simple SCORING example: -- -- To award player with points, using the SCORING Class (SCORING: my_Scoring, CTLD: CTLD_Cargotransport) -- @@ -381,7 +389,8 @@ do -- -- ## 4.2 Manage Troops -- --- Use this entry to load and drop troops. +-- Use this entry to load, drop and extract troops. NOTE - with extract you can only load troops from the field that were deployed prior. +-- Currently limited CTLD_CARGO troops, which are build from **one** template. Also, this will heal/complete your units as they are respawned. -- -- ## 4.3 List boarded cargo -- @@ -519,7 +528,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="0.1.4r1" +CTLD.version="0.1.4r2" --- Instantiate a new CTLD. -- @param #CTLD self @@ -577,7 +586,7 @@ function CTLD:New(Coalition, Prefixes, Alias) self:AddTransition("Stopped", "Start", "Running") -- Start FSM. self:AddTransition("*", "Status", "*") -- CTLD status update. self:AddTransition("*", "TroopsPickedUp", "*") -- CTLD pickup event. - self:AddTransition("*", "TroopsExtracted", "*") -- CTLD extract event. + self:AddTransition("*", "TroopsExtracted", "*") -- CTLD extract event. self:AddTransition("*", "CratesPickedUp", "*") -- CTLD pickup event. self:AddTransition("*", "TroopsDeployed", "*") -- CTLD deploy event. self:AddTransition("*", "TroopsRTB", "*") -- CTLD deploy event. @@ -596,7 +605,6 @@ function CTLD:New(Coalition, Prefixes, Alias) self.UsedVHFFrequencies = {} self.UsedUHFFrequencies = {} self.UsedFMFrequencies = {} - --self.jtacGeneratedLaserCodes = {} -- radio beacons self.RadioSound = "beacon.ogg" @@ -651,7 +659,6 @@ function CTLD:New(Coalition, Prefixes, Alias) self:_GenerateVHFrequencies() self:_GenerateUHFrequencies() self:_GenerateFMFrequencies() - --self:_GenerateLaserCodes() -- curr unused ------------------------ --- Pseudo Functions --- @@ -694,16 +701,16 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param #CTLD_CARGO Cargo Cargo troops. -- @return #CTLD self - --- FSM Function OnAfterTroopsExtracted. - -- @function [parent=#CTLD] OnAfterTroopsExtracted - -- @param #CTLD self - -- @param #string From State. - -- @param #string Event Trigger. - -- @param #string To State. - -- @param Wrapper.Group#GROUP Group Group Object. - -- @param Wrapper.Unit#UNIT Unit Unit Object. - -- @param #CTLD_CARGO Cargo Cargo troops. - -- @return #CTLD self + --- FSM Function OnAfterTroopsExtracted. + -- @function [parent=#CTLD] OnAfterTroopsExtracted + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #CTLD_CARGO Cargo Cargo troops. + -- @return #CTLD self --- FSM Function OnAfterCratesPickedUp. -- @function [parent=#CTLD] OnAfterCratesPickedUp @@ -793,14 +800,6 @@ function CTLD:_GenerateUHFrequencies() self:T(self.lid .. " _GenerateUHFrequencies") self.FreeUHFFrequencies = {} self.FreeUHFFrequencies = UTILS.GenerateUHFrequencies() - --[[ - local _start = 220000000 - - while _start < 399000000 do - table.insert(self.FreeUHFFrequencies, _start) - _start = _start + 500000 - end - --]] return self end @@ -810,16 +809,6 @@ function CTLD:_GenerateFMFrequencies() self:T(self.lid .. " _GenerateFMrequencies") self.FreeFMFrequencies = {} self.FreeFMFrequencies = UTILS.GenerateFMFrequencies() - --[[ - for _first = 3, 7 do - for _second = 0, 5 do - for _third = 0, 9 do - local _frequency = ((100 * _first) + (10 * _second) + _third) * 100000 --extra 0 because we didnt bother with 4th digit - table.insert(self.FreeFMFrequencies, _frequency) - end - end - end - --]] return self end @@ -827,121 +816,16 @@ end -- @param #CTLD self function CTLD:_GenerateVHFrequencies() self:T(self.lid .. " _GenerateVHFrequencies") - self.FreeVHFFrequencies = {} self.UsedVHFFrequencies = {} - self.FreeVHFFrequencies = UTILS.GenerateVHFrequencies() - - --[[ - local _skipFrequencies = self.SkipFrequencies - - self.FreeVHFFrequencies = {} - self.UsedVHFFrequencies = {} - - -- first range - local _start = 200000 - while _start < 400000 do - - -- skip existing NDB frequencies# - local _found = false - for _, value in pairs(_skipFrequencies) do - if value * 1000 == _start then - _found = true - break - end - end - if _found == false then - table.insert(self.FreeVHFFrequencies, _start) - end - _start = _start + 10000 - end - - -- second range - _start = 400000 - while _start < 850000 do - -- skip existing NDB frequencies - local _found = false - for _, value in pairs(_skipFrequencies) do - if value * 1000 == _start then - _found = true - break - end - end - if _found == false then - table.insert(self.FreeVHFFrequencies, _start) - end - _start = _start + 10000 - end - - -- third range - _start = 850000 - while _start <= 999000 do -- adjusted for Gazelle - -- skip existing NDB frequencies - local _found = false - for _, value in pairs(_skipFrequencies) do - if value * 1000 == _start then - _found = true - break - end - end - if _found == false then - table.insert(self.FreeVHFFrequencies, _start) - end - _start = _start + 50000 - end - --]] return self end ---[[ -- unused ---- (Internal) Function to generate valid laser codes. --- @param #CTLD self -function CTLD:_GenerateLaserCodes() - self:T(self.lid .. " _GenerateLaserCodes") - self.jtacGeneratedLaserCodes = {} - -- generate list of laser codes - local _code = 1111 - local _count = 1 - while _code < 1777 and _count < 30 do - while true do - _code = _code + 1 - if not self:_ContainsDigit(_code, 8) - and not self:_ContainsDigit(_code, 9) - and not self:_ContainsDigit(_code, 0) then - table.insert(self.jtacGeneratedLaserCodes, _code) - break - end - end - _count = _count + 1 - end -end - ---- (Internal) Helper function to generate laser codes. --- @param #CTLD self --- @param #number _number --- @param #number _numberToFind -function CTLD:_ContainsDigit(_number, _numberToFind) - self:T(self.lid .. " _ContainsDigit") - local _thisNumber = _number - local _thisDigit = 0 - while _thisNumber ~= 0 do - _thisDigit = _thisNumber % 10 - _thisNumber = math.floor(_thisNumber / 10) - if _thisDigit == _numberToFind then - return true - end - end - return false -end - ---]] - --- (Internal) Event handler function -- @param #CTLD self -- @param Core.Event#EVENTDATA EventData function CTLD:_EventHandler(EventData) - -- TODO: events dead and playerleaveunit - nil table entries self:T(string.format("%s Event = %d",self.lid, EventData.id)) local event = EventData -- Core.Event#EVENTDATA if event.id == EVENTS.PlayerEnterAircraft or event.id == EVENTS.PlayerEnterUnit then @@ -960,6 +844,7 @@ function CTLD:_EventHandler(EventData) -- Herc support --self:T_unit:GetTypeName()) if _unit:GetTypeName() == "Hercules" and self.enableHercules then + self.Loaded_Cargo[unitname] = nil self:_RefreshF10Menus() end return @@ -1003,7 +888,6 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) if not self.debug then return self end elseif not grounded and not hoverload then self:_SendMessage("You need to land or hover in position to load!", 10, false, Group) - --local m = MESSAGE:New("You need to land or hover in position to load!",15,"CTLD"):ToGroup(Group) if not self.debug then return self end end -- load troops into heli @@ -1012,10 +896,8 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) local unitname = unit:GetName() local cargotype = Cargotype -- #CTLD_CARGO local cratename = cargotype:GetName() -- #string - --self:Tself.lid .. string.format("Troops %s requested", cratename)) -- see if this heli can load troops local unittype = unit:GetTypeName() - --local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities local capabilities = self:_GetUnitCapabilities(Unit) local cantroops = capabilities.troops -- #boolean local trooplimit = capabilities.trooplimit -- #number @@ -1034,7 +916,6 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) end if troopsize + numberonboard > trooplimit then self:_SendMessage("Sorry, we\'re crammed already!", 10, false, Group) - --local m = MESSAGE:New("Sorry, we\'re crammed already!",10,"CTLD",true):ToGroup(group) return else self.CargoCounter = self.CargoCounter + 1 @@ -1044,7 +925,6 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) table.insert(loaded.Cargo,loadcargotype) self.Loaded_Cargo[unitname] = loaded self:_SendMessage("Troops boarded!", 10, false, Group) - --local m = MESSAGE:New("Troops boarded!",10,"CTLD",true):ToGroup(group) self:__TroopsPickedUp(1,Group, Unit, Cargotype) end return self @@ -1055,7 +935,7 @@ end -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit -- @param #CTLD_CARGO Cargotype - function CTLD:_ExtractTroops(Group, Unit) + function CTLD:_ExtractTroops(Group, Unit) -- #1574 thanks to @bbirchnz! self:T(self.lid .. " _ExtractTroops") -- landed or hovering over load zone? local grounded = not self:IsUnitInAir(Unit) @@ -1150,7 +1030,6 @@ end function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) self:T(self.lid .. " _GetCrates") local cgoname = Cargo:GetName() - --self:T{cgoname, number, drop}) -- check if we are in LOAD zone local inzone = false local drop = drop or false @@ -1176,7 +1055,6 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) local nearcrates, numbernearby = self:_FindCratesNearby(Group,Unit,loaddist) if numbernearby >= canloadcratesno and not drop then self:_SendMessage("There are enough crates nearby already! Take care of those first!", 10, false, Group) - return self end -- spawn crates in front of helicopter @@ -1311,9 +1189,6 @@ function CTLD:_FindCratesNearby( _group, _unit, _dist) end end end - --self:Tstring.format("Found crates = %d",index)) - -- table.sort(found) - --self:T({found}) return found, index end @@ -1344,13 +1219,10 @@ function CTLD:_LoadCratesNearby(Group, Unit) ----------------------------------------- if not cancrates then self:_SendMessage("Sorry this chopper cannot carry crates!", 10, false, Group) - --local m = MESSAGE:New("Sorry this chopper cannot carry crates!",10,"CTLD"):ToGroup(Group) elseif self.forcehoverload and not canhoverload then self:_SendMessage("Hover over the crates to pick them up!", 10, false, Group) - --local m = MESSAGE:New("Hover over the crates to pick them up!",10,"CTLD"):ToGroup(Group) elseif not grounded and not canhoverload then self:_SendMessage("Land or hover over the crates to pick them up!", 10, false, Group) - --local m = MESSAGE:New("Land or hover over the crates to pick them up!",10,"CTLD"):ToGroup(Group) else -- have we loaded stuff already? local numberonboard = 0 @@ -1369,7 +1241,6 @@ function CTLD:_LoadCratesNearby(Group, Unit) local nearcrates,number = self:_FindCratesNearby(Group,Unit,finddist) -- #table if number == 0 or numberonboard == cratelimit then self:_SendMessage("Sorry no loadable crates nearby or fully loaded!", 10, false, Group) - --local m = MESSAGE:New("Sorry no loadable crates nearby or fully loaded!",10,"CTLD"):ToGroup(Group) return -- exit else -- go through crates and load @@ -1377,13 +1248,11 @@ function CTLD:_LoadCratesNearby(Group, Unit) local crateidsloaded = {} local loops = 0 while loaded.Cratesloaded < cratelimit and loops < number do - --for _ind,_crate in pairs (nearcrates) do loops = loops + 1 local crateind = 0 -- get crate with largest index for _ind,_crate in pairs (nearcrates) do if not _crate:HasMoved() and not _crate:WasDropped() and _crate:GetID() > crateind then - --crate = _crate crateind = _crate:GetID() end end @@ -1398,10 +1267,8 @@ function CTLD:_LoadCratesNearby(Group, Unit) crate:GetPositionable():Destroy() crate.Positionable = nil self:_SendMessage(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()), 10, false, Group) - --local m = MESSAGE:New(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()),10,"CTLD"):ToGroup(Group) self:__CratesPickedUp(1, Group, Unit, crate) end - --if loaded.Cratesloaded == cratelimit then break end end self.Loaded_Cargo[unitname] = loaded -- clean up real world crates @@ -1433,7 +1300,6 @@ function CTLD:_ListCargo(Group, Unit) local unitname = Unit:GetName() local unittype = Unit:GetTypeName() local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities - --local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities local trooplimit = capabilities.trooplimit -- #boolean local cratelimit = capabilities.cratelimit -- #number local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo @@ -1473,10 +1339,8 @@ function CTLD:_ListCargo(Group, Unit) report:Add("------------------------------------------------------------") local text = report:Text() self:_SendMessage(text, 30, true, Group) - --local m = MESSAGE:New(text,30,"CTLD",true):ToGroup(Group) else self:_SendMessage(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d",trooplimit,cratelimit), 10, false, Group) - --local m = MESSAGE:New(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d",trooplimit,cratelimit),10,"CTLD"):ToGroup(Group) end return self end @@ -1550,13 +1414,11 @@ function CTLD:_UnloadTroops(Group, Unit) end -- template loop cargo:SetWasDropped(true) self:_SendMessage(string.format("Dropped Troops %s into action!",name), 10, false, Group) - --local m = MESSAGE:New(string.format("Dropped Troops %s into action!",name),10,"CTLD"):ToGroup(Group) self:__TroopsDeployed(1, Group, Unit, self.DroppedTroops[self.TroopCounter]) end -- if type end end -- cargotable loop else -- droppingatbase self:_SendMessage("Troops have returned to base!", 10, false, Group) - --local m = MESSAGE:New("Troops have returned to base!",15,"CTLD"):ToGroup(Group) self:__TroopsRTB(1, Group, Unit) end -- cleanup load list @@ -1570,7 +1432,6 @@ function CTLD:_UnloadTroops(Group, Unit) local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum local dropped = cargo:WasDropped() - --local moved = cargo:HasMoved() if type ~= CTLD_CARGO.Enum.TROOP and not dropped then table.insert(loaded.Cargo,_cargo) loaded.Cratesloaded = loaded.Cratesloaded + 1 @@ -1581,10 +1442,8 @@ function CTLD:_UnloadTroops(Group, Unit) else if IsHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) - --local m = MESSAGE:New("Nothing loaded or not within airdrop parameters!",10,"CTLD"):ToGroup(Group) else self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) - --local m = MESSAGE:New("Nothing loaded or not hovering within parameters!",10,"CTLD"):ToGroup(Group) end end return self @@ -1602,7 +1461,6 @@ function CTLD:_UnloadCrates(Group, Unit) local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) if not inzone then self:_SendMessage("You are not close enough to a drop zone!", 10, false, Group) - --local m = MESSAGE:New("You are not close enough to a drop zone!",15,"CTLD"):ToGroup(Group) if not self.debug then return self end @@ -1631,8 +1489,6 @@ function CTLD:_UnloadCrates(Group, Unit) self:_GetCrates(Group, Unit, cargo, 1, true) cargo:SetWasDropped(true) cargo:SetHasMoved(true) - --local name cargo:GetName() - --local m = MESSAGE:New(string.format("Dropped Crate for %s!",name),10,"CTLD"):ToGroup(Group) end end -- cleanup load list @@ -1654,10 +1510,8 @@ function CTLD:_UnloadCrates(Group, Unit) else if IsHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) - --local m = MESSAGE:New("Nothing loaded or not within airdrop parameters!",10,"CTLD"):ToGroup(Group) else self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) - --local m = MESSAGE:New("Nothing loaded or not hovering within parameters!",10,"CTLD"):ToGroup(Group) end end return self @@ -1718,7 +1572,6 @@ function CTLD:_BuildCrates(Group, Unit) if build.CanBuild then txtok = "YES" end - --self:T{name,needed,found,txtok}) local text = string.format("Type: %s | Required %d | Found %d | Can Build %s", name, needed, found, txtok) report:Add(text) end -- end list buildables @@ -1726,7 +1579,6 @@ function CTLD:_BuildCrates(Group, Unit) report:Add("------------------------------------------------------------") local text = report:Text() self:_SendMessage(text, 30, true, Group) - --local m = MESSAGE:New(text,30,"CTLD",true):ToGroup(Group) -- let\'s get going if canbuild then -- loop again @@ -1740,7 +1592,6 @@ function CTLD:_BuildCrates(Group, Unit) end else self:_SendMessage(string.format("No crates within %d meters!",finddist), 10, false, Group) - --local m = MESSAGE:New(string.format("No crates within %d meters!",finddist),15,"CTLD",true):ToGroup(Group) end -- number > 0 return self end @@ -1784,17 +1635,14 @@ function CTLD:_MoveGroupToZone(Group) self:T(self.lid .. " _MoveGroupToZone") local groupname = Group:GetName() or "none" local groupcoord = Group:GetCoordinate() - --self:Tself.lid .. " _MoveGroupToZone for " .. groupname) -- Get closest zone of type local outcome, name, zone, distance = self:IsUnitInZone(Group,CTLD.CargoZoneType.MOVE) --self:Tstring.format("Closest WP zone %s is %d meters",name,distance)) if (distance <= self.movetroopsdistance) and zone then -- yes, we can ;) local groupname = Group:GetName() - --self:Tstring.format("Moving troops %s to zone %s, distance %d!",groupname,name,distance)) local zonecoord = zone:GetRandomCoordinate(20,125) -- Core.Point#COORDINATE local coordinate = zonecoord:GetVec2() - --self:T{coordinate=coordinate}) Group:SetAIOn() Group:OptionAlarmStateAuto() Group:OptionDisperseOnAttack(30) @@ -1813,7 +1661,6 @@ function CTLD:_CleanUpCrates(Crates,Build,Number) self:T(self.lid .. " _CleanUpCrates") -- clean up real world crates local build = Build -- #CTLD.Buildable - --self:T{Build = Build}) local existingcrates = self.Spawned_Cargo -- #table of exising crates local newexcrates = {} -- get right number of crates to destroy @@ -1827,18 +1674,15 @@ function CTLD:_CleanUpCrates(Crates,Build,Number) for _,_crate in pairs(Crates) do local nowcrate = _crate -- #CTLD_CARGO local name = nowcrate:GetName() - --self:Tstring.format("Looking for Crate for %s", name)) local thisID = nowcrate:GetID() if name == nametype then -- matching crate type table.insert(destIDs,thisID) found = found + 1 nowcrate:GetPositionable():Destroy() nowcrate.Positionable = nil - --self:Tstring.format("%s Found %d Need %d", name, found, numberdest)) end if found == numberdest then break end -- got enough end - --self:T{destIDs}) -- loop and remove from real world representation for _,_crate in pairs(existingcrates) do local excrate = _crate -- #CTLD_CARGO @@ -1863,7 +1707,6 @@ function CTLD:_RefreshF10Menus() self:T(self.lid .. " _RefreshF10Menus") local PlayerSet = self.PilotGroups -- Core.Set#SET_GROUP local PlayerTable = PlayerSet:GetSetObjects() -- #table of #GROUP objects - --self:T({PlayerTable=PlayerTable}) -- rebuild units table local _UnitList = {} for _key, _group in pairs (PlayerTable) do @@ -1880,7 +1723,6 @@ function CTLD:_RefreshF10Menus() self.CtldUnits = _UnitList -- build unit menus - local menucount = 0 local menus = {} for _, _unitName in pairs(self.CtldUnits) do @@ -1892,7 +1734,6 @@ function CTLD:_RefreshF10Menus() -- get chopper capabilities local unittype = _unit:GetTypeName() local capabilities = self:_GetUnitCapabilities(_unit) -- #CTLD.UnitCapabilities - --local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities local cantroops = capabilities.troops local cancrates = capabilities.crates -- top menu @@ -1982,13 +1823,10 @@ function CTLD:AddZone(Zone) local zone = Zone -- #CTLD.CargoZone if zone.type == CTLD.CargoZoneType.LOAD then table.insert(self.pickupZones,zone) - --self:T"Registered LOAD zone " .. zone.name) elseif zone.type == CTLD.CargoZoneType.DROP then table.insert(self.dropOffZones,zone) - --self:T"Registered DROP zone " .. zone.name) else table.insert(self.wpZones,zone) - --self:T"Registered MOVE zone " .. zone.name) end return self end @@ -2053,7 +1891,6 @@ function CTLD:_GetFMBeacon(Name) beacon.name = Name beacon.frequency = FM / 1000000 beacon.modulation = radio.modulation.FM - return beacon end @@ -2164,7 +2001,6 @@ function CTLD:_ListRadioBeacons(Group, Unit) end report:Add("------------------------------------------------------------") self:_SendMessage(report:Text(), 30, true, Group) - --local m = MESSAGE:New(report:Text(),30,"CTLD",true):ToGroup(Group) return self end @@ -2227,7 +2063,6 @@ end function CTLD:IsUnitInZone(Unit,Zonetype) self:T(self.lid .. " IsUnitInZone") local unitname = Unit:GetName() - --self:Tstring.format("%s | Zone search for %s | Type %s",self.lid,unitname,Zonetype)) local zonetable = {} local outcome = false if Zonetype == CTLD.CargoZoneType.LOAD then @@ -2253,7 +2088,6 @@ function CTLD:IsUnitInZone(Unit,Zonetype) local color = czone.color local zoneradius = zone:GetRadius() local distance = self:_GetDistance(zonecoord,unitcoord) - --self:Tstring.format("Check distance: %d",distance)) if distance <= zoneradius and active then outcome = true end @@ -2264,7 +2098,6 @@ function CTLD:IsUnitInZone(Unit,Zonetype) colorret = color end end - --self:T{outcome, zonenameret, zoneret, maxdist}) return outcome, zonenameret, zoneret, maxdist end @@ -2300,7 +2133,6 @@ function CTLD:SmokeZoneNearBy(Unit, Flare) local txt = "smoking" if Flare then txt = "flaring" end self:_SendMessage(string.format("Roger, %s zone %s!",txt, zonename), 10, false, Group) - --local m = MESSAGE:New(string.format("Roger, %s zone %s!",txt, zonename),10,"CTLD"):ToGroup(Group) smoked = true end end @@ -2308,7 +2140,6 @@ function CTLD:SmokeZoneNearBy(Unit, Flare) if not smoked then local distance = UTILS.MetersToNM(self.smokedistance) self:_SendMessage(string.format("Negative, need to be closer than %dnm to a zone!",distance), 10, false, Group) - --local m = MESSAGE:New(string.format("Negative, need to be closer than %dnm to a zone!",distance),10,"CTLD"):ToGroup(Group) end return self end @@ -2361,7 +2192,6 @@ end local maxh = self.maximumHoverHeight -- 15 local minh = self.minimumHoverHeight -- 5 local mspeed = 2 -- 2 m/s - --self:Tstring.format("%s Unit parameters: at %dm AGL with %dmps",self.lid,aheight,uspeed)) if (uspeed <= mspeed) and (aheight <= maxh) and (aheight >= minh) then -- yep within parameters outcome = true @@ -2388,7 +2218,7 @@ end local maxh = self.HercMinAngels-- 1500m local minh = self.HercMaxAngels -- 5000m local maxspeed = self.HercMaxSpeed -- 77 mps - -- TODO: TEST - Speed test for Herc, should not be above 280kph/150kn + -- DONE: TEST - Speed test for Herc, should not be above 280kph/150kn local kmspeed = uspeed * 3.6 local knspeed = kmspeed / 1.86 self:T(string.format("%s Unit parameters: at %dm AGL with %dmps | %dkph | %dkn",self.lid,aheight,uspeed,kmspeed,knspeed)) @@ -2417,7 +2247,6 @@ end text = string.format("Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 6fts \n - In parameter: %s", minheight, maxheight, htxt) end self:_SendMessage(text, 10, false, Group) - --local m = MESSAGE:New(text,10,"CTLD",false):ToGroup(Group) return self end @@ -2440,7 +2269,6 @@ end text = string.format("Flight parameters (airdrop):\n - Min height %dm \n - Max height %dm \n - In parameter: %s", minheight, maxheight, htxt) end self:_SendMessage(text, 10, false, Group) - --local m = MESSAGE:New(text,15,"CTLD",false):ToGroup(Group) return self end @@ -2488,7 +2316,6 @@ end local unitname = Unit:GetName() local Group = Unit:GetGroup() local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities - --local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities local cancrates = capabilities.crates -- #boolean local cratelimit = capabilities.cratelimit -- #number if cancrates then @@ -2536,16 +2363,12 @@ end self:I(self.lid .. "Started.") if self.useprefix or self.enableHercules then local prefix = self.prefixes - --self:T{prefix=prefix}) if self.enableHercules then - --self:T("CTLD with prefixes and Hercules") self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterStart() else - --self:T("CTLD with prefixes NO Hercules") self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterCategories("helicopter"):FilterStart() end else - --self:T("CTLD NO prefixes NO Hercules") self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategories("helicopter"):FilterStart() end -- Events @@ -2581,8 +2404,7 @@ end -- gather some stats -- pilots local pilots = 0 - for _,_pilot in pairs (self.CtldUnits) do - + for _,_pilot in pairs (self.CtldUnits) do pilots = pilots + 1 end From 6690f70b05b28cb8df047df66462dd5b03a844ba Mon Sep 17 00:00:00 2001 From: bbirchnz Date: Mon, 19 Jul 2021 15:16:21 +1000 Subject: [PATCH 106/111] fix CTLD:ActivateZone not processing default correctly and using wrong zone table (#1575) --- Moose Development/Moose/Ops/CTLD.lua | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 3488ee961..33c33a9d5 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1840,15 +1840,15 @@ function CTLD:ActivateZone(Name,ZoneType,NewState) self:T(self.lid .. " AddZone") local newstate = true -- set optional in case we\'re deactivating - if not NewState or NewState == false then - newstate = false - end + if NewState ~= nil then + newstate = NewState + end + -- get correct table - local zone = ZoneType -- #CTLD.CargoZone local table = {} - if zone.type == CTLD.CargoZoneType.LOAD then + if ZoneType == CTLD.CargoZoneType.LOAD then table = self.pickupZones - elseif zone.type == CTLD.CargoZoneType.DROP then + elseif ZoneType == CTLD.CargoZoneType.DROP then table = self.dropOffZones else table = self.wpZones @@ -1864,6 +1864,7 @@ function CTLD:ActivateZone(Name,ZoneType,NewState) return self end + --- User function - Deactivate Name #CTLD.CargoZoneType ZoneType for this CTLD instance. -- @param #CTLD self -- @param #string Name Name of the zone to change in the ME. From 8a539982517777f57e2fc95392350f6c8558ac0f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 20 Jul 2021 18:30:06 +0200 Subject: [PATCH 107/111] MANTIS - make start random async, so detection for multiple MANTIS objects works better INTEL - added function to alter detection types, typos corrected etc --- Moose Development/Moose/Functional/Mantis.lua | 2 +- Moose Development/Moose/Ops/Intelligence.lua | 89 +++++++++++++------ 2 files changed, 65 insertions(+), 26 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 3b185043c..c381fc5f8 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -1081,7 +1081,7 @@ do if self.advAwacs then self.AWACS_Detection = self:StartAwacsDetection() end - self:__Status(self.detectinterval) + self:__Status(-math.random(1,10)) return self end diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index 7ee0a79fd..40ae21786 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -56,30 +56,30 @@ -- -- ## set up a detection SET_GROUP -- --- `Red_DetectionSetGroup = SET_GROUP:New()` --- `Red_DetectionSetGroup:FilterPrefixes( { "Red EWR" } )` --- `Red_DetectionSetGroup:FilterOnce()` +-- `Red_DetectionSetGroup = SET_GROUP:New()` +-- `Red_DetectionSetGroup:FilterPrefixes( { "Red EWR" } )` +-- `Red_DetectionSetGroup:FilterOnce()` -- -- ## New Intel type detection for the red side, logname "KGB" -- --- `RedIntel = INTEL:New(Red_DetectionSetGroup,"red","KGB")` --- `RedIntel:SetClusterAnalysis(true,true)` --- `RedIntel:SetVerbosity(2)` --- `RedIntel:Start()` +-- `RedIntel = INTEL:New(Red_DetectionSetGroup,"red","KGB")` +-- `RedIntel:SetClusterAnalysis(true,true)` +-- `RedIntel:SetVerbosity(2)` +-- `RedIntel:__Start(2)` -- -- ## Hook into new contacts found -- --- `function RedIntel:OnAfterNewContact(From, Event, To, Contact)` --- `local text = string.format("NEW contact %s detected by %s", Contact.groupname, Contact.recce or "unknown")` --- `local m = MESSAGE:New(text,15,"KGB"):ToAll()` --- `end` +-- `function RedIntel:OnAfterNewContact(From, Event, To, Contact)` +-- `local text = string.format("NEW contact %s detected by %s", Contact.groupname, Contact.recce or "unknown")` +-- `local m = MESSAGE:New(text,15,"KGB"):ToAll()` +-- `end` -- -- ## And/or new clusters found -- --- `function RedIntel:OnAfterNewCluster(From, Event, To, Contact, Cluster)` --- `local text = string.format("NEW cluster %d size %d with contact %s", Cluster.index, Cluster.size, Contact.groupname)` --- `local m = MESSAGE:New(text,15,"KGB"):ToAll()` --- `end` +-- `function RedIntel:OnAfterNewCluster(From, Event, To, Contact, Cluster)` +-- `local text = string.format("NEW cluster %d size %d with contact %s", Cluster.index, Cluster.size, Contact.groupname)` +-- `local m = MESSAGE:New(text,15,"KGB"):ToAll()` +-- `end` -- -- -- @field #INTEL @@ -116,7 +116,7 @@ INTEL = { -- @field #number speed Last known speed in m/s. -- @field #boolean isship -- @field #boolean ishelo --- @field #boolean isgrund +-- @field #boolean isground -- @field Ops.Auftrag#AUFTRAG mission The current Auftrag attached to this contact -- @field #string recce The name of the recce unit that detected this contact @@ -135,13 +135,13 @@ INTEL = { --- INTEL class version. -- @field #string version -INTEL.version="0.2.2" +INTEL.version="0.2.5" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Filter detection methods. +-- DONE: Filter detection methods. -- TODO: process detected set asynchroniously for better performance. -- DONE: Accept zones. -- DONE: Reject zones. @@ -203,7 +203,14 @@ function INTEL:New(DetectionSet, Coalition, Alias) self.alias="CIA" end end - end + end + + self.DetectVisual = true + self.DetectOptical = true + self.DetectRadar = true + self.DetectIRST = true + self.DetectRWR = true + self.DetectDLINK = true -- Set some string id for output to DCS.log file. self.lid=string.format("INTEL %s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown") @@ -475,6 +482,39 @@ function INTEL:SetClusterRadius(radius) return self end +--- Set detection types for this #INTEL - all default to true. +-- @param #INTEL self +-- @param #boolean DetectVisual Visual detection +-- @param #boolean DetectOptical Optical detection +-- @param #boolean DetectRadar Radar detection +-- @param #boolean DetectIRST IRST detection +-- @param #boolean DetectRWR RWR detection +-- @param #boolean DetectDLINK Data link detection +-- @return self +function INTEL:SetDetectionTypes(DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK) + self.DetectVisual = DetectVisual and true + self.DetectOptical = DetectOptical and true + self.DetectRadar = DetectRadar and true + self.DetectIRST = DetectIRST and true + self.DetectRWR = DetectRWR and true + self.DetectDLINK = DetectDLINK and true + return self +end + +--- Get table of #INTEL.Contact objects +-- @param #INTEL self +-- @return #table Contacts +function INTEL:GetContactTable() + return self.Contacts +end + +--- Get table of #INTEL.Cluster objects +-- @param #INTEL self +-- @return #table Clusters +function INTEL:GetClusterTable() + return self.Clusters +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Start & Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -530,7 +570,7 @@ function INTEL:onafterStatus(From, Event, To) text=text..string.format("\n- %s (%s): %s, units=%d, T=%d sec", contact.categoryname, contact.attribute, contact.groupname, contact.group:CountAliveUnits(), dT) if contact.mission then local mission=contact.mission --Ops.Auftrag#AUFTRAG - text=text..string.format(" mission name=%s type=%s target=%s", mission.name, mission.type, mission:GetTargetName() or "unkown") + text=text..string.format(" mission name=%s type=%s target=%s", mission.name, mission.type, mission:GetTargetName() or "unknown") end end self:I(self.lid..text) @@ -558,14 +598,13 @@ function INTEL:UpdateIntel() local recce=_recce --Wrapper.Unit#UNIT -- Get detected units. - self:GetDetectedUnits(recce, DetectedUnits, RecceDetecting) + self:GetDetectedUnits(recce, DetectedUnits, RecceDetecting, self.DetectVisual, self.DetectOptical, self.DetectRadar, self.DetectIRST, self.DetectRWR, self.DetectDLINK) end end end - -- TODO: Filter detection methods? local remove={} for unitname,_unit in pairs(DetectedUnits) do local unit=_unit --Wrapper.Unit#UNIT @@ -700,7 +739,7 @@ function INTEL:CreateDetectedItems(DetectedGroups, RecceDetecting) item.velocity=group:GetVelocityVec3() item.speed=group:GetVelocityMPS() item.recce=RecceDetecting[groupname] - self:T(string.format("%s group detect by %s/%s", groupname, RecceDetecting[groupname] or "unknonw", item.recce or "unknown")) + self:T(string.format("%s group detect by %s/%s", groupname, RecceDetecting[groupname] or "unknown", item.recce or "unknown")) -- Add contact to table. self:AddContact(item) @@ -728,7 +767,7 @@ function INTEL:CreateDetectedItems(DetectedGroups, RecceDetecting) end ---- Return the detected target groups of the controllable as a @{SET_GROUP}. +--- (Internal) Return the detected target groups of the controllable as a @{SET_GROUP}. -- The optional parametes specify the detection methods that can be applied. -- If no detection method is given, the detection will use all the available methods by default. -- @param #INTEL self @@ -815,7 +854,7 @@ function INTEL:onafterLostCluster(From, Event, To, Cluster, Mission) local text = self.lid..string.format("LOST cluster %d", Cluster.index) if Mission then local mission=Mission --Ops.Auftrag#AUFTRAG - text=text..string.format(" mission name=%s type=%s target=%s", mission.name, mission.type, mission:GetTargetName() or "unkown") + text=text..string.format(" mission name=%s type=%s target=%s", mission.name, mission.type, mission:GetTargetName() or "unknown") end self:T(text) end From 4dfaca610f04882493c6c1883f6d5c47390bdf82 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 21 Jul 2021 18:22:31 +0200 Subject: [PATCH 108/111] INTEL - bug fixes, added new datalink class --- Moose Development/Moose/Ops/Intelligence.lua | 266 ++++++++++++++++++- 1 file changed, 260 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index 40ae21786..98c62cd68 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -135,7 +135,7 @@ INTEL = { --- INTEL class version. -- @field #string version -INTEL.version="0.2.5" +INTEL.version="0.2.6" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -229,7 +229,8 @@ function INTEL:New(DetectionSet, Coalition, Alias) self:AddTransition("*", "LostContact", "*") -- Contact could not be detected any more. self:AddTransition("*", "NewCluster", "*") -- New cluster has been detected. - self:AddTransition("*", "LostCluster", "*") -- Cluster could not be detected any more. + self:AddTransition("*", "LostCluster", "*") -- Cluster could not be detected any more. + self:AddTransition("*", "Stop", "Stopped") -- Defaults self:SetForgetTime() @@ -503,16 +504,24 @@ end --- Get table of #INTEL.Contact objects -- @param #INTEL self --- @return #table Contacts +-- @return #table Contacts or nil if not running function INTEL:GetContactTable() - return self.Contacts + if self:Is("Running") then + return self.Contacts + else + return nil + end end --- Get table of #INTEL.Cluster objects -- @param #INTEL self --- @return #table Clusters +-- @return #table Clusters or nil if not running function INTEL:GetClusterTable() - return self.Clusters + if self:Is("Running") and self.clusteranalysis then + return self.Clusters + else + return nil + end end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1430,3 +1439,248 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +---------------------------------------------------------------------------------------------- +-- Start INTEL_DLINK +---------------------------------------------------------------------------------------------- + +--- **Ops_DLink** - Support for Office of Military Intelligence. +-- +-- **Main Features:** +-- +-- * Overcome limitations of (non-available) datalinks between ground radars +-- * Detect and track contacts consistently across INTEL instances +-- * Use FSM events to link functionality into your scripts +-- * Easy setup +-- +--- === +-- +-- ### Author: **applevangelist** +-- @module Ops.Intel_DLink +-- @image OPS_Intel.png + +--- INTEL_DLINK class. +-- @type INTEL_DLINK +-- @field #string ClassName Name of the class. +-- @field #string lid Class id string for output to DCS log file. +-- @field #number verbose Make the logging verbose. +-- @field #string alias Alias name for logging. +-- @field #number cachetime Number of seconds to keep an object. +-- @field #number interval Number of seconds between collection runs. +-- @field #table contacts Table of Ops.Intelligence#INTEL.Contact contacts. +-- @field #table clusters Table of Ops.Intelligence#INTEL.Cluster clusters. +-- @field #table contactcoords Table of contacts' Core.Point#COORDINATE objects. +-- @extends Core.Fsm#FSM + +--- INTEL_DLINK data aggregator +-- @field #INTEL_DLINK +INTEL_DLINK = { + ClassName = "INTEL_DLINK", + verbose = 0, + lid = nil, + alias = nil, + cachetime = 300, + interval = 20, + contacts = {}, + clusters = {}, + contactcoords = {}, +} + +--- Version string +-- @field #string version +INTEL_DLINK.version = "0.0.1" + +--- Function to instantiate a new object +-- @param #INTEL_DLINK self +-- @param #table Intels Table of Ops.Intelligence#INTEL objects. +-- @param #string Alias (optional) Name of this instance. Default "SPECTRE" +-- @param #number Interval (optional) When to query #INTEL objects for detected items (default 20 seconds). +-- @param #number Cachetime (optional) How long to cache detected items (default 300 seconds). +function INTEL_DLINK:New(Intels, Alias, Interval, Cachetime) + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, FSM:New()) -- #INTEL + + self.intels = Intels or {} + self.contacts = {} + self.clusters = {} + self.contactcoords = {} + + -- Set alias. + if Alias then + self.alias=tostring(Alias) + else + self.alias="SPECTRE" + end + + -- Cache time + self.cachetime = Cachetime or 300 + + -- Interval + self.interval = Interval or 20 + + -- Set some string id for output to DCS.log file. + self.lid=string.format("INTEL_DLINK %s | ", self.alias) + + -- Start State. + self:SetStartState("Stopped") + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("Stopped", "Start", "Running") -- Start FSM. + self:AddTransition("*", "Collect", "*") -- Collect data. + self:AddTransition("*", "Collected", "*") -- Collection of data done. + self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. + + return self +end +---------------------------------------------------------------------------------------------- +-- Helper & User Functions +---------------------------------------------------------------------------------------------- + +--- Function to add an #INTEL object to the aggregator +-- @param #INTEL_DLINK self +-- @param Ops.Intelligence#INTEL Intel the #INTEL object to add +-- @return #INTEL_DLINK self +function INTEL_DLINK:AddIntel(Intel) + self:T(self.lid .. "AddIntel") + if Intel then + table.insert(self.intels,Intel) + end + return self +end + +---------------------------------------------------------------------------------------------- +-- FSM Functions +---------------------------------------------------------------------------------------------- + +--- Function to start the work. +-- @param #INTEL_DLINK self +-- @param #string From The From state +-- @param #string Event The Event triggering this call +-- @param #string To The To state +-- @return #INTEL_DLINK self +function INTEL_DLINK:onafterStart(From, Event, To) + self:T({From, Event, To}) + local text = string.format("Version %s started.", self.version) + self:I(self.lid .. text) + self:__Collect(-math.random(1,10)) + return self +end + +--- Function to collect data from the various #INTEL +-- @param #INTEL_DLINK self +-- @param #string From The From state +-- @param #string Event The Event triggering this call +-- @param #string To The To state +-- @return #INTEL_DLINK self +function INTEL_DLINK:onbeforeCollect(From, Event, To) + self:T({From, Event, To}) + -- run through our #INTEL objects and gather the contacts tables + self:T("Contacts Data Gathering") + local newcontacts = {} + local intels = self.intels -- #table + for _,_intel in pairs (intels) do + _intel = _intel -- #INTEL + if _intel:Is("Running") then + local ctable = _intel:GetContactTable() or {} -- #INTEL.Contact + for _,_contact in pairs (ctable) do + local _ID = string.format("%s-%d",_contact.groupname, _contact.Tdetected) + self:T(string.format("Adding %s",_ID)) + newcontacts[_ID] = _contact + end + end + end + -- clean up for stale contacts and dupes + self:T("Cleanup") + local contacttable = {} + local coordtable = {} + local TNow = timer.getAbsTime() + local Tcache = self.cachetime + for _ind, _contact in pairs(newcontacts) do -- #string, #INTEL.Contact + if TNow - _contact.Tdetected < Tcache then + if (not contacttable[_contact.groupname]) or (contacttable[_contact.groupname] and contacttable[_contact.groupname].Tdetected < _contact.Tdetected) then + self:T(string.format("Adding %s",_contact.groupname)) + contacttable[_contact.groupname] = _contact + table.insert(coordtable,_contact.position) + end + end + end + -- run through our #INTEL objects and gather the clusters tables + self:T("Clusters Data Gathering") + local newclusters = {} + local intels = self.intels -- #table + for _,_intel in pairs (intels) do + _intel = _intel -- #INTEL + if _intel:Is("Running") then + local ctable = _intel:GetClusterTable() or {} -- #INTEL.Cluster + for _,_cluster in pairs (ctable) do + local _ID = string.format("%s-%d", _intel.alias, _cluster.index) + self:T(string.format("Adding %s",_ID)) + table.insert(newclusters,_cluster) + end + end + end + -- update self tables + self.contacts = contacttable + self.contactcoords = coordtable + self.clusters = newclusters + self:__Collected(1, contacttable, newclusters) -- make table available via FSM Event + -- schedule next round + local interv = self.interval * -1 + self:__Collect(interv) + return self +end + +--- Function called after collection is done +-- @param #INTEL_DLINK self +-- @param #string From The From state +-- @param #string Event The Event triggering this call +-- @param #string To The To state +-- @param #table Contacts The table of collected #INTEL.Contact contacts +-- @param #table Clusters The table of collected #INTEL.Cluster clusters +-- @return #INTEL_DLINK self +function INTEL_DLINK:onbeforeCollected(From, Event, To, Contacts, Clusters) + self:T({From, Event, To}) + return self +end + +--- Function to stop +-- @param #INTEL_DLINK self +-- @param #string From The From state +-- @param #string Event The Event triggering this call +-- @param #string To The To state +-- @return #INTEL_DLINK self +function INTEL_DLINK:onafterStop(From, Event, To) + self:T({From, Event, To}) + local text = string.format("Version %s stopped.", self.version) + self:I(self.lid .. text) + return self +end + +--- Function to query the detected contacts +-- @param #INTEL_DLINK self +-- @return #table Table of #INTEL.Contact contacts +function INTEL_DLINK:GetContactTable() + self:T(self.lid .. "GetContactTable") + return self.contacts +end + +--- Function to query the detected clusters -- not yet implemented! +-- @param #INTEL_DLINK self +-- @return #table Table of #INTEL.Cluster clusters +function INTEL_DLINK:GetClusterTable() + self:T(self.lid .. "GetClusterTable") + return self.clusters +end + +--- Function to query the detected contact coordinates +-- @param #INTEL_DLINK self +-- @return #table Table of the contacts' Core.Point#COORDINATE objects. +function INTEL_DLINK:GetDetectedItemCoordinates() + self:T(self.lid .. "GetDetectedItemCoordinates") + return self.contactcoords +end + +---------------------------------------------------------------------------------------------- +-- End INTEL_DLINK +---------------------------------------------------------------------------------------------- From 684c4ea113840606c4e3f97b4912be2249a248aa Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 21 Jul 2021 18:22:46 +0200 Subject: [PATCH 109/111] Allow MANTIS to use INTEL_DLINK --- Moose Development/Moose/Functional/Mantis.lua | 107 ++++++++++++------ 1 file changed, 74 insertions(+), 33 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index c381fc5f8..6891fd3fd 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -59,7 +59,7 @@ -- @extends Core.Base#BASE ---- *The worst thing that can happen to a good cause is, not to be skillfully attacked, but to be ineptly defended.* - Frédéric Bastiat +--- *The worst thing that can happen to a good cause is, not to be skillfully attacked, but to be ineptly defended.* - Frédéric Bastiat -- -- Simple Class for a more intelligent Air Defense System -- @@ -195,6 +195,8 @@ MANTIS = { TimeStamp = 0, state2flag = false, SamStateTracker = {}, + DLink = false, + DLTimeStamp = 0, } --- Advanced state enumerator @@ -278,7 +280,8 @@ do self.relointerval = math.random(1800,3600) -- random between 30 and 60 mins self.state2flag = false self.SamStateTracker = {} -- table to hold alert states, so we don't trigger state changes twice in adv mode - + self.DLink = false + if EmOnOff then if EmOnOff == false then self.UseEmOnOff = false @@ -325,7 +328,7 @@ do end -- @field #string version - self.version="0.5.2" + self.version="0.6.2" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) --- FSM Functions --- @@ -593,7 +596,7 @@ do -- E.g. `mymantis:SetAdvancedMode(true, 90)` function MANTIS:SetAdvancedMode(onoff, ratio) self:T(self.lid .. "SetAdvancedMode") - self:T({onoff, ratio}) + --self.T({onoff, ratio}) local onoff = onoff or false local ratio = ratio or 100 if (type(self.HQ_Template_CC) == "string") and onoff and self.dynamic then @@ -619,6 +622,17 @@ do return self end + --- Set using an #INTEL_DLINK object instead of #DETECTION + -- @param #MANTIS self + -- @param Ops.Intelligence#INTEL_DLINK DLink The data link object to be used. + function MANTIS:SetUsingDLink(DLink) + self:T(self.lid .. "SetUsingDLink") + self.DLink = true + self.Detection = DLink + self.DLTimeStamp = timer.getAbsTime() + return self + end + --- [Internal] Function to check if HQ is alive -- @param #MANTIS self -- @return #boolean True if HQ is alive, else false @@ -633,10 +647,10 @@ do local hqgrp = GROUP:FindByName(hq) if hqgrp then if hqgrp:IsAlive() then -- ok we're on, hq exists and as alive - self:T(self.lid.." HQ is alive!") + --self.T(self.lid.." HQ is alive!") return true else - self:T(self.lid.." HQ is dead!") + --self.T(self.lid.." HQ is dead!") return false end end @@ -650,7 +664,7 @@ do function MANTIS:_CheckEWRState() self:T(self.lid .. "CheckEWRState") local text = self.lid.." Checking EWR State" - self:T(text) + --self.T(text) local m= MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) if self.verbose then self:I(text) end -- start check @@ -666,7 +680,7 @@ do end end end - self:T(self.lid..string.format(" No of EWR alive is %d", nalive)) + --self.T(self.lid..string.format(" No of EWR alive is %d", nalive)) if nalive > 0 then return true else @@ -682,10 +696,8 @@ do -- @return #number Previous state for tracking 0, 1, or 2 function MANTIS:_CalcAdvState() self:T(self.lid .. "CalcAdvState") - local text = self.lid.." Calculating Advanced State" - self:T(text) - local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then self:I(text) end + local m=MESSAGE:New(self.lid.." Calculating Advanced State",10,"MANTIS"):ToAllIf(self.debug) + if self.verbose then self:I(self.lid.." Calculating Advanced State") end -- start check local currstate = self.adv_state -- save curr state for comparison later local EWR_State = self:_CheckEWRState() @@ -703,10 +715,12 @@ do local ratio = self.adv_ratio / 100 -- e.g. 80/100 = 0.8 ratio = ratio * self.adv_state -- e.g 0.8*2 = 1.6 local newinterval = interval + (interval * ratio) -- e.g. 30+(30*1.6) = 78 - local text = self.lid..string.format(" Calculated OldState/NewState/Interval: %d / %d / %d", currstate, self.adv_state, newinterval) - self:T(text) - local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then self:I(text) end + if self.debug or self.verbose then + local text = self.lid..string.format(" Calculated OldState/NewState/Interval: %d / %d / %d", currstate, self.adv_state, newinterval) + --self.T(text) + local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) + if self.verbose then self:I(text) end + end return newinterval, currstate end @@ -716,13 +730,13 @@ do -- @param #boolean ewr If true, will relocate EWR objects function MANTIS:SetAutoRelocate(hq, ewr) self:T(self.lid .. "SetAutoRelocate") - self:T({hq, ewr}) + --self.T({hq, ewr}) local hqrel = hq or false local ewrel = ewr or false if hqrel or ewrel then self.autorelocate = true self.autorelocateunits = { HQ = hqrel, EWR = ewrel } - self:T({self.autorelocate, self.autorelocateunits}) + --self.T({self.autorelocate, self.autorelocateunits}) end return self end @@ -739,7 +753,7 @@ do local HQGroup = self.HQ_CC if self.autorelocateunits.HQ and self.HQ_CC and HQGroup:IsAlive() then --only relocate if HQ exists local _hqgrp = self.HQ_CC - self:T(self.lid.." Relocating HQ") + --self.T(self.lid.." Relocating HQ") local text = self.lid.." Relocating HQ" --local m= MESSAGE:New(text,10,"MANTIS"):ToAll() _hqgrp:RelocateGroundRandomInRadius(20,500,true,true) @@ -752,7 +766,7 @@ do local EWR_Grps = EWR_GRP.Set --table of objects in SET_GROUP for _,_grp in pairs (EWR_Grps) do if _grp:IsAlive() and _grp:IsGround() then - self:T(self.lid.." Relocating EWR ".._grp:GetName()) + --self.T(self.lid.." Relocating EWR ".._grp:GetName()) local text = self.lid.." Relocating EWR ".._grp:GetName() local m= MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) if self.verbose then self:I(text) end @@ -778,12 +792,14 @@ do for _,_coord in pairs (set) do local coord = _coord -- get current coord to check -- output for cross-check - local dectstring = coord:ToStringLLDMS() - local samstring = samcoordinate:ToStringLLDMS() local targetdistance = samcoordinate:DistanceFromPointVec2(coord) - local text = string.format("Checking SAM at % s - Distance %d m - Target %s", samstring, targetdistance, dectstring) - local m = MESSAGE:New(text,10,"Check"):ToAllIf(self.debug) - if self.verbose then self:I(self.lid..text) end + if self.verbose or self.debug then + local dectstring = coord:ToStringLLDMS() + local samstring = samcoordinate:ToStringLLDMS() + local text = string.format("Checking SAM at % s - Distance %d m - Target %s", samstring, targetdistance, dectstring) + local m = MESSAGE:New(text,10,"Check"):ToAllIf(self.debug) + self:I(self.lid..text) + end -- end output to cross-check if targetdistance <= radius then return true, targetdistance @@ -999,9 +1015,11 @@ do self:__ShoradActivated(1,name, radius, ontime) end -- debug output - local text = string.format("SAM %s switched to alarm state RED!", name) - local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then self:I(self.lid..text) end + if self.debug or self.verbose then + local text = string.format("SAM %s switched to alarm state RED!", name) + local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) + if self.verbose then self:I(self.lid..text) end + end end --end alive else if samgroup:IsAlive() then @@ -1014,9 +1032,11 @@ do self:__GreenState(1,samgroup) self.SamStateTracker[name] = "GREEN" end - local text = string.format("SAM %s switched to alarm state GREEN!", name) - local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then self:I(self.lid..text) end + if self.debug or self.verbose then + local text = string.format("SAM %s switched to alarm state GREEN!", name) + local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) + if self.verbose then self:I(self.lid..text) end + end end --end alive end --end check end --for for loop @@ -1066,6 +1086,20 @@ do end -- end newstate vs oldstate return self end + + --- [Internal] Check DLink state + -- @param #MANTIS self + -- @return #MANTIS self + function MANTIS:_CheckDLinkState() + self:T(self.lid .. "_CheckDLinkState") + local dlink = self.Detection -- Ops.Intelligence#INTEL_DLINK + local TS = timer.getAbsTime() + if not dlink:Is("Running") and (TS - self.DLTimeStamp > 29) then + self.DLink = false + self.Detection = self:StartDetection() -- fall back + self:I(self.lid .. "Intel DLink not running - switching back to single detection!") + end + end --- [Internal] Function to set start state -- @param #MANTIS self @@ -1077,7 +1111,9 @@ do self:T({From, Event, To}) self:T(self.lid.."Starting MANTIS") self:SetSAMStartState() - self.Detection = self:StartDetection() + if not self.DLink then + self.Detection = self:StartDetection() + end if self.advAwacs then self.AWACS_Detection = self:StartAwacsDetection() end @@ -1120,11 +1156,16 @@ do end end - -- timer for advanced state check + -- advanced state check if self.advanced then self:_CheckAdvState() end + -- check DLink state + if self.DLink then + self:_CheckDLinkState() + end + return self end From 552eb5b9d83eaeb57ec1894989946319226f4a56 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 21 Jul 2021 18:40:43 +0200 Subject: [PATCH 110/111] Update CTLD.lua Add housekeeping for dropped troops --- Moose Development/Moose/Ops/CTLD.lua | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 33c33a9d5..9143eacf1 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -528,7 +528,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="0.1.4r2" +CTLD.version="0.1.4r3" --- Instantiate a new CTLD. -- @param #CTLD self @@ -961,7 +961,7 @@ end local nearestDistance = 10000000 for k,v in pairs(self.DroppedTroops) do local distance = self:_GetDistance(v:GetCoordinate(),unitcoord) - if distance < nearestDistance then + if distance < nearestDistance and distance ~= -1 then nearestGroup = v nearestGroupIndex = k nearestDistance = distance @@ -2349,6 +2349,20 @@ end return self end + --- (Internal) Run through DroppedTroops and capture alive units + -- @param #CTLD self + -- @return #CTLD self + function CTLD:CleanDroppedTroops() + local troops = self.DroppedTroops + local newtable = {} + for _index, _group in pairs (troops) do + if _group and _group:IsAlive() then + newtable[_index] = _group + end + end + self.DroppedTroops = newtable + return self + end ------------------------------------------------------------------- -- FSM functions ------------------------------------------------------------------- @@ -2388,6 +2402,7 @@ end -- @return #CTLD self function CTLD:onbeforeStatus(From, Event, To) self:T({From, Event, To}) + self:CleanDroppedTroops() self:_RefreshF10Menus() self:_RefreshRadioBeacons() self:CheckAutoHoverload() From d517cba7653683eab27d25803822d93bfdcdf273 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 21 Jul 2021 18:45:57 +0200 Subject: [PATCH 111/111] Update Intelligence.lua no dupe docs --- Moose Development/Moose/Ops/Intelligence.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index 98c62cd68..0b98a85ed 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -1456,8 +1456,6 @@ end --- === -- -- ### Author: **applevangelist** --- @module Ops.Intel_DLink --- @image OPS_Intel.png --- INTEL_DLINK class. -- @type INTEL_DLINK