Merge branch 'FF/Ops' into FF/OpsDev
2
.github/workflows/gh-pages.yml
vendored
@ -75,4 +75,4 @@ jobs:
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
- run: npm install linkinator
|
||||
- run: npx linkinator https://flightcontrol-master.github.io/MOOSE/ --verbosity error --timeout 5000 --recurse --skip "(java.com)"
|
||||
- run: npx linkinator https://flightcontrol-master.github.io/MOOSE/ --verbosity error --timeout 5000 --recurse --skip "(java.com)" --retry-errors --retry-errors-count 3 --retry-errors-jitter
|
||||
|
||||
@ -396,7 +396,7 @@ end
|
||||
CLIENTMENUMANAGER = {
|
||||
ClassName = "CLIENTMENUMANAGER",
|
||||
lid = "",
|
||||
version = "0.1.3",
|
||||
version = "0.1.4",
|
||||
name = nil,
|
||||
clientset = nil,
|
||||
menutree = {},
|
||||
@ -439,18 +439,18 @@ function CLIENTMENUMANAGER:_EventHandler(EventData)
|
||||
--self:I(self.lid.."_EventHandler: "..tostring(EventData.IniPlayerName))
|
||||
if EventData.id == EVENTS.PlayerLeaveUnit or EventData.id == EVENTS.Ejection or EventData.id == EVENTS.Crash or EventData.id == EVENTS.PilotDead then
|
||||
self:T(self.lid.."Leave event for player: "..tostring(EventData.IniPlayerName))
|
||||
local Client = _DATABASE:FindClient( EventData.IniPlayerName )
|
||||
local Client = _DATABASE:FindClient( EventData.IniUnitName )
|
||||
if Client then
|
||||
self:ResetMenu(Client)
|
||||
end
|
||||
elseif (EventData.id == EVENTS.PlayerEnterAircraft) and EventData.IniCoalition == self.Coalition then
|
||||
if EventData.IniPlayerName and EventData.IniGroup then
|
||||
if (not self.clientset:IsIncludeObject(_DATABASE:FindClient( EventData.IniPlayerName ))) then
|
||||
if (not self.clientset:IsIncludeObject(_DATABASE:FindClient( EventData.IniUnitName ))) then
|
||||
self:T(self.lid.."Client not in SET: "..EventData.IniPlayerName)
|
||||
return self
|
||||
end
|
||||
--self:I(self.lid.."Join event for player: "..EventData.IniPlayerName)
|
||||
local player = _DATABASE:FindClient( EventData.IniPlayerName )
|
||||
local player = _DATABASE:FindClient( EventData.IniUnitName )
|
||||
self:Propagate(player)
|
||||
end
|
||||
elseif EventData.id == EVENTS.PlayerEnterUnit then
|
||||
@ -668,7 +668,7 @@ function CLIENTMENUMANAGER:Propagate(Client)
|
||||
for _,_client in pairs(Set) do
|
||||
local client = _client -- Wrapper.Client#CLIENT
|
||||
if client and client:IsAlive() then
|
||||
local playername = client:GetPlayerName()
|
||||
local playername = client:GetPlayerName() or "none"
|
||||
if not self.playertree[playername] then
|
||||
self.playertree[playername] = {}
|
||||
end
|
||||
|
||||
@ -261,6 +261,15 @@ EVENTS = {
|
||||
SimulationStart = world.event.S_EVENT_SIMULATION_START or -1,
|
||||
WeaponRearm = world.event.S_EVENT_WEAPON_REARM or -1,
|
||||
WeaponDrop = world.event.S_EVENT_WEAPON_DROP or -1,
|
||||
-- Added with DCS 2.9.0
|
||||
UnitTaskTimeout = world.event.S_EVENT_UNIT_TASK_TIMEOUT or -1,
|
||||
UnitTaskStage = world.event.S_EVENT_UNIT_TASK_STAGE or -1,
|
||||
MacSubtaskScore = world.event.S_EVENT_MAC_SUBTASK_SCORE or -1,
|
||||
MacExtraScore = world.event.S_EVENT_MAC_EXTRA_SCORE or -1,
|
||||
MissionRestart = world.event.S_EVENT_MISSION_RESTART or -1,
|
||||
MissionWinner = world.event.S_EVENT_MISSION_WINNER or -1,
|
||||
PostponedTakeoff = world.event.S_EVENT_POSTPONED_TAKEOFF or -1,
|
||||
PostponedLand = world.event.S_EVENT_POSTPONED_LAND or -1,
|
||||
}
|
||||
|
||||
--- The Event structure
|
||||
@ -636,6 +645,55 @@ local _EVENTMETA = {
|
||||
Event = "OnEventWeaponDrop",
|
||||
Text = "S_EVENT_WEAPON_DROP"
|
||||
},
|
||||
-- DCS 2.9
|
||||
[EVENTS.UnitTaskTimeout] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventUnitTaskTimeout",
|
||||
Text = "S_EVENT_UNIT_TASK_TIMEOUT "
|
||||
},
|
||||
[EVENTS.UnitTaskStage] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventUnitTaskStage",
|
||||
Text = "S_EVENT_UNIT_TASK_STAGE "
|
||||
},
|
||||
[EVENTS.MacSubtaskScore] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventMacSubtaskScore",
|
||||
Text = "S_EVENT_MAC_SUBTASK_SCORE"
|
||||
},
|
||||
[EVENTS.MacExtraScore] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventMacExtraScore",
|
||||
Text = "S_EVENT_MAC_EXTRA_SCOREP"
|
||||
},
|
||||
[EVENTS.MissionRestart] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventMissionRestart",
|
||||
Text = "S_EVENT_MISSION_RESTART"
|
||||
},
|
||||
[EVENTS.MissionWinner] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventMissionWinner",
|
||||
Text = "S_EVENT_MISSION_WINNER"
|
||||
},
|
||||
[EVENTS.PostponedTakeoff] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventPostponedTakeoff",
|
||||
Text = "S_EVENT_POSTPONED_TAKEOFF"
|
||||
},
|
||||
[EVENTS.PostponedLand] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventPostponedLand",
|
||||
Text = "S_EVENT_POSTPONED_LAND"
|
||||
},
|
||||
}
|
||||
|
||||
--- The Events structure
|
||||
@ -1245,11 +1303,14 @@ function EVENT:onEvent( Event )
|
||||
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()
|
||||
-- Workaround for borked target info on cruise missiles
|
||||
if Event.TgtDCSUnitName and Event.TgtDCSUnitName ~= "" then
|
||||
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()
|
||||
end
|
||||
else
|
||||
Event.TgtDCSUnitName = string.format("No target object for Event ID %s", tostring(Event.id))
|
||||
Event.TgtUnitName = Event.TgtDCSUnitName
|
||||
@ -1287,7 +1348,8 @@ function EVENT:onEvent( Event )
|
||||
Event.Weapon = Event.weapon
|
||||
Event.WeaponName = Event.Weapon:getTypeName()
|
||||
Event.WeaponUNIT = CLIENT:Find( Event.Weapon, '', true ) -- Sometimes, the weapon is a player unit!
|
||||
Event.WeaponPlayerName = Event.WeaponUNIT and Event.Weapon:getPlayerName()
|
||||
Event.WeaponPlayerName = Event.WeaponUNIT and Event.Weapon.getPlayerName and Event.Weapon:getPlayerName()
|
||||
--Event.WeaponPlayerName = Event.WeaponUNIT and Event.Weapon:getPlayerName()
|
||||
Event.WeaponCoalition = Event.WeaponUNIT and Event.Weapon:getCoalition()
|
||||
Event.WeaponCategory = Event.WeaponUNIT and Event.Weapon:getDesc().category
|
||||
Event.WeaponTypeName = Event.WeaponUNIT and Event.Weapon:getTypeName()
|
||||
|
||||
@ -8,22 +8,6 @@
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # Demo Missions
|
||||
--
|
||||
-- ### [POINT_VEC Demo Missions source code]()
|
||||
--
|
||||
-- ### [POINT_VEC Demo Missions, only for beta testers]()
|
||||
--
|
||||
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # YouTube Channel
|
||||
--
|
||||
-- ### [POINT_VEC YouTube Channel]()
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Authors:
|
||||
--
|
||||
-- * FlightControl (Design & Programming)
|
||||
@ -937,7 +921,7 @@ do -- COORDINATE
|
||||
end
|
||||
|
||||
|
||||
--- Return an angle in radians from the COORDINATE using a direction vector in Vec3 format.
|
||||
--- Return an angle in radians from the COORDINATE using a **direction vector in Vec3 format**.
|
||||
-- @param #COORDINATE self
|
||||
-- @param DCS#Vec3 DirectionVec3 The direction vector in Vec3 format.
|
||||
-- @return #number DirectionRadians The angle in radians.
|
||||
@ -950,10 +934,12 @@ do -- COORDINATE
|
||||
return DirectionRadians
|
||||
end
|
||||
|
||||
--- Return an angle in degrees from the COORDINATE using a direction vector in Vec3 format.
|
||||
--- Return an angle in degrees from the COORDINATE using a **direction vector in Vec3 format**.
|
||||
-- @param #COORDINATE self
|
||||
-- @param DCS#Vec3 DirectionVec3 The direction vector in Vec3 format.
|
||||
-- @return #number DirectionRadians The angle in degrees.
|
||||
-- @usage
|
||||
-- local directionAngle = currentCoordinate:GetAngleDegrees(currentCoordinate:GetDirectionVec3(sourceCoordinate:GetVec3()))
|
||||
function COORDINATE:GetAngleDegrees( DirectionVec3 )
|
||||
local AngleRadians = self:GetAngleRadians( DirectionVec3 )
|
||||
local Angle = UTILS.ToDegree( AngleRadians )
|
||||
@ -3038,6 +3024,16 @@ do -- COORDINATE
|
||||
return BRAANATO
|
||||
end
|
||||
|
||||
--- Return the BULLSEYE as COORDINATE Object
|
||||
-- @param #number Coalition Coalition of the bulls eye to return, e.g. coalition.side.BLUE
|
||||
-- @return #COORDINATE self
|
||||
-- @usage
|
||||
-- -- note the dot (.) here,not using the colon (:)
|
||||
-- local redbulls = COORDINATE.GetBullseyeCoordinate(coalition.side.RED)
|
||||
function COORDINATE.GetBullseyeCoordinate(Coalition)
|
||||
return COORDINATE:NewFromVec3( coalition.getMainRefPoint( Coalition ) )
|
||||
end
|
||||
|
||||
--- Return a BULLS string out of the BULLS of the coalition to the COORDINATE.
|
||||
-- @param #COORDINATE self
|
||||
-- @param DCS#coalition.side Coalition The coalition.
|
||||
|
||||
@ -1065,8 +1065,15 @@ do
|
||||
self:FilterActive( false )
|
||||
|
||||
return self
|
||||
|
||||
--- Filter the set once
|
||||
-- @function [parent=#SET_GROUP] FilterOnce
|
||||
-- @param #SET_GROUP self
|
||||
-- @return #SET_GROUP self
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Get a *new* set that only contains alive groups.
|
||||
-- @param #SET_GROUP self
|
||||
-- @return #SET_GROUP Set of alive groups.
|
||||
@ -1976,6 +1983,7 @@ do
|
||||
--- Get the closest group of the set with respect to a given reference coordinate. Optionally, only groups of given coalitions are considered in the search.
|
||||
-- @param #SET_GROUP self
|
||||
-- @param Core.Point#COORDINATE Coordinate Reference Coordinate from which the closest group is determined.
|
||||
-- @param #table Coalitions (Optional) Table of coalition #number entries to filter for.
|
||||
-- @return Wrapper.Group#GROUP The closest group (if any).
|
||||
-- @return #number Distance in meters to the closest group.
|
||||
function SET_GROUP:GetClosestGroup(Coordinate, Coalitions)
|
||||
|
||||
@ -320,7 +320,7 @@ function SPAWN:New( SpawnTemplatePrefix )
|
||||
self.AIOnOff = true -- The AI is on by default when spawning a group.
|
||||
self.SpawnUnControlled = false
|
||||
self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name.
|
||||
self.DelayOnOff = false -- No intial delay when spawning the first group.
|
||||
self.DelayOnOff = false -- No initial delay when spawning the first group.
|
||||
self.SpawnGrouping = nil -- No grouping.
|
||||
self.SpawnInitLivery = nil -- No special livery.
|
||||
self.SpawnInitSkill = nil -- No special skill.
|
||||
@ -332,6 +332,7 @@ function SPAWN:New( SpawnTemplatePrefix )
|
||||
self.SpawnInitModexPostfix = nil
|
||||
self.SpawnInitAirbase = nil
|
||||
self.TweakedTemplate = false -- Check if the user is using self made template.
|
||||
self.SpawnRandomCallsign = false
|
||||
|
||||
self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned.
|
||||
else
|
||||
@ -1099,6 +1100,14 @@ function SPAWN:InitRandomizeZones( SpawnZoneTable )
|
||||
return self
|
||||
end
|
||||
|
||||
--- [AIR/Fighter only!] This method randomizes the callsign for a new group.
|
||||
-- @param #SPAWN self
|
||||
-- @return #SPAWN self
|
||||
function SPAWN:InitRandomizeCallsign()
|
||||
self.SpawnRandomCallsign = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- This method sets a spawn position for the group that is different from the location of the template.
|
||||
-- @param #SPAWN self
|
||||
-- @param Core.Point#COORDINATE Coordinate The position to spawn from
|
||||
@ -2783,7 +2792,7 @@ end
|
||||
-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned.
|
||||
-- @usage
|
||||
--
|
||||
-- local SpawnPointVec2 = ZONE:New( ZoneName ):GetPointVec2()
|
||||
-- local SpawnPointVec2 = ZONE:New( ZoneName ):GetPointVec2()
|
||||
--
|
||||
-- -- Spawn at the zone center position at the height specified in the ME of the group template!
|
||||
-- SpawnAirplanes:SpawnFromPointVec2( SpawnPointVec2 )
|
||||
@ -3275,22 +3284,143 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2
|
||||
end
|
||||
|
||||
-- Callsign
|
||||
|
||||
if self.SpawnRandomCallsign and SpawnTemplate.units[1].callsign then
|
||||
if type( SpawnTemplate.units[1].callsign ) ~= "number" then
|
||||
-- change callsign
|
||||
local min = 1
|
||||
local max = 8
|
||||
local ctable = CALLSIGN.Aircraft
|
||||
if string.find(SpawnTemplate.units[1].type, "A-10",1,true) then
|
||||
max = 12
|
||||
end
|
||||
if string.find(SpawnTemplate.units[1].type, "18",1,true) then
|
||||
min = 9
|
||||
max = 20
|
||||
ctable = CALLSIGN.F18
|
||||
end
|
||||
if string.find(SpawnTemplate.units[1].type, "16",1,true) then
|
||||
min = 9
|
||||
max = 20
|
||||
ctable = CALLSIGN.F16
|
||||
end
|
||||
if SpawnTemplate.units[1].type == "F-15E" then
|
||||
min = 9
|
||||
max = 18
|
||||
ctable = CALLSIGN.F15E
|
||||
end
|
||||
local callsignnr = math.random(min,max)
|
||||
local callsignname = "Enfield"
|
||||
for name, value in pairs(ctable) do
|
||||
if value==callsignnr then
|
||||
callsignname = name
|
||||
end
|
||||
end
|
||||
for UnitID = 1, #SpawnTemplate.units do
|
||||
SpawnTemplate.units[UnitID].callsign[1] = callsignnr
|
||||
SpawnTemplate.units[UnitID].callsign[2] = UnitID
|
||||
SpawnTemplate.units[UnitID].callsign[3] = "1"
|
||||
SpawnTemplate.units[UnitID].callsign["name"] = tostring(callsignname)..tostring(UnitID).."1"
|
||||
-- UTILS.PrintTableToLog(SpawnTemplate.units[UnitID].callsign,1)
|
||||
end
|
||||
else
|
||||
-- Russkis
|
||||
for UnitID = 1, #SpawnTemplate.units do
|
||||
SpawnTemplate.units[UnitID].callsign = math.random(1,999)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for UnitID = 1, #SpawnTemplate.units do
|
||||
local Callsign = SpawnTemplate.units[UnitID].callsign
|
||||
if Callsign then
|
||||
if type( Callsign ) ~= "number" then -- blue callsign
|
||||
-- UTILS.PrintTableToLog(Callsign,1)
|
||||
Callsign[2] = ((SpawnIndex - 1) % 10) + 1
|
||||
local CallsignName = SpawnTemplate.units[UnitID].callsign["name"] -- #string
|
||||
CallsignName = string.match(CallsignName,"^(%a+)") -- 2.8 - only the part w/o numbers
|
||||
local CallsignLen = CallsignName:len()
|
||||
SpawnTemplate.units[UnitID].callsign[2] = UnitID
|
||||
SpawnTemplate.units[UnitID].callsign["name"] = CallsignName:sub( 1, CallsignLen ) .. SpawnTemplate.units[UnitID].callsign[2] .. SpawnTemplate.units[UnitID].callsign[3]
|
||||
else
|
||||
SpawnTemplate.units[UnitID].callsign = Callsign + SpawnIndex
|
||||
end
|
||||
end
|
||||
-- Link16
|
||||
local AddProps = SpawnTemplate.units[UnitID].AddPropAircraft
|
||||
if AddProps then
|
||||
if SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16 then
|
||||
-- 4 digit octal with leading 0
|
||||
if tonumber(SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16) ~= nil then
|
||||
local octal = SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16
|
||||
local decimal = UTILS.OctalToDecimal(octal)+UnitID-1
|
||||
SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16 = string.format("%05d",UTILS.DecimalToOctal(decimal))
|
||||
else -- ED bug - chars in here
|
||||
local STN = math.floor(UTILS.RandomGaussian(4088/2,nil,1000,4088))
|
||||
STN = STN+UnitID-1
|
||||
local OSTN = UTILS.DecimalToOctal(STN)
|
||||
SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16 = string.format("%05d",OSTN)
|
||||
end
|
||||
end
|
||||
-- A10CII
|
||||
if SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN then
|
||||
-- 3 digit octal with leading 0
|
||||
if tonumber(SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN) ~= nil then
|
||||
local octal = SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN
|
||||
local decimal = UTILS.OctalToDecimal(octal)+UnitID-1
|
||||
SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN = string.format("%04d",UTILS.DecimalToOctal(decimal))
|
||||
else -- ED bug - chars in here
|
||||
local STN = math.floor(UTILS.RandomGaussian(504/2,nil,100,504))
|
||||
STN = STN+UnitID-1
|
||||
local OSTN = UTILS.DecimalToOctal(STN)
|
||||
SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN = string.format("%04d",OSTN)
|
||||
end
|
||||
end
|
||||
-- VoiceCallsignNumber
|
||||
if SpawnTemplate.units[UnitID].AddPropAircraft.VoiceCallsignNumber then
|
||||
SpawnTemplate.units[UnitID].AddPropAircraft.VoiceCallsignNumber = SpawnTemplate.units[UnitID].callsign[2] .. SpawnTemplate.units[UnitID].callsign[3]
|
||||
end
|
||||
-- VoiceCallsignLabel
|
||||
if SpawnTemplate.units[UnitID].AddPropAircraft.VoiceCallsignLabel then
|
||||
local CallsignName = SpawnTemplate.units[UnitID].callsign["name"] -- #string
|
||||
CallsignName = string.match(CallsignName,"^(%a+)") -- 2.8 - only the part w/o numbers
|
||||
local label = "NY" -- Navy One exception
|
||||
if not string.find(CallsignName," ") then
|
||||
label = string.upper(string.match(CallsignName,"^%a")..string.match(CallsignName,"%a$"))
|
||||
end
|
||||
SpawnTemplate.units[UnitID].AddPropAircraft.VoiceCallsignLabel = label
|
||||
end
|
||||
-- UTILS.PrintTableToLog(SpawnTemplate.units[UnitID].AddPropAircraft,1)
|
||||
-- FlightLead
|
||||
if SpawnTemplate.units[UnitID].datalinks and SpawnTemplate.units[UnitID].datalinks.Link16 and SpawnTemplate.units[UnitID].datalinks.Link16.settings then
|
||||
SpawnTemplate.units[UnitID].datalinks.Link16.settings.flightLead = UnitID == 1 and true or false
|
||||
end
|
||||
-- A10CII
|
||||
if SpawnTemplate.units[UnitID].datalinks and SpawnTemplate.units[UnitID].datalinks.SADL and SpawnTemplate.units[UnitID].datalinks.SADL.settings then
|
||||
SpawnTemplate.units[UnitID].datalinks.SADL.settings.flightLead = UnitID == 1 and true or false
|
||||
end
|
||||
-- UTILS.PrintTableToLog(SpawnTemplate.units[UnitID].datalinks,1)
|
||||
end
|
||||
end
|
||||
-- Link16 team members
|
||||
for UnitID = 1, #SpawnTemplate.units do
|
||||
if SpawnTemplate.units[UnitID].datalinks and SpawnTemplate.units[UnitID].datalinks.Link16 and SpawnTemplate.units[UnitID].datalinks.Link16.network then
|
||||
local team = {}
|
||||
local isF16 = string.find(SpawnTemplate.units[UnitID].type,"F-16",1,true) and true or false
|
||||
for ID = 1, #SpawnTemplate.units do
|
||||
local member = {}
|
||||
member.missionUnitId = ID
|
||||
if isF16 then
|
||||
member.TDOA = true
|
||||
end
|
||||
table.insert(team,member)
|
||||
end
|
||||
SpawnTemplate.units[UnitID].datalinks.Link16.network.teamMembers = team
|
||||
end
|
||||
end
|
||||
|
||||
self:T3( { "Template:", SpawnTemplate } )
|
||||
--UTILS.PrintTableToLog(SpawnTemplate,1)
|
||||
return SpawnTemplate
|
||||
|
||||
end
|
||||
|
||||
@ -20,13 +20,15 @@
|
||||
-- ### Author: FlightControl - Framework Design & Programming
|
||||
-- ### Refactoring to use the Runway auto-detection: Applevangelist
|
||||
-- @date August 2022
|
||||
-- Last Update Nov 2023
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Functional.ATC_Ground
|
||||
-- @image Air_Traffic_Control_Ground_Operations.JPG
|
||||
|
||||
--- @type ATC_GROUND
|
||||
---
|
||||
-- @type ATC_GROUND
|
||||
-- @field Core.Set#SET_CLIENT SetClient
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
@ -39,7 +41,8 @@ ATC_GROUND = {
|
||||
AirbaseNames = nil,
|
||||
}
|
||||
|
||||
--- @type ATC_GROUND.AirbaseNames
|
||||
---
|
||||
-- @type ATC_GROUND.AirbaseNames
|
||||
-- @list <#string>
|
||||
|
||||
|
||||
@ -51,7 +54,7 @@ function ATC_GROUND:New( Airbases, AirbaseList )
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- #ATC_GROUND
|
||||
self:E( { self.ClassName, Airbases } )
|
||||
self:T( { self.ClassName, Airbases } )
|
||||
|
||||
self.Airbases = Airbases
|
||||
self.AirbaseList = AirbaseList
|
||||
@ -82,7 +85,7 @@ function ATC_GROUND:New( Airbases, AirbaseList )
|
||||
end
|
||||
|
||||
self.SetClient:ForEachClient(
|
||||
--- @param Wrapper.Client#CLIENT Client
|
||||
-- @param Wrapper.Client#CLIENT Client
|
||||
function( Client )
|
||||
Client:SetState( self, "Speeding", false )
|
||||
Client:SetState( self, "Warnings", 0)
|
||||
@ -246,11 +249,11 @@ function ATC_GROUND:SetMaximumKickSpeedMiph( MaximumKickSpeedMiph, Airbase )
|
||||
return self
|
||||
end
|
||||
|
||||
--- @param #ATC_GROUND self
|
||||
-- @param #ATC_GROUND self
|
||||
function ATC_GROUND:_AirbaseMonitor()
|
||||
|
||||
self.SetClient:ForEachClient(
|
||||
--- @param Wrapper.Client#CLIENT Client
|
||||
-- @param Wrapper.Client#CLIENT Client
|
||||
function( Client )
|
||||
|
||||
if Client:IsAlive() then
|
||||
@ -258,7 +261,7 @@ function ATC_GROUND:_AirbaseMonitor()
|
||||
local IsOnGround = Client:InAir() == false
|
||||
|
||||
for AirbaseID, AirbaseMeta in pairs( self.Airbases ) do
|
||||
self:E( AirbaseID, AirbaseMeta.KickSpeed )
|
||||
self:T( AirbaseID, AirbaseMeta.KickSpeed )
|
||||
|
||||
if AirbaseMeta.Monitor == true and Client:IsInZone( AirbaseMeta.ZoneBoundary ) then
|
||||
|
||||
@ -271,7 +274,7 @@ function ATC_GROUND:_AirbaseMonitor()
|
||||
|
||||
if IsOnGround then
|
||||
local Taxi = Client:GetState( self, "Taxi" )
|
||||
self:E( Taxi )
|
||||
self:T( Taxi )
|
||||
if Taxi == false then
|
||||
local Velocity = VELOCITY:New( AirbaseMeta.KickSpeed or self.KickSpeed )
|
||||
Client:Message( "Welcome to " .. AirbaseID .. ". The maximum taxiing speed is " ..
|
||||
@ -331,7 +334,7 @@ function ATC_GROUND:_AirbaseMonitor()
|
||||
Client:SetState( self, "Warnings", SpeedingWarnings + 1 )
|
||||
else
|
||||
MESSAGE:New( "Penalty! Player " .. Client:GetPlayerName() .. " has been kicked, due to a severe airbase traffic rule violation ...", 10, "ATC" ):ToAll()
|
||||
--- @param Wrapper.Client#CLIENT Client
|
||||
-- @param Wrapper.Client#CLIENT Client
|
||||
Client:Destroy()
|
||||
Client:SetState( self, "Speeding", false )
|
||||
Client:SetState( self, "Warnings", 0 )
|
||||
@ -363,7 +366,7 @@ function ATC_GROUND:_AirbaseMonitor()
|
||||
Client:SetState( self, "OffRunwayWarnings", OffRunwayWarnings + 1 )
|
||||
else
|
||||
MESSAGE:New( "Penalty! Player " .. Client:GetPlayerName() .. " has been kicked, due to a severe airbase traffic rule violation ...", 10, "ATC" ):ToAll()
|
||||
--- @param Wrapper.Client#CLIENT Client
|
||||
-- @param Wrapper.Client#CLIENT Client
|
||||
Client:Destroy()
|
||||
Client:SetState( self, "IsOffRunway", false )
|
||||
Client:SetState( self, "OffRunwayWarnings", 0 )
|
||||
@ -424,13 +427,20 @@ ATC_GROUND_UNIVERSAL = {
|
||||
|
||||
--- Creates a new ATC\_GROUND\_UNIVERSAL object. This works on any map.
|
||||
-- @param #ATC_GROUND_UNIVERSAL self
|
||||
-- @param AirbaseList (Optional) A table of Airbase Names.
|
||||
-- @param AirbaseList A table of Airbase Names. Leave empty to cover **all** airbases of the map.
|
||||
-- @return #ATC_GROUND_UNIVERSAL self
|
||||
-- @usage
|
||||
-- -- define monitoring for one airbase
|
||||
-- local atc=ATC_GROUND_UNIVERSAL:New({AIRBASE.Syria.Gecitkale})
|
||||
-- -- set kick speed
|
||||
-- atc:SetKickSpeed(UTILS.KnotsToMps(20))
|
||||
-- -- start monitoring evey 10 secs
|
||||
-- atc:Start(10)
|
||||
function ATC_GROUND_UNIVERSAL:New(AirbaseList)
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- #ATC_GROUND
|
||||
self:E( { self.ClassName } )
|
||||
self:T( { self.ClassName } )
|
||||
|
||||
self.Airbases = {}
|
||||
|
||||
@ -440,6 +450,13 @@ function ATC_GROUND_UNIVERSAL:New(AirbaseList)
|
||||
|
||||
self.AirbaseList = AirbaseList
|
||||
|
||||
if not self.AirbaseList then
|
||||
self.AirbaseList = {}
|
||||
for _name,_ in pairs(_DATABASE.AIRBASES) do
|
||||
self.AirbaseList[_name]=_name
|
||||
end
|
||||
end
|
||||
|
||||
self.SetClient = SET_CLIENT:New():FilterCategories( "plane" ):FilterStart()
|
||||
|
||||
|
||||
@ -460,8 +477,9 @@ function ATC_GROUND_UNIVERSAL:New(AirbaseList)
|
||||
self.Airbases[AirbaseName].Monitor = true
|
||||
end
|
||||
|
||||
|
||||
self.SetClient:ForEachClient(
|
||||
--- @param Wrapper.Client#CLIENT Client
|
||||
-- @param Wrapper.Client#CLIENT Client
|
||||
function( Client )
|
||||
Client:SetState( self, "Speeding", false )
|
||||
Client:SetState( self, "Warnings", 0)
|
||||
@ -679,7 +697,7 @@ end
|
||||
-- @param #ATC_GROUND_UNIVERSAL self
|
||||
-- @return #ATC_GROUND_UNIVERSAL self
|
||||
function ATC_GROUND_UNIVERSAL:_AirbaseMonitor()
|
||||
|
||||
self:I("_AirbaseMonitor")
|
||||
self.SetClient:ForEachClient(
|
||||
--- @param Wrapper.Client#CLIENT Client
|
||||
function( Client )
|
||||
@ -689,7 +707,7 @@ function ATC_GROUND_UNIVERSAL:_AirbaseMonitor()
|
||||
local IsOnGround = Client:InAir() == false
|
||||
|
||||
for AirbaseID, AirbaseMeta in pairs( self.Airbases ) do
|
||||
self:E( AirbaseID, AirbaseMeta.KickSpeed )
|
||||
self:T( AirbaseID, AirbaseMeta.KickSpeed )
|
||||
|
||||
if AirbaseMeta.Monitor == true and Client:IsInZone( AirbaseMeta.ZoneBoundary ) then
|
||||
|
||||
@ -706,7 +724,7 @@ function ATC_GROUND_UNIVERSAL:_AirbaseMonitor()
|
||||
|
||||
if IsOnGround then
|
||||
local Taxi = Client:GetState( self, "Taxi" )
|
||||
self:E( Taxi )
|
||||
self:T( Taxi )
|
||||
if Taxi == false then
|
||||
local Velocity = VELOCITY:New( AirbaseMeta.KickSpeed or self.KickSpeed )
|
||||
Client:Message( "Welcome to " .. AirbaseID .. ". The maximum taxiing speed is " ..
|
||||
@ -766,7 +784,7 @@ function ATC_GROUND_UNIVERSAL:_AirbaseMonitor()
|
||||
Client:SetState( self, "Warnings", SpeedingWarnings + 1 )
|
||||
else
|
||||
MESSAGE:New( "Penalty! Player " .. Client:GetPlayerName() .. " has been kicked, due to a severe airbase traffic rule violation ...", 10, "ATC" ):ToAll()
|
||||
--- @param Wrapper.Client#CLIENT Client
|
||||
-- @param Wrapper.Client#CLIENT Client
|
||||
Client:Destroy()
|
||||
Client:SetState( self, "Speeding", false )
|
||||
Client:SetState( self, "Warnings", 0 )
|
||||
@ -798,7 +816,7 @@ function ATC_GROUND_UNIVERSAL:_AirbaseMonitor()
|
||||
Client:SetState( self, "OffRunwayWarnings", OffRunwayWarnings + 1 )
|
||||
else
|
||||
MESSAGE:New( "Penalty! Player " .. Client:GetPlayerName() .. " has been kicked, due to a severe airbase traffic rule violation ...", 10, "ATC" ):ToAll()
|
||||
--- @param Wrapper.Client#CLIENT Client
|
||||
-- @param Wrapper.Client#CLIENT Client
|
||||
Client:Destroy()
|
||||
Client:SetState( self, "IsOffRunway", false )
|
||||
Client:SetState( self, "OffRunwayWarnings", 0 )
|
||||
@ -838,15 +856,16 @@ end
|
||||
|
||||
--- Start SCHEDULER for ATC_GROUND_UNIVERSAL object.
|
||||
-- @param #ATC_GROUND_UNIVERSAL self
|
||||
-- @param RepeatScanSeconds Time in second for defining occurency of alerts.
|
||||
-- @param RepeatScanSeconds Time in second for defining schedule of alerts.
|
||||
-- @return #ATC_GROUND_UNIVERSAL self
|
||||
function ATC_GROUND_UNIVERSAL:Start( RepeatScanSeconds )
|
||||
RepeatScanSeconds = RepeatScanSeconds or 0.05
|
||||
self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, 2, RepeatScanSeconds )
|
||||
self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, RepeatScanSeconds )
|
||||
return self
|
||||
end
|
||||
|
||||
--- @type ATC_GROUND_CAUCASUS
|
||||
---
|
||||
-- @type ATC_GROUND_CAUCASUS
|
||||
-- @extends #ATC_GROUND
|
||||
|
||||
--- # ATC\_GROUND\_CAUCASUS, extends @{#ATC_GROUND_UNIVERSAL}
|
||||
@ -981,12 +1000,12 @@ end
|
||||
-- @return nothing
|
||||
function ATC_GROUND_CAUCASUS:Start( RepeatScanSeconds )
|
||||
RepeatScanSeconds = RepeatScanSeconds or 0.05
|
||||
self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, 2, RepeatScanSeconds )
|
||||
self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, RepeatScanSeconds )
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- @type ATC_GROUND_NEVADA
|
||||
---
|
||||
-- @type ATC_GROUND_NEVADA
|
||||
-- @extends #ATC_GROUND
|
||||
|
||||
|
||||
@ -1120,11 +1139,11 @@ end
|
||||
-- @return nothing
|
||||
function ATC_GROUND_NEVADA:Start( RepeatScanSeconds )
|
||||
RepeatScanSeconds = RepeatScanSeconds or 0.05
|
||||
self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, 2, RepeatScanSeconds )
|
||||
self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, RepeatScanSeconds )
|
||||
end
|
||||
|
||||
|
||||
--- @type ATC_GROUND_NORMANDY
|
||||
---
|
||||
-- @type ATC_GROUND_NORMANDY
|
||||
-- @extends #ATC_GROUND
|
||||
|
||||
|
||||
@ -1277,10 +1296,11 @@ end
|
||||
-- @return nothing
|
||||
function ATC_GROUND_NORMANDY:Start( RepeatScanSeconds )
|
||||
RepeatScanSeconds = RepeatScanSeconds or 0.05
|
||||
self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, 2, RepeatScanSeconds )
|
||||
self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, RepeatScanSeconds )
|
||||
end
|
||||
|
||||
--- @type ATC_GROUND_PERSIANGULF
|
||||
---
|
||||
-- @type ATC_GROUND_PERSIANGULF
|
||||
-- @extends #ATC_GROUND
|
||||
|
||||
|
||||
@ -1419,11 +1439,11 @@ end
|
||||
-- @return nothing
|
||||
function ATC_GROUND_PERSIANGULF:Start( RepeatScanSeconds )
|
||||
RepeatScanSeconds = RepeatScanSeconds or 0.05
|
||||
self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, 2, RepeatScanSeconds )
|
||||
self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, RepeatScanSeconds )
|
||||
end
|
||||
|
||||
|
||||
--- @type ATC_GROUND_MARIANAISLANDS
|
||||
-- @type ATC_GROUND_MARIANAISLANDS
|
||||
-- @extends #ATC_GROUND
|
||||
|
||||
|
||||
@ -1517,7 +1537,7 @@ end
|
||||
-- * @{#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
|
||||
-- @field #ATC_GROUND_MARIANAISLANDS
|
||||
ATC_GROUND_MARIANAISLANDS = {
|
||||
ClassName = "ATC_GROUND_MARIANAISLANDS",
|
||||
}
|
||||
@ -1529,7 +1549,7 @@ ATC_GROUND_MARIANAISLANDS = {
|
||||
function ATC_GROUND_MARIANAISLANDS:New( AirbaseNames )
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, ATC_GROUND_UNIVERSAL:New( self.Airbases, AirbaseNames ) )
|
||||
local self = BASE:Inherit( self, ATC_GROUND_UNIVERSAL:New( AirbaseNames ) )
|
||||
|
||||
self:SetKickSpeedKmph( 50 )
|
||||
self:SetMaximumKickSpeedKmph( 150 )
|
||||
@ -1543,5 +1563,5 @@ end
|
||||
-- @return nothing
|
||||
function ATC_GROUND_MARIANAISLANDS:Start( RepeatScanSeconds )
|
||||
RepeatScanSeconds = RepeatScanSeconds or 0.05
|
||||
self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, 2, RepeatScanSeconds )
|
||||
self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, RepeatScanSeconds )
|
||||
end
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
-- @module Functional.Mantis
|
||||
-- @image Functional.Mantis.jpg
|
||||
--
|
||||
-- Last Update: Oct 2023
|
||||
-- Last Update: Nov 2023
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
--- **MANTIS** class, extends Core.Base#BASE
|
||||
@ -799,12 +799,16 @@ do
|
||||
-- @param #MANTIS self
|
||||
-- @param Core.Set#SET_ZONE ZoneSet Set of zones to be used. Units will move around to the next (random) zone between 100m and 3000m away.
|
||||
-- @param #number Number Number of closest zones to be considered, defaults to 3.
|
||||
-- @param #boolean Random If true, use a random coordinate inside the next zone to scoot to.
|
||||
-- @param #string Formation Formation to use, defaults to "Cone". See mission editor dropdown for options.
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:AddScootZones(ZoneSet, Number)
|
||||
function MANTIS:AddScootZones(ZoneSet, Number, Random, Formation)
|
||||
self:T(self.lid .. " AddScootZones")
|
||||
self.SkateZones = ZoneSet
|
||||
self.SkateNumber = Number or 3
|
||||
self.shootandscoot = true
|
||||
self.shootandscoot = true
|
||||
self.ScootRandom = Random
|
||||
self.ScootFormation = Formation or "Cone"
|
||||
return self
|
||||
end
|
||||
|
||||
@ -1809,8 +1813,8 @@ do
|
||||
self.Shorad.Groupset=self.ShoradGroupSet
|
||||
self.Shorad.debug = self.debug
|
||||
end
|
||||
if self.shootandscoot and self.SkateZones then
|
||||
self.Shorad:AddScootZones(self.SkateZones,self.SkateNumber or 3)
|
||||
if self.shootandscoot and self.SkateZones and self.Shorad then
|
||||
self.Shorad:AddScootZones(self.SkateZones,self.SkateNumber or 3,self.ScootRandom,self.ScootFormation)
|
||||
end
|
||||
self:__Status(-math.random(1,10))
|
||||
return self
|
||||
|
||||
@ -1234,7 +1234,7 @@ function RANGE:SetSRS(PathToSRS, Port, Coalition, Frequency, Modulation, Volume,
|
||||
return self
|
||||
end
|
||||
|
||||
--- (SRS) Set range control frequency and voice.
|
||||
--- (SRS) Set range control frequency and voice. Use `RANGE:SetSRS()` once first before using this function.
|
||||
-- @param #RANGE self
|
||||
-- @param #number frequency Frequency in MHz. Default 256 MHz.
|
||||
-- @param #number modulation Modulation, defaults to radio.modulation.AM.
|
||||
@ -1244,6 +1244,10 @@ end
|
||||
-- @param #string relayunitname Name of the unit used for transmission location.
|
||||
-- @return #RANGE self
|
||||
function RANGE:SetSRSRangeControl( frequency, modulation, voice, culture, gender, relayunitname )
|
||||
if not self.instructmsrs then
|
||||
self:E(self.lid.."Use myrange:SetSRS() once first before using myrange:SetSRSRangeControl!")
|
||||
return self
|
||||
end
|
||||
self.rangecontrolfreq = frequency or 256
|
||||
self.controlmsrs:SetFrequencies(self.rangecontrolfreq)
|
||||
self.controlmsrs:SetModulations(modulation or radio.modulation.AM)
|
||||
@ -1259,7 +1263,7 @@ function RANGE:SetSRSRangeControl( frequency, modulation, voice, culture, gender
|
||||
return self
|
||||
end
|
||||
|
||||
--- (SRS) Set range instructor frequency and voice.
|
||||
--- (SRS) Set range instructor frequency and voice. Use `RANGE:SetSRS()` once first before using this function.
|
||||
-- @param #RANGE self
|
||||
-- @param #number frequency Frequency in MHz. Default 305 MHz.
|
||||
-- @param #number modulation Modulation, defaults to radio.modulation.AM.
|
||||
@ -1269,6 +1273,10 @@ end
|
||||
-- @param #string relayunitname Name of the unit used for transmission location.
|
||||
-- @return #RANGE self
|
||||
function RANGE:SetSRSRangeInstructor( frequency, modulation, voice, culture, gender, relayunitname )
|
||||
if not self.instructmsrs then
|
||||
self:E(self.lid.."Use myrange:SetSRS() once first before using myrange:SetSRSRangeInstructor!")
|
||||
return self
|
||||
end
|
||||
self.instructorfreq = frequency or 305
|
||||
self.instructmsrs:SetFrequencies(self.instructorfreq)
|
||||
self.instructmsrs:SetModulations(modulation or radio.modulation.AM)
|
||||
|
||||
@ -41,10 +41,14 @@
|
||||
-- @field #boolean DefendMavs Default true, intercept incoming AG-Missiles
|
||||
-- @field #number DefenseLowProb Default 70, minimum detection limit
|
||||
-- @field #number DefenseHighProb Default 90, maximum detection limit
|
||||
-- @field #boolean UseEmOnOff Decide if we are using Emission on/off (default) or AlarmState red/green.
|
||||
-- @field #boolean shootandscoot
|
||||
-- @field #number SkateNumber
|
||||
-- @field Core.Set#SET_ZONE SkateZones
|
||||
-- @field #boolean UseEmOnOff Decide if we are using Emission on/off (default) or AlarmState red/green
|
||||
-- @field #boolean shootandscoot If true, shoot and scoot between zones
|
||||
-- @field #number SkateNumber Number of zones to consider
|
||||
-- @field Core.Set#SET_ZONE SkateZones Zones in this set are considered
|
||||
-- @field #number minscootdist Min distance of the next zone
|
||||
-- @field #number maxscootdist Max distance of the next zone
|
||||
-- @field #boolean scootrandomcoord If true, use a random coordinate in the zone and not the center
|
||||
-- @field #string scootformation Formation to take for scooting, e.g. "Vee" or "Cone"
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
@ -77,14 +81,15 @@
|
||||
--
|
||||
-- `myshorad = SHORAD:New("RedShorad", "Red SHORAD", SamSet, 25000, 600, "red")`
|
||||
--
|
||||
-- ## Customize options
|
||||
-- ## Customization options
|
||||
--
|
||||
-- * SHORAD:SwitchDebug(debug)
|
||||
-- * SHORAD:SwitchHARMDefense(onoff)
|
||||
-- * SHORAD:SwitchAGMDefense(onoff)
|
||||
-- * SHORAD:SetDefenseLimits(low,high)
|
||||
-- * SHORAD:SetActiveTimer(seconds)
|
||||
-- * SHORAD:SetDefenseRadius(meters)
|
||||
-- * myshorad:SwitchDebug(debug)
|
||||
-- * myshorad:SwitchHARMDefense(onoff)
|
||||
-- * myshorad:SwitchAGMDefense(onoff)
|
||||
-- * myshorad:SetDefenseLimits(low,high)
|
||||
-- * myshorad:SetActiveTimer(seconds)
|
||||
-- * myshorad:SetDefenseRadius(meters)
|
||||
-- * myshorad:AddScootZones(ZoneSet,Number,Random,Formation)
|
||||
--
|
||||
-- @field #SHORAD
|
||||
SHORAD = {
|
||||
@ -107,6 +112,9 @@ SHORAD = {
|
||||
shootandscoot = false,
|
||||
SkateNumber = 3,
|
||||
SkateZones = nil,
|
||||
minscootdist = 100,
|
||||
minscootdist = 3000,
|
||||
scootrandomcoord = false,
|
||||
}
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
@ -174,7 +182,7 @@ do
|
||||
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
|
||||
if UseEmOnOff == false then self.UseEmOnOff = UseEmOnOff end
|
||||
self:I("*** SHORAD - Started Version 0.3.2")
|
||||
self:I("*** SHORAD - Started Version 0.3.4")
|
||||
-- Set the string id for output to DCS.log file.
|
||||
self.lid=string.format("SHORAD %s | ", self.name)
|
||||
self:_InitState()
|
||||
@ -219,12 +227,16 @@ do
|
||||
-- @param #SHORAD self
|
||||
-- @param Core.Set#SET_ZONE ZoneSet Set of zones to be used. Units will move around to the next (random) zone between 100m and 3000m away.
|
||||
-- @param #number Number Number of closest zones to be considered, defaults to 3.
|
||||
-- @param #boolean Random If true, use a random coordinate inside the next zone to scoot to.
|
||||
-- @param #string Formation Formation to use, defaults to "Cone". See mission editor dropdown for options.
|
||||
-- @return #SHORAD self
|
||||
function SHORAD:AddScootZones(ZoneSet, Number)
|
||||
function SHORAD:AddScootZones(ZoneSet, Number, Random, Formation)
|
||||
self:T(self.lid .. " AddScootZones")
|
||||
self.SkateZones = ZoneSet
|
||||
self.SkateNumber = Number or 3
|
||||
self.shootandscoot = true
|
||||
self.shootandscoot = true
|
||||
self.scootrandomcoord = Random
|
||||
self.scootformation = Formation or "Cone"
|
||||
return self
|
||||
end
|
||||
|
||||
@ -613,8 +625,8 @@ do
|
||||
function SHORAD:onafterShootAndScoot(From,Event,To,Shorad)
|
||||
self:T( { From,Event,To } )
|
||||
local possibleZones = {}
|
||||
local mindist = 100
|
||||
local maxdist = 3000
|
||||
local mindist = self.minscootdist or 100
|
||||
local maxdist = self.maxscootdist or 3000
|
||||
if Shorad and Shorad:IsAlive() then
|
||||
local NowCoord = Shorad:GetCoordinate()
|
||||
for _,_zone in pairs(self.SkateZones.Set) do
|
||||
@ -630,7 +642,11 @@ do
|
||||
if rand == 0 then rand = 1 end
|
||||
self:T(self.lid .. " ShootAndScoot to zone "..rand)
|
||||
local ToCoordinate = possibleZones[rand]:GetCoordinate()
|
||||
Shorad:RouteGroundTo(ToCoordinate,20,"Cone",1)
|
||||
if self.scootrandomcoord then
|
||||
ToCoordinate = possibleZones[rand]:GetRandomCoordinate(nil,nil,{land.SurfaceType.LAND,land.SurfaceType.ROAD})
|
||||
end
|
||||
local formation = self.scootformation or "Cone"
|
||||
Shorad:RouteGroundTo(ToCoordinate,20,formation,1)
|
||||
end
|
||||
end
|
||||
return self
|
||||
@ -731,4 +747,4 @@ do
|
||||
end
|
||||
-----------------------------------------------------------------------
|
||||
-- SHORAD end
|
||||
-----------------------------------------------------------------------
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
@ -7404,6 +7404,8 @@ function WAREHOUSE:_CheckRequestNow(request)
|
||||
|
||||
-- Check if at least one (cargo) asset is available.
|
||||
if _nassets>0 then
|
||||
|
||||
local asset=_assets[1] --#WAREHOUSE.Assetitem
|
||||
|
||||
-- Get the attibute of the requested asset.
|
||||
_assetattribute=_assets[1].attribute
|
||||
@ -7414,11 +7416,24 @@ function WAREHOUSE:_CheckRequestNow(request)
|
||||
if _assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER then
|
||||
|
||||
if self.airbase and self.airbase:GetCoalition()==self:GetCoalition() then
|
||||
|
||||
-- Check if DCS warehouse of airbase has enough assets
|
||||
if self.airbase.storage then
|
||||
local nS=self.airbase.storage:GetAmount(asset.unittype)
|
||||
local nA=asset.nunits*request.nasset -- Number of units requested
|
||||
if nS<nA then
|
||||
local text=string.format("Warehouse %s: Request denied! DCS Warehouse has only %d assets of type %s ==> NOT enough to spawn the requested %d asset units (%d groups)",
|
||||
self.alias, nS, asset.unittype, nA, request.nasset)
|
||||
self:_InfoMessage(text, 5)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if self:IsRunwayOperational() or _assetairstart then
|
||||
|
||||
if _assetairstart then
|
||||
-- Airstart no need to check parking
|
||||
-- Airstart no need to check parking
|
||||
else
|
||||
|
||||
-- Check parking.
|
||||
@ -7530,6 +7545,9 @@ function WAREHOUSE:_CheckRequestNow(request)
|
||||
self:_InfoMessage(text, 5)
|
||||
return false
|
||||
end
|
||||
|
||||
elseif _assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER then
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
@ -312,10 +312,16 @@
|
||||
--
|
||||
-- atis=ATIS:New("Batumi", 305, radio.modulation.AM)
|
||||
-- atis:SetSRS("D:\\DCS\\_SRS\\", "male", "en-US")
|
||||
-- atis:Start()
|
||||
-- atis:Start()
|
||||
--
|
||||
-- This uses a male voice with US accent. It requires SRS to be installed in the `D:\DCS\_SRS\` directory. Note that backslashes need to be escaped or simply use slashes (as in linux).
|
||||
--
|
||||
-- ### SRS can use multiple frequencies:
|
||||
--
|
||||
-- atis=ATIS:New("Batumi", {305,103.85}, {radio.modulation.AM,radio.modulation.FM})
|
||||
-- atis:SetSRS("D:\\DCS\\_SRS\\", "male", "en-US")
|
||||
-- atis:Start()
|
||||
--
|
||||
-- ### SRS Localization
|
||||
--
|
||||
-- You can localize the SRS output, all you need is to provide a table of translations and set the `locale` of your instance. You need to provide the translations in your script **before you instantiate your ATIS**.
|
||||
@ -884,13 +890,14 @@ _ATIS = {}
|
||||
|
||||
--- ATIS class version.
|
||||
-- @field #string version
|
||||
ATIS.version = "0.10.3"
|
||||
ATIS.version = "0.10.4"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: Correct fog for elevation.
|
||||
-- DONE: Option to add multiple frequencies for SRS
|
||||
-- DONE: Zulu time --> Zulu in output.
|
||||
-- DONE: Fix for AB not having a runway - Helopost like Naqoura
|
||||
-- DONE: Add new Normandy airfields.
|
||||
@ -899,7 +906,7 @@ ATIS.version = "0.10.3"
|
||||
-- DONE: Visibility reported twice over SRS
|
||||
-- DONE: Add text report for output.
|
||||
-- DONE: Add stop FMS functions.
|
||||
-- NOGO: Use local time. Not realisitc!
|
||||
-- NOGO: Use local time. Not realistic!
|
||||
-- DONE: Dew point. Approx. done.
|
||||
-- DONE: Metric units.
|
||||
-- DONE: Set UTC correction.
|
||||
@ -915,8 +922,8 @@ ATIS.version = "0.10.3"
|
||||
--- Create a new ATIS class object for a specific airbase.
|
||||
-- @param #ATIS 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 #number Frequency Radio frequency in MHz. Default 143.00 MHz. When using **SRS** this can be passed as a table of multiple frequencies.
|
||||
-- @param #number Modulation Radio modulation: 0=AM, 1=FM. Default 0=AM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators. When using **SRS** this can be passed as a table of multiple modulations.
|
||||
-- @return #ATIS self
|
||||
function ATIS:New(AirbaseName, Frequency, Modulation)
|
||||
|
||||
@ -1594,8 +1601,16 @@ function ATIS:onafterStart( From, Event, To )
|
||||
end
|
||||
|
||||
-- Info.
|
||||
self:I( self.lid .. string.format( "Starting ATIS v%s for airbase %s on %.3f MHz Modulation=%d", ATIS.version, self.airbasename, self.frequency, self.modulation ) )
|
||||
|
||||
if type(self.frequency) == "table" then
|
||||
local frequency = table.concat(self.frequency,"/")
|
||||
local modulation = self.modulation
|
||||
if type(self.modulation) == "table" then
|
||||
modulation = table.concat(self.modulation,"/")
|
||||
end
|
||||
self:I( self.lid .. string.format( "Starting ATIS v%s for airbase %s on %s MHz Modulation=%s", ATIS.version, self.airbasename, frequency, modulation ) )
|
||||
else
|
||||
self:I( self.lid .. string.format( "Starting ATIS v%s for airbase %s on %.3f MHz Modulation=%d", ATIS.version, self.airbasename, self.frequency, self.modulation ) )
|
||||
end
|
||||
-- Start radio queue.
|
||||
if not self.useSRS then
|
||||
self.radioqueue = RADIOQUEUE:New( self.frequency, self.modulation, string.format( "ATIS %s", self.airbasename ) )
|
||||
@ -1653,7 +1668,17 @@ function ATIS:onafterStatus( From, Event, To )
|
||||
end
|
||||
|
||||
-- Info text.
|
||||
local text = string.format( "State %s: Freq=%.3f MHz %s", fsmstate, self.frequency, UTILS.GetModulationName( self.modulation ) )
|
||||
local text = ""
|
||||
if type(self.frequency) == "table" then
|
||||
local frequency = table.concat(self.frequency,"/")
|
||||
local modulation = self.modulation
|
||||
if type(self.modulation) == "table" then
|
||||
modulation = table.concat(self.modulation,"/")
|
||||
end
|
||||
text = string.format( "State %s: Freq=%s MHz %s", fsmstate, frequency, modulation )
|
||||
else
|
||||
text = string.format( "State %s: Freq=%.3f MHz %s", fsmstate, self.frequency, UTILS.GetModulationName( self.modulation ) )
|
||||
end
|
||||
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
|
||||
@ -2919,8 +2944,17 @@ function ATIS:UpdateMarker( information, runact, wind, altimeter, temperature )
|
||||
if self.markerid then
|
||||
self.airbase:GetCoordinate():RemoveMark( self.markerid )
|
||||
end
|
||||
|
||||
local text = string.format( "ATIS on %.3f %s, %s:\n", self.frequency, UTILS.GetModulationName( self.modulation ), tostring( information ) )
|
||||
local text = ""
|
||||
if type(self.frequency) == "table" then
|
||||
local frequency = table.concat(self.frequency,"/")
|
||||
local modulation = self.modulation
|
||||
if type(modulation) == "table" then
|
||||
modulation = table.concat(self.modulation,"/")
|
||||
end
|
||||
text = string.format( "ATIS on %s %s, %s:\n", tostring(frequency), tostring(modulation), tostring( information ) )
|
||||
else
|
||||
text = string.format( "ATIS on %.3f %s, %s:\n", self.frequency, UTILS.GetModulationName( self.modulation ), tostring( information ) )
|
||||
end
|
||||
text = text .. string.format( "%s\n", tostring( runact ) )
|
||||
text = text .. string.format( "%s\n", tostring( wind ) )
|
||||
text = text .. string.format( "%s\n", tostring( altimeter ) )
|
||||
|
||||
@ -976,7 +976,8 @@ function ARMYGROUP:onafterSpawned(From, Event, To)
|
||||
-- Update route.
|
||||
if Nwp>1 and self.isMobile then
|
||||
self:T(self.lid..string.format("Got %d waypoints on spawn ==> Cruise in -1.0 sec!", Nwp))
|
||||
self:__Cruise(-1, nil, self.option.Formation)
|
||||
--self:__Cruise(-1, nil, self.option.Formation)
|
||||
self:__Cruise(-1)
|
||||
else
|
||||
self:T(self.lid.."No waypoints on spawn ==> Full Stop!")
|
||||
self:FullStop()
|
||||
@ -1948,7 +1949,7 @@ function ARMYGROUP:onafterCruise(From, Event, To, Speed, Formation)
|
||||
self.dTwait=nil
|
||||
|
||||
-- Debug info.
|
||||
self:T(self.lid.."Cruise ==> Update route in 0.01 sec")
|
||||
self:T(self.lid..string.format("Cruise ==> Update route in 0.01 sec (speed=%s, formation=%s)", tostring(Speed), tostring(Formation)))
|
||||
|
||||
-- Update route.
|
||||
self:__UpdateRoute(-0.01, nil, nil, Speed, Formation)
|
||||
@ -2003,7 +2004,7 @@ function ARMYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation
|
||||
elseif self.optionDefault.Formation then
|
||||
Formation = self.optionDefault.Formation
|
||||
elseif self.option.Formation then
|
||||
Formation = self.option.Formation
|
||||
Formation = self.option.Formation
|
||||
else
|
||||
-- Default formation is on road.
|
||||
Formation = ENUMS.Formation.Vehicle.OnRoad
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
-- @module Ops.CTLD
|
||||
-- @image OPS_CTLD.jpg
|
||||
|
||||
-- Last Update October 2023
|
||||
-- Last Update November 2023
|
||||
|
||||
do
|
||||
|
||||
@ -741,7 +741,7 @@ 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, up to 4000 kgs:
|
||||
-- my_ctld:UnitCapabilities("SA342L", true, true, 8, 8, 12, 4000)
|
||||
-- my_ctld:SetUnitCapabilities("SA342L", true, true, 8, 8, 12, 4000)
|
||||
--
|
||||
-- -- Default unit type capabilities are:
|
||||
-- ["SA342Mistral"] = {type="SA342Mistral", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12, cargoweightlimit = 400},
|
||||
@ -1200,14 +1200,14 @@ CTLD.CargoZoneType = {
|
||||
-- @field #CTLD_CARGO.Enum Type Type enumerator (for moves).
|
||||
|
||||
--- Unit capabilities.
|
||||
-- @type CTLD.UnitCapabilities
|
||||
-- @type CTLD.UnitTypeCapabilities
|
||||
-- @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.
|
||||
-- @field #number cargoweightlimit Max loadable kgs of cargo.
|
||||
CTLD.UnitTypes = {
|
||||
CTLD.UnitTypeCapabilities = {
|
||||
["SA342Mistral"] = {type="SA342Mistral", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12, cargoweightlimit = 400},
|
||||
["SA342L"] = {type="SA342L", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12, cargoweightlimit = 400},
|
||||
["SA342M"] = {type="SA342M", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12, cargoweightlimit = 400},
|
||||
@ -1228,7 +1228,7 @@ CTLD.UnitTypes = {
|
||||
|
||||
--- CTLD class version.
|
||||
-- @field #string version
|
||||
CTLD.version="1.0.41"
|
||||
CTLD.version="1.0.43"
|
||||
|
||||
--- Instantiate a new CTLD.
|
||||
-- @param #CTLD self
|
||||
@ -1293,6 +1293,8 @@ function CTLD:New(Coalition, Prefixes, Alias)
|
||||
self:AddTransition("*", "CratesDropped", "*") -- CTLD deploy event.
|
||||
self:AddTransition("*", "CratesBuild", "*") -- CTLD build event.
|
||||
self:AddTransition("*", "CratesRepaired", "*") -- CTLD repair event.
|
||||
self:AddTransition("*", "CratesBuildStarted", "*") -- CTLD build event.
|
||||
self:AddTransition("*", "CratesRepairStarted", "*") -- CTLD repair event.
|
||||
self:AddTransition("*", "Load", "*") -- CTLD load event.
|
||||
self:AddTransition("*", "Save", "*") -- CTLD save event.
|
||||
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
|
||||
@ -1475,7 +1477,7 @@ function CTLD:New(Coalition, Prefixes, Alias)
|
||||
-- @param #CTLD self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- FSM Function OnBeforeTroopsPickedUp.
|
||||
--- FSM Function OnBeforeTroopsPickedUp.
|
||||
-- @function [parent=#CTLD] OnBeforeTroopsPickedUp
|
||||
-- @param #CTLD self
|
||||
-- @param #string From State.
|
||||
@ -1627,6 +1629,46 @@ 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 OnAfterCratesBuildStarted. Info event that a build has been started.
|
||||
-- @function [parent=#CTLD] OnAfterCratesBuildStarted
|
||||
-- @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
|
||||
|
||||
--- FSM Function OnAfterCratesRepairStarted. Info event that a repair has been started.
|
||||
-- @function [parent=#CTLD] OnAfterCratesRepairStarted
|
||||
-- @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
|
||||
|
||||
--- FSM Function OnBeforeCratesBuildStarted. Info event that a build has been started.
|
||||
-- @function [parent=#CTLD] OnBeforeCratesBuildStarted
|
||||
-- @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
|
||||
|
||||
--- FSM Function OnBeforeCratesRepairStarted. Info event that a repair has been started.
|
||||
-- @function [parent=#CTLD] OnBeforeCratesRepairStarted
|
||||
-- @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
|
||||
|
||||
--- FSM Function OnAfterCratesRepaired.
|
||||
-- @function [parent=#CTLD] OnAfterCratesRepaired
|
||||
-- @param #CTLD self
|
||||
@ -1680,7 +1722,7 @@ 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
|
||||
local capabilities = self.UnitTypeCapabilities[unittype] -- #CTLD.UnitTypeCapabilities
|
||||
if not capabilities or capabilities == {} then
|
||||
-- e.g. ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0},
|
||||
capabilities = {}
|
||||
@ -1871,7 +1913,7 @@ function CTLD:_PreloadCrates(Group, Unit, Cargo, NumberOfCrates)
|
||||
local unitname = unit:GetName()
|
||||
-- see if this heli can load crates
|
||||
local unittype = unit:GetTypeName()
|
||||
local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities
|
||||
local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitTypeCapabilities
|
||||
local cancrates = capabilities.crates -- #boolean
|
||||
local cratelimit = capabilities.cratelimit -- #number
|
||||
if not cancrates then
|
||||
@ -2124,6 +2166,7 @@ function CTLD:_RepairObjectFromCrates(Group,Unit,Crates,Build,Number,Engineering
|
||||
desttimer:Start(self.repairtime - 1)
|
||||
local buildtimer = TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,object,true,NearestGroup:GetCoordinate())
|
||||
buildtimer:Start(self.repairtime)
|
||||
self:__CratesRepairStarted(1,Group,Unit)
|
||||
else
|
||||
if not Engineering then
|
||||
self:_SendMessage("Can't repair this unit with " .. build.Name, 10, false, Group)
|
||||
@ -2308,7 +2351,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack)
|
||||
end
|
||||
|
||||
-- avoid crate spam
|
||||
local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities
|
||||
local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitTypeCapabilities
|
||||
local canloadcratesno = capabilities.cratelimit
|
||||
local loaddist = self.CrateDistance or 35
|
||||
local nearcrates, numbernearby = self:_FindCratesNearby(Group,Unit,loaddist,true)
|
||||
@ -2601,8 +2644,8 @@ function CTLD:_LoadCratesNearby(Group, Unit)
|
||||
local unitname = unit:GetName()
|
||||
-- see if this heli can load crates
|
||||
local unittype = unit:GetTypeName()
|
||||
local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities
|
||||
--local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities
|
||||
local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitTypeCapabilities
|
||||
--local capabilities = self.UnitTypeCapabilities[unittype] -- #CTLD.UnitTypeCapabilities
|
||||
local cancrates = capabilities.crates -- #boolean
|
||||
local cratelimit = capabilities.cratelimit -- #number
|
||||
local grounded = not self:IsUnitInAir(Unit)
|
||||
@ -2753,7 +2796,7 @@ function CTLD:_GetMaxLoadableMass(Unit)
|
||||
if not Unit then return 0 end
|
||||
local loadable = 0
|
||||
local loadedmass = self:_GetUnitCargoMass(Unit)
|
||||
local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities
|
||||
local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitTypeCapabilities
|
||||
local maxmass = capabilities.cargoweightlimit or 2000 -- max 2 tons
|
||||
loadable = maxmass - loadedmass
|
||||
return loadable
|
||||
@ -2778,7 +2821,7 @@ function CTLD:_ListCargo(Group, Unit)
|
||||
self:T(self.lid .. " _ListCargo")
|
||||
local unitname = Unit:GetName()
|
||||
local unittype = Unit:GetTypeName()
|
||||
local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities
|
||||
local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitTypeCapabilities
|
||||
local trooplimit = capabilities.trooplimit -- #boolean
|
||||
local cratelimit = capabilities.cratelimit -- #number
|
||||
local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo
|
||||
@ -3226,6 +3269,7 @@ function CTLD:_BuildCrates(Group, Unit,Engineering)
|
||||
local buildtimer = TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,build,false,Group:GetCoordinate())
|
||||
buildtimer:Start(self.buildtime)
|
||||
self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group)
|
||||
self:__CratesBuildStarted(1,Group,Unit)
|
||||
else
|
||||
self:_BuildObjectFromCrates(Group,Unit,build)
|
||||
end
|
||||
@ -3536,13 +3580,19 @@ function CTLD:_RefreshF10Menus()
|
||||
if _group then
|
||||
-- get chopper capabilities
|
||||
local unittype = _unit:GetTypeName()
|
||||
local capabilities = self:_GetUnitCapabilities(_unit) -- #CTLD.UnitCapabilities
|
||||
local capabilities = self:_GetUnitCapabilities(_unit) -- #CTLD.UnitTypeCapabilities
|
||||
local cantroops = capabilities.troops
|
||||
local cancrates = capabilities.crates
|
||||
-- top menu
|
||||
local topmenu = MENU_GROUP:New(_group,"CTLD",nil)
|
||||
local toptroops = MENU_GROUP:New(_group,"Manage Troops",topmenu)
|
||||
local topcrates = MENU_GROUP:New(_group,"Manage Crates",topmenu)
|
||||
local toptroops = nil
|
||||
local topcrates = nil
|
||||
if cantroops then
|
||||
toptroops = MENU_GROUP:New(_group,"Manage Troops",topmenu)
|
||||
end
|
||||
if cancrates then
|
||||
topcrates = MENU_GROUP:New(_group,"Manage Crates",topmenu)
|
||||
end
|
||||
local listmenu = MENU_GROUP_COMMAND:New(_group,"List boarded cargo",topmenu, self._ListCargo, self, _group, _unit)
|
||||
local invtry = MENU_GROUP_COMMAND:New(_group,"Inventory",topmenu, self._ListInventory, self, _group, _unit)
|
||||
local rbcns = MENU_GROUP_COMMAND:New(_group,"List active zone beacons",topmenu, self._ListRadioBeacons, self, _group, _unit)
|
||||
@ -4339,7 +4389,7 @@ end
|
||||
-- @param #number Trooplimit Unit can carry number of troops. Default 0.
|
||||
-- @param #number Length Unit lenght (in metres) for the load radius. Default 20.
|
||||
-- @param #number Maxcargoweight Maxmimum weight in kgs this helo can carry. Default 500.
|
||||
function CTLD:UnitCapabilities(Unittype, Cancrates, Cantroops, Cratelimit, Trooplimit, Length, Maxcargoweight)
|
||||
function CTLD:SetUnitCapabilities(Unittype, Cancrates, Cantroops, Cratelimit, Trooplimit, Length, Maxcargoweight)
|
||||
self:T(self.lid .. " UnitCapabilities")
|
||||
local unittype = nil
|
||||
local unit = nil
|
||||
@ -4353,13 +4403,13 @@ end
|
||||
end
|
||||
local length = 20
|
||||
local maxcargo = 500
|
||||
local existingcaps = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities
|
||||
local existingcaps = self.UnitTypeCapabilities[unittype] -- #CTLD.UnitTypeCapabilities
|
||||
if existingcaps then
|
||||
length = existingcaps.length or 20
|
||||
maxcargo = existingcaps.cargoweightlimit or 500
|
||||
end
|
||||
-- set capabilities
|
||||
local capabilities = {} -- #CTLD.UnitCapabilities
|
||||
local capabilities = {} -- #CTLD.UnitTypeCapabilities
|
||||
capabilities.type = unittype
|
||||
capabilities.crates = Cancrates or false
|
||||
capabilities.troops = Cantroops or false
|
||||
@ -4367,10 +4417,26 @@ end
|
||||
capabilities.trooplimit = Trooplimit or 0
|
||||
capabilities.length = Length or length
|
||||
capabilities.cargoweightlimit = Maxcargoweight or maxcargo
|
||||
self.UnitTypes[unittype] = capabilities
|
||||
self.UnitTypeCapabilities[unittype] = capabilities
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Deprecated] - Function to add/adjust unittype capabilities. Has been replaced with `SetUnitCapabilities()` - pls use the new one going forward!
|
||||
-- @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. Default false.
|
||||
-- @param #boolean Cantroops Unit can load troops. Default false.
|
||||
-- @param #number Cratelimit Unit can carry number of crates. Default 0.
|
||||
-- @param #number Trooplimit Unit can carry number of troops. Default 0.
|
||||
-- @param #number Length Unit lenght (in metres) for the load radius. Default 20.
|
||||
-- @param #number Maxcargoweight Maxmimum weight in kgs this helo can carry. Default 500.
|
||||
function CTLD:UnitCapabilities(Unittype, Cancrates, Cantroops, Cratelimit, Trooplimit, Length, Maxcargoweight)
|
||||
self:I(self.lid.."This function been replaced with `SetUnitCapabilities()` - pls use the new one going forward!")
|
||||
self:SetUnitCapabilities(Unittype, Cancrates, Cantroops, Cratelimit, Trooplimit, Length, Maxcargoweight)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- (Internal) Check if a unit is hovering *in parameters*.
|
||||
-- @param #CTLD self
|
||||
-- @param Wrapper.Unit#UNIT Unit
|
||||
@ -4523,7 +4589,7 @@ end
|
||||
local unittype = Unit:GetTypeName()
|
||||
local unitname = Unit:GetName()
|
||||
local Group = Unit:GetGroup()
|
||||
local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities
|
||||
local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitTypeCapabilities
|
||||
local cancrates = capabilities.crates -- #boolean
|
||||
local cratelimit = capabilities.cratelimit -- #number
|
||||
if cancrates then
|
||||
|
||||
@ -2934,7 +2934,11 @@ function FLIGHTCONTROL:_PlayerInfoATIS(groupname)
|
||||
|
||||
-- Radio message.
|
||||
self:TransmissionPilot(rtext, flight)
|
||||
self:TransmissionTower(srstxt,flight,10)
|
||||
if self.atis then
|
||||
self:TransmissionTower(srstxt,flight,10)
|
||||
else
|
||||
self:TransmissionTower(text,flight,10)
|
||||
end
|
||||
|
||||
else
|
||||
self:E(self.lid..string.format("Cannot find flight group %s.", tostring(groupname)))
|
||||
|
||||
@ -2890,8 +2890,11 @@ function FLIGHTGROUP:_CheckGroupDone(delay, waittime)
|
||||
end
|
||||
|
||||
else
|
||||
-- Check if not parking (could be on ALERT5 and just spawned (current mission=nil)
|
||||
if not self:IsParking() then
|
||||
self:T(self.lid..string.format("Passed Final WP but Tasks=%d or Missions=%d left in the queue. Wait!", nTasks, nMissions))
|
||||
self:__Wait(-1)
|
||||
end
|
||||
end
|
||||
else
|
||||
self:T(self.lid..string.format("Passed Final WP but still have current Task (#%s) or Mission (#%s) left to do", tostring(self.taskcurrent), tostring(self.currentmission)))
|
||||
|
||||
@ -1483,11 +1483,11 @@ function OPSZONE:_GetZoneColor()
|
||||
local color={0,0,0}
|
||||
|
||||
if self.ownerCurrent==coalition.side.NEUTRAL then
|
||||
color={1, 1, 1}
|
||||
color=self.ZoneOwnerNeutral or {1, 1, 1}
|
||||
elseif self.ownerCurrent==coalition.side.BLUE then
|
||||
color={0, 0, 1}
|
||||
color=self.ZoneOwnerBlue or {0, 0, 1}
|
||||
elseif self.ownerCurrent==coalition.side.RED then
|
||||
color={1, 0, 0}
|
||||
color=self.ZoneOwnerRed or {1, 0, 0}
|
||||
else
|
||||
|
||||
end
|
||||
@ -1495,6 +1495,21 @@ function OPSZONE:_GetZoneColor()
|
||||
return color
|
||||
end
|
||||
|
||||
--- Set custom RGB color of zone depending on current owner.
|
||||
-- @param #OPSZONE self
|
||||
-- @param #table Neutral Color is a table of RGB values 0..1 for Red, Green, and Blue respectively, e.g. {1,0,0} for red.
|
||||
-- @param #table Blue Color is a table of RGB values 0..1 for Red, Green, and Blue respectively, e.g. {0,1,0} for green.
|
||||
-- @param #table Red Color is a table of RGB values 0..1 for Red, Green, and Blue respectively, e.g. {0,0,1} for blue.
|
||||
-- @return #OPSZONE self
|
||||
function OPSZONE:SetZoneColor(Neutral, Blue, Red)
|
||||
|
||||
self.ZoneOwnerNeutral = Neutral or {1, 1, 1}
|
||||
self.ZoneOwnerBlue = Blue or {0, 0, 1}
|
||||
self.ZoneOwnerRed = Red or {1, 0, 0}
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Update marker on the F10 map.
|
||||
-- @param #OPSZONE self
|
||||
function OPSZONE:_UpdateMarker()
|
||||
|
||||
@ -104,7 +104,7 @@ PLAYERRECCE = {
|
||||
ClassName = "PLAYERRECCE",
|
||||
verbose = true,
|
||||
lid = nil,
|
||||
version = "0.0.21",
|
||||
version = "0.0.22",
|
||||
ViewZone = {},
|
||||
ViewZoneVisual = {},
|
||||
ViewZoneLaser = {},
|
||||
@ -469,8 +469,10 @@ function PLAYERRECCE:_GetClockDirection(unit, target)
|
||||
local _playerPosition = unit:GetCoordinate() -- get position of helicopter
|
||||
local _targetpostions = target:GetCoordinate() -- get position of downed pilot
|
||||
local _heading = unit:GetHeading() -- heading
|
||||
--self:I("Heading = ".._heading)
|
||||
local DirectionVec3 = _playerPosition:GetDirectionVec3( _targetpostions )
|
||||
local Angle = _playerPosition:GetAngleDegrees( DirectionVec3 )
|
||||
--self:I("Angle = "..Angle)
|
||||
local clock = 12
|
||||
local hours = 0
|
||||
if _heading and Angle then
|
||||
@ -478,10 +480,13 @@ function PLAYERRECCE:_GetClockDirection(unit, target)
|
||||
--if angle == 0 then angle = 360 end
|
||||
clock = _heading-Angle
|
||||
hours = (clock/30)*-1
|
||||
--self:I("hours = "..hours)
|
||||
clock = 12+hours
|
||||
clock = UTILS.Round(clock,0)
|
||||
if clock > 12 then clock = clock-12 end
|
||||
end
|
||||
if clock == 0 then clock = 12 end
|
||||
end
|
||||
--self:I("Clock ="..clock)
|
||||
return clock
|
||||
end
|
||||
|
||||
@ -709,8 +714,8 @@ function PLAYERRECCE:_GetViewZone(unit, vheading, minview, maxview, angle, camon
|
||||
local heading2 = (vheading-90)%360
|
||||
self:T({heading1,heading2})
|
||||
local startpos = startp:Translate(minview,vheading)
|
||||
local pos1 = startpos:Translate(10,heading1)
|
||||
local pos2 = startpos:Translate(10,heading2)
|
||||
local pos1 = startpos:Translate(12.5,heading1)
|
||||
local pos2 = startpos:Translate(12.5,heading2)
|
||||
local pos3 = pos1:Translate(maxview,vheading)
|
||||
local pos4 = pos2:Translate(maxview,vheading)
|
||||
local array = {}
|
||||
@ -912,32 +917,41 @@ function PLAYERRECCE:_LaseTarget(client,targetset)
|
||||
else
|
||||
laser = self.LaserSpots[playername]
|
||||
end
|
||||
-- old target
|
||||
if self.LaserTarget[playername] then
|
||||
-- still looking at target?
|
||||
local target=self.LaserTarget[playername] -- Ops.Target#TARGET
|
||||
local oldtarget = target:GetObject() --or laser.Target
|
||||
--self:I("Targetstate: "..target:GetState())
|
||||
--self:I("Laser State: "..tostring(laser:IsLasing()))
|
||||
if not oldtarget or targetset:IsNotInSet(oldtarget) or target:IsDead() or target:IsDestroyed() then
|
||||
self:T("Targetstate: "..target:GetState())
|
||||
self:T("Laser State: "..tostring(laser:IsLasing()))
|
||||
if (not oldtarget) or targetset:IsNotInSet(oldtarget) or target:IsDead() or target:IsDestroyed() then
|
||||
-- lost LOS or dead
|
||||
laser:LaseOff()
|
||||
if target:IsDead() or target:IsDestroyed() or target:GetLife() < 2 then
|
||||
self:__Shack(-1,client,oldtarget)
|
||||
self.LaserTarget[playername] = nil
|
||||
--self.LaserTarget[playername] = nil
|
||||
else
|
||||
self:__TargetLOSLost(-1,client,oldtarget)
|
||||
self.LaserTarget[playername] = nil
|
||||
--self.LaserTarget[playername] = nil
|
||||
end
|
||||
end
|
||||
if oldtarget and (not laser:IsLasing()) then
|
||||
--self:I("Switching laser back on ..")
|
||||
self.LaserTarget[playername] = nil
|
||||
oldtarget = nil
|
||||
self.LaserSpots[playername] = nil
|
||||
elseif oldtarget and laser and (not laser:IsLasing()) then
|
||||
--laser:LaseOff()
|
||||
self:T("Switching laser back on ..")
|
||||
local lasercode = self.UnitLaserCodes[playername] or laser.LaserCode or 1688
|
||||
local lasingtime = self.lasingtime or 60
|
||||
--local targettype = target:GetTypeName()
|
||||
laser:LaseOn(oldtarget,lasercode,lasingtime)
|
||||
--self:__TargetLasing(-1,client,oldtarget,lasercode,lasingtime)
|
||||
else
|
||||
-- we should not be here...
|
||||
self:T("Target alive and laser is on!")
|
||||
--self.LaserSpots[playername] = nil
|
||||
end
|
||||
elseif not laser:IsLasing() and target then
|
||||
-- new target
|
||||
elseif (not laser:IsLasing()) and target then
|
||||
local relativecam = self.LaserRelativePos[client:GetTypeName()]
|
||||
laser:SetRelativeStartPosition(relativecam)
|
||||
local lasercode = self.UnitLaserCodes[playername] or laser.LaserCode or 1688
|
||||
@ -945,7 +959,7 @@ function PLAYERRECCE:_LaseTarget(client,targetset)
|
||||
--local targettype = target:GetTypeName()
|
||||
laser:LaseOn(target,lasercode,lasingtime)
|
||||
self.LaserTarget[playername] = TARGET:New(target)
|
||||
self.LaserTarget[playername].TStatus = 9
|
||||
--self.LaserTarget[playername].TStatus = 9
|
||||
self:__TargetLasing(-1,client,target,lasercode,lasingtime)
|
||||
end
|
||||
return self
|
||||
@ -1027,6 +1041,13 @@ function PLAYERRECCE:_SwitchLasing(client,group,playername)
|
||||
MESSAGE:New("Lasing is now ON",10,self.Name or "FACA"):ToClient(client)
|
||||
else
|
||||
self.AutoLase[playername] = false
|
||||
if self.LaserSpots[playername] then
|
||||
local laser = self.LaserSpots[playername] -- Core.Spot#SPOT
|
||||
if laser:IsLasing() then
|
||||
laser:LaseOff()
|
||||
end
|
||||
self.LaserSpots[playername] = nil
|
||||
end
|
||||
MESSAGE:New("Lasing is now OFF",10,self.Name or "FACA"):ToClient(client)
|
||||
end
|
||||
if self.ClientMenus[playername] then
|
||||
@ -1681,7 +1702,7 @@ function PLAYERRECCE:onafterRecceOnStation(From, Event, To, Client, Playername)
|
||||
local text2tts = string.format("All stations, FACA %s on station at %s!",callsign, coordtext)
|
||||
text2tts = self:_GetTextForSpeech(text2tts)
|
||||
if self.debug then
|
||||
self:I(text2.."\n"..text2tts)
|
||||
self:T(text2.."\n"..text2tts)
|
||||
end
|
||||
if self.UseSRS then
|
||||
local grp = Client:GetGroup()
|
||||
@ -1720,7 +1741,7 @@ function PLAYERRECCE:onafterRecceOffStation(From, Event, To, Client, Playername)
|
||||
local texttts = string.format("All stations, FACA %s leaving station at %s, good bye!",callsign, coordtext)
|
||||
texttts = self:_GetTextForSpeech(texttts)
|
||||
if self.debug then
|
||||
self:I(text.."\n"..texttts)
|
||||
self:T(text.."\n"..texttts)
|
||||
end
|
||||
local text1 = "Going home!"
|
||||
if self.UseSRS then
|
||||
|
||||
@ -1579,7 +1579,7 @@ function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter)
|
||||
self.ClusterRadius = 0.5
|
||||
self.TargetRadius = 500
|
||||
|
||||
self.ClientFilter = ClientFilter or ""
|
||||
self.ClientFilter = ClientFilter --or ""
|
||||
|
||||
self.TargetQueue = FIFO:New() -- Utilities.FiFo#FIFO
|
||||
self.TaskQueue = FIFO:New() -- Utilities.FiFo#FIFO
|
||||
|
||||
@ -50,6 +50,7 @@
|
||||
-- @field #string ConfigFileName Name of the standard config file
|
||||
-- @field #string ConfigFilePath Path to the standard config file
|
||||
-- @field #boolean ConfigLoaded
|
||||
-- @field #string ttsprovider Default provider TTS backend, e.g. "Google" or "Microsoft", default is Microsoft
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde
|
||||
@ -127,6 +128,10 @@
|
||||
--
|
||||
-- Use @{#MSRS.SetVolume} to define the SRS volume. Defaults to 1.0. Allowed values are between 0.0 and 1.0, from silent to loudest.
|
||||
--
|
||||
-- ## Config file for many variables, auto-loaded by Moose
|
||||
--
|
||||
-- See @{#MSRS.LoadConfigFile} for details on how to set this up.
|
||||
--
|
||||
-- ## Set DCS-gRPC as an alternative to 'DCS-SR-ExternalAudio.exe' for TTS
|
||||
--
|
||||
-- Use @{#MSRS.SetDefaultBackendGRPC} to enable [DCS-gRPC](https://github.com/DCS-gRPC/rust-server) as an alternate backend for transmitting text-to-speech over SRS.
|
||||
@ -191,11 +196,12 @@ MSRS = {
|
||||
ConfigFileName = "Moose_MSRS.lua",
|
||||
ConfigFilePath = "Config\\",
|
||||
ConfigLoaded = false,
|
||||
ttsprovider = "Microsoft",
|
||||
}
|
||||
|
||||
--- MSRS class version.
|
||||
-- @field #string version
|
||||
MSRS.version="0.1.2"
|
||||
MSRS.version="0.1.3"
|
||||
|
||||
--- Voices
|
||||
-- @type MSRS.Voices
|
||||
@ -377,9 +383,7 @@ function MSRS:New(PathToSRS, Frequency, Modulation, Volume, AltBackend)
|
||||
return self:_NewAltBackend(Backend)
|
||||
end
|
||||
|
||||
local success = self:LoadConfigFile(nil,nil,self.ConfigLoaded)
|
||||
|
||||
if (not success) and (not self.ConfigLoaded) then
|
||||
if not self.ConfigLoaded then
|
||||
|
||||
-- If no AltBackend table, the proceed with default initialisation
|
||||
self:SetPath(PathToSRS)
|
||||
@ -446,7 +450,7 @@ function MSRS:SetPath(Path)
|
||||
end
|
||||
|
||||
-- Debug output.
|
||||
self:I(string.format("SRS path=%s", self:GetPath()))
|
||||
self:T(string.format("SRS path=%s", self:GetPath()))
|
||||
end
|
||||
return self
|
||||
end
|
||||
@ -674,7 +678,7 @@ function MSRS:SetCoordinate(Coordinate)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Use google text-to-speech.
|
||||
--- Use google text-to-speech credentials. Also sets Google as default TTS provider.
|
||||
-- @param #MSRS self
|
||||
-- @param #string PathToCredentials Full path to the google credentials JSON file, e.g. "C:\Users\username\Downloads\service-account-file.json". Can also be the Google API key.
|
||||
-- @return #MSRS self
|
||||
@ -688,13 +692,14 @@ function MSRS:SetGoogle(PathToCredentials)
|
||||
|
||||
self.GRPCOptions.DefaultProvider = "gcloud"
|
||||
self.GRPCOptions.gcloud.key = PathToCredentials
|
||||
self.ttsprovider = "Google"
|
||||
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Use google text-to-speech.
|
||||
--- gRPC Backend: Use google text-to-speech set the API key.
|
||||
-- @param #MSRS self
|
||||
-- @param #string APIKey API Key, usually a string of length 40 with characters and numbers.
|
||||
-- @return #MSRS self
|
||||
@ -708,6 +713,22 @@ function MSRS:SetGoogleAPIKey(APIKey)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Use Google text-to-speech as default.
|
||||
-- @param #MSRS self
|
||||
-- @return #MSRS self
|
||||
function MSRS:SetTTSProviderGoogle()
|
||||
self.ttsprovider = "Google"
|
||||
return self
|
||||
end
|
||||
|
||||
--- Use Microsoft text-to-speech as default.
|
||||
-- @param #MSRS self
|
||||
-- @return #MSRS self
|
||||
function MSRS:SetTTSProviderMicrosoft()
|
||||
self.ttsprovider = "Microsoft"
|
||||
return self
|
||||
end
|
||||
|
||||
--- Print SRS STTS help to DCS log file.
|
||||
-- @param #MSRS self
|
||||
-- @return #MSRS self
|
||||
@ -1114,7 +1135,7 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp
|
||||
end
|
||||
|
||||
-- Set google.
|
||||
if self.google then
|
||||
if self.google and self.ttsprovider == "Google" then
|
||||
command=command..string.format(' --ssml -G "%s"', self.google)
|
||||
end
|
||||
|
||||
@ -1128,7 +1149,6 @@ end
|
||||
-- @param #MSRS self
|
||||
-- @param #string Path Path to config file, defaults to "C:\Users\<yourname>\Saved Games\DCS\Config"
|
||||
-- @param #string Filename File to load, defaults to "Moose_MSRS.lua"
|
||||
-- @param #boolean ConfigLoaded - if true, skip the loading
|
||||
-- @return #boolean success
|
||||
-- @usage
|
||||
-- 0) Benefits: Centralize configuration of SRS, keep paths and keys out of the mission source code, making it safer and easier to move missions to/between servers,
|
||||
@ -1138,18 +1158,19 @@ end
|
||||
--
|
||||
-- -- Moose MSRS default Config
|
||||
-- MSRS_Config = {
|
||||
-- Path = "C:\\Program Files\\DCS-SimpleRadio-Standalone", -- adjust as needed
|
||||
-- Path = "C:\\Program Files\\DCS-SimpleRadio-Standalone", -- adjust as needed, note double \\
|
||||
-- Port = 5002, -- adjust as needed
|
||||
-- Frequency = {127,243}, -- must be a table, 1..n entries!
|
||||
-- Modulation = {0,0}, -- must be a table, 1..n entries, one for each frequency!
|
||||
-- Volume = 1.0,
|
||||
-- Volume = 1.0, -- 0.0 to 1.0
|
||||
-- Coalition = 0, -- 0 = Neutral, 1 = Red, 2 = Blue
|
||||
-- Coordinate = {0,0,0}, -- x,y,alt - optional
|
||||
-- Coordinate = {0,0,0}, -- x,y,altitude - optional, all in meters
|
||||
-- Culture = "en-GB",
|
||||
-- Gender = "male",
|
||||
-- Google = "C:\\Program Files\\DCS-SimpleRadio-Standalone\\yourfilename.json", -- path to google json key file - optional
|
||||
-- Google = "C:\\Program Files\\DCS-SimpleRadio-Standalone\\yourfilename.json", -- path to google json key file - optional.
|
||||
-- Label = "MSRS",
|
||||
-- Voice = "Microsoft Hazel Desktop",
|
||||
-- Provider = "Microsoft", -- this is the default TTS provider, e.g. "Google" or "Microsoft"
|
||||
-- -- gRPC (optional)
|
||||
-- GRPC = { -- see https://github.com/DCS-gRPC/rust-server
|
||||
-- coalition = "blue", -- blue, red, neutral
|
||||
@ -1166,14 +1187,18 @@ end
|
||||
-- }
|
||||
-- }
|
||||
--
|
||||
-- 3) Load the config into the MSRS raw class before you do anything else:
|
||||
-- 3) The config file is automatically loaded when Moose starts. YOu can also load the config into the MSRS raw class manually before you do anything else:
|
||||
--
|
||||
-- MSRS.LoadConfigFile() -- Note the "." here
|
||||
--
|
||||
-- Optionally, your might want to provide a specific path and filename:
|
||||
--
|
||||
-- MSRS.LoadConfigFile(nil,MyPath,MyFilename) -- Note the "." here
|
||||
--
|
||||
-- This will populate variables for the MSRS raw class and all instances you create with e.g. `mysrs = MSRS:New()`
|
||||
-- Optionally you can also load this per **single instance** if so needed, i.e.
|
||||
--
|
||||
-- mysrs:LoadConfig(Path,Filename)
|
||||
-- mysrs:LoadConfigFile(Path,Filename)
|
||||
--
|
||||
-- 4) Use the config in your code like so, variable names are basically the same as in the config file, but all lower case, examples:
|
||||
--
|
||||
@ -1190,46 +1215,21 @@ end
|
||||
-- atis:SetSRS(nil,nil,nil,MSRS.Voices.Google.Standard.en_US_Standard_H)
|
||||
-- --Start ATIS
|
||||
-- atis:Start()
|
||||
function MSRS:LoadConfigFile(Path,Filename,ConfigLoaded)
|
||||
|
||||
function MSRS:LoadConfigFile(Path,Filename)
|
||||
|
||||
if lfs == nil then
|
||||
env.info("*****Note - lfs and os need to be desanitized for MSRS to work!")
|
||||
return false
|
||||
end
|
||||
local path = Path or lfs.writedir()..MSRS.ConfigFilePath
|
||||
local file = Filename or MSRS.ConfigFileName or "Moose_MSRS.lua"
|
||||
local pathandfile = path..file
|
||||
local filexsists = UTILS.FileExists(pathandfile)
|
||||
|
||||
if UTILS.CheckFileExists(path,file) and not ConfigLoaded then
|
||||
if filexsists and not MSRS.ConfigLoaded then
|
||||
assert(loadfile(path..file))()
|
||||
-- now we should have a global var MSRS_Config
|
||||
if MSRS_Config then
|
||||
--[[
|
||||
-- Moose MSRS default Config
|
||||
MSRS_Config = {
|
||||
Path = "C:\\Program Files\\DCS-SimpleRadio-Standalone", -- adjust as needed
|
||||
Port = 5002, -- adjust as needed
|
||||
Frequency = {127,243}, -- must be a table, 1..n entries!
|
||||
Modulation = {0,0}, -- must be a table, 1..n entries, one for each frequency!
|
||||
Volume = 1.0,
|
||||
Coalition = 0, -- 0 = Neutral, 1 = Red, 2 = Blue
|
||||
Coordinate = {0,0,0}, -- x,y,alt - optional
|
||||
Culture = "en-GB",
|
||||
Gender = "male",
|
||||
Google = "C:\\Program Files\\DCS-SimpleRadio-Standalone\\yourfilename.json", -- path to google json key file - optional
|
||||
Label = "MSRS",
|
||||
Voice = "Microsoft Hazel Desktop",
|
||||
-- gRPC (optional)
|
||||
GRPC = { -- see https://github.com/DCS-gRPC/rust-server
|
||||
coalition = "blue", -- blue, red, neutral
|
||||
DefaultProvider = "gcloud", -- win, gcloud, aws, or azure, some of the values below depend on your cloud provider
|
||||
gcloud = {
|
||||
key = "<API Google Key>", -- for gRPC Google API key
|
||||
--secret = "", -- needed for aws
|
||||
--region = "",-- needed for aws
|
||||
defaultVoice = MSRS.Voices.Google.Standard.en_GB_Standard_F,
|
||||
},
|
||||
win = {
|
||||
defaultVoice = "Hazel",
|
||||
},
|
||||
}
|
||||
}
|
||||
--]]
|
||||
if self then
|
||||
self.path = MSRS_Config.Path or "C:\\Program Files\\DCS-SimpleRadio-Standalone"
|
||||
self.port = MSRS_Config.Port or 5002
|
||||
@ -1242,6 +1242,9 @@ function MSRS:LoadConfigFile(Path,Filename,ConfigLoaded)
|
||||
self.culture = MSRS_Config.Culture or "en-GB"
|
||||
self.gender = MSRS_Config.Gender or "male"
|
||||
self.google = MSRS_Config.Google
|
||||
if MSRS_Config.Provider then
|
||||
self.ttsprovider = MSRS_Config.Provider
|
||||
end
|
||||
self.Label = MSRS_Config.Label or "MSRS"
|
||||
self.voice = MSRS_Config.Voice --or MSRS.Voices.Microsoft.Hazel
|
||||
if MSRS_Config.GRPC then
|
||||
@ -1266,6 +1269,9 @@ function MSRS:LoadConfigFile(Path,Filename,ConfigLoaded)
|
||||
MSRS.culture = MSRS_Config.Culture or "en-GB"
|
||||
MSRS.gender = MSRS_Config.Gender or "male"
|
||||
MSRS.google = MSRS_Config.Google
|
||||
if MSRS_Config.Provider then
|
||||
MSRS.ttsprovider = MSRS_Config.Provider
|
||||
end
|
||||
MSRS.Label = MSRS_Config.Label or "MSRS"
|
||||
MSRS.voice = MSRS_Config.Voice --or MSRS.Voices.Microsoft.Hazel
|
||||
if MSRS_Config.GRPC then
|
||||
@ -1280,9 +1286,10 @@ function MSRS:LoadConfigFile(Path,Filename,ConfigLoaded)
|
||||
MSRS.ConfigLoaded = true
|
||||
end
|
||||
end
|
||||
env.info("MSRS - Sucessfully loaded default configuration from disk!",false)
|
||||
else
|
||||
env.info("MSRS - Cannot load default configuration from disk!",false)
|
||||
env.info("MSRS - Successfully loaded default configuration from disk!",false)
|
||||
end
|
||||
if not filexsists then
|
||||
env.info("MSRS - Cannot find default configuration file!",false)
|
||||
return false
|
||||
end
|
||||
|
||||
@ -1995,6 +2002,7 @@ function MSRSQUEUE:_CheckRadioQueue(delay)
|
||||
|
||||
end
|
||||
|
||||
MSRS.LoadConfigFile()
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@ -29,7 +29,6 @@ ENUMS = {}
|
||||
--- Suppress the error box
|
||||
env.setErrorMessageBoxEnabled( false )
|
||||
|
||||
|
||||
--- Rules of Engagement.
|
||||
-- @type ENUMS.ROE
|
||||
-- @field #number WeaponFree [AIR] AI will engage any enemy group it detects. Target prioritization is based based on the threat of the target.
|
||||
@ -567,6 +566,14 @@ ENUMS.ReportingName =
|
||||
}
|
||||
}
|
||||
|
||||
--- Enums for Link16 transmit power
|
||||
-- @type ENUMS.Link16Power
|
||||
ENUMS.Link16Power = {
|
||||
none = 0,
|
||||
low = 1,
|
||||
medium = 2,
|
||||
high = 3,
|
||||
}
|
||||
|
||||
|
||||
--- Enums for the STORAGE class for stores - which need to be in ""
|
||||
|
||||
@ -441,19 +441,22 @@ UTILS.BasicSerialize = function(s)
|
||||
end
|
||||
end
|
||||
|
||||
--- Print a table to log in a nice format
|
||||
-- @param #table table The table to print
|
||||
-- @param #number indent Number of idents
|
||||
function UTILS.PrintTableToLog(table, indent)
|
||||
if not table then
|
||||
BASE:E("No table passed!")
|
||||
env.warning("No table passed!")
|
||||
return
|
||||
end
|
||||
if not indent then indent = 0 end
|
||||
for k, v in pairs(table) do
|
||||
if type(v) == "table" then
|
||||
BASE:I(string.rep(" ", indent) .. tostring(k) .. " = {")
|
||||
env.info(string.rep(" ", indent) .. tostring(k) .. " = {")
|
||||
UTILS.PrintTableToLog(v, indent + 1)
|
||||
BASE:I(string.rep(" ", indent) .. "}")
|
||||
env.info(string.rep(" ", indent) .. "}")
|
||||
else
|
||||
BASE:I(string.rep(" ", indent) .. tostring(k) .. " = " .. tostring(v))
|
||||
env.info(string.rep(" ", indent) .. tostring(k) .. " = " .. tostring(v))
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1372,6 +1375,11 @@ function UTILS.VecSubstract(a, b)
|
||||
return {x=a.x-b.x, y=a.y-b.y, z=a.z-b.z}
|
||||
end
|
||||
|
||||
--- Substract is not a word, don't want to rename the original function because it's been around since forever
|
||||
function UTILS.VecSubtract(a, b)
|
||||
return UTILS.VecSubstract(a, b)
|
||||
end
|
||||
|
||||
--- Calculate the difference between two 2D vectors by substracting the x,y components from each other.
|
||||
-- @param DCS#Vec2 a Vector in 2D with x, y components.
|
||||
-- @param DCS#Vec2 b Vector in 2D with x, y components.
|
||||
@ -1380,6 +1388,11 @@ function UTILS.Vec2Substract(a, b)
|
||||
return {x=a.x-b.x, y=a.y-b.y}
|
||||
end
|
||||
|
||||
--- Substract is not a word, don't want to rename the original function because it's been around since forever
|
||||
function UTILS.Vec2Subtract(a, b)
|
||||
return UTILS.Vec2Substract(a, b)
|
||||
end
|
||||
|
||||
--- Calculate the total vector of two 3D vectors by adding the x,y,z components of each other.
|
||||
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.
|
||||
-- @param DCS#Vec3 b Vector in 3D with x, y, z components.
|
||||
@ -2466,7 +2479,7 @@ function UTILS.LoadFromFile(Path,Filename)
|
||||
-- Check if file exists.
|
||||
local exists=UTILS.CheckFileExists(Path,Filename)
|
||||
if not exists then
|
||||
BASE:E(string.format("ERROR: File %s does not exist!",filename))
|
||||
BASE:I(string.format("ERROR: File %s does not exist!",filename))
|
||||
return false
|
||||
end
|
||||
|
||||
@ -3232,3 +3245,518 @@ function UTILS.PlotRacetrack(Coordinate, Altitude, Speed, Heading, Leg, Coalitio
|
||||
circle_center_two_three:CircleToAll(UTILS.NMToMeters(turn_radius), coalition, color, alpha, nil, 0, lineType)--, ReadOnly, Text)
|
||||
|
||||
end
|
||||
|
||||
--- Get the current time in a "nice" format like 21:01:15
|
||||
-- @return #string Returns string with the current time
|
||||
function UTILS.TimeNow()
|
||||
return UTILS.SecondsToClock(timer.getAbsTime(), false, false)
|
||||
end
|
||||
|
||||
|
||||
--- Given 2 "nice" time string, returns the difference between the two in seconds
|
||||
-- @param #string start_time Time string like "07:15:22"
|
||||
-- @param #string end_time Time string like "08:11:27"
|
||||
-- @return #number Seconds between start_time and end_time
|
||||
function UTILS.TimeDifferenceInSeconds(start_time, end_time)
|
||||
return UTILS.ClockToSeconds(end_time) - UTILS.ClockToSeconds(start_time)
|
||||
end
|
||||
|
||||
--- Check if the current time is later than time_string.
|
||||
-- @param #string start_time Time string like "07:15:22"
|
||||
-- @return #boolean True if later, False if before
|
||||
function UTILS.TimeLaterThan(time_string)
|
||||
if timer.getAbsTime() > UTILS.ClockToSeconds(time_string) then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
--- Check if the current time is before time_string.
|
||||
-- @param #string start_time Time string like "07:15:22"
|
||||
-- @return #boolean False if later, True if before
|
||||
function UTILS.TimeBefore(time_string)
|
||||
if timer.getAbsTime() < UTILS.ClockToSeconds(time_string) then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
--- Combines two time strings to give you a new time. For example "15:16:32" and "02:06:24" would return "17:22:56"
|
||||
-- @param #string time_string_01 Time string like "07:15:22"
|
||||
-- @param #string time_string_02 Time string like "08:11:27"
|
||||
-- @return #string Result of the two time string combined
|
||||
function UTILS.CombineTimeStrings(time_string_01, time_string_02)
|
||||
local hours1, minutes1, seconds1 = time_string_01:match("(%d+):(%d+):(%d+)")
|
||||
local hours2, minutes2, seconds2 = time_string_02:match("(%d+):(%d+):(%d+)")
|
||||
local total_seconds = tonumber(seconds1) + tonumber(seconds2) + tonumber(minutes1) * 60 + tonumber(minutes2) * 60 + tonumber(hours1) * 3600 + tonumber(hours2) * 3600
|
||||
|
||||
total_seconds = total_seconds % (24 * 3600)
|
||||
if total_seconds < 0 then
|
||||
total_seconds = total_seconds + 24 * 3600
|
||||
end
|
||||
|
||||
local hours = math.floor(total_seconds / 3600)
|
||||
total_seconds = total_seconds - hours * 3600
|
||||
local minutes = math.floor(total_seconds / 60)
|
||||
local seconds = total_seconds % 60
|
||||
|
||||
return string.format("%02d:%02d:%02d", hours, minutes, seconds)
|
||||
end
|
||||
|
||||
|
||||
--- Subtracts two time string to give you a new time. For example "15:16:32" and "02:06:24" would return "13:10:08"
|
||||
-- @param #string time_string_01 Time string like "07:15:22"
|
||||
-- @param #string time_string_02 Time string like "08:11:27"
|
||||
-- @return #string Result of the two time string subtracted
|
||||
function UTILS.SubtractTimeStrings(time_string_01, time_string_02)
|
||||
local hours1, minutes1, seconds1 = time_string_01:match("(%d+):(%d+):(%d+)")
|
||||
local hours2, minutes2, seconds2 = time_string_02:match("(%d+):(%d+):(%d+)")
|
||||
local total_seconds = tonumber(seconds1) - tonumber(seconds2) + tonumber(minutes1) * 60 - tonumber(minutes2) * 60 + tonumber(hours1) * 3600 - tonumber(hours2) * 3600
|
||||
|
||||
total_seconds = total_seconds % (24 * 3600)
|
||||
if total_seconds < 0 then
|
||||
total_seconds = total_seconds + 24 * 3600
|
||||
end
|
||||
|
||||
local hours = math.floor(total_seconds / 3600)
|
||||
total_seconds = total_seconds - hours * 3600
|
||||
local minutes = math.floor(total_seconds / 60)
|
||||
local seconds = total_seconds % 60
|
||||
|
||||
return string.format("%02d:%02d:%02d", hours, minutes, seconds)
|
||||
end
|
||||
|
||||
--- Checks if the current time is in between start_time and end_time
|
||||
-- @param #string time_string_01 Time string like "07:15:22"
|
||||
-- @param #string time_string_02 Time string like "08:11:27"
|
||||
-- @return #bool True if it is, False if it's not
|
||||
function UTILS.TimeBetween(start_time, end_time)
|
||||
return UTILS.TimeLaterThan(start_time) and UTILS.TimeBefore(end_time)
|
||||
end
|
||||
|
||||
--- Easy to read one line to roll the dice on something. 1% is very unlikely to happen, 99% is very likely to happen
|
||||
-- @param #number chance (optional) Percentage chance you want something to happen. Defaults to a random number if not given
|
||||
-- @return #bool True if the dice roll was within the given percentage chance of happening
|
||||
function UTILS.PercentageChance(chance)
|
||||
chance = chance or math.random(0, 100)
|
||||
chance = UTILS.Clamp(chance, 0, 100)
|
||||
local percentage = math.random(0, 100)
|
||||
if percentage < chance then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
--- Easy to read one liner to clamp a value
|
||||
-- @param #number value Input value
|
||||
-- @param #number min Minimal value that should be respected
|
||||
-- @param #number max Maximal value that should be respected
|
||||
-- @return #number Clamped value
|
||||
function UTILS.Clamp(value, min, max)
|
||||
if value < min then value = min end
|
||||
if value > max then value = max end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
--- Clamp an angle so that it's always between 0 and 360 while still being correct
|
||||
-- @param #number value Input value
|
||||
-- @return #number Clamped value
|
||||
function UTILS.ClampAngle(value)
|
||||
if value > 360 then return value - 360 end
|
||||
if value < 0 then return value + 360 end
|
||||
return value
|
||||
end
|
||||
|
||||
--- Remap an input to a new value in a given range. For example:
|
||||
--- UTILS.RemapValue(20, 10, 30, 0, 200) would return 100
|
||||
--- 20 is 50% between 10 and 30
|
||||
--- 50% between 0 and 200 is 100
|
||||
-- @param #number value Input value
|
||||
-- @param #number old_min Min value to remap from
|
||||
-- @param #number old_max Max value to remap from
|
||||
-- @param #number new_min Min value to remap to
|
||||
-- @param #number new_max Max value to remap to
|
||||
-- @return #number Remapped value
|
||||
function UTILS.RemapValue(value, old_min, old_max, new_min, new_max)
|
||||
new_min = new_min or 0
|
||||
new_max = new_max or 100
|
||||
|
||||
local old_range = old_max - old_min
|
||||
local new_range = new_max - new_min
|
||||
local percentage = (value - old_min) / old_range
|
||||
return (new_range * percentage) + new_min
|
||||
end
|
||||
|
||||
--- Given a triangle made out of 3 vector 2s, return a vec2 that is a random number in this triangle
|
||||
-- @param #Vec2 pt1 Min value to remap from
|
||||
-- @param #Vec2 pt2 Max value to remap from
|
||||
-- @param #Vec2 pt3 Max value to remap from
|
||||
-- @return #Vec2 Random point in triangle
|
||||
function UTILS.RandomPointInTriangle(pt1, pt2, pt3)
|
||||
local pt = {math.random(), math.random()}
|
||||
table.sort(pt)
|
||||
local s = pt[1]
|
||||
local t = pt[2] - pt[1]
|
||||
local u = 1 - pt[2]
|
||||
|
||||
return {x = s * pt1.x + t * pt2.x + u * pt3.x,
|
||||
y = s * pt1.y + t * pt2.y + u * pt3.y}
|
||||
end
|
||||
|
||||
--- Checks if a given angle (heading) is between 2 other angles. Min and max have to be given in clockwise order For example:
|
||||
--- UTILS.AngleBetween(350, 270, 15) would return True
|
||||
--- UTILS.AngleBetween(22, 95, 20) would return False
|
||||
-- @param #number angle Min value to remap from
|
||||
-- @param #number min Max value to remap from
|
||||
-- @param #number max Max value to remap from
|
||||
-- @return #bool
|
||||
function UTILS.AngleBetween(angle, min, max)
|
||||
angle = (360 + (angle % 360)) % 360
|
||||
min = (360 + min % 360) % 360
|
||||
max = (360 + max % 360) % 360
|
||||
|
||||
if min < max then return min <= angle and angle <= max end
|
||||
return min <= angle or angle <= max
|
||||
end
|
||||
|
||||
--- Easy to read one liner to write a JSON file. Everything in @data should be serializable
|
||||
--- json.lua exists in the DCS install Scripts folder
|
||||
-- @param #table data table to write
|
||||
-- @param #string file_path File path
|
||||
function UTILS.WriteJSON(data, file_path)
|
||||
package.path = package.path .. ";.\\Scripts\\?.lua"
|
||||
local JSON = require("json")
|
||||
local pretty_json_text = JSON:encode_pretty(data)
|
||||
local write_file = io.open(file_path, "w")
|
||||
write_file:write(pretty_json_text)
|
||||
write_file:close()
|
||||
end
|
||||
|
||||
--- Easy to read one liner to read a JSON file.
|
||||
--- json.lua exists in the DCS install Scripts folder
|
||||
-- @param #string file_path File path
|
||||
-- @return #table
|
||||
function UTILS.ReadJSON(file_path)
|
||||
package.path = package.path .. ";.\\Scripts\\?.lua"
|
||||
local JSON = require("json")
|
||||
local read_file = io.open(file_path, "r")
|
||||
local contents = read_file:read( "*a" )
|
||||
io.close(read_file)
|
||||
return JSON:decode(contents)
|
||||
end
|
||||
|
||||
--- Get the properties names and values of properties set up on a Zone in the Mission Editor.
|
||||
--- This doesn't work for any zones created in MOOSE
|
||||
-- @param #string zone_name Name of the zone as set up in the Mission Editor
|
||||
-- @return #table with all the properties on a zone
|
||||
function UTILS.GetZoneProperties(zone_name)
|
||||
local return_table = {}
|
||||
for _, zone in pairs(env.mission.triggers.zones) do
|
||||
if zone["name"] == zone_name then
|
||||
if table.length(zone["properties"]) > 0 then
|
||||
for _, property in pairs(zone["properties"]) do
|
||||
return_table[property["key"]] = property["value"]
|
||||
end
|
||||
return return_table
|
||||
else
|
||||
BASE:I(string.format("%s doesn't have any properties", zone_name))
|
||||
return {}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Rotates a point around another point with a given angle. Useful if you're loading in groups or
|
||||
--- statics but you want to rotate them all as a collection. You can get the center point of everything
|
||||
--- and then rotate all the positions of every object around this center point.
|
||||
-- @param #Vec2 point Point that you want to rotate
|
||||
-- @param #Vec2 pivot Pivot point of the rotation
|
||||
-- @param #number angle How many degrees the point should be rotated
|
||||
-- @return #Vec Rotated point
|
||||
function UTILS.RotatePointAroundPivot(point, pivot, angle)
|
||||
local radians = math.rad(angle)
|
||||
|
||||
local x = point.x - pivot.x
|
||||
local y = point.y - pivot.y
|
||||
|
||||
local rotated_x = x * math.cos(radians) - y * math.sin(radians)
|
||||
local rotatex_y = x * math.sin(radians) + y * math.cos(radians)
|
||||
|
||||
local original_x = rotated_x + pivot.x
|
||||
local original_y = rotatex_y + pivot.y
|
||||
|
||||
return { x = original_x, y = original_y }
|
||||
end
|
||||
|
||||
--- Makes a string semi-unique by attaching a random number between 0 and 1 million to it
|
||||
-- @param #string base String you want to unique-fy
|
||||
-- @return #string Unique string
|
||||
function UTILS.UniqueName(base)
|
||||
base = base or ""
|
||||
local ran = tostring(math.random(0, 1000000))
|
||||
|
||||
if base == "" then
|
||||
return ran
|
||||
end
|
||||
return base .. "_" .. ran
|
||||
end
|
||||
|
||||
--- Check if a string starts with something
|
||||
-- @param #string str String to check
|
||||
-- @param #string value
|
||||
-- @return #bool True if str starts with value
|
||||
function string.startswith(str, value)
|
||||
return string.sub(str,1,string.len(value)) == value
|
||||
end
|
||||
|
||||
|
||||
--- Check if a string ends with something
|
||||
-- @param #string str String to check
|
||||
-- @param #string value
|
||||
-- @return #bool True if str ends with value
|
||||
function string.endswith(str, value)
|
||||
return value == "" or str:sub(-#value) == value
|
||||
end
|
||||
|
||||
--- Splits a string on a separator. For example:
|
||||
--- string.split("hello_dcs_world", "-") would return {"hello", "dcs", "world"}
|
||||
-- @param #string input String to split
|
||||
-- @param #string separator What to split on
|
||||
-- @return #table individual strings
|
||||
function string.split(input, separator)
|
||||
local parts = {}
|
||||
for part in input:gmatch("[^" .. separator .. "]+") do
|
||||
table.insert(parts, part)
|
||||
end
|
||||
return parts
|
||||
end
|
||||
|
||||
|
||||
--- Checks if a string contains a substring. Easier to remember for Python people :)
|
||||
--- string.split("hello_dcs_world", "-") would return {"hello", "dcs", "world"}
|
||||
-- @param #string str
|
||||
-- @param #string value
|
||||
-- @return #bool True if str contains value
|
||||
function string.contains(str, value)
|
||||
return string.match(str, value)
|
||||
end
|
||||
|
||||
--- Given tbl is a indexed table ({"hello", "dcs", "world"}), checks if element exists in the table.
|
||||
--- The table can be made up out of complex tables or values as well
|
||||
-- @param #table tbl
|
||||
-- @param #string element
|
||||
-- @return #bool True if tbl contains element
|
||||
function table.contains(tbl, element)
|
||||
if element == nil or tbl == nil then return false end
|
||||
|
||||
local index = 1
|
||||
while tbl[index] do
|
||||
if tbl[index] == element then
|
||||
return true
|
||||
end
|
||||
index = index + 1
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
--- Checks if a table contains a specific key.
|
||||
-- @param #table tbl Table to check
|
||||
-- @param #string key Key to look for
|
||||
-- @return #bool True if tbl contains key
|
||||
function table.contains_key(tbl, key)
|
||||
if tbl[key] ~= nil then return true else return false end
|
||||
end
|
||||
|
||||
--- Inserts a unique element into a table.
|
||||
-- @param #table tbl Table to insert into
|
||||
-- @param #string element Element to insert
|
||||
function table.insert_unique(tbl, element)
|
||||
if element == nil or tbl == nil then return end
|
||||
|
||||
if not table.contains(tbl, element) then
|
||||
table.insert(tbl, element)
|
||||
end
|
||||
end
|
||||
|
||||
--- Removes an element from a table by its value.
|
||||
-- @param #table tbl Table to remove from
|
||||
-- @param #string element Element to remove
|
||||
function table.remove_by_value(tbl, element)
|
||||
local indices_to_remove = {}
|
||||
local index = 1
|
||||
for _, value in pairs(tbl) do
|
||||
if value == element then
|
||||
table.insert(indices_to_remove, index)
|
||||
end
|
||||
index = index + 1
|
||||
end
|
||||
|
||||
for _, idx in pairs(indices_to_remove) do
|
||||
table.remove(tbl, idx)
|
||||
end
|
||||
end
|
||||
|
||||
--- Removes an element from a table by its key.
|
||||
-- @param #table table Table to remove from
|
||||
-- @param #string key Key of the element to remove
|
||||
-- @return #string Removed element
|
||||
function table.remove_key(table, key)
|
||||
local element = table[key]
|
||||
table[key] = nil
|
||||
return element
|
||||
end
|
||||
|
||||
--- Finds the index of an element in a table.
|
||||
-- @param #table table Table to search
|
||||
-- @param #string element Element to find
|
||||
-- @return #int Index of the element, or nil if not found
|
||||
function table.index_of(table, element)
|
||||
for i, v in ipairs(table) do
|
||||
if v == element then
|
||||
return i
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Counts the number of elements in a table.
|
||||
-- @param #table T Table to count
|
||||
-- @return #int Number of elements in the table
|
||||
function table.length(T)
|
||||
local count = 0
|
||||
for _ in pairs(T) do count = count + 1 end
|
||||
return count
|
||||
end
|
||||
|
||||
--- Slices a table between two indices, much like Python's my_list[2:-1]
|
||||
-- @param #table tbl Table to slice
|
||||
-- @param #int first Starting index
|
||||
-- @param #int last Ending index
|
||||
-- @return #table Sliced table
|
||||
function table.slice(tbl, first, last)
|
||||
local sliced = {}
|
||||
local start = first or 1
|
||||
local stop = last or table.length(tbl)
|
||||
local count = 1
|
||||
|
||||
for key, value in pairs(tbl) do
|
||||
if count >= start and count <= stop then
|
||||
sliced[key] = value
|
||||
end
|
||||
count = count + 1
|
||||
end
|
||||
|
||||
return sliced
|
||||
end
|
||||
|
||||
--- Counts the number of occurrences of a value in a table.
|
||||
-- @param #table tbl Table to search
|
||||
-- @param #string value Value to count
|
||||
-- @return #int Number of occurrences of the value
|
||||
function table.count_value(tbl, value)
|
||||
local count = 0
|
||||
for _, item in pairs(tbl) do
|
||||
if item == value then count = count + 1 end
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
--- Add 2 table together, t2 gets added to t1
|
||||
-- @param #table t1 First table
|
||||
-- @param #table t2 Second table
|
||||
-- @return #table Combined table
|
||||
function table.combine(t1, t2)
|
||||
if t1 == nil and t2 == nil then
|
||||
BASE:E("Both tables were empty!")
|
||||
end
|
||||
|
||||
if t1 == nil then return t2 end
|
||||
if t2 == nil then return t1 end
|
||||
for i=1,#t2 do
|
||||
t1[#t1+1] = t2[i]
|
||||
end
|
||||
return t1
|
||||
end
|
||||
|
||||
--- Merges two tables into one. If a key exists in both t1 and t2, the value of t1 with be overwritten by the value of t2
|
||||
-- @param #table t1 First table
|
||||
-- @param #table t2 Second table
|
||||
-- @return #table Merged table
|
||||
function table.merge(t1, t2)
|
||||
for k, v in pairs(t2) do
|
||||
if (type(v) == "table") and (type(t1[k] or false) == "table") then
|
||||
table.merge(t1[k], t2[k])
|
||||
else
|
||||
t1[k] = v
|
||||
end
|
||||
end
|
||||
return t1
|
||||
end
|
||||
|
||||
--- Adds an item to the end of a table.
|
||||
-- @param #table tbl Table to add to
|
||||
-- @param #string item Item to add
|
||||
function table.add(tbl, item)
|
||||
tbl[#tbl + 1] = item
|
||||
end
|
||||
|
||||
--- Shuffles the elements of a table.
|
||||
-- @param #table tbl Table to shuffle
|
||||
-- @return #table Shuffled table
|
||||
function table.shuffle(tbl)
|
||||
local new_table = {}
|
||||
for _, value in ipairs(tbl) do
|
||||
local pos = math.random(1, #new_table +1)
|
||||
table.insert(new_table, pos, value)
|
||||
end
|
||||
return new_table
|
||||
end
|
||||
|
||||
--- Finds a key-value pair in a table.
|
||||
-- @param #table tbl Table to search
|
||||
-- @param #string key Key to find
|
||||
-- @param #string value Value to find
|
||||
-- @return #table Table containing the key-value pair, or nil if not found
|
||||
function table.find_key_value_pair(tbl, key, value)
|
||||
for k, v in pairs(tbl) do
|
||||
if type(v) == "table" then
|
||||
local result = table.find_key_value_pair(v, key, value)
|
||||
if result ~= nil then
|
||||
return result
|
||||
end
|
||||
elseif k == key and v == value then
|
||||
return tbl
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Convert a decimal to octal
|
||||
-- @param #number Number the number to convert
|
||||
-- @return #number Octal
|
||||
function UTILS.DecimalToOctal(Number)
|
||||
if Number < 8 then return Number end
|
||||
local number = tonumber(Number)
|
||||
local octal = ""
|
||||
local n=1
|
||||
while number > 7 do
|
||||
local number1 = number%8
|
||||
octal = string.format("%d",number1)..octal
|
||||
local number2 = math.abs(number/8)
|
||||
if number2 < 8 then
|
||||
octal = string.format("%d",number2)..octal
|
||||
end
|
||||
number = number2
|
||||
n=n+1
|
||||
end
|
||||
return tonumber(octal)
|
||||
end
|
||||
|
||||
--- Convert an octal to decimal
|
||||
-- @param #number Number the number to convert
|
||||
-- @return #number Decimal
|
||||
function UTILS.OctalToDecimal(Number)
|
||||
return tonumber(Number,8)
|
||||
end
|
||||
|
||||
@ -241,6 +241,13 @@ AIRBASE.Nevada = {
|
||||
-- * AIRBASE.Normandy.Broglie
|
||||
-- * AIRBASE.Normandy.Bernay_Saint_Martin
|
||||
-- * AIRBASE.Normandy.Saint_Andre_de_lEure
|
||||
-- * AIRBASE.Normandy.Biggin_Hill
|
||||
-- * AIRBASE.Normandy.Manston
|
||||
-- * AIRBASE.Normandy.Detling
|
||||
-- * AIRBASE.Normandy.Lympne
|
||||
-- * AIRBASE.Normandy.Abbeville_Drucat
|
||||
-- * AIRBASE.Normandy.Merville_Calonne
|
||||
-- * AIRBASE.Normandy.Saint_Omer_Wizernes
|
||||
--
|
||||
-- @field Normandy
|
||||
AIRBASE.Normandy = {
|
||||
@ -313,7 +320,14 @@ AIRBASE.Normandy = {
|
||||
["Beaumont_le_Roger"] = "Beaumont-le-Roger",
|
||||
["Broglie"] = "Broglie",
|
||||
["Bernay_Saint_Martin"] = "Bernay Saint Martin",
|
||||
["Saint_Andre_de_lEure"] = "Saint-Andre-de-lEure",
|
||||
["Saint_Andre_de_lEure"] = "Saint-Andre-de-lEure",
|
||||
["Biggin_Hill"] = "Biggin Hill",
|
||||
["Manston"] = "Manston",
|
||||
["Detling"] = "Detling",
|
||||
["Lympne"] = "Lympne",
|
||||
["Abbeville_Drucat"] = "Abbeville Drucat",
|
||||
["Merville_Calonne"] = "Merville Calonne",
|
||||
["Saint_Omer_Wizernes"] = "Saint-Omer Wizernes",
|
||||
}
|
||||
|
||||
--- Airbases of the Persion Gulf Map:
|
||||
|
||||
@ -3974,7 +3974,7 @@ function CONTROLLABLE:OptionAAAttackRange( range )
|
||||
local Controller = self:_GetController()
|
||||
if Controller then
|
||||
if self:IsAir() then
|
||||
self:SetOption( AI.Option.Air.val.MISSILE_ATTACK, range )
|
||||
self:SetOption( AI.Option.Air.id.MISSILE_ATTACK, range )
|
||||
end
|
||||
end
|
||||
return self
|
||||
|
||||
@ -2942,7 +2942,7 @@ function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations)
|
||||
return callsign
|
||||
end
|
||||
|
||||
---
|
||||
--- Set a GROUP to act as recovery tanker
|
||||
-- @param #GROUP self
|
||||
-- @param Wrapper.Group#GROUP CarrierGroup.
|
||||
-- @param #number Speed Speed in knots.
|
||||
@ -2968,3 +2968,37 @@ function GROUP:SetAsRecoveryTanker(CarrierGroup,Speed,ToKIAS,Altitude,Delay,Last
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get a list of Link16 S/TN data from a GROUP. Can (as of Nov 2023) be obtained from F-18, F-16, F-15E (not the user flyable one) and A-10C-II groups.
|
||||
-- @param #GROUP self
|
||||
-- @return #table Table of data entries, indexed by unit name, each entry is a table containing STN, VCL (voice call label), VCN (voice call number), and Lead (#boolean, if true it's the flight lead)
|
||||
-- @return #string Report Formatted report of all data
|
||||
function GROUP:GetGroupSTN()
|
||||
local tSTN = {} -- table
|
||||
local units = self:GetUnits()
|
||||
local gname = self:GetName()
|
||||
gname = string.gsub(gname,"(#%d+)$","")
|
||||
local report = REPORT:New()
|
||||
report:Add("Link16 S/TN Report")
|
||||
report:Add("Group: "..gname)
|
||||
report:Add("==================")
|
||||
for _,_unit in pairs(units) do
|
||||
local unit = _unit -- Wrapper.Unit#UNIT
|
||||
if unit and unit:IsAlive() then
|
||||
local STN, VCL, VCN, Lead = unit:GetSTN()
|
||||
local name = unit:GetName()
|
||||
tSTN[name] = {
|
||||
STN=STN,
|
||||
VCL=VCL,
|
||||
VCN=VCN,
|
||||
Lead=Lead,
|
||||
}
|
||||
local lead = Lead == true and "(*)" or ""
|
||||
report:Add(string.format("| %s%s %s %s",tostring(VCL),tostring(VCN),tostring(STN),lead))
|
||||
end
|
||||
end
|
||||
report:Add("==================")
|
||||
local text = report:Text()
|
||||
return tSTN,text
|
||||
end
|
||||
|
||||
|
||||
@ -1659,3 +1659,36 @@ function UNIT:GetSkill()
|
||||
local skill = _DATABASE.Templates.Units[name].Template.skill or "Random"
|
||||
return skill
|
||||
end
|
||||
|
||||
--- Get Link16 STN or SADL TN and other datalink info from Unit, if any.
|
||||
-- @param #UNIT self
|
||||
-- @return #string STN STN or TN Octal as string, or nil if not set/capable.
|
||||
-- @return #string VCL Voice Callsign Label or nil if not set/capable.
|
||||
-- @return #string VCN Voice Callsign Number or nil if not set/capable.
|
||||
-- @return #string Lead If true, unit is Flight Lead, else false or nil.
|
||||
function UNIT:GetSTN()
|
||||
self:F2(self.UnitName)
|
||||
local STN = nil -- STN/TN
|
||||
local VCL = nil -- VoiceCallsignLabel
|
||||
local VCN = nil -- VoiceCallsignNumber
|
||||
local FGL = false -- FlightGroupLeader
|
||||
local template = self:GetTemplate()
|
||||
if template.AddPropAircraft then
|
||||
if template.AddPropAircraft.STN_L16 then
|
||||
STN = template.AddPropAircraft.STN_L16
|
||||
elseif template.AddPropAircraft.SADL_TN then
|
||||
STN = template.AddPropAircraft.SADL_TN
|
||||
end
|
||||
VCN = template.AddPropAircraft.VoiceCallsignNumber
|
||||
VCL = template.AddPropAircraft.VoiceCallsignLabel
|
||||
end
|
||||
if template.datalinks and template.datalinks.Link16 and template.datalinks.Link16.settings then
|
||||
FGL = template.datalinks.Link16.settings.flightLead
|
||||
end
|
||||
-- A10CII
|
||||
if template.datalinks and template.datalinks.SADL and template.datalinks.SADL.settings then
|
||||
FGL = template.datalinks.SADL.settings.flightLead
|
||||
end
|
||||
|
||||
return STN, VCL, VCN, FGL
|
||||
end
|
||||
|
||||
@ -223,7 +223,15 @@ function WEAPON:New(WeaponObject)
|
||||
|
||||
-- Set log ID.
|
||||
self.lid=string.format("[%s] %s | ", self.typeName, self.name)
|
||||
|
||||
|
||||
if self.launcherUnit then
|
||||
self.releaseHeading = self.launcherUnit:GetHeading()
|
||||
self.releaseAltitudeASL = self.launcherUnit:GetAltitude()
|
||||
self.releaseAltitudeAGL = self.launcherUnit:GetAltitude(true)
|
||||
self.releaseCoordinate = self.launcherUnit:GetCoordinate()
|
||||
self.releasePitch = self.launcherUnit:GetPitch()
|
||||
end
|
||||
|
||||
-- Set default parameters
|
||||
self:SetTimeStepTrack()
|
||||
self:SetDistanceInterceptPoint()
|
||||
@ -552,6 +560,52 @@ function WEAPON:GetImpactCoordinate()
|
||||
return self.impactCoord
|
||||
end
|
||||
|
||||
--- Get the heading on which the weapon was released
|
||||
-- @param #WEAPON self
|
||||
-- @param #bool AccountForMagneticInclination (Optional) If true will account for the magnetic declination of the current map. Default is true
|
||||
-- @return #number Heading
|
||||
function WEAPON:GetReleaseHeading(AccountForMagneticInclination)
|
||||
AccountForMagneticInclination = AccountForMagneticInclination or true
|
||||
if AccountForMagneticInclination then return UTILS.ClampAngle(self.releaseHeading - UTILS.GetMagneticDeclination()) else return UTILS.ClampAngle(self.releaseHeading) end
|
||||
end
|
||||
|
||||
--- Get the altitude above sea level at which the weapon was released
|
||||
-- @param #WEAPON self
|
||||
-- @return #number Altitude in meters
|
||||
function WEAPON:GetReleaseAltitudeASL()
|
||||
return self.releaseAltitudeASL
|
||||
end
|
||||
|
||||
--- Get the altitude above ground level at which the weapon was released
|
||||
-- @param #WEAPON self
|
||||
-- @return #number Altitude in meters
|
||||
function WEAPON:GetReleaseAltitudeAGL()
|
||||
return self.releaseAltitudeAGL
|
||||
end
|
||||
|
||||
--- Get the coordinate where the weapon was released
|
||||
-- @param #WEAPON self
|
||||
-- @return Core.Point#COORDINATE Impact coordinate (if any).
|
||||
function WEAPON:GetReleaseCoordinate()
|
||||
return self.releaseCoordinate
|
||||
end
|
||||
|
||||
--- Get the pitch of the unit when the weapon was released
|
||||
-- @param #WEAPON self
|
||||
-- @return #number Degrees
|
||||
function WEAPON:GetReleasePitch()
|
||||
return self.releasePitch
|
||||
end
|
||||
|
||||
--- Get the heading of the weapon when it impacted. Note that this might not exist if the weapon has not impacted yet!
|
||||
-- @param #WEAPON self
|
||||
-- @param #bool AccountForMagneticInclination (Optional) If true will account for the magnetic declination of the current map. Default is true
|
||||
-- @return #number Heading
|
||||
function WEAPON:GetImpactHeading(AccountForMagneticInclination)
|
||||
AccountForMagneticInclination = AccountForMagneticInclination or true
|
||||
if AccountForMagneticInclination then return UTILS.ClampAngle(self.impactHeading - UTILS.GetMagneticDeclination()) else return self.impactHeading end
|
||||
end
|
||||
|
||||
--- Check if weapon is in the air. Obviously not really useful for torpedos. Well, then again, this is DCS...
|
||||
-- @param #WEAPON self
|
||||
-- @return #boolean If `true`, weapon is in the air and `false` if not. Returns `nil` if weapon object itself is `nil`.
|
||||
@ -712,7 +766,10 @@ function WEAPON:_TrackWeapon(time)
|
||||
|
||||
-- Update coordinate.
|
||||
self.coordinate:UpdateFromVec3(self.vec3)
|
||||
|
||||
|
||||
-- Safe the last velocity of the weapon. This is needed to get the impact heading
|
||||
self.last_velocity = self.weapon:getVelocity()
|
||||
|
||||
-- Keep on tracking by returning the next time below.
|
||||
self.tracking=true
|
||||
|
||||
@ -781,7 +838,10 @@ function WEAPON:_TrackWeapon(time)
|
||||
|
||||
-- Safe impact coordinate.
|
||||
self.impactCoord=COORDINATE:NewFromVec3(self.vec3)
|
||||
|
||||
|
||||
-- Safe impact heading, using last_velocity because self:GetVelocityVec3() is no longer possible
|
||||
self.impactHeading = UTILS.VecHdg(self.last_velocity)
|
||||
|
||||
-- Mark impact point on F10 map.
|
||||
if self.impactMark then
|
||||
self.impactCoord:MarkToAll(string.format("Impact point of weapon %s\ntype=%s\nlauncher=%s", self.name, self.typeName, self.launcherName))
|
||||
|
||||
9
docs/beginner/hello-world-build.md
Normal file
@ -0,0 +1,9 @@
|
||||
---
|
||||
parent: Beginner
|
||||
nav_order: 03
|
||||
---
|
||||
|
||||
# Create your own Hello world
|
||||
|
||||
{: .warning }
|
||||
> THIS DOCUMENT IS STILL WORK IN PROGRESS!
|
||||
178
docs/beginner/hello-world.md
Normal file
@ -0,0 +1,178 @@
|
||||
---
|
||||
parent: Beginner
|
||||
nav_order: 02
|
||||
---
|
||||
|
||||
# Hello world mission
|
||||
{: .no_toc }
|
||||
|
||||
1. Table of contents
|
||||
{:toc}
|
||||
|
||||
## Let's see MOOSE in action
|
||||
|
||||
It is tradition that the first piece of code is a very simple example on showing
|
||||
a "Hello world!" to the user. We have prepared this example mission for you. So
|
||||
you can download and run it. Later on we will analyze it to explain the basics
|
||||
on how to add MOOSE to your own missions.
|
||||
|
||||
- Download the demo mission [001-hello-world.miz] by clicking on the link.
|
||||
- Put the .miz file into your Missions subfolder of your [Saved Games folder].
|
||||
- Start DCS, choose `MISSION` in the menu on the right side:
|
||||
|
||||

|
||||
|
||||
- Click on `My Missions`, choose the `hello-world` mission and click on `OK`.
|
||||
|
||||

|
||||
|
||||
- It is an empty mission, so skip `BRIEFING` with `START` and then `FLY`.
|
||||
- You spawn as a spectator. After some seconds you will see this message in
|
||||
the upper right corner:
|
||||
|
||||

|
||||
|
||||
Ok, that's all. There is nothing more to see in this mission. This is not
|
||||
particularly impressive and can also be achieved using standard Lua in DCS
|
||||
(i.e. without MOOSE), but we want to keep it simple at the beginning.
|
||||
|
||||
{: .note }
|
||||
> If the text don't show up, the mission might be corrupted. Please contact the
|
||||
> team on Discord for futher instructions.
|
||||
|
||||
## Let's take a look under the hood
|
||||
|
||||
- Go back to the main window and open the `MISSION EDITOR`.
|
||||
- Choose `open mission` navigate to `My Missions` and open 001-hello-world.miz.
|
||||
- On the left side activate `TRIGGERS`:
|
||||
|
||||

|
||||
|
||||
- On the right side the `TRIGGERS` dialog opens with a lot of options.
|
||||
- First take a look at the available triggers:
|
||||
|
||||

|
||||
|
||||
- You will see two:
|
||||
- One in yellow with type `4 MISSION START` and name `Load MOOSE` and
|
||||
- one in green with type `1 ONCE` and name `Load Mission Script`.
|
||||
|
||||
### Execution of Moose
|
||||
|
||||
- Click on the yellow one to show all of it options.
|
||||
|
||||
- In the middle part the `CONDITIONS` will be shown.
|
||||
For this trigger there are no conditions configured.
|
||||
|
||||

|
||||
|
||||
{: .important }
|
||||
> The trigger type `4 MISSION START` does not support `CONDITIONS`. <br />
|
||||
> So `CONDITIONS` must left blank when using it. <br />
|
||||
> **If you add a condition the trigger will never be executed!**
|
||||
|
||||
- On the right side the `ACTIONS` will be shown:
|
||||
|
||||

|
||||
|
||||
- A `DO SCRIPT FILE` is configured, which executes the file `Moose_.lua`
|
||||
|
||||
{: .highlight }
|
||||
> This is the execution of the Moose framework included in the mission as one single file. <br />
|
||||
> The difference between `Moose_.lua` and `Moose.lua` will be explained later. <br />
|
||||
> This doesn't matter at this time.
|
||||
|
||||
{: .important }
|
||||
> The trigger `4 MISSION START` will be executed **before** the mission is started! <br />
|
||||
> This is important, because Moose **must** be executed before other scripts, that want to use Moose!
|
||||
|
||||
### Execution of the mission script
|
||||
|
||||
- Now move back to the left `TRIGGERS` area and click on the green trigger <br />
|
||||
`1 ONCE (Load Mission Script ...)`
|
||||
|
||||

|
||||
|
||||
- The configured options will be shown. <br />
|
||||
In the middle part the `CONDITIONS` will be shown. <br />
|
||||
For this trigger there is one condition configured:
|
||||
|
||||

|
||||
|
||||
- The combination of `1 ONCE` with `TIME MORE(1)` will ensure, that the mission
|
||||
script is executed 1 second after the mission is started.
|
||||
|
||||
- On the right side the `ACTIONS` will be shown:
|
||||
|
||||

|
||||
|
||||
- A `DO SCRIPT FILE` is configured, which executes the file `001-hello-world.lua`.
|
||||
|
||||
{: .highlight }
|
||||
> This is the execution of the mission script, which you will create in the future.
|
||||
|
||||
{: .important }
|
||||
> Most important is the fact, that the mission script (`001-hello-world.lua`)
|
||||
> is executed **after** `Moose_.lua`, because the mission script needs the
|
||||
> classes defined in `Moose_.lua`. And they are only available when `Moose_.lua`
|
||||
> is executed before the mission script.
|
||||
|
||||
### Inspect the code of the mission script
|
||||
|
||||
The file `001-hello-world.lua` consists of following code:
|
||||
|
||||
```lua
|
||||
--
|
||||
-- Simple example mission to show the very basics of MOOSE
|
||||
--
|
||||
MESSAGE:New( "Hello World! This messages is printed by MOOSE", 35, "INFO" ):ToAll()
|
||||
```
|
||||
|
||||
- The first three lines starting with `--` are comments and will be ignored.
|
||||
|
||||
- Line 4 is the one with the "magic":
|
||||
|
||||
- With `MESSAGE` we use the class [Core.Message].
|
||||
|
||||
The part before the dot (Core) is the section where the class is placed.
|
||||
It is important for the Moose programmes to have a structure where the classes
|
||||
are placed. But in the code itself it is not used.
|
||||
|
||||
#### What is a class?
|
||||
|
||||
{: .highlight }
|
||||
> In object-oriented programming, a class is an extensible program-code-template
|
||||
> for creating objects, providing initial values for state (member variables)
|
||||
> and implementations of behavior (member functions or methods). <br />
|
||||
> *Source [Wikipedia:Class]{:target="_blank"}*
|
||||
|
||||
After the class name we call a method of that class. We do this with semicolon
|
||||
followed by the name of the method and a pair of round brackets.
|
||||
Here we call the method `New`, which creates a new MESSAGE object.
|
||||
|
||||
We give it three parameters within the round brackets, which are divided by commas:
|
||||
1. The text we want to show: `"Hello World! ..."`
|
||||
1. The time in seconds the messages should be visible: `35`
|
||||
1. And the type of message: `"INFO"`
|
||||
|
||||
- With `New` the MESSAGE object is created, but the message is still not printed
|
||||
to the screen.
|
||||
- This is done by `:ToAll()`. Another method of [Core.Message] which sends the
|
||||
message to all players, no matter if they belong to the RED or BLUE coalition.
|
||||
|
||||
If you you want to read more about [Core.Message] click on the link.
|
||||
The page with all the Methods and Fields is very long and this might be
|
||||
daunting, but for the copy and paste approach, you won't need it often.
|
||||
|
||||
And if you want to learn how to use more of that stuff, you will become
|
||||
compftable in filtering these informations fast.
|
||||
|
||||
## Next step
|
||||
|
||||
Now it is time to [create your own Hello world] mission.
|
||||
|
||||
[Saved Games folder]: ../beginner/tipps-and-tricks.md#find-the-saved-games-folder
|
||||
[hello-world demo mission]: https://raw.githubusercontent.com/FlightControl-Master/MOOSE_MISSIONS/master/Core/Message/001-hello-world.miz
|
||||
[Core.Message]: https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Core.Message.html
|
||||
[Wikipedia:Class]: https://en.wikipedia.org/wiki/Class_(computer_programming)
|
||||
[create your own Hello world]: hello-world-build.md
|
||||
106
docs/beginner/introduction.md
Normal file
@ -0,0 +1,106 @@
|
||||
---
|
||||
parent: Beginner
|
||||
nav_order: 01
|
||||
---
|
||||
# Introduction
|
||||
{: .no_toc }
|
||||
|
||||
1. Table of contents
|
||||
{:toc}
|
||||
|
||||
This very short chapter is for people identifying as a consumer of MOOSE and not
|
||||
wishing to learn to script. This is a condensed FAQ and set of links to get you
|
||||
up and running. It specifically avoids any complexity.
|
||||
|
||||
## What is MOOSE?
|
||||
|
||||
[DCS] has included a [Simulator Scripting Engine] (short SSE). This SSE gives
|
||||
mission designers access to objects in the game using [Lua] scripts.
|
||||
|
||||
**M**ission **O**bject **O**riented **S**cripting **E**nvironment, is a
|
||||
scripting framework written in [Lua] that attempts to make the scripting of
|
||||
missions within DCS easier, simpler and shorter than with the standard methods.
|
||||
|
||||
MOOSE is over 5 MB of code, with as many words as the Bible and the core of it
|
||||
was written over several years by one person.
|
||||
|
||||
MOOSE is the brain-child of an talented programmer with the alias FlightControl.
|
||||
If you want to know more about this topic, check out FC’s [MOOSE for Dummies]
|
||||
videos on YouTube.
|
||||
|
||||
{: .note }
|
||||
> We recommend video playback at 1.5x speed, as FC speaks slowly and distinctly.
|
||||
|
||||
## What is Lua?
|
||||
|
||||
[Lua] is a lightweight, programming language designed primarily to be embedded
|
||||
in applications. It's main advantages are:
|
||||
|
||||
- It is fast,
|
||||
- it is portable (Windows, Linux, MacOS),
|
||||
- it is easy to use.
|
||||
|
||||
[Lua] is embedded in DCS, so we can use it without any modification to the game.
|
||||
|
||||
## What are scripts, frameworks and classes?
|
||||
|
||||
A script is a set of instructions in plain text read by a computer and processed
|
||||
on the fly. Scripts do not need to be compiled before execution, unlike exe
|
||||
files.
|
||||
|
||||
A framework is a structure that you can build software (or in this case missions)
|
||||
on. It serves as a foundation, so you're not starting entirely from scratch.
|
||||
It takes a lot of work off your hands because someone else has thought about it
|
||||
and provides ready-made building blocks for many situations.
|
||||
|
||||
These building blocks are called classes in object oriented programming.
|
||||
|
||||
## What can MOOSE do for me?
|
||||
|
||||
Whilst MOOSE can be used to write customised [Lua] scripts, you are probably not
|
||||
caring for learning [Lua] right now. Instead you can use a MOOSE script written
|
||||
by someone else by just copy and paste it. You can configure the basic settings
|
||||
of the classes to fit your needs in your mission.
|
||||
|
||||
Here are a few suggestions for well-known and popular classes:
|
||||
|
||||
- [Ops.Airboss] manages recoveries of human pilots and AI aircraft on aircraft
|
||||
carriers.
|
||||
- [Functional.RAT] creates random airtraffic in your missions.
|
||||
- [Functional.Range] (which counts hits on targets so you can practice),
|
||||
- [Functional.Fox] to practice to evade missiles without being destroyed.
|
||||
- and many more!
|
||||
|
||||
You will need to look through examples to know what functionallity you want to
|
||||
add to your missions.
|
||||
|
||||
## What if I don’t want to learn scripting?
|
||||
|
||||
The good news for you: You don't need to become a professional [Lua] programmer
|
||||
to use MOOSE. As explained already, you can copy and paste the code from example
|
||||
missions. You need some basics how to add triggers in the mission editor. But we
|
||||
will cover this later.
|
||||
|
||||
If you want to modify the behaviour of the classes slightly, some basics about
|
||||
the [Lua] synthax (the rules how to write the code) will help you to avoid
|
||||
errors.
|
||||
|
||||
The more customizations you want to make, the more knowledge about [Lua] you
|
||||
will need. But you can learn this step by step.
|
||||
|
||||
## Next step
|
||||
|
||||
We will start with a very simple demonstartion of MOOSE in the next section
|
||||
[Hello world mission].
|
||||
|
||||
[DCS]: https://www.digitalcombatsimulator.com/en/
|
||||
[Simulator Scripting Engine]: https://wiki.hoggitworld.com/view/Simulator_Scripting_Engine_Documentation
|
||||
[Lua]: https://www.lua.org/
|
||||
[MOOSE for Dummies]: https://www.youtube.com/watch?v=ZqvdUFhKX4o&list=PL7ZUrU4zZUl04jBoOSX_rmqE6cArquhM4&index=2&t=618s
|
||||
|
||||
[Ops.Airboss]: https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Ops.Airboss.html
|
||||
[Functional.RAT]: https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Functional.RAT.html
|
||||
[Functional.Range]: https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Functional.Range.html
|
||||
[Functional.Fox]: https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Functional.Fox.html
|
||||
|
||||
[Hello world mission]: hello-world.md
|
||||
@ -25,5 +25,30 @@ It depends on the platform and the version you choosed to install:
|
||||
- If you changed the installation folder of the Standalone version, right
|
||||
click on the game icon, open Properties and click on `Open File Location`.
|
||||
|
||||
## Find the Saved Games folder
|
||||
|
||||
DCS creates a folder to store all user specific configuration and data.
|
||||
This folder can be found in your userprofile as subfolder of `Saved Games`.
|
||||
|
||||
The easiest way to find it, is to open search and paste the text below into it
|
||||
and press Enter:
|
||||
|
||||
```%userprofile%\Saved Games```
|
||||
|
||||
{: .note }
|
||||
> The text will work even if your Windows is installed with another language,
|
||||
> e.g. German. This is really usefull.
|
||||
|
||||
Depending on the DCS version you will find one of the following folders:
|
||||
|
||||
- DCS
|
||||
- DCS.openbeta
|
||||
|
||||
{: .note }
|
||||
> It is good idea to add the folder to the quick access area in the windows
|
||||
> explorer. You will use it very often!
|
||||
|
||||
For MOOSE users the folders `Missions`, `Logs` and `Config` are most important!
|
||||
|
||||
[DCS World Steam Edition]: https://store.steampowered.com/app/223750/DCS_World_Steam_Edition/
|
||||
[DCS World Standalone installer]: https://www.digitalcombatsimulator.com/en/downloads/world/
|
||||
|
||||
BIN
docs/images/beginner/dcs-menu-mission.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
docs/images/beginner/dcs-message.jpg
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
docs/images/beginner/dcs-my-missions.png
Normal file
|
After Width: | Height: | Size: 117 KiB |
BIN
docs/images/beginner/dcs-triggers-mission-start-actions-conf.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
docs/images/beginner/dcs-triggers-mission-start-actions.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
docs/images/beginner/dcs-triggers-mission-start-conditions.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
docs/images/beginner/dcs-triggers-mission-start-conf.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
docs/images/beginner/dcs-triggers-mission-start.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
docs/images/beginner/dcs-triggers-once-actions.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
docs/images/beginner/dcs-triggers-once-conditions.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
docs/images/beginner/dcs-triggers-once-conf.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
docs/images/beginner/dcs-triggers-once.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
docs/images/beginner/dcs-triggers-toolbar.png
Normal file
|
After Width: | Height: | Size: 14 KiB |