mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2026-07-21 06:04:49 +00:00
Fox
This commit is contained in:
@@ -291,7 +291,7 @@ end
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param Core.Zone#ZONE_BASE Zone The Zone where to spawn the static.
|
||||
-- @param #number Heading The heading of the static, which is a number in degrees from 0 to 360.
|
||||
-- @param #string (optional) The name of the new static.
|
||||
-- @param #string NewName (optional) The name of the new static.
|
||||
-- @return #SPAWNSTATIC
|
||||
function SPAWNSTATIC:SpawnFromZone( Zone, Heading, NewName ) --R2.1
|
||||
self:F( { Zone, Heading, NewName } )
|
||||
|
||||
@@ -0,0 +1,544 @@
|
||||
--- **Functional** - (R2.5) - Monitor and export flight model data.
|
||||
--
|
||||
--
|
||||
--
|
||||
-- RAT2 creates random air traffic on the map.
|
||||
--
|
||||
--
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * It's very random.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
-- @module Functional.FMData
|
||||
-- @image Functional_Rat2.png
|
||||
|
||||
|
||||
--- FMD class.
|
||||
-- @type FMD
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #boolean Debug Debug mode. Messages to all about status.
|
||||
-- @field #string lid Class id string for output to DCS log file.
|
||||
-- @field #table players Player table.
|
||||
-- @field #table menuadded Table of units where the F10 radio menu was added.
|
||||
-- @field #string savepath Path to save data files.
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- Be surprised!
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- # The RAT2 Concept
|
||||
--
|
||||
--
|
||||
--
|
||||
-- @field #FMD
|
||||
FMD = {
|
||||
ClassName = "FMD",
|
||||
Debug = false,
|
||||
lid = nil,
|
||||
players = {},
|
||||
menuadded = {},
|
||||
savepath = nil,
|
||||
}
|
||||
|
||||
--- Player data table.
|
||||
-- @type FMD.PlayerData
|
||||
-- @field Core.Scheduler#SCHEDULER scheduler Scheduler
|
||||
-- @field Wrapper.Unit#UNIT unit Player unit.
|
||||
-- @field #string unitname Name of the unit.
|
||||
-- @field #string actype Aircraft type.
|
||||
-- @field #string name Player name.
|
||||
-- @field #number dt Time step for data recording in seconds. Default 0.01 sec ==> 100 data points per second!
|
||||
-- @field #table data Data table.
|
||||
-- @field #number SID Scheduler ID.
|
||||
|
||||
--- Data point table.
|
||||
-- @type FMD.DataPoint
|
||||
-- @field #number time Abs mission time.
|
||||
-- @field #number T Temperature in degrees Celsius.
|
||||
-- @field #number P Pressure.
|
||||
-- @field #number Alt Altitude ASL in meters.
|
||||
-- @field #number AoA Angle of Attack in degrees.
|
||||
-- @field #number Pitch Pitch angle in degrees.
|
||||
-- @field #number Roll Roll angle in degrees.
|
||||
-- @field #number Yaw Yaw angle in degrees.
|
||||
-- @field #number Climbrate Climb rate in m/s.
|
||||
-- @field #number Vtot Total velocity in m/s.
|
||||
-- @field DCS#Vec3 v Velocity vector. Components x,y,z in m/s.
|
||||
-- @field DCS#Vec3 o Orientation vector. Components x,y,z in meters.
|
||||
-- @field #number omega Angle velocity in m/s.
|
||||
-- @field #number hdg Heading in degrees.
|
||||
-- @field #number Atot Total acceleration m/s^2.
|
||||
-- @field DCS#Vec3 a Accelleration vector.
|
||||
-- @field #number DRoll Roll speed in degrees/second.
|
||||
-- @field #number DPitch Pitch speed in degrees/second.
|
||||
-- @field #number DYaw Yaw speed in degrees/second.
|
||||
|
||||
--- Main group level radio menu: F10 Other/Airboss.
|
||||
-- @field #table MenuF10
|
||||
FMD.MenuF10={}
|
||||
|
||||
--- FMD mission level F10 root menu.
|
||||
-- @field #table MenuF10Root
|
||||
FMD.MenuF10Root=nil
|
||||
|
||||
--- FMD class version.
|
||||
-- @field #string version
|
||||
FMD.version="0.0.1"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new FMD class object.
|
||||
-- @param #FMD self
|
||||
-- @return #FMD self.
|
||||
function FMD:New()
|
||||
|
||||
-- Inherit everthing from FSM class.
|
||||
local self=BASE:Inherit(self, FSM:New()) -- #FMD
|
||||
|
||||
-- Start State.
|
||||
self:SetStartState("Stopped")
|
||||
|
||||
-- Log string.
|
||||
self.lid="FMD | "
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:AddTransition("Stopped", "Start", "Running") -- Start FMD script.
|
||||
self:AddTransition("*", "Status", "*") -- Start FMD script.
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- On after Start event.
|
||||
-- @param #FMD self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function FMD:onafterStart(From, Event, To)
|
||||
|
||||
-- Handle events.
|
||||
self:HandleEvent(EVENTS.Birth)
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Event Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- FMD event handler for event birth.
|
||||
-- @param #FMD self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function FMD:OnEventBirth(EventData)
|
||||
self:F3({eventbirth = EventData})
|
||||
|
||||
local _unitName=EventData.IniUnitName
|
||||
local _unit, _playername=self:_GetPlayerUnitAndName(_unitName)
|
||||
|
||||
self:T2(self.lid.."BIRTH: unit = "..tostring(EventData.IniUnitName))
|
||||
self:T2(self.lid.."BIRTH: group = "..tostring(EventData.IniGroupName))
|
||||
self:T2(self.lid.."BIRTH: player = "..tostring(_playername))
|
||||
|
||||
if _unit and _playername then
|
||||
|
||||
local _uid=_unit:GetID()
|
||||
local _group=_unit:GetGroup()
|
||||
local _callsign=_unit:GetCallsign()
|
||||
|
||||
-- Debug output.
|
||||
local text=string.format("Pilot %s, callsign %s entered unit %s of group %s.", _playername, _callsign, _unitName, _group:GetName())
|
||||
self:T(self.lid..text)
|
||||
MESSAGE:New(text, 5):ToAllIf(self.Debug)
|
||||
|
||||
|
||||
-- Add Menu commands.
|
||||
self:_AddF10Commands(_unitName)
|
||||
|
||||
self.players[_playername]={}
|
||||
|
||||
local player=self.players[_playername] --#FMD.PlayerData
|
||||
|
||||
player.scheduler=SCHEDULER:New()
|
||||
player.unit=_unit
|
||||
player.unitname=_unitName
|
||||
player.name=_playername
|
||||
player.actype=_unit:GetTypeName()
|
||||
player.dt=0.01
|
||||
player.data={}
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Data Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Get data of unit.
|
||||
-- @param #FMD self
|
||||
-- @param Wrapper.Unit#UNIT unit
|
||||
-- @return #FMD.DataPoint Datapoint.
|
||||
function FMD:_GetDataPoint(unit)
|
||||
|
||||
-- Current coordinate.
|
||||
local coord=unit:GetCoordinate()
|
||||
|
||||
local dp={} --#FMD.DataPoint
|
||||
|
||||
dp.a={}
|
||||
dp.Alt=coord.y
|
||||
dp.AoA=unit:GetAoA()
|
||||
dp.Atot=nil
|
||||
dp.P=coord:GetPressure()
|
||||
dp.Pitch=unit:GetPitch()
|
||||
dp.Roll=unit:GetRoll()
|
||||
dp.time=timer.getAbsTime()
|
||||
dp.T=coord:GetTemperature()
|
||||
dp.v=unit:GetVelocityVec3()
|
||||
dp.Vtot=UTILS.VecNorm(dp.v)
|
||||
dp.Yaw=unit:GetYaw()
|
||||
dp.o=unit:GetOrientation()
|
||||
dp.Hdg=unit:GetHeading()
|
||||
|
||||
return dp
|
||||
end
|
||||
|
||||
--- Get data of unit.
|
||||
-- @param #FMD self
|
||||
-- @param #FMD.PlayerData playerData Player data.
|
||||
-- @return #FMD.DataPoint Datapoint.
|
||||
function FMD:_Derivative(playerData)
|
||||
|
||||
local function numderiv(fpm,fpp,h)
|
||||
return 0.5*(fpp-fpm)/h
|
||||
end
|
||||
|
||||
self:E("derivative #"..#playerData.data)
|
||||
|
||||
local datapoints=playerData.data
|
||||
local dt=playerData.dt
|
||||
|
||||
for i=2,#datapoints-1 do
|
||||
|
||||
self:E("i="..i)
|
||||
|
||||
local dpm=datapoints[i-1] --#FMD.DataPoint
|
||||
local dpp=datapoints[i+1] --#FMD.DataPoint
|
||||
local dpi=datapoints[i] --#FMD.DataPoint
|
||||
|
||||
self:E(dpm)
|
||||
self:E(dpp)
|
||||
self:E(dpi)
|
||||
|
||||
dpi.Atot=numderiv(dpm.Vtot, dpp.Vtot, dt)
|
||||
|
||||
dpi.a.x=numderiv(dpm.v.x, dpp.v.x, dt)
|
||||
dpi.a.y=numderiv(dpm.v.y, dpp.v.y, dt)
|
||||
dpi.a.z=numderiv(dpm.v.z, dpp.v.z, dt)
|
||||
|
||||
dpi.DPitch=numderiv(dpm.Pitch, dpp.Pitch, dt)
|
||||
dpi.DRoll=numderiv(dpm.Roll, dpp.Roll, dt)
|
||||
dpi.DYaw=numderiv(dpm.Yaw, dpp.Yaw, dt)
|
||||
|
||||
local ang=UTILS.VecAngle(dpm.o.x, dpp.o.x)
|
||||
--local dvpp=UTILS.VecAngle(dpi.o.x, dpp.o.x)
|
||||
|
||||
dpi.omega=numderiv(0, ang)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Record data
|
||||
-- @param #FMD self
|
||||
-- @param #FMD.PlayerData playerData Player data table.
|
||||
function FMD:_RecordData(playerData)
|
||||
|
||||
local dp=self:_GetDataPoint(playerData.unit)
|
||||
|
||||
table.insert(playerData.data, dp)
|
||||
|
||||
end
|
||||
|
||||
--- Save data.
|
||||
-- @param #FMD self
|
||||
-- @param #FMD.PlayerData playerData Player data table.
|
||||
function FMD:_SaveData(playerData)
|
||||
|
||||
-- Nothing to save.
|
||||
if playerData==nil or #playerData.data==0 or not io then
|
||||
return
|
||||
end
|
||||
|
||||
--- Function that saves data to file
|
||||
local function _savefile(filename, data)
|
||||
local f = assert(io.open(filename, "wb"))
|
||||
f:write(data)
|
||||
f:close()
|
||||
end
|
||||
|
||||
-- Set path or default.
|
||||
local path=self.savepath
|
||||
if lfs then
|
||||
path=path or lfs.writedir()
|
||||
end
|
||||
|
||||
self:E("FF save data")
|
||||
|
||||
-- Create unused file name.
|
||||
local filename=nil
|
||||
for i=1,9999 do
|
||||
|
||||
-- Create file name.
|
||||
filename=string.format("FMD-%s_%s-%04d.csv", playerData.name, playerData.actype, i)
|
||||
|
||||
-- Set path.
|
||||
if path~=nil then
|
||||
filename=path.."\\"..filename
|
||||
end
|
||||
|
||||
-- Check if file exists.
|
||||
local _exists=UTILS.FileExists(filename)
|
||||
if not _exists then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Info
|
||||
local text=string.format("Saving player %s flight data to file %s", playerData.name, filename)
|
||||
self:I(self.lid..text)
|
||||
|
||||
-- Header line
|
||||
local data="#Time,Altitude,Temperature,Pressure,Vtot,Vx,Vy,Vz,Atot,ax,ay,az,AoA,Pitch,dPitch/dt,Roll,dRoll/dt,Yaw,dYaw/dt\n"
|
||||
|
||||
local g0=playerData.data[1] --#FMD.DataPoint
|
||||
local T0=g0.time
|
||||
|
||||
-- Calculate derivatives.
|
||||
self:_Derivative(playerData)
|
||||
|
||||
self:E("looping data.")
|
||||
|
||||
for i=2,#playerData.data-1 do
|
||||
|
||||
local dp=playerData.data[i] --#FMD.DataPoint
|
||||
|
||||
self:E("i="..i)
|
||||
self:E(dp)
|
||||
|
||||
--
|
||||
local ms2mach=0.00291545
|
||||
|
||||
local t=(dp.time-T0) or 0
|
||||
local a=dp.Alt or 0
|
||||
local b=dp.T or 0
|
||||
local c=dp.P or 0
|
||||
local d=dp.Vtot or 0
|
||||
local e=dp.v.x or 0
|
||||
local f=dp.v.y or 0
|
||||
local g=dp.v.z or 0
|
||||
local h=dp.Atot or 0
|
||||
local i=dp.a.x or 0
|
||||
local j=dp.a.y or 0
|
||||
local k=dp.a.z or 0
|
||||
local l=dp.AoA or 0
|
||||
local m=dp.Pitch or 0
|
||||
local n=dp.DPitch or 0
|
||||
local o=dp.Roll or 0
|
||||
local p=dp.DRoll or 0
|
||||
local q=dp.Yaw or 0
|
||||
local r=dp.DYaw or 0
|
||||
local s=dp.omega or 0
|
||||
self:E(data)
|
||||
self:E(t)
|
||||
self:E(a)
|
||||
self:E(b)
|
||||
self:E(c)
|
||||
self:E(d)
|
||||
self:E(e)
|
||||
self:E(f)
|
||||
self:E(g)
|
||||
self:E(h)
|
||||
self:E(i)
|
||||
self:E(j)
|
||||
self:E(k)
|
||||
self:E(l)
|
||||
self:E(m)
|
||||
self:E(n)
|
||||
self:E(o)
|
||||
self:E(p)
|
||||
self:E(q)
|
||||
self:E(r)
|
||||
self:E(s)
|
||||
--self:E(u)
|
||||
-- t a b c d e f g h i j k l m n o p q r s
|
||||
data=data..string.format("%.2f,%.2f,%.2f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f \n",
|
||||
t, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s)
|
||||
--data=data..string.format("%.2f\n",t)
|
||||
self:E(data)
|
||||
end
|
||||
|
||||
-- Save file.
|
||||
_savefile(filename, data)
|
||||
|
||||
-- Clear data.
|
||||
playerData.data={}
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- RADIO MENU Functions
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Add menu commands for player.
|
||||
-- @param #FMD self
|
||||
-- @param #string _unitName Name of player unit.
|
||||
function FMD:_AddF10Commands(_unitName)
|
||||
self:F(_unitName)
|
||||
|
||||
-- Get player unit and name.
|
||||
local _unit, playername = self:_GetPlayerUnitAndName(_unitName)
|
||||
|
||||
-- Check for player unit.
|
||||
if _unit and playername then
|
||||
|
||||
-- Get group and ID.
|
||||
local group=_unit:GetGroup()
|
||||
local gid=group:GetID()
|
||||
|
||||
if group and gid then
|
||||
|
||||
if not self.menuadded[gid] then
|
||||
|
||||
-- Enable switch so we don't do this twice.
|
||||
self.menuadded[gid]=true
|
||||
|
||||
-- Set menu root path.
|
||||
local _rootPath=nil
|
||||
if FMD.MenuF10Root then
|
||||
------------------------
|
||||
-- MISSON LEVEL MENUE --
|
||||
------------------------
|
||||
|
||||
-- F10/FMD/...
|
||||
_rootPath=FMD.MenuF10Root
|
||||
|
||||
else
|
||||
------------------------
|
||||
-- GROUP LEVEL MENUES --
|
||||
------------------------
|
||||
|
||||
-- Main F10 menu: F10/FMD/
|
||||
if FMD.MenuF10[gid]==nil then
|
||||
FMD.MenuF10[gid]=missionCommands.addSubMenuForGroup(gid, "FMD")
|
||||
end
|
||||
|
||||
|
||||
-- F10/FMD/...
|
||||
_rootPath=FMD.MenuF10[gid]
|
||||
|
||||
end
|
||||
|
||||
--------------------------------
|
||||
-- F10/FMD/<Carrier>/F1 Help
|
||||
--------------------------------
|
||||
|
||||
-- F10/FMD/Start Recording
|
||||
missionCommands.addCommandForGroup(gid, "Start Recording", _rootPath, self._StartRecording, self, _unitName) -- F1
|
||||
missionCommands.addCommandForGroup(gid, "Stop Recording", _rootPath, self._StopRecording, self, _unitName) -- F2
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Start data recording.
|
||||
-- @param #FMD self
|
||||
-- @param #string _unitName Name of player unit.
|
||||
function FMD:_StartRecording(_unitName)
|
||||
|
||||
-- Get player unit and name.
|
||||
local _unit, _playername = self:_GetPlayerUnitAndName(_unitName)
|
||||
|
||||
-- Check if we have a unit which is a player.
|
||||
if _unit and _playername then
|
||||
local playerData=self.players[_playername] --#FMD.PlayerData
|
||||
|
||||
if playerData then
|
||||
|
||||
local delay=3
|
||||
|
||||
MESSAGE:New(string.format("Flight data recording will be started in %d seconds. dt=%.3f sec", delay, playerData.dt)):ToAll()
|
||||
|
||||
playerData.SID=playerData.scheduler:Schedule(nil, self._RecordData, {self, playerData}, delay, playerData.dt)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Stop data recording.
|
||||
-- @param #FMD self
|
||||
-- @param #string _unitName Name of player unit.
|
||||
function FMD:_StopRecording(_unitName)
|
||||
|
||||
-- Get player unit and name.
|
||||
local _unit, _playername = self:_GetPlayerUnitAndName(_unitName)
|
||||
|
||||
-- Check if we have a unit which is a player.
|
||||
if _unit and _playername then
|
||||
local playerData=self.players[_playername] --#FMD.PlayerData
|
||||
|
||||
if playerData then
|
||||
|
||||
-- Stop scheduler.
|
||||
playerData.scheduler:Stop(playerData.SID)
|
||||
|
||||
-- Save data.
|
||||
self:_SaveData(playerData)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned.
|
||||
-- @param #FMD self
|
||||
-- @param #string _unitName Name of the player unit.
|
||||
-- @return Wrapper.Unit#UNIT Unit of player or nil.
|
||||
-- @return #string Name of the player or nil.
|
||||
function FMD:_GetPlayerUnitAndName(_unitName)
|
||||
self:F2(_unitName)
|
||||
|
||||
if _unitName ~= nil then
|
||||
|
||||
-- Get DCS unit from its name.
|
||||
local DCSunit=Unit.getByName(_unitName)
|
||||
|
||||
if DCSunit then
|
||||
|
||||
local playername=DCSunit:getPlayerName()
|
||||
local unit=UNIT:Find(DCSunit)
|
||||
|
||||
self:T2({DCSunit=DCSunit, unit=unit, playername=playername})
|
||||
if DCSunit and unit and playername then
|
||||
return unit, playername
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Return nil if we could not find a player.
|
||||
return nil,nil
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,724 @@
|
||||
--- **Functional** - (R2.5) - Yet another missile trainer.
|
||||
--
|
||||
--
|
||||
-- Train to evade missiles without being destroyed.
|
||||
--
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Adaptive update of missile-to-player distance.
|
||||
-- * F10 radio menu.
|
||||
-- * Easy to use.
|
||||
-- * Handles air-to-air and surface-to-air missiles.
|
||||
-- * Alert on missile launch (optional).
|
||||
-- * Marker of missile launch position (optional).
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
-- @module Functional.FOX2
|
||||
-- @image Functional_FOX2.png
|
||||
|
||||
|
||||
--- FOX2 class.
|
||||
-- @type FOX2
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #boolean Debug Debug mode. Messages to all about status.
|
||||
-- @field #string lid Class id string for output to DCS log file.
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- Fox 2!
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- # The FOX2 Concept
|
||||
--
|
||||
--
|
||||
--
|
||||
-- @field #FOX2
|
||||
FOX2 = {
|
||||
ClassName = "FOX2",
|
||||
Debug = false,
|
||||
lid = nil,
|
||||
menuadded = {},
|
||||
missiles = {},
|
||||
players = {},
|
||||
destroy = nil,
|
||||
safezones = {},
|
||||
launchzones = {},
|
||||
exlosionpower = 5,
|
||||
}
|
||||
|
||||
|
||||
--- Player data table holding all important parameters of each player.
|
||||
-- @type FOX2.PlayerData
|
||||
-- @field Wrapper.Unit#UNIT unit Aircraft of the player.
|
||||
-- @field #string unitname Name of the unit.
|
||||
-- @field Wrapper.Client#CLIENT client Client object of player.
|
||||
-- @field #string callsign Callsign of player.
|
||||
-- @field Wrapper.Group#GROUP group Aircraft group of player.
|
||||
-- @field #string groupname Name of the the player aircraft group.
|
||||
-- @field #string name Player name.
|
||||
-- @field #number coalition Coalition number of player.
|
||||
-- @field #boolean destroy Destroy missile.
|
||||
-- @field #boolean launchalert Alert player on detected missile launch.
|
||||
-- @field #boolean marklaunch Mark position of launched missile on F10 map.
|
||||
-- @field #number defeated Number of missiles defeated.
|
||||
-- @field #number dead Number of missiles not defeated.
|
||||
|
||||
--- Missile data
|
||||
-- @type FOX2.MissileData
|
||||
-- @field #string missiletype Type of missile.
|
||||
-- @field Wrapper.Unit#UNIT shooterunit Unit that shot the missile.
|
||||
-- @field Wrapper.Group#GROUP shootergroup Group that shot the missile.
|
||||
-- @field #number shottime Abs mission time in seconds the missile was fired.
|
||||
-- @field Wrapper.Unit#UNIT targetunit Unit that was targeted.
|
||||
|
||||
|
||||
--- Main radio menu on group level.
|
||||
-- @field #table MenuF10 Root menu table on group level.
|
||||
FOX2.MenuF10={}
|
||||
|
||||
--- Main radio menu on mission level.
|
||||
-- @field #table MenuF10Root Root menu on mission level.
|
||||
FOX2.MenuF10Root=nil
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- ToDo list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO list:
|
||||
-- TODO: safe zones
|
||||
-- TODO: mark shooter on F10
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new FOX2 class object.
|
||||
-- @param #FOX2 self
|
||||
-- @return #FOX2 self.
|
||||
function FOX2:New()
|
||||
|
||||
self.lid="FOX2 | "
|
||||
|
||||
-- Inherit everthing from FSM class.
|
||||
local self=BASE:Inherit(self, FSM:New()) -- #FOX2
|
||||
|
||||
-- Start State.
|
||||
self:SetStartState("Stopped")
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:AddTransition("Stopped", "Start", "Running") -- Start FOX2 script.
|
||||
self:AddTransition("*", "Status", "*") -- Start FOX2 script.
|
||||
|
||||
------------------------
|
||||
--- Pseudo Functions ---
|
||||
------------------------
|
||||
|
||||
--- Triggers the FSM event "Start". Starts the FOX2. Initializes parameters and starts event handlers.
|
||||
-- @function [parent=#FOX2] Start
|
||||
-- @param #FOX2 self
|
||||
|
||||
--- Triggers the FSM event "Start" after a delay. Starts the FOX2. Initializes parameters and starts event handlers.
|
||||
-- @function [parent=#FOX2] __Start
|
||||
-- @param #FOX2 self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- Triggers the FSM event "Stop". Stops the FOX2 and all its event handlers.
|
||||
-- @param #FOX2 self
|
||||
|
||||
--- Triggers the FSM event "Stop" after a delay. Stops the FOX2 and all its event handlers.
|
||||
-- @function [parent=#FOX2] __Stop
|
||||
-- @param #FOX2 self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- Triggers the FSM event "Status".
|
||||
-- @function [parent=#FOX2] Status
|
||||
-- @param #FOX2 self
|
||||
|
||||
--- Triggers the FSM event "Status" after a delay.
|
||||
-- @function [parent=#FOX2] __Status
|
||||
-- @param #FOX2 self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- On after Start event. Starts the missile trainer and adds event handlers.
|
||||
-- @param #FOX2 self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function FOX2:onafterStart(From, Event, To)
|
||||
|
||||
-- Short info.
|
||||
local text=string.format("Starting FOX2 Missile Trainer v0.0.1")
|
||||
env.info(text)
|
||||
|
||||
-- Handle events:
|
||||
self:HandleEvent(EVENTS.Birth)
|
||||
self:HandleEvent(EVENTS.Shot)
|
||||
|
||||
self:TraceClass(self.ClassName)
|
||||
self:TraceLevel(2)
|
||||
|
||||
self:__Status(-1)
|
||||
end
|
||||
|
||||
--- On after Stop event. Stops the missile trainer and unhandles events.
|
||||
-- @param #FOX2 self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function FOX2:onafterStop(From, Event, To)
|
||||
|
||||
-- Short info.
|
||||
local text=string.format("Stopping FOX2 Missile Trainer v0.0.1")
|
||||
env.info(text)
|
||||
|
||||
-- Handle events:
|
||||
self:UnhandleEvent(EVENTS.Birth)
|
||||
self:UnhandleEvent(EVENTS.Shot)
|
||||
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Status Functions
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Check spawn queue and spawn aircraft if necessary.
|
||||
-- @param #FOX2 self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function FOX2:onafterStatus(From, Event, To)
|
||||
|
||||
self:I(self.lid..string.format("Missile trainer status."))
|
||||
|
||||
self:__Status(-30)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Event Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- FOX2 event handler for event birth.
|
||||
-- @param #FOX2 self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function FOX2:OnEventBirth(EventData)
|
||||
self:F3({eventbirth = EventData})
|
||||
|
||||
-- Nil checks.
|
||||
if EventData==nil then
|
||||
self:E(self.lid.."ERROR: EventData=nil in event BIRTH!")
|
||||
self:E(EventData)
|
||||
return
|
||||
end
|
||||
if EventData.IniUnit==nil then
|
||||
self:E(self.lid.."ERROR: EventData.IniUnit=nil in event BIRTH!")
|
||||
self:E(EventData)
|
||||
return
|
||||
end
|
||||
|
||||
-- Player unit and name.
|
||||
local _unitName=EventData.IniUnitName
|
||||
local playerunit, playername=self:_GetPlayerUnitAndName(_unitName)
|
||||
|
||||
-- Debug info.
|
||||
self:T(self.lid.."BIRTH: unit = "..tostring(EventData.IniUnitName))
|
||||
self:T(self.lid.."BIRTH: group = "..tostring(EventData.IniGroupName))
|
||||
self:T(self.lid.."BIRTH: player = "..tostring(playername))
|
||||
|
||||
-- Check if player entered.
|
||||
if playerunit and playername then
|
||||
|
||||
local _uid=playerunit:GetID()
|
||||
local _group=playerunit:GetGroup()
|
||||
local _callsign=playerunit:GetCallsign()
|
||||
|
||||
-- Debug output.
|
||||
local text=string.format("Pilot %s, callsign %s entered unit %s of group %s.", playername, _callsign, _unitName, _group:GetName())
|
||||
self:T(self.lid..text)
|
||||
MESSAGE:New(text, 5):ToAllIf(self.Debug)
|
||||
|
||||
-- Add Menu commands.
|
||||
self:_AddF10Commands(_unitName)
|
||||
|
||||
-- Player data.
|
||||
local playerData={} --#FOX2.PlayerData
|
||||
|
||||
-- Player unit, client and callsign.
|
||||
playerData.unit = playerunit
|
||||
playerData.unitname = _unitName
|
||||
playerData.group = _group
|
||||
playerData.groupname = _group:GetName()
|
||||
playerData.name = playername
|
||||
playerData.callsign = playerData.unit:GetCallsign()
|
||||
playerData.client = CLIENT:FindByName(_unitName, nil, true)
|
||||
playerData.coalition = _group:GetCoalition()
|
||||
|
||||
playerData.destroy=playerData.destroy or true
|
||||
playerData.launchalert=playerData.launchalert or true
|
||||
playerData.marklaunch=playerData.marklaunch or true
|
||||
|
||||
playerData.defeated=playerData.defeated or 0
|
||||
playerData.dead=playerData.dead or 0
|
||||
|
||||
-- Init player data.
|
||||
self.players[playername]=playerData
|
||||
|
||||
-- Init player grades table if necessary.
|
||||
--self.playerscores[playername]=self.playerscores[playername] or {}
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
--- FOX2 event handler for event shot (when a unit releases a rocket or bomb (but not a fast firing gun).
|
||||
-- @param #FOX2 self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function FOX2:OnEventShot(EventData)
|
||||
self:I({eventshot = EventData})
|
||||
|
||||
if EventData.Weapon==nil then
|
||||
return
|
||||
end
|
||||
if EventData.IniDCSUnit==nil then
|
||||
return
|
||||
end
|
||||
|
||||
-- Weapon data.
|
||||
local _weapon = EventData.WeaponName
|
||||
local _target = EventData.Weapon:getTarget()
|
||||
local _targetName = "unknown"
|
||||
local _targetUnit = nil --Wrapper.Unit#UNIT
|
||||
|
||||
-- Weapon descriptor.
|
||||
local desc=EventData.Weapon:getDesc()
|
||||
--self:E({desc=desc})
|
||||
|
||||
-- Weapon category: 0=Shell, 1=Missile, 2=Rocket, 3=BOMB
|
||||
local weaponcategory=desc.category
|
||||
|
||||
-- Missile category=
|
||||
local missilecategory=desc.missileCategory
|
||||
|
||||
-- Debug info.
|
||||
self:E(FOX2.lid.."EVENT SHOT: FOX2")
|
||||
self:E(FOX2.lid..string.format("EVENT SHOT: Ini unit = %s", tostring(EventData.IniUnitName)))
|
||||
self:E(FOX2.lid..string.format("EVENT SHOT: Ini group = %s", tostring(EventData.IniGroupName)))
|
||||
self:E(FOX2.lid..string.format("EVENT SHOT: Weapon type = %s", tostring(_weapon)))
|
||||
self:E(FOX2.lid..string.format("EVENT SHOT: Weapon cate = %s", tostring(weaponcategory)))
|
||||
self:E(FOX2.lid..string.format("EVENT SHOT: Missil cate = %s", tostring(missilecategory)))
|
||||
if _target then
|
||||
self:E({target=_target})
|
||||
--_targetName=Unit.getName(_target)
|
||||
--_targetUnit=UNIT:FindByName(_targetName)
|
||||
_targetUnit=UNIT:Find(_target)
|
||||
end
|
||||
self:E(FOX2.lid..string.format("EVENT SHOT: Target name = %s", tostring(_targetName)))
|
||||
|
||||
-- Track missiles of type AAM=1, SAM=2 or OTHER=6
|
||||
local _track = weaponcategory==1 and missilecategory and (missilecategory==1 or missilecategory==2 or missilecategory==6)
|
||||
|
||||
-- Get shooter.
|
||||
local shooterUnit = EventData.IniUnit
|
||||
local shooterName = EventData.IniUnitName
|
||||
local shooterCoalition=shooterUnit:GetCoalition()
|
||||
local shooterCoord=shooterUnit:GetCoordinate()
|
||||
|
||||
-- Only track missiles
|
||||
if _track then
|
||||
|
||||
local missile={} --#FOX2.MissileData
|
||||
|
||||
--missile.missiletype=_weapontype
|
||||
|
||||
-- Tracking info and init of last bomb position.
|
||||
self:I(FOX2.lid..string.format("FOX2: Tracking %s - %s.", _weapon, EventData.weapon:getName()))
|
||||
|
||||
-- Loop over players.
|
||||
for _,_player in pairs(self.players) do
|
||||
local player=_player --#FOX2.PlayerData
|
||||
|
||||
-- Player position.
|
||||
local playerUnit=player.unit
|
||||
|
||||
if playerUnit and playerUnit:IsAlive() then
|
||||
|
||||
local distance=playerUnit:GetCoordinate():Get3DDistance(shooterCoord)
|
||||
local bearing=playerUnit:GetCoordinate():HeadingTo(shooterCoord)
|
||||
|
||||
if _targetUnit and player.launchalert and player.coalition~=shooterCoalition then
|
||||
|
||||
-- Inform players.
|
||||
local text=string.format("Missile launch detected! Distance %.1f NM, bearing %03d°.", UTILS.MetersToNM(distance), bearing)
|
||||
MESSAGE:New(text, 5, "ALERT"):ToClient(player.client)
|
||||
|
||||
if player.marklaunch then
|
||||
local text=string.format("Missile launch coordinates:\n%s\n%s", shooterCoord:ToStringLLDMS(), shooterCoord:ToStringBULLS(player.coalition))
|
||||
shooterCoord:MarkToGroup(text, player.group)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Init missile position.
|
||||
local _lastBombPos = {x=0,y=0,z=0}
|
||||
|
||||
-- Target unit of the missile.
|
||||
local target=nil --Wrapper.Unit#UNIT
|
||||
|
||||
--- Function monitoring the position of a bomb until impact.
|
||||
local function trackMissile(_ordnance)
|
||||
|
||||
-- When the pcall returns a failure the weapon has hit.
|
||||
local _status,_bombPos = pcall(
|
||||
function()
|
||||
return _ordnance:getPoint()
|
||||
end)
|
||||
|
||||
--self:T3(FOX2.lid..string.format("FOX2: Missile still in air: %s", tostring(_status)))
|
||||
if _status then
|
||||
|
||||
----------------------------------------------
|
||||
-- Still in the air. Remember this position --
|
||||
----------------------------------------------
|
||||
|
||||
-- Missile position.
|
||||
_lastBombPos = {x=_bombPos.x, y=_bombPos.y, z=_bombPos.z}
|
||||
|
||||
-- Missile coordinate.
|
||||
local missileCoord=COORDINATE:NewFromVec3(_lastBombPos)
|
||||
|
||||
-- Missile velocity in m/s.
|
||||
local missileVelocity=UTILS.VecNorm(_ordnance:getVelocity())
|
||||
|
||||
if _targetUnit then
|
||||
-----------------------------------
|
||||
-- Missile has a specific target --
|
||||
-----------------------------------
|
||||
|
||||
target=_targetUnit
|
||||
|
||||
else
|
||||
|
||||
-- Distance to closest player.
|
||||
local mindist=nil
|
||||
|
||||
-- Loop over players.
|
||||
for _,_player in pairs(self.players) do
|
||||
local player=_player --#FOX2.PlayerData
|
||||
|
||||
-- Player position.
|
||||
local playerCoord=player.unit:GetCoordinate()
|
||||
|
||||
-- Distance.
|
||||
local dist=missileCoord:Get3DDistance(playerCoord)
|
||||
|
||||
-- Update mindist if necessary.
|
||||
if mindist==nil or dist<mindist then
|
||||
mindist=dist
|
||||
target=player.unit
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Check if missile has a valid target.
|
||||
if target then
|
||||
|
||||
local targetCoord=target:GetCoordinate()
|
||||
|
||||
local distance=missileCoord:Get3DDistance(targetCoord)
|
||||
local bearing=targetCoord:HeadingTo(missileCoord)
|
||||
local eta=distance/missileVelocity
|
||||
|
||||
self:T2(self.lid..string.format("Distance = %.1f m, v=%.1f m/s, bearing=%03d°, eta=%.1f sec", distance, missileVelocity, bearing, eta))
|
||||
|
||||
if distance<100 then
|
||||
|
||||
-- Destroy missile.
|
||||
self:T(self.lid..string.format("Destroying missile at distance %.1f m", distance))
|
||||
_ordnance:destroy()
|
||||
|
||||
-- Little explosion for the visual effect.
|
||||
missileCoord:Explosion(10)
|
||||
|
||||
local text="Destroying missile. You're dead!"
|
||||
MESSAGE:New(text, 10):ToGroup(target:GetGroup())
|
||||
|
||||
-- Terminate timer.
|
||||
return nil
|
||||
else
|
||||
|
||||
-- Time step.
|
||||
local dt=1.0
|
||||
if distance>50000 then
|
||||
-- > 50 km
|
||||
dt=5.0
|
||||
elseif distance>10000 then
|
||||
-- 10-50 km
|
||||
dt=1.0
|
||||
elseif distance>5000 then
|
||||
-- 5-10 km
|
||||
dt=0.5
|
||||
elseif distance>1000 then
|
||||
-- 1-5 km
|
||||
dt=0.1
|
||||
else
|
||||
-- < 1 km
|
||||
dt=0.01
|
||||
end
|
||||
|
||||
-- Check again in dt seconds.
|
||||
return timer.getTime()+dt
|
||||
end
|
||||
else
|
||||
|
||||
-- No target ==> terminate timer.
|
||||
return nil
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
-------------------------------------
|
||||
-- Missile does not exist any more --
|
||||
-------------------------------------
|
||||
|
||||
if target then
|
||||
local player=self:_GetPlayerFromUnitname(target:GetName())
|
||||
if player then
|
||||
local text=string.format("Missile defeated. Well done, %s!", player.name)
|
||||
MESSAGE:New(text, 10):ToClient(player.client)
|
||||
end
|
||||
end
|
||||
|
||||
--Terminate the timer.
|
||||
self:T(FOX2.lid..string.format("Terminating missile track timer."))
|
||||
return nil
|
||||
|
||||
end -- _status check
|
||||
|
||||
end -- end function trackBomb
|
||||
|
||||
-- Weapon is not yet "alife" just yet. Start timer with a little delay.
|
||||
self:T(FOX2.lid..string.format("Tracking of missile starts in 0.1 seconds."))
|
||||
timer.scheduleFunction(trackMissile, EventData.weapon, timer.getTime()+0.1)
|
||||
|
||||
end --if _track
|
||||
|
||||
end
|
||||
|
||||
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- RADIO MENU Functions
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Add menu commands for player.
|
||||
-- @param #FOX2 self
|
||||
-- @param #string _unitName Name of player unit.
|
||||
function FOX2:_AddF10Commands(_unitName)
|
||||
self:F(_unitName)
|
||||
|
||||
-- Get player unit and name.
|
||||
local _unit, playername = self:_GetPlayerUnitAndName(_unitName)
|
||||
|
||||
-- Check for player unit.
|
||||
if _unit and playername then
|
||||
|
||||
-- Get group and ID.
|
||||
local group=_unit:GetGroup()
|
||||
local gid=group:GetID()
|
||||
|
||||
if group and gid then
|
||||
|
||||
if not self.menuadded[gid] then
|
||||
|
||||
-- Enable switch so we don't do this twice.
|
||||
self.menuadded[gid]=true
|
||||
|
||||
-- Set menu root path.
|
||||
local _rootPath=nil
|
||||
if FOX2.MenuF10Root then
|
||||
------------------------
|
||||
-- MISSON LEVEL MENUE --
|
||||
------------------------
|
||||
|
||||
-- F10/FOX2/...
|
||||
_rootPath=FOX2.MenuF10Root
|
||||
|
||||
else
|
||||
------------------------
|
||||
-- GROUP LEVEL MENUES --
|
||||
------------------------
|
||||
|
||||
-- Main F10 menu: F10/FOX2/
|
||||
if FOX2.MenuF10[gid]==nil then
|
||||
FOX2.MenuF10[gid]=missionCommands.addSubMenuForGroup(gid, "FOX2")
|
||||
end
|
||||
|
||||
-- F10/FOX2/...
|
||||
_rootPath=FOX2.MenuF10[gid]
|
||||
|
||||
end
|
||||
|
||||
|
||||
--------------------------------
|
||||
-- F10/F<X> FOX2/F1 Help
|
||||
--------------------------------
|
||||
local _helpPath=missionCommands.addSubMenuForGroup(gid, "Help", _rootPath)
|
||||
-- F10/FOX2/F1 Help/
|
||||
--missionCommands.addCommandForGroup(gid, "Subtitles On/Off", _helpPath, self._SubtitlesOnOff, self, _unitName) -- F7
|
||||
--missionCommands.addCommandForGroup(gid, "Trapsheet On/Off", _helpPath, self._TrapsheetOnOff, self, _unitName) -- F8
|
||||
|
||||
-------------------------
|
||||
-- F10/F<X> FOX2/
|
||||
-------------------------
|
||||
|
||||
missionCommands.addCommandForGroup(gid, "Launch Alerts On/Off", _rootPath, self._ToggleLaunchAlert, self, _unitName) -- F2
|
||||
missionCommands.addCommandForGroup(gid, "Destroy Missiles On/Off", _rootPath, self._ToggleDestroyMissiles, self, _unitName) -- F3
|
||||
|
||||
end
|
||||
else
|
||||
self:E(self.lid..string.format("ERROR: Could not find group or group ID in AddF10Menu() function. Unit name: %s.", _unitName))
|
||||
end
|
||||
else
|
||||
self:E(self.lid..string.format("ERROR: Player unit does not exist in AddF10Menu() function. Unit name: %s.", _unitName))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Turn player's launch alert on/off.
|
||||
-- @param #FOX2 self
|
||||
-- @param #string _unitname Name of the player unit.
|
||||
function FOX2:_ToggleLaunchAlert(_unitname)
|
||||
self:F2(_unitname)
|
||||
|
||||
-- Get player unit and player name.
|
||||
local unit, playername = self:_GetPlayerUnitAndName(_unitname)
|
||||
|
||||
-- Check if we have a player.
|
||||
if unit and playername then
|
||||
|
||||
-- Player data.
|
||||
local playerData=self.players[playername] --#FOX2.PlayerData
|
||||
|
||||
if playerData then
|
||||
|
||||
-- Invert state.
|
||||
playerData.launchalert=not playerData.launchalert
|
||||
|
||||
-- Inform player.
|
||||
local text=""
|
||||
if playerData.launchalert==true then
|
||||
text=string.format("%s, missile launch alerts are now ENABLED.", playerData.name)
|
||||
else
|
||||
text=string.format("%s, missile launch alerts are now DISABLED.", playerData.name)
|
||||
end
|
||||
MESSAGE:New(text, 5):ToClient(playerData.client)
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Turn player's
|
||||
-- @param #FOX2 self
|
||||
-- @param #string _unitname Name of the player unit.
|
||||
function FOX2:_ToggleDestroyMissiles(_unitname)
|
||||
self:F2(_unitname)
|
||||
|
||||
-- Get player unit and player name.
|
||||
local unit, playername = self:_GetPlayerUnitAndName(_unitname)
|
||||
|
||||
-- Check if we have a player.
|
||||
if unit and playername then
|
||||
|
||||
-- Player data.
|
||||
local playerData=self.players[playername] --#FOX2.PlayerData
|
||||
|
||||
if playerData then
|
||||
|
||||
-- Invert state.
|
||||
playerData.destroy=not playerData.destroy
|
||||
|
||||
-- Inform player.
|
||||
local text=""
|
||||
if playerData.destroy==true then
|
||||
text=string.format("%s, incoming missiles will be DESTROYED.", playerData.name)
|
||||
else
|
||||
text=string.format("%s, incoming missiles will NOT be DESTROYED.", playerData.name)
|
||||
end
|
||||
MESSAGE:New(text, 5):ToClient(playerData.client)
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Misc Functions
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned.
|
||||
-- @param #FOX2 self
|
||||
-- @param #string unitName Name of the unit.
|
||||
-- @return #FOX2.PlayerData Player data.
|
||||
function FOX2:_GetPlayerFromUnitname(unitName)
|
||||
|
||||
for _,_player in pairs(self.players) do
|
||||
local player=_player --#FOX2.PlayerData
|
||||
|
||||
if player.unitname==unitName then
|
||||
return player
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned.
|
||||
-- @param #FOX2 self
|
||||
-- @param #string _unitName Name of the player unit.
|
||||
-- @return Wrapper.Unit#UNIT Unit of player or nil.
|
||||
-- @return #string Name of the player or nil.
|
||||
function FOX2:_GetPlayerUnitAndName(_unitName)
|
||||
self:F2(_unitName)
|
||||
|
||||
if _unitName ~= nil then
|
||||
|
||||
-- Get DCS unit from its name.
|
||||
local DCSunit=Unit.getByName(_unitName)
|
||||
|
||||
if DCSunit then
|
||||
|
||||
-- Get player name if any.
|
||||
local playername=DCSunit:getPlayerName()
|
||||
|
||||
-- Unit object.
|
||||
local unit=UNIT:Find(DCSunit)
|
||||
|
||||
-- Debug.
|
||||
self:T2({DCSunit=DCSunit, unit=unit, playername=playername})
|
||||
|
||||
-- Check if enverything is there.
|
||||
if DCSunit and unit and playername then
|
||||
self:T(self.lid..string.format("Found DCS unit %s with player %s.", tostring(_unitName), tostring(playername)))
|
||||
return unit, playername
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Return nil if we could not find a player.
|
||||
return nil,nil
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -0,0 +1,793 @@
|
||||
--- **Ops** - (R2.5) - Random Air Traffic.
|
||||
--
|
||||
--
|
||||
--
|
||||
-- RAT2 creates random air traffic on the map.
|
||||
--
|
||||
--
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * It's very random.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
-- @module Functional.Rat2
|
||||
-- @image Functional_Rat2.png
|
||||
|
||||
|
||||
--- RAT2 class.
|
||||
-- @type RAT2
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #boolean Debug Debug mode. Messages to all about status.
|
||||
-- @field #string lid Class id string for output to DCS log file.
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- Be surprised!
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- # The RAT2 Concept
|
||||
--
|
||||
--
|
||||
--
|
||||
-- @field #RAT2
|
||||
RAT2 = {
|
||||
ClassName = "RAT2",
|
||||
Debug = false,
|
||||
lid = nil,
|
||||
}
|
||||
|
||||
--- Aircraft types capable of landing on carrier (human+AI).
|
||||
-- @type RAT2.Ratcraft
|
||||
-- @field Functional.RatCraft#RATCRAFT ratcraft RAT aircraft group.
|
||||
-- @field #number ntot Total number of alive aircraft groups alive.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new RAT2 class object.
|
||||
-- @param #RAT2 self
|
||||
-- @return #RAT2 self.
|
||||
function RAT2:New()
|
||||
|
||||
-- Inherit everthing from FSM class.
|
||||
local self=BASE:Inherit(self, FSM:New()) -- #RAT2
|
||||
|
||||
|
||||
-- Start State.
|
||||
self:SetStartState("Stopped")
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:AddTransition("Stopped", "Load", "Stopped") -- Load player scores from file.
|
||||
self:AddTransition("Stopped", "Start", "Running") -- Start RAT2 script.
|
||||
self:AddTransition("*", "Status", "*") -- Start RAT2 script.
|
||||
|
||||
end
|
||||
|
||||
--- Create a new RAT2 class object.
|
||||
-- @param #RAT2 self
|
||||
-- @return #RAT2 self.
|
||||
function RAT2:AddAircraft(template, n)
|
||||
|
||||
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Status Functions
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Check spawn queue and spawn aircraft if necessary.
|
||||
-- @param #RAT2 self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function RAT2:onafterStatus(From, Event, To)
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Check spawn queue and spawn aircraft if necessary.
|
||||
-- @param #RAT2 self
|
||||
function RAT2:_CheckQueue()
|
||||
|
||||
for i,_ratcraft in pairs(self.Qspawn) do
|
||||
local ratcraft=_ratcraft --Functional.RatCraft#RATCRAFT
|
||||
|
||||
--- Check if
|
||||
-- Time has passed.
|
||||
-- Already enough aircraft are alive
|
||||
|
||||
local nalive=ratcraft:_GetAliveGroups()
|
||||
|
||||
if nalive<ratcraft.ntot then
|
||||
|
||||
-- Try to spawn a ratcraft group.
|
||||
local spawned=self:_TrySpawnRatcraft(ratcraft)
|
||||
|
||||
-- Remove queue item and break loop.
|
||||
if spawned then
|
||||
table.remove(self.Qspawn, i)
|
||||
break
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Flightplan functions
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Calculate the maximum height an aircraft can reach for the given parameters.
|
||||
-- @param #RAT2 self
|
||||
-- @param #number D Total distance in meters from Departure to holding point at destination.
|
||||
-- @param #number alphaC Climb angle in rad.
|
||||
-- @param #number alphaD Descent angle in rad.
|
||||
-- @param #number Hdep AGL altitude of departure point.
|
||||
-- @param #number Hdest AGL altitude of destination point.
|
||||
-- @param #number Deltahhold Relative altitude of holding point above destination.
|
||||
-- @return #number Maximum height the aircraft can reach.
|
||||
function RAT2:_GetMaxHeight(D, alphaC, alphaD, Hdep, Hdest, Deltahhold)
|
||||
|
||||
local Hhold=Hdest+Deltahhold
|
||||
local hdest=Hdest-Hdep
|
||||
local hhold=hdest+Deltahhold
|
||||
|
||||
local Dp=math.sqrt(D^2 + hhold^2)
|
||||
|
||||
local alphaS=math.atan(hdest/D) -- slope angle
|
||||
local alphaH=math.atan(hhold/D) -- angle to holding point (could be necative!)
|
||||
|
||||
local alphaCp=alphaC-alphaH -- climb angle with slope
|
||||
local alphaDp=alphaD+alphaH -- descent angle with slope
|
||||
|
||||
-- ASA triangle.
|
||||
local gammap=math.pi-alphaCp-alphaDp
|
||||
local sCp=Dp*math.sin(alphaDp)/math.sin(gammap)
|
||||
local sDp=Dp*math.sin(alphaCp)/math.sin(gammap)
|
||||
|
||||
-- Max height from departure.
|
||||
local hmax=sCp*math.sin(alphaC)
|
||||
|
||||
return hmax
|
||||
end
|
||||
|
||||
|
||||
--- Make a flight plan from a departure to a destination airport.
|
||||
-- @param #RAT2 self
|
||||
-- @param Functional.RatCraft#RATCRAFT ratcraft Ratcraft object.
|
||||
-- @param Wrapper.Airbase#AIRBASE departure Departure airbase.
|
||||
-- @param Wrapper.Airbase#AIRBASE destination Destination airbase.
|
||||
-- @return #table Table of flightplan waypoints.
|
||||
-- @return #table Table of flightplan coordinates.
|
||||
function RAT2:_GetFlightplan(ratcraft, departure, destination)
|
||||
|
||||
-- Parameters in SI units (m/s, m).
|
||||
local Vmax=ratcraft.speedmax/3.6
|
||||
local Range=ratcraft.range
|
||||
local category=ratcraft.category
|
||||
local ceiling=ratcraft.DCSdesc.Hmax
|
||||
local Vymax=ratcraft.DCSdesc.VyMax
|
||||
|
||||
-- Max cruise speed 90% of max speed.
|
||||
local VxCruiseMax=0.90*Vmax
|
||||
|
||||
-- Min cruise speed 70% of max cruise or 600 km/h whichever is lower.
|
||||
local VxCruiseMin = math.min(VxCruiseMax*0.70, 166)
|
||||
|
||||
-- Cruise speed (randomized). Expectation value at midpoint between min and max.
|
||||
local VxCruise = UTILS.RandomGaussian((VxCruiseMax-VxCruiseMin)/2+VxCruiseMin, (VxCruiseMax-VxCruiseMax)/4, VxCruiseMin, VxCruiseMax)
|
||||
|
||||
-- Climb speed 90% ov Vmax but max 720 km/h.
|
||||
local VxClimb = math.min(Vmax*0.90, 200)
|
||||
|
||||
-- Descent speed 60% of Vmax but max 500 km/h.
|
||||
local VxDescent = math.min(Vmax*0.60, 140)
|
||||
|
||||
-- Holding speed is 90% of descent speed.
|
||||
local VxHolding = VxDescent*0.9
|
||||
|
||||
-- Final leg is 90% of holding speed.
|
||||
local VxFinal = VxHolding*0.9
|
||||
|
||||
-- Reasonably civil climb speed Vy=1500 ft/min = 7.6 m/s but max aircraft specific climb rate.
|
||||
local VyClimb=math.min(7.6, Vymax)
|
||||
|
||||
-- Climb angle in rad.
|
||||
--local AlphaClimb=math.asin(VyClimb/VxClimb)
|
||||
local AlphaClimb=math.rad(4)
|
||||
|
||||
-- Descent angle in rad. Moderate 4 degrees.
|
||||
local AlphaDescent=math.rad(4)
|
||||
|
||||
-- Expected cruise level (peak of Gaussian distribution)
|
||||
local FLcruise_expect=150*RAT.unit.FL2m
|
||||
if category==Group.Category.HELICOPTER then
|
||||
FLcruise_expect=1000 -- 1000 m ASL
|
||||
end
|
||||
|
||||
-------------------------
|
||||
--- DEPARTURE AIRPORT ---
|
||||
-------------------------
|
||||
|
||||
-- Coordinates of departure point.
|
||||
local Pdeparture=departure:GetCoordinate()
|
||||
|
||||
-- Height ASL of departure point.
|
||||
local H_departure=Pdeparture.y
|
||||
|
||||
---------------------------
|
||||
--- DESTINATION AIRPORT ---
|
||||
---------------------------
|
||||
|
||||
-- Position of destination airport.
|
||||
local Pdestination=destination:GetCoordinate()
|
||||
|
||||
-- Height ASL of destination airport/zone.
|
||||
local H_destination=Pdestination.y
|
||||
|
||||
-----------------------------
|
||||
--- DESCENT/HOLDING POINT ---
|
||||
-----------------------------
|
||||
|
||||
-- Get a random point between 5 and 10 km away from the destination.
|
||||
local Rhmin=5000
|
||||
local Rhmax=10000
|
||||
|
||||
-- For helos we set a distance between 500 to 1000 m.
|
||||
if category==Group.Category.HELICOPTER then
|
||||
Rhmin=500
|
||||
Rhmax=1000
|
||||
end
|
||||
|
||||
-- Coordinates of the holding point. y is the land height at that point.
|
||||
local Pholding=Pdestination:GetRandomCoordinateInRadius(Rhmax, Rhmin)
|
||||
|
||||
-- Distance from holding point to final destination (not used).
|
||||
local d_holding=Pholding:Get2DDistance(Pdestination)
|
||||
|
||||
-- AGL height of holding point.
|
||||
local H_holding=Pholding.y
|
||||
|
||||
---------------
|
||||
--- GENERAL ---
|
||||
---------------
|
||||
|
||||
-- We go directly to the holding point not the destination airport. From there, planes are guided by DCS to final approach.
|
||||
local heading=Pdeparture:HeadingTo(Pholding)
|
||||
local d_total=Pdeparture:Get2DDistance(Pholding)
|
||||
|
||||
------------------------------
|
||||
--- Holding Point Altitude ---
|
||||
------------------------------
|
||||
|
||||
-- Holding point altitude. For planes between 1600 and 2400 m AGL. For helos 160 to 240 m AGL.
|
||||
local h_holding=1200
|
||||
if category==Group.Category.HELICOPTER then
|
||||
h_holding=150
|
||||
end
|
||||
h_holding=UTILS.Randomize(h_holding, 0.2)
|
||||
|
||||
-- Max holding altitude.
|
||||
local DeltaholdingMax=self:_GetMaxHeight(d_total, AlphaClimb, AlphaDescent, H_departure, H_holding, 0)
|
||||
|
||||
if h_holding>DeltaholdingMax then
|
||||
h_holding=math.abs(DeltaholdingMax)
|
||||
end
|
||||
|
||||
-- This is the height ASL of the holding point we want to fly to.
|
||||
local Hh_holding=H_holding+h_holding
|
||||
|
||||
---------------------------
|
||||
--- Max Flight Altitude ---
|
||||
---------------------------
|
||||
|
||||
-- Get max flight altitude relative to H_departure.
|
||||
local h_max=self:_GetMaxHeight(d_total, AlphaClimb, AlphaDescent, H_departure, H_holding, h_holding)
|
||||
|
||||
-- Max flight level ASL aircraft can reach for given angles and distance.
|
||||
local FLmax = h_max+H_departure
|
||||
|
||||
--CRUISE
|
||||
-- Min cruise alt is just above holding point at destination or departure height, whatever is larger.
|
||||
local FLmin=math.max(H_departure, Hh_holding)
|
||||
|
||||
-- Ensure that FLmax not above its service ceiling.
|
||||
FLmax=math.min(FLmax, ceiling)
|
||||
|
||||
-- If the route is very short we set FLmin a bit lower than FLmax.
|
||||
if FLmin>FLmax then
|
||||
FLmin=FLmax
|
||||
end
|
||||
|
||||
-- Expected cruise altitude - peak of gaussian distribution.
|
||||
if FLcruise_expect<FLmin then
|
||||
FLcruise_expect=FLmin
|
||||
end
|
||||
if FLcruise_expect>FLmax then
|
||||
FLcruise_expect=FLmax
|
||||
end
|
||||
|
||||
-- Set cruise altitude. Selected from Gaussian distribution but limited to FLmin and FLmax.
|
||||
local FLcruise=UTILS.RandomGaussian(FLcruise_expect, math.abs(FLmax-FLmin)/4, FLmin, FLmax)
|
||||
|
||||
-- Climb and descent heights.
|
||||
local h_climb = FLcruise - H_departure
|
||||
local h_descent = FLcruise - Hh_holding
|
||||
|
||||
-- Get distances.
|
||||
local d_climb = h_climb/math.tan(AlphaClimb)
|
||||
local d_descent = h_descent/math.tan(AlphaDescent)
|
||||
local d_cruise = d_total-d_climb-d_descent
|
||||
|
||||
-- Debug.
|
||||
local text=string.format("Flight plan:\n")
|
||||
text=text..string.format("Vx max = %.2f km/h\n", Vmax*3.6)
|
||||
text=text..string.format("Vx climb = %.2f km/h\n", VxClimb*3.6)
|
||||
text=text..string.format("Vx cruise = %.2f km/h\n", VxCruise*3.6)
|
||||
text=text..string.format("Vx descent = %.2f km/h\n", VxDescent*3.6)
|
||||
text=text..string.format("Vx holding = %.2f km/h\n", VxHolding*3.6)
|
||||
text=text..string.format("Vx final = %.2f km/h\n", VxFinal*3.6)
|
||||
text=text..string.format("Vy max = %.2f m/s\n", Vymax)
|
||||
text=text..string.format("Vy climb = %.2f m/s\n", VyClimb)
|
||||
text=text..string.format("Alpha Climb = %.2f Deg\n", math.deg(AlphaClimb))
|
||||
text=text..string.format("Alpha Descent = %.2f Deg\n", math.deg(AlphaDescent))
|
||||
text=text..string.format("Dist climb = %.3f km\n", d_climb/1000)
|
||||
text=text..string.format("Dist cruise = %.3f km\n", d_cruise/1000)
|
||||
text=text..string.format("Dist descent = %.3f km\n", d_descent/1000)
|
||||
text=text..string.format("Dist total = %.3f km\n", d_total/1000)
|
||||
text=text..string.format("h_climb = %.3f km\n", h_climb/1000)
|
||||
text=text..string.format("h_desc = %.3f km\n", h_descent/1000)
|
||||
text=text..string.format("h_holding = %.3f km\n", h_holding/1000)
|
||||
text=text..string.format("h_max = %.3f km\n", h_max/1000)
|
||||
text=text..string.format("FL min = %.3f km\n", FLmin/1000)
|
||||
text=text..string.format("FL expect = %.3f km\n", FLcruise_expect/1000)
|
||||
text=text..string.format("FL cruise * = %.3f km\n", FLcruise/1000)
|
||||
text=text..string.format("FL max = %.3f km\n", FLmax/1000)
|
||||
text=text..string.format("Ceiling = %.3f km\n", ceiling/1000)
|
||||
text=text..string.format("Max range = %.3f km\n", Range/1000)
|
||||
self:T(self.wid..text)
|
||||
|
||||
-- Ensure that cruise distance is positve. Can be slightly negative in special cases. And we don't want to turn back.
|
||||
if d_cruise<0 then
|
||||
d_cruise=100
|
||||
end
|
||||
|
||||
------------------------
|
||||
--- Create Waypoints ---
|
||||
------------------------
|
||||
|
||||
-- Waypoints and coordinates
|
||||
local wp={}
|
||||
local c={}
|
||||
|
||||
--- Departure/Take-off
|
||||
c[#c+1]=Pdeparture
|
||||
wp[#wp+1]=Pdeparture:WaypointAir("RADIO", COORDINATE.WaypointType.TakeOffParking, COORDINATE.WaypointAction.FromParkingArea, VxClimb, true, departure, nil, "Departure")
|
||||
|
||||
--- Begin of Cruise
|
||||
local Pcruise=Pdeparture:Translate(d_climb, heading)
|
||||
Pcruise.y=FLcruise
|
||||
c[#c+1]=Pcruise
|
||||
wp[#wp+1]=Pcruise:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxCruise, true, nil, nil, "Cruise")
|
||||
|
||||
--- Descent
|
||||
local Pdescent=Pcruise:Translate(d_cruise, heading)
|
||||
Pdescent.y=FLcruise
|
||||
c[#c+1]=Pdescent
|
||||
wp[#wp+1]=Pdescent:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxDescent, true, nil, nil, "Descent")
|
||||
|
||||
--- Holding point
|
||||
Pholding.y=H_holding+h_holding
|
||||
c[#c+1]=Pholding
|
||||
wp[#wp+1]=Pholding:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxHolding, true, nil, nil, "Holding")
|
||||
|
||||
--- Final destination.
|
||||
c[#c+1]=Pdestination
|
||||
wp[#wp+1]=Pdestination:WaypointAir("RADIO", COORDINATE.WaypointType.Land, COORDINATE.WaypointAction.Landing, VxFinal, true, destination, nil, "Final Destination")
|
||||
|
||||
|
||||
-- Mark points at waypoints for debugging.
|
||||
if self.Debug then
|
||||
for i,coord in pairs(c) do
|
||||
local coord=coord --Core.Point#COORDINATE
|
||||
local dist=0
|
||||
if i>1 then
|
||||
dist=coord:Get2DDistance(c[i-1])
|
||||
end
|
||||
coord:MarkToAll(string.format("Waypoint %i, distance = %.2f km",i, dist/1000))
|
||||
end
|
||||
end
|
||||
|
||||
return wp,c
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Spawn functions
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Spawn an aircraft asset (plane or helo) at the airbase associated with the warehouse.
|
||||
-- @param #RAT2 self
|
||||
-- @param Funtional.RatCraft#RATCRAFT ratcraft
|
||||
-- @return #boolean If true, ratcraft was spawned.
|
||||
function RAT2:_TrySpawnRatcraft(ratcraft)
|
||||
|
||||
-- TODO: loop over departure airbases.
|
||||
|
||||
for _,airbase in pairs(ratcraft.departures) do
|
||||
|
||||
-- Get parking data.
|
||||
local parking=self:_FindParking(airbase, ratcraft)
|
||||
|
||||
-- Check if enough parking is available.
|
||||
if parking then
|
||||
self:_SpawnRatcraft()
|
||||
return true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Spawn an aircraft asset (plane or helo) at the airbase associated with the warehouse.
|
||||
-- @param #RAT2 self
|
||||
-- @param #string alias Alias name of the asset group.
|
||||
-- @param #WAREHOUSE.Assetitem asset Ground asset that will be spawned.
|
||||
-- @param #WAREHOUSE.Queueitem request Request belonging to this asset. Needed for the name/alias.
|
||||
-- @param #table parking Parking data for this asset.
|
||||
-- @param #boolean uncontrolled Spawn aircraft in uncontrolled state.
|
||||
-- @return Wrapper.Group#GROUP The spawned group or nil if the group could not be spawned.
|
||||
function RAT2:_SpawnRatcraft(ratcraft, departure, destination, parking, uncontrolled)
|
||||
|
||||
-- Prepare the spawn template.
|
||||
local template=self:_SpawnAssetPrepareTemplate(ratcraft, alias)
|
||||
|
||||
-- Get flight path if the group goes to another warehouse by itself.
|
||||
template.route.points=self:_GetFlightplan(asset, self.airbase, request.warehouse.airbase)
|
||||
|
||||
-- Get airbase ID and category.
|
||||
local AirbaseID = self.airbase:GetID()
|
||||
local AirbaseCategory = self:GetAirbaseCategory()
|
||||
|
||||
-- Check enough parking spots.
|
||||
if AirbaseCategory==Airbase.Category.HELIPAD or AirbaseCategory==Airbase.Category.SHIP then
|
||||
|
||||
--TODO Figure out what's necessary in this case.
|
||||
|
||||
else
|
||||
|
||||
if #parking<#template.units then
|
||||
local text=string.format("ERROR: Not enough parking! Free parking = %d < %d aircraft to be spawned.", #parking, #template.units)
|
||||
self:_DebugMessage(text)
|
||||
return nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Position the units.
|
||||
for i=1,#template.units do
|
||||
|
||||
-- Unit template.
|
||||
local unit = template.units[i]
|
||||
|
||||
if AirbaseCategory == Airbase.Category.HELIPAD or AirbaseCategory == Airbase.Category.SHIP then
|
||||
|
||||
-- Helipads we take the position of the airbase location, since the exact location of the spawn point does not make sense.
|
||||
local coord=self.airbase:GetCoordinate()
|
||||
|
||||
unit.x=coord.x
|
||||
unit.y=coord.z
|
||||
unit.alt=coord.y
|
||||
|
||||
unit.parking_id = nil
|
||||
unit.parking = nil
|
||||
|
||||
else
|
||||
|
||||
local coord=parking[i].Coordinate --Core.Point#COORDINATE
|
||||
local terminal=parking[i].TerminalID --#number
|
||||
|
||||
if self.Debug then
|
||||
coord:MarkToAll(string.format("Spawnplace unit %s terminal %d.", unit.name, terminal))
|
||||
end
|
||||
|
||||
unit.x=coord.x
|
||||
unit.y=coord.z
|
||||
unit.alt=coord.y
|
||||
|
||||
unit.parking_id = nil
|
||||
unit.parking = terminal
|
||||
|
||||
end
|
||||
|
||||
if asset.livery then
|
||||
unit.livery_id = asset.livery
|
||||
end
|
||||
if asset.skill then
|
||||
unit.skill= asset.skill
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- And template position.
|
||||
template.x = template.units[1].x
|
||||
template.y = template.units[1].y
|
||||
|
||||
-- Uncontrolled spawning.
|
||||
template.uncontrolled=uncontrolled
|
||||
|
||||
-- Debug info.
|
||||
self:T2({airtemplate=template})
|
||||
|
||||
-- Spawn group.
|
||||
local group=_DATABASE:Spawn(template) --Wrapper.Group#GROUP
|
||||
|
||||
return group
|
||||
end
|
||||
|
||||
|
||||
--- Prepare a spawn template for the asset. Deep copy of asset template, adjusting template and unit names, nillifying group and unit ids.
|
||||
-- @param #RAT2 self
|
||||
-- @param Functional.RatCraft#RATCRAFT ratcraft Aircraft that will be spawned.
|
||||
-- @param #string alias Alias name of the group.
|
||||
-- @return #table Prepared new spawn template.
|
||||
function RAT2:_SpawnAssetPrepareTemplate(ratcraft, alias)
|
||||
|
||||
-- Create an own copy of the template!
|
||||
local template=UTILS.DeepCopy(ratcraft.template)
|
||||
|
||||
-- Set unique name.
|
||||
template.name=alias
|
||||
|
||||
-- Set current(!) coalition and country.
|
||||
template.CoalitionID=ratcraft.coalition
|
||||
template.CountryID=ratcraft.country
|
||||
|
||||
-- Nillify the group ID.
|
||||
template.groupId=nil
|
||||
|
||||
-- No late activation.
|
||||
template.lateActivation=false
|
||||
|
||||
-- Set and empty route.
|
||||
template.route = {}
|
||||
template.route.routeRelativeTOT=true
|
||||
template.route.points = {}
|
||||
|
||||
-- Handle units.
|
||||
for i=1,#template.units do
|
||||
|
||||
-- Unit template.
|
||||
local unit = template.units[i]
|
||||
|
||||
-- Nillify the unit ID.
|
||||
unit.unitId=nil
|
||||
|
||||
-- Set unit name: <alias>-01, <alias>-02, ...
|
||||
unit.name=string.format("%s-%02d", template.name , i)
|
||||
|
||||
end
|
||||
|
||||
return template
|
||||
end
|
||||
|
||||
--- Get the proper terminal type based on generalized attribute of the group.
|
||||
--@param #RAT2 self
|
||||
--@param #RATCRAFT.Attribute _attribute Generlized attribute of unit.
|
||||
--@param #number _category Airbase category.
|
||||
--@return Wrapper.Airbase#AIRBASE.TerminalType Terminal type for this group.
|
||||
function RAT2:_GetTerminal(_attribute, _category)
|
||||
|
||||
-- Default terminal is "large".
|
||||
local _terminal=AIRBASE.TerminalType.OpenBig
|
||||
|
||||
if _attribute==RATCRAFT.Attribute.FIGHTER then
|
||||
-- Fighter ==> small.
|
||||
_terminal=AIRBASE.TerminalType.FighterAircraft
|
||||
elseif _attribute==RATCRAFT.Attribute.BOMBER or _attribute==RATCRAFT.Attribute.TRANSPORTPLANE or _attribute==RATCRAFT.Attribute.TANKER or _attribute==RATCRAFT.Attribute.AWACS then
|
||||
-- Bigger aircraft.
|
||||
_terminal=AIRBASE.TerminalType.OpenBig
|
||||
elseif _attribute==RATCRAFT.Attribute.TRANSPORTHELO or _attribute==RATCRAFT.Attribute.ATTACKHELO then
|
||||
-- Helicopter.
|
||||
_terminal=AIRBASE.TerminalType.HelicopterUsable
|
||||
else
|
||||
--_terminal=AIRBASE.TerminalType.OpenMedOrBig
|
||||
end
|
||||
|
||||
-- For ships, we allow medium spots for all fixed wing aircraft. There are smaller tankers and AWACS aircraft that can use a carrier.
|
||||
if _category==Airbase.Category.SHIP then
|
||||
if not (_attribute==RATCRAFT.Attribute.TRANSPORTHELO or _attribute==RATCRAFT.Attribute.ATTACKHELO) then
|
||||
_terminal=AIRBASE.TerminalType.OpenMedOrBig
|
||||
end
|
||||
end
|
||||
|
||||
return _terminal
|
||||
end
|
||||
|
||||
|
||||
--- Seach unoccupied parking spots at the airbase for a list of assets. For each asset group a list of parking spots is returned.
|
||||
-- During the search also the not yet spawned asset aircraft are considered.
|
||||
-- If not enough spots for all asset units could be found, the routine returns nil!
|
||||
-- @param #RAT2 self
|
||||
-- @param Wrapper.Airbase#AIRBASE airbase The airbase where we search for parking spots.
|
||||
-- @param #table assets A table of assets for which the parking spots are needed.
|
||||
-- @return #table Table of coordinates and terminal IDs of free parking spots. Each table entry has the elements .Coordinate and .TerminalID.
|
||||
function RAT2:_FindParking(airbase, ratcraft)
|
||||
|
||||
-- Init default
|
||||
local scanradius=100
|
||||
local scanunits=true
|
||||
local scanstatics=true
|
||||
local scanscenery=false
|
||||
local verysafe=false
|
||||
|
||||
-- Function calculating the overlap of two (square) objects.
|
||||
local function _overlap(l1,l2,dist)
|
||||
local safedist=(l1/2+l2/2)*1.05 -- 5% safety margine added to safe distance!
|
||||
local safe = (dist > safedist)
|
||||
self:T3(string.format("l1=%.1f l2=%.1f s=%.1f d=%.1f ==> safe=%s", l1,l2,safedist,dist,tostring(safe)))
|
||||
return safe
|
||||
end
|
||||
|
||||
-- Get parking spot data table. This contains all free and "non-free" spots.
|
||||
local parkingdata=airbase:GetParkingSpotsTable()
|
||||
|
||||
-- List of obstacles.
|
||||
local obstacles={}
|
||||
|
||||
-- Loop over all parking spots and get the currently present obstacles.
|
||||
-- How long does this take on very large airbases, i.e. those with hundereds of parking spots? Seems to be okay!
|
||||
for _,parkingspot in pairs(parkingdata) do
|
||||
|
||||
-- Coordinate of the parking spot.
|
||||
local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE
|
||||
local _termid=parkingspot.TerminalID
|
||||
|
||||
-- Scan a radius of 100 meters around the spot.
|
||||
local _,_,_,_units,_statics,_sceneries=_spot:ScanObjects(scanradius, scanunits, scanstatics, scanscenery)
|
||||
|
||||
-- Check all units.
|
||||
for _,_unit in pairs(_units) do
|
||||
local unit=_unit --Wrapper.Unit#UNIT
|
||||
local _coord=unit:GetCoordinate()
|
||||
local _size=self:_GetObjectSize(unit:GetDCSObject())
|
||||
local _name=unit:GetName()
|
||||
table.insert(obstacles, {coord=_coord, size=_size, name=_name, type="unit"})
|
||||
end
|
||||
|
||||
-- Check all statics.
|
||||
for _,static in pairs(_statics) do
|
||||
local _vec3=static:getPoint()
|
||||
local _coord=COORDINATE:NewFromVec3(_vec3)
|
||||
local _name=static:getName()
|
||||
local _size=self:_GetObjectSize(static)
|
||||
table.insert(obstacles, {coord=_coord, size=_size, name=_name, type="static"})
|
||||
end
|
||||
|
||||
-- Check all scenery.
|
||||
for _,scenery in pairs(_sceneries) do
|
||||
local _vec3=scenery:getPoint()
|
||||
local _coord=COORDINATE:NewFromVec3(_vec3)
|
||||
local _name=scenery:getTypeName()
|
||||
local _size=self:_GetObjectSize(scenery)
|
||||
table.insert(obstacles,{coord=_coord, size=_size, name=_name, type="scenery"})
|
||||
end
|
||||
|
||||
-- TODO check clients. Clients cannot be spawned. So we can loop over them.
|
||||
|
||||
end
|
||||
|
||||
-- Parking data for all assets.
|
||||
local parking={}
|
||||
|
||||
-- Get terminal type of this asset
|
||||
local terminaltype=self:_GetTerminal(ratcraft.attribute, self:GetAirbaseCategory())
|
||||
|
||||
-- Loop over all units - each one needs a spot.
|
||||
--TODO: nunits should be counted from alive units.
|
||||
for i=1,ratcraft.nunits do
|
||||
|
||||
-- Loop over all parking spots.
|
||||
local gotit=false
|
||||
for _,_parkingspot in pairs(parkingdata) do
|
||||
local parkingspot=_parkingspot --Wrapper.Airbase#AIRBASE.ParkingSpot
|
||||
|
||||
-- Check correct terminal type for asset. We don't want helos in shelters etc.
|
||||
if AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) then
|
||||
|
||||
-- Coordinate of the parking spot.
|
||||
local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE
|
||||
local _termid=parkingspot.TerminalID
|
||||
local _toac=parkingspot.TOAC
|
||||
|
||||
--env.info(string.format("FF asset=%s (id=%d): needs terminal type=%d, id=%d, #obstacles=%d", _asset.templatename, _asset.uid, terminaltype, _termid, #obstacles))
|
||||
|
||||
local free=true
|
||||
local problem=nil
|
||||
|
||||
-- Safe parking using TO_AC from DCS result.
|
||||
if self.safeparking and _toac then
|
||||
free=false
|
||||
self:T("Parking spot %d is occupied by other aircraft taking off or landing.", _termid)
|
||||
end
|
||||
|
||||
-- Loop over all obstacles.
|
||||
for _,obstacle in pairs(obstacles) do
|
||||
|
||||
-- Check if aircraft overlaps with any obstacle.
|
||||
local dist=_spot:Get2DDistance(obstacle.coord)
|
||||
-- TODO: ratcraft size!
|
||||
local safe=_overlap(ratcraft.size, obstacle.size, dist)
|
||||
|
||||
-- Spot is blocked.
|
||||
if not safe then
|
||||
--env.info(string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is NOT SAFE", _asset.templatename, _asset.uid, _termid, dist))
|
||||
free=false
|
||||
problem=obstacle
|
||||
problem.dist=dist
|
||||
break
|
||||
else
|
||||
--env.info(string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is SAFE", _asset.templatename, _asset.uid, _termid, dist))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Check if spot is free
|
||||
if free then
|
||||
|
||||
-- Add parkingspot for this asset unit.
|
||||
table.insert(parking, parkingspot)
|
||||
|
||||
self:T(self.wid..string.format("Parking spot #%d is free for ratcraft unit id=%d!", _termid, i))
|
||||
|
||||
-- Add the unit as obstacle so that this spot will not be available for the next unit.
|
||||
-- TODO: ratcraft templatename.
|
||||
table.insert(obstacles, {coord=_spot, size=ratcraft.size, name=ratcraft.templatename, type="asset"})
|
||||
|
||||
-- Break loop over parking spots.
|
||||
gotit=true
|
||||
break
|
||||
|
||||
else
|
||||
|
||||
-- Debug output for occupied spots.
|
||||
self:T(self.wid..string.format("Parking spot #%d is occupied or not big enough!", _termid))
|
||||
if self.Debug then
|
||||
local coord=problem.coord --Core.Point#COORDINATE
|
||||
local text=string.format("Obstacle blocking spot #%d is %s type %s with size=%.1f m and distance=%.1f m.", _termid, problem.name, problem.type, problem.size, problem.dist)
|
||||
coord:MarkToAll(string.format(text))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end -- check terminal type
|
||||
end -- loop over parking spots
|
||||
|
||||
-- No parking spot for at least one unit :(
|
||||
if not gotit then
|
||||
self:T(self.wid..string.format("WARNING: No free parking spot for ratcraft unit i=%d", i))
|
||||
return nil
|
||||
end
|
||||
|
||||
end -- loop over units
|
||||
|
||||
return parking
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Flightplan functions
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -290,7 +290,7 @@ RANGE.id="RANGE | "
|
||||
|
||||
--- Range script version.
|
||||
-- @field #string version
|
||||
RANGE.version="1.2.5"
|
||||
RANGE.version="1.3.0"
|
||||
|
||||
--TODO list:
|
||||
--TODO: Verbosity level for messages.
|
||||
@@ -1183,25 +1183,39 @@ end
|
||||
function RANGE:OnEventShot(EventData)
|
||||
self:F({eventshot = EventData})
|
||||
|
||||
-- Nil checks.
|
||||
if EventData.Weapon==nil then
|
||||
return
|
||||
end
|
||||
if EventData.IniDCSUnit==nil then
|
||||
return
|
||||
end
|
||||
|
||||
-- Weapon data.
|
||||
local _weapon = EventData.Weapon:getTypeName() -- should be the same as Event.WeaponTypeName
|
||||
local _weaponStrArray = UTILS.Split(_weapon,"%.")
|
||||
local _weaponName = _weaponStrArray[#_weaponStrArray]
|
||||
|
||||
-- Weapon descriptor.
|
||||
local desc=EventData.Weapon:getDesc()
|
||||
|
||||
-- Weapon category: 0=SHELL, 1=MISSILE, 2=ROCKET, 3=BOMB (Weapon.Category.X)
|
||||
local weaponcategory=desc.category
|
||||
|
||||
-- Debug info.
|
||||
self:T(RANGE.id.."EVENT SHOT: Range "..self.rangename)
|
||||
self:T(RANGE.id.."EVENT SHOT: Ini unit = "..EventData.IniUnitName)
|
||||
self:T(RANGE.id.."EVENT SHOT: Ini group = "..EventData.IniGroupName)
|
||||
self:T(RANGE.id.."EVENT SHOT: Weapon type = ".._weapon)
|
||||
self:T(RANGE.id.."EVENT SHOT: Weapon name = ".._weaponName)
|
||||
|
||||
self:T(RANGE.id.."EVENT SHOT: Weapon cate = "..weaponcategory)
|
||||
-- Special cases:
|
||||
local _viggen=string.match(_weapon, "ROBOT") or string.match(_weapon, "RB75") or string.match(_weapon, "BK90") or string.match(_weapon, "RB15") or string.match(_weapon, "RB04")
|
||||
--local _viggen=string.match(_weapon, "ROBOT") or string.match(_weapon, "RB75") or string.match(_weapon, "BK90") or string.match(_weapon, "RB15") or string.match(_weapon, "RB04")
|
||||
|
||||
-- Tracking conditions for bombs, rockets and missiles.
|
||||
local _bombs=string.match(_weapon, "weapons.bombs")
|
||||
local _rockets=string.match(_weapon, "weapons.nurs")
|
||||
local _missiles=string.match(_weapon, "weapons.missiles") or _viggen
|
||||
local _bombs = weaponcategory==Weapon.Category.BOMB --string.match(_weapon, "weapons.bombs")
|
||||
local _rockets = weaponcategory==Weapon.Category.ROCKET --string.match(_weapon, "weapons.nurs")
|
||||
local _missiles = weaponcategory==Weapon.Category.MISSILE --string.match(_weapon, "weapons.missiles") or _viggen
|
||||
|
||||
-- Check if any condition applies here.
|
||||
local _track = (_bombs and self.trackbombs) or (_rockets and self.trackrockets) or (_missiles and self.trackmissiles)
|
||||
@@ -1221,8 +1235,8 @@ function RANGE:OnEventShot(EventData)
|
||||
self:T(RANGE.id..string.format("Range %s, player %s, player-range distance = %d km.", self.rangename, _playername, dPR/1000))
|
||||
end
|
||||
|
||||
-- Only track if distance player to range is < 25 km.
|
||||
if _track and dPR<=self.BombtrackThreshold then
|
||||
-- Only track if distance player to range is < 25 km. Also check that a player shot. No need to track AI weapons.
|
||||
if _track and dPR<=self.BombtrackThreshold and _unit and _playername then
|
||||
|
||||
-- Tracking info and init of last bomb position.
|
||||
self:T(RANGE.id..string.format("RANGE %s: Tracking %s - %s.", self.rangename, _weapon, EventData.weapon:getName()))
|
||||
@@ -1346,8 +1360,8 @@ function RANGE:OnEventShot(EventData)
|
||||
end -- end function trackBomb
|
||||
|
||||
-- Weapon is not yet "alife" just yet. Start timer in one second.
|
||||
self:T(RANGE.id..string.format("Range %s, player %s: Tracking of weapon starts in one second.", self.rangename, _playername))
|
||||
timer.scheduleFunction(trackBomb, EventData.weapon, timer.getTime() + 1.0)
|
||||
self:T(RANGE.id..string.format("Range %s, player %s: Tracking of weapon starts in 0.1 seconds.", self.rangename, _playername))
|
||||
timer.scheduleFunction(trackBomb, EventData.weapon, timer.getTime()+0.1)
|
||||
|
||||
end --if _track (string.match) and player-range distance < threshold.
|
||||
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
--- **Ops** - (R2.5) - Random Air Traffic.
|
||||
--
|
||||
--
|
||||
--
|
||||
-- RAT2 creates random air traffic on the map.
|
||||
--
|
||||
--
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * It's very random.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
-- @module Functional.Ratcraft
|
||||
-- @image Functional_Ratcraft.png
|
||||
|
||||
|
||||
--- RATCRAFT class.
|
||||
-- @type RATCRAFT
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #boolean Debug Debug mode. Messages to all about status.
|
||||
-- @field #string lid Class id string for output to DCS log file.
|
||||
-- @field #number coalition Coalition side.
|
||||
-- @field #number country Country number.
|
||||
-- @field #number size Max size in meters.
|
||||
-- @field #table liveries Table of liveries.
|
||||
-- @field #string livery Livery.
|
||||
-- @field #string actype Aircraft type name.
|
||||
-- @field #number category Category (plane=X, helo=Y).
|
||||
-- @field #RATCRAFT.Attribute attribute Attribute.
|
||||
-- @field #table template Spawn template table.
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- Be surprised!
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- # The RAT2 Concept
|
||||
--
|
||||
--
|
||||
--
|
||||
-- @field #RATCRAFT
|
||||
RATCRAFT = {
|
||||
ClassName = "RATCRAFT",
|
||||
Debug = false,
|
||||
lid = nil,
|
||||
liveries = {},
|
||||
livery = nil,
|
||||
actype = nil,
|
||||
attribute = nil,
|
||||
ceiling = nil,
|
||||
speedmax = nil,
|
||||
sizex = nil,
|
||||
sizez = nil,
|
||||
size = nil,
|
||||
commute = nil,
|
||||
coalition = nil,
|
||||
country = nil,
|
||||
skill = nil,
|
||||
onboard = nil,
|
||||
departures = {},
|
||||
destinations = {},
|
||||
departure = nil,
|
||||
destination = nil,
|
||||
commute = nil,
|
||||
takeoff = nil,
|
||||
landing = nil,
|
||||
}
|
||||
|
||||
--- Generalized asset attributes. Can be used to request assets with certain general characteristics. See [DCS attributes](https://wiki.hoggitworld.com/view/DCS_enum_attributes) on hoggit.
|
||||
-- @type RATCRAFT.Attribute
|
||||
-- @field #string TRANSPORTPLANE Airplane with transport capability. This can be used to transport other assets.
|
||||
-- @field #string AWACS Airborne Early Warning and Control System.
|
||||
-- @field #string FIGHTER Fighter, interceptor, ... airplane.
|
||||
-- @field #string BOMBER Aircraft which can be used for strategic bombing.
|
||||
-- @field #string TANKER Airplane which can refuel other aircraft.
|
||||
-- @field #string TRANSPORTHELO Helicopter with transport capability. This can be used to transport other assets.
|
||||
-- @field #string ATTACKHELO Attack helicopter.
|
||||
-- @field #string UAV Unpiloted Aerial Vehicle, e.g. drones.
|
||||
RATCRAFT.Attribute = {
|
||||
TRANSPORTPLANE="TransportPlane",
|
||||
AWACS="AWACS",
|
||||
FIGHTER="Fighter",
|
||||
BOMBER="Bomber",
|
||||
TANKER="Tanker",
|
||||
TRANSPORTHELO="TransportHelo",
|
||||
ATTACKHELO="AttackHelo",
|
||||
UAV="UAV",
|
||||
OTHER="Other",
|
||||
}
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new AIRBOSS class object for a specific aircraft carrier unit.
|
||||
-- @param #RATCRAFT self
|
||||
-- @return #RATCRAFT self
|
||||
function RATCRAFT:New(group)
|
||||
|
||||
-- Inherit everything from FSM class.
|
||||
local self=BASE:Inherit(self, FSM:New()) -- #RATCRAFT
|
||||
|
||||
-- Start State.
|
||||
self:SetStartState("Stopped")
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:AddTransition("Stopped", "Load", "Stopped") -- Load player scores from file.
|
||||
self:AddTransition("Stopped", "Start", "Running") -- Start RAT2 script.
|
||||
|
||||
end
|
||||
|
||||
function RATCRAFT:_Init()
|
||||
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Add a departure to
|
||||
-- @param #RATCRAFT self
|
||||
-- @return #RATCRAFT self
|
||||
function RATCRAFT:AddDeparture(departure)
|
||||
|
||||
if type(departure)=="string" then
|
||||
|
||||
else
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Departure Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Get departure.
|
||||
-- @param #RATCRAFT self
|
||||
-- @return Wrapper.Airbase#AIRBASE
|
||||
function RATCRAFT:_GetDeparture()
|
||||
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Status Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Get number of alive groups
|
||||
-- @param #RATCRAFT self
|
||||
-- @return #number N live.
|
||||
function RATCRAFT:_GetAliveGroups()
|
||||
|
||||
return 0
|
||||
end
|
||||
|
||||
|
||||
@@ -60,10 +60,15 @@ __Moose.Include( 'Scripts/Moose/Functional/Artillery.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/Suppression.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/PseudoATC.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/Warehouse.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/RAT2.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/RatCraft.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/Fox2.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/FlightModelData.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Ops/Airboss.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/RecoveryTanker.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/RescueHelo.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/FlightControl.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' )
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -655,6 +655,21 @@ function POSITIONABLE:GetVelocityVec3()
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Get relative velocity with respect to another POSITIONABLE.
|
||||
-- @param #POSITIONABLE self
|
||||
-- @param #POSITIONABLE positionable Other positionable.
|
||||
-- @return #number Relative velocity in m/s.
|
||||
function POSITIONABLE:GetRelativeVelocity(positionable)
|
||||
self:F2( self.PositionableName )
|
||||
|
||||
local v1=self:GetVelocityVec3()
|
||||
local v2=positionable:GetVelocityVec3()
|
||||
|
||||
local vtot=UTILS.VecAdd(v1,v2)
|
||||
|
||||
return UTILS.VecNorm(vtot)
|
||||
end
|
||||
|
||||
|
||||
--- Returns the POSITIONABLE height in meters.
|
||||
-- @param Wrapper.Positionable#POSITIONABLE self
|
||||
|
||||
@@ -58,10 +58,15 @@ Functional/Artillery.lua
|
||||
Functional/Suppression.lua
|
||||
Functional/PseudoATC.lua
|
||||
Functional/Warehouse.lua
|
||||
Functional/RAT2.lua
|
||||
Functional/RatCraft.lua
|
||||
Functional/Fox2.lua
|
||||
Functional/FlightModelData.lua
|
||||
|
||||
Ops/Airboss.lua
|
||||
Ops/RecoveryTanker.lua
|
||||
Ops/RescueHelo.lua
|
||||
Ops/FlightControl.lua
|
||||
|
||||
AI/AI_Balancer.lua
|
||||
AI/AI_Air.lua
|
||||
|
||||
Reference in New Issue
Block a user