AIRBOSS v0.3.6

This commit is contained in:
Frank 2018-11-27 00:03:45 +01:00
parent 6b24673b6f
commit e16f306e1d
2 changed files with 223 additions and 118 deletions

View File

@ -8,7 +8,6 @@
-- * Supports human pilots as well as AI flight groups.
-- * Automatic LSO grading.
-- * Different skill levels from tipps on-the-fly for students to complete ziplip for pros.
-- * Rescue helo option.
-- * Recovery tanker option.
-- * Voice overs for LSO and AIRBOSS calls. Can easily be customized by users.
-- * Automatic TACAN and ICLS channel setting.
@ -70,7 +69,7 @@
-- @field #table flights List of all flights in the CCA.
-- @field #table Qmarshal Queue of marshalling aircraft groups.
-- @field #table Qpattern Queue of aircraft groups in the landing pattern.
-- @field Ops.RescueHelo#RESCUEHELO rescuehelo Rescue helo flying in close formation with the carrier.
-- @field #number Nmaxpattern Max number of aircraft in landing pattern.
-- @field Ops.RecoveryTanker#RECOVERYTANKER tanker Recovery tanker flying overhead of carrier.
-- @field Functional.Warehouse#WAREHOUSE warehouse Warehouse object of the carrier.
-- @field #table recoverytime List of time intervals when aircraft are recovered.
@ -142,11 +141,10 @@ AIRBOSS = {
Qpattern = {},
Qmarshal = {},
Nmaxpattern = nil,
rescuehelo = nil,
tanker = nil,
warehouse = nil,
recoverytime = {},
holdingoffset= 0,
holdingoffset= nil,
}
--- Player aircraft types capable of landing on carriers.
@ -456,7 +454,7 @@ AIRBOSS.MenuF10={}
--- Airboss class version.
-- @field #string version
AIRBOSS.version="0.3.5w"
AIRBOSS.version="0.3.6"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@ -466,17 +464,17 @@ AIRBOSS.version="0.3.5w"
-- TODO: Add radio transmission queue for LSO and airboss.
-- TODO: Get correct wire when trapped.
-- TODO: Add radio check (LSO, AIRBOSS) to F10 radio menu.
-- TODO: Right pattern step after bolter/wo/patternWO?
-- TODO: Handle crash event. Delete A/C from queue, send rescue helo, stop carrier?
-- TODO: Get fuel state in pounds.
-- TODO: Add user functions.
-- TODO: Generalize parameters for other carriers.
-- TODO: Generalize parameters for other aircraft.
-- TODO: CASE II.
-- TODO: CASE III.
-- TODO: Foul deck check.
-- TODO: Persistence of results.
-- TODO: Strike group with helo bringing cargo etc.
-- TODO: Right pattern step after bolter/wo/patternWO?
-- TODO: CASE II.
-- TODO: CASE III.
-- DONE: Handle crash event. Delete A/C from queue, send rescue helo.
-- DONE: Get fuel state in pounds. (working for the hornet, did not check others)
-- DONE: Add aircraft numbers in queue to carrier info F10 radio output.
-- DONE: Monitor holding of players/AI in zoneHolding.
-- DONE: Transmission via radio.
@ -531,6 +529,8 @@ function AIRBOSS:New(carriername, alias)
-- Create carrier beacon.
self.beacon=BEACON:New(self.carrier)
-- Defaults:
-- Set up Airboss radio.
self.Carrierradio=RADIO:New(self.carrier)
self.Carrierradio:SetAlias("AIRBOSS")
@ -541,6 +541,28 @@ function AIRBOSS:New(carriername, alias)
self.LSOradio:SetAlias("LSO")
self:SetLSOradio()
-- Set ICSL to channel 1.
self:SetICLS()
-- Set TACAN to channel 74X
self:SetTACAN()
-- Set max aircraft in landing pattern.
self:SetMaxLandingPattern(1)
-- Set holding offset to 0 degrees.
self:SetHoldingOffsetAngle(30)
-- Default recovery case.
self:SetRecoveryCase(1)
-- CCA 50 NM radius zone around the carrier.
self:SetCarrierControlledArea()
-- CCZ 5 NM radius zone around the carrier.
self:SetCarrierControlledZone()
-- Init carrier parameters.
if self.carriertype==AIRBOSS.CarrierType.STENNIS then
self:_InitStennis()
@ -562,10 +584,13 @@ function AIRBOSS:New(carriername, alias)
self.zoneInitial=ZONE_UNIT:New("Initial Zone", self.carrier, 0.5*1000, {dx=-UTILS.NMToMeters(3), dy=100, relative_to_unit=true})
-- CASE II/III moving zones.
local angle=180+self.carrierparam.rwyangle
self.zonePlatform = ZONE_UNIT:New("Platform Zone", self.carrier, 1.5*1000, {rho=UTILS.NMToMeters(20), theta=angle, relative_to_unit=true})
self.zoneDirtyup = ZONE_UNIT:New("Dirty Up Zone", self.carrier, 1.5*1000, {rho=UTILS.NMToMeters(10), theta=angle, relative_to_unit=true})
self.zoneBullseye = ZONE_UNIT:New("Bulleye Zone", self.carrier, 1.5*1000, {rho=UTILS.NMToMeters( 3), theta=angle, relative_to_unit=true})
local radial=180+self.carrierparam.rwyangle
local radius=UTILS.NMToMeters(1)
self.zonePlatform = ZONE_UNIT:New("Platform Zone", self.carrier, radius, {rho=UTILS.NMToMeters(19), theta=radial+self.holdingoffset, relative_to_unit=true})
self.zoneArcturn1 = ZONE_UNIT:New("ArcTurn1 Zone", self.carrier, radius, {rho=UTILS.NMToMeters(14), theta=radial+self.holdingoffset, relative_to_unit=true})
self.zoneArcturn2 = ZONE_UNIT:New("ArcTurn2 Zone", self.carrier, radius, {rho=UTILS.NMToMeters(12), theta=radial, relative_to_unit=true})
self.zoneDirtyup = ZONE_UNIT:New("DirtyUp Zone", self.carrier, radius, {rho=UTILS.NMToMeters( 9), theta=radial, relative_to_unit=true})
self.zoneBullseye = ZONE_UNIT:New("Bulleye Zone", self.carrier, radius, {rho=UTILS.NMToMeters( 3), theta=radial, relative_to_unit=true})
-- Smoke zones.
if self.Debug then
@ -576,15 +601,6 @@ function AIRBOSS:New(carriername, alias)
--local zp=self:_GetCase23ValidZone():SmokeZone(SMOKECOLOR.Green, 45)
end
-- CCA 50 NM radius zone around the carrier.
self:SetCarrierControlledArea()
-- CCZ 5 NM radius zone around the carrier.
self:SetCarrierControlledZone()
-- Default recovery case.
self:SetRecoveryCase(1)
-- Init default sound files.
for _name,_sound in pairs(AIRBOSS.Soundfile) do
local sound=_sound --#AIRBOSS.RadioSound
@ -698,6 +714,18 @@ function AIRBOSS:SetRecoveryCase(case)
return self
end
--- Set holding pattern offset from final bearing for Case II/III recoveries.
-- Usually, this is +-15 or +-30 degrees.
-- @param #AIRBOSS self
-- @param #number offset Offset angle in degrees. Default 0.
-- @return #AIRBOSS self
function AIRBOSS:SetHoldingOffsetAngle(offset)
self.holdingoffset=offset or 0
return self
end
--- Add recovery time slot.
-- @param #AIRBOSS self
-- @param #string starttime Start time, e.g. "8:00" for eight o'clock.
@ -787,12 +815,12 @@ function AIRBOSS:SetCarrierradio(frequency, modulation)
end
--- Define rescue helicopter associated with the carrier.
--- Set number of aircraft units which can be in the landing pattern before the pattern is full.
-- @param #AIRBOSS self
-- @param Ops.RescueHelo#RESCUEHELO rescuehelo Rescue helo object.
-- @param #number nmax Max number. Default 4.
-- @return #ARIBOSS self
function AIRBOSS:SetRescueHelo(rescuehelo)
self.rescuehelo=rescuehelo
function AIRBOSS:SetMaxLandingPattern(nmax)
self.Nmaxpattern=nmax or 4
return self
end
@ -862,12 +890,12 @@ function AIRBOSS:onafterStart(From, Event, To)
self:HandleEvent(EVENTS.Birth)
self:HandleEvent(EVENTS.Land)
self:HandleEvent(EVENTS.Crash)
--self:HandleEvent(EVENTS.Ejection)
self:HandleEvent(EVENTS.Ejection)
-- Time stamp for checking queues.
self.Tqueue=timer.getTime()
-- Init status check
-- Start status check in 1 second.
self:__Status(1)
end
@ -994,7 +1022,7 @@ function AIRBOSS:onbeforeRecover(From, Event, To)
end
--- On after Stop event. Unhandle events and stop status updates.
--- On after Stop event. Unhandle events.
-- @param #AIRBOSS self
-- @param #string From From state.
-- @param #string Event Event.
@ -1003,6 +1031,7 @@ function AIRBOSS:onafterStop(From, Event, To)
self:UnHandleEvent(EVENTS.Birth)
self:UnHandleEvent(EVENTS.Land)
self:UnHandleEvent(EVENTS.Crash)
self:UnHandleEvent(EVENTS.Ejection)
end
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -1178,10 +1207,14 @@ end
-- @return #number Speed in m/s or nil.
function AIRBOSS:_GetAircraftParameters(playerData, step)
-- Get parameters depended on step.
step=step or playerData.step
-- Get AC type.
local hornet=playerData.actype==AIRBOSS.AircraftCarrier.HORNET
local skyhawk=playerData.actype==AIRBOSS.AircraftCarrier.A4EC
-- Return values.
local alt
local aoa
local dist
@ -1318,7 +1351,7 @@ function AIRBOSS:_CheckQueue()
local nmarshal,_=self:_GetQueueInfo(self.Qmarshal)
-- Check if there are flights in marshal strack and if the pattern is free.
if nmarshal>0 and npattern<1 then
if nmarshal>0 and npattern<self.Nmaxpattern then
-- Next flight in line to be send from marshal to pattern.
local marshalflight=self.Qmarshal[1] --#AIRBOSS.Flightitem
@ -2343,8 +2376,12 @@ function AIRBOSS:OnEventBirth(EventData)
--self:RadioTransmission(self.LSOradio, self.radiocall["LONGINGROOVE"], false, 5)
--self:RadioTransmission(self.LSOradio, self.radiocall.LONGINGROOVE, false, 20)
if self.Debug then
self:_MarkCase23Zones(_unit:GetName())
end
-- Start in the groove for debugging.
self.groovedebug=true
self.groovedebug=false
end
end
@ -2480,6 +2517,29 @@ function AIRBOSS:OnEventCrash(EventData)
self:_RemoveUnitFromFlight(EventData.IniUnit)
end
--- Airboss event handler for event Ejection.
-- @param #AIRBOSS self
-- @param Core.Event#EVENTDATA EventData
function AIRBOSS:OnEventEjection(EventData)
self:F3({eventland = EventData})
local _unitName=EventData.IniUnitName
local _unit, _playername=self:_GetPlayerUnitAndName(_unitName)
self:I(self.lid.."EJECT: unit = "..tostring(EventData.IniUnitName))
self:I(self.lid.."EJECT: group = "..tostring(EventData.IniGroupName))
self:I(self.lid.."EJECT: player = "..tostring(_playername))
if _unit and _playername then
self:I(self.lid..string.format("Player %s ejected!",_playername))
else
self:I(self.lid..string.format("AI unit %s ejected!", EventData.IniUnitName))
end
-- Remove unit from flight and queues.
self:_RemoveUnitFromFlight(EventData.IniUnit)
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- PATTERN functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -2579,7 +2639,9 @@ end
function AIRBOSS:_GetCase23ValidZone()
-- Radial.
local hdg=self:GetRadialCase3(false)
local hdg=self:GetRadialCase3(false, false)
local off=self:GetRadialCase3(false, true)
self:I("FF radial case 3 = "..hdg)
@ -2597,13 +2659,27 @@ function AIRBOSS:_GetCase23ValidZone()
local c4=c0:Translate(w, hdg-90) -- Port close
local c3=c4:Translate(l, hdg) -- Port far
local beta=hdg-self.holdingoffset
env.info("FF beta = "..beta)
local x=(50-9)/math.cos(math.rad(beta))
env.info("FF x [NM] = "..x)
local c={}
c[1]=self:GetCoordinate()
c[2]=c[1]:Translate( UTILS.NMToMeters( 5), hdg-90)
c[3]=c[2]:Translate( UTILS.NMToMeters( 9), hdg)
c[4]=c[3]:Translate( UTILS.NMToMeters( x), -beta)
--[[
c[5]=c[4]:Translate( UTILS.NMToMeters(10), hdg+90)
c[6]=c[5]:Translate(-UTILS.NMToMeters( x), beta)
c[7]=c[6]:Translate(-UTILS.NMToMeters( 5), hdg-90)
]]
-- Create an array of a square!
local p={}
p[1]=c1:GetVec2()
p[2]=c2:GetVec2()
p[3]=c3:GetVec2()
p[4]=c4:GetVec2()
for _i,_c in ipairs(c) do
p[_i]=_c:GetVec2()
end
-- Square zone length=10NM width=6 NM behind the carrier starting at angels+15 NM behind the carrier.
-- So stay 0-5 NM (+1 NM error margin) port of carrier.
@ -3651,14 +3727,17 @@ end
--- Get radial, i.e. the final bearing FB-180 degrees including holding offset for Case II/III recoveries.
-- @param #AIRBOSS self
-- @param #boolean magnetic If true, magnetic radial is returned. Default is true radial.
-- @param #boolean offset If true, inlcude holding offset.
-- @return #number Radial in degrees.
function AIRBOSS:GetRadialCase3(magnetic)
function AIRBOSS:GetRadialCase3(magnetic, offset)
-- Get radial.
local radial=self:GetFinalBearing(magnetic)-180
-- Holding offset angle (+-15 or 30 degrees usually)
if offset then
radial=radial-self.holdingoffset
end
-- Adjust for negative values.
if radial<0 then
@ -3712,23 +3791,6 @@ function AIRBOSS:_GetRelativeHeading(unit, runway)
rhdg=rhdg-self.carrierparam.rwyangle
end
--[[
-- Heading of unit.
local unitheading=unit:GetHeading()
-- Heading of carrier.
local carrierheading
-- Include runway?
if runway then
carrierheading=self:GetFinalBearing(false)
else
carrierheading=self:GetHeading(false)
end
local rhdg=unitheading-carrierheading
]]
-- Return heading in degrees.
return rhdg
end
@ -4495,8 +4557,8 @@ function AIRBOSS:_Debrief(playerData)
table.insert(playerData.grades, mygrade)
-- LSO grade message.
local text=string.format("%s %.1f PT - %s", grade, points, analysis)
text=text..string.format("Detailed debriefing can be found in the F10 radio menu.")
local text=string.format("%s %.1f PT - %s\n", grade, points, analysis)
text=text..string.format("Your detailed debriefing can be found via the F10 radio menu.")
self:MessageToPlayer(playerData,text, "LSO", "", 30, true)
-- Check if boltered or waved off?
@ -4518,39 +4580,7 @@ function AIRBOSS:_Debrief(playerData)
-- TODO: CASE III: After bolter/wo turn left and climb to 1200 ft and re-enter pattern?
-- TODO: CASE III: After pattern wo? No idea...
-- Get flight.
--[[
local flight=self:_GetFlightFromGroupInQueue(playerData.group, self.flight)
if flight then
if flight.case==1 then
-- CASE I
if playerData.boltered or playerData.waveoff then
-- CASE I bolter or waveoff ==> stay in pattern and try again.
playerData.step=AIRBOSS.PatternStep.COMMENCING
elseif playerData.patternwo then
-- CASE I pattern wave off.
-- Ask again? Back to marshal.
playerData.step=AIRBOSS.PatternStep.COMMENCING
end
elseif flight.case==2 then
elseif flight.case==3 then
end
else
end
]]
playerData.step=AIRBOSS.PatternStep.COMMENCING
elseif playerData.landed and not playerData.unit:InAir() then
@ -4558,7 +4588,7 @@ function AIRBOSS:_Debrief(playerData)
self:_RemoveUnitFromFlight(playerData.unit)
-- Message to player.
self:MessageToPlayer(playerData, "Welcome to the carrier!", "LSO", nil, 10)
self:MessageToPlayer(playerData, "Welcome on board!", "LSO", nil, 10)
else
@ -4721,6 +4751,56 @@ function AIRBOSS:_IsHuman(group)
return false
end
--- Get fuel state in pounds.
-- @param #AIRBOSS self
-- @param Wrapper.Unit#UNIT unit The unit for which the mass is determined.
-- @return #number Fuel state in pounds.
function AIRBOSS:_GetFuelState(unit)
-- Get relative fuel [0,1].
local fuel=unit:GetFuel()
-- Get max weight of fuel in kg.
local maxfuel=self:_GetUnitMasses(unit)
-- Fuel state, i.e. what let's
local fuelstate=fuel*maxfuel
-- Debug info.
self:I(self.lid..string.format("Unit %s fuel state = %.1f kg = %.1f lbs", unit:GetName(), fuelstate, UTILS.kg2lbs(fuelstate)))
return UTILS.kg2lbs(fuelstate)
end
--- Get unit masses especially fuel from DCS descriptor values.
-- @param #AIRBOSS self
-- @param Wrapper.Unit#UNIT unit The unit for which the mass is determined.
-- @return #number Mass of fuel in kg.
-- @return #number Empty weight of unit in kg.
-- @return #number Max weight of unit in kg.
-- @return #number Max cargo weight in kg.
function AIRBOSS:_GetUnitMasses(unit)
-- Get DCS descriptors table.
local Desc=unit:GetDesc()
-- Mass of fuel in kg.
local massfuel=Desc.fuelMassMax or 0
-- Mass of empty unit in km.
local massempty=Desc.massEmpty or 0
-- Max weight of unit in kg.
local massmax=Desc.massMax or 0
-- Rest is cargo.
local masscargo=massmax-massfuel-massempty
-- Debug info.
self:I(self.lid..string.format("Unit %s mass fuel=%.1f kg, empty=%.1f kg, max=%.1f kg, cargo=%.1f kg", unit:GetName(), massfuel, massempty, massmax, masscargo))
return massfuel, massempty, massmax, masscargo
end
--- Get player data from unit object
-- @param #AIRBOSS self
@ -5015,6 +5095,7 @@ function AIRBOSS:_RequestCommence(_unitName)
-- Get stack value.
local stack=playerData.flag:Get()
-- Check if player is in the lowest stack.
if stack>1 then
-- We are in a higher stack.
text="Negative ghostrider, it's not your turn yet!"
@ -5023,8 +5104,8 @@ function AIRBOSS:_RequestCommence(_unitName)
-- Number of aircraft currently in pattern.
local _,npattern=self:_GetQueueInfo(self.Qpattern)
-- TODO: set nmax for pattern. Should be ~6 but let's make this 4.
if npattern>0 then
-- Check if pattern is already full.
if npattern>self.Nmaxpattern then
-- Patern is full!
text=string.format("Negative ghostrider, pattern is full! There are %d aircraft currently in pattern.", npattern)
else
@ -5494,6 +5575,14 @@ function AIRBOSS:_DisplayPlayerStatus(_unitName)
if playerData then
-- Stack and stack altitude.
local stack=playerData.flag:Get()
local stackalt=UTILS.MetersToFeet(self:_GetMarshalAltitude(stack))
-- Fuel and fuel state.
local fuel=playerData.unit:GetFuel()*100
local fuelstate=self:_GetFuelState(playerData.unit)
-- Player data.
local text=string.format("Status of player %s (%s)\n", playerData.name, playerData.callsign)
text=text..string.format("=============================================\n")
@ -5501,14 +5590,10 @@ function AIRBOSS:_DisplayPlayerStatus(_unitName)
text=text..string.format("Skil level: %s\n", playerData.difficulty)
text=text..string.format("Aircraft: %s\n", playerData.actype)
text=text..string.format("Board number: %s\n", playerData.onboard)
text=text..string.format("Fuel: %.1f %%\n", playerData.unit:GetFuel()*100)
local stack=playerData.flag:Get()
local stackalt=UTILS.MetersToFeet(self:_GetMarshalAltitude(stack))
text=text..string.format("Flag/stack: %d\n", stack)
text=text..string.format("Stack alt: %d ft\n", stackalt)
text=text..string.format("Fuel state: %.1f lbs/1000 (%.1f %%)\n", fuelstate/1000, fuel)
text=text..string.format("Stack: %d alt=%d ft\n", stack, stackalt)
text=text..string.format("Group: %s\n", playerData.group:GetName())
text=text..string.format("# units: %d\n", #playerData.group:GetUnits())
text=text..string.format("n units: %d\n", playerData.nunits)
text=text..string.format("# units: %d (n=%d)\n", #playerData.group:GetUnits(), playerData.nunits)
text=text..string.format("Section Lead: %s\n", tostring(playerData.seclead))
text=text..string.format("# section: %d", #playerData.section)
for _,_sec in pairs(playerData.section) do
@ -5601,31 +5686,46 @@ function AIRBOSS:_MarkCase23Zones(_unitName, flare)
if playerData then
local text="CASE II/III: Marking:\n"
-- Initial
local text="Marking CASE II/III zone:\n"
--TODO: Add height!
if flare then
text=text.."* Valid zone with GREEN flares\n"
text=text.."* valid area with GREEN flares\n"
local zp=self:_GetCase23ValidZone()
zp:FlareZone(FLARECOLOR.Green, 45)
text=text.."* Platform zone with RED flares\n"
text=text.."* platform with RED flares\n"
self.zonePlatform:FlareZone(FLARECOLOR.Red, 45)
text=text.."* Dirty up zone with YELLOW flares\n"
text=text.."* dirty up with YELLOW flares\n"
self.zoneDirtyup:FlareZone(FLARECOLOR.Yellow, 45)
text=text.."* Bullseye zone with WHITE flares\n"
if math.abs(self.holdingoffset)>0 then
self.zoneArcturn1:FlareZone(FLARECOLOR.Yellow, 45)
text=text.."* arc turn in with YELLOW flares\n"
self.zoneArcturn2:FlareZone(FLARECOLOR.White, 45)
text=text.."* arc trun out with WHITE flares\n"
end
text=text.."* bullseye with WHITE flares\n"
self.zoneBullseye:FlareZone(FLARECOLOR.White, 45)
else
text=text.."* Valid zone with GREEN smoke\n"
text=text.."* valid area with GREEN smoke\n"
local zp=self:_GetCase23ValidZone()
zp:SmokeZone(SMOKECOLOR.Green, 45)
text=text.."* Platform zone with RED smoke\n"
text=text.."* platform with RED smoke\n"
self.zonePlatform:SmokeZone(SMOKECOLOR.Red, 45)
text=text.."* Dirty up zone with ORANGE flares\n"
text=text.."* dirty up with ORANGE flares\n"
if math.abs(self.holdingoffset)>0 then
self.zoneArcturn1:SmokeZone(SMOKECOLOR.Red, 45)
text=text.."* arc turn in with YELLOW flares\n"
self.zoneArcturn2:SmokeZone(SMOKECOLOR.Orange, 45)
text=text.."* arc trun out with WHITE flares\n"
end
self.zoneDirtyup:SmokeZone(SMOKECOLOR.Orange, 45)
text=text.."* Bullseye zone with BLUE smoke\n"
text=text.."* bullseye with BLUE smoke\n"
self.zoneBullseye:SmokeZone(SMOKECOLOR.Blue, 45)
end
-- Send message to player.
self:MessageToPlayer(playerData, text, "AIRBOSS", "", 10)
end

View File

@ -308,7 +308,12 @@ UTILS.hPa2mmHg = function( hPa )
return hPa * 0.7500615613030
end
--- Convert kilo gramms (kg) to pounds (lbs).
-- @param #number kg Mass in kg.
-- @return #number Mass in lbs.
UTILS.kg2lbs = function( kg )
return kg * 2.20462
end
--[[acc:
in DM: decimal point of minutes.