-- ***************************************************************************** -- ** Tankers ** -- ********************************************************* -- region TankerConfigFunctions -- @type TankerCallsignConfig -- @field #string alias Alias for the tanker. -- @field #number name CallSign name for the tanker. -- @field #number number CallSign number (1-7) for the tanker. --- Parse an Tanker Callsign config Object. -- @param #JsonObject config Config object to parse -- @param #string parser_name Parser name ("AIRBASE", "AIRBOSS", etc...). -- @return #TankerCallsignConfig tankerCallsignConfigJson Parsed CallSign object function ParseTankerCallsignConfigJson(config, parser_name) local tankerCallsignConfigJson = {} local default_number = 1 -- ************************************************************************** -- name -- ************************************************************************** if type(config.name) == "number" then if table.count_value(CALLSIGN.Tanker, config.name) > 0 then tankerCallsignConfigJson.name = config.name else Jtff_log.error("Tanker Callsign name is not a valid CallSign name, skipping tanker configuration", parser_name) return {} end else Jtff_log.error("Tanker Callsign name is not valid, skipping tanker configuration", parser_name) return {} end -- ************************************************************************** -- alias -- ************************************************************************** if type(config.alias) == "string" then tankerCallsignConfigJson.alias = config.alias else Jtff_log.warn("Tanker Callsign alias is not a string, defaulting to Tanker name", parser_name) tankerCallsignConfigJson.alias = tankerCallsignConfigJson.name end -- ************************************************************************** -- number -- ************************************************************************** if type(config.number) == "number" then if config.number >= 1 and config.number <= 7 then tankerCallsignConfigJson.number = config.number else Jtff_log.warn( string.format("Tanker Callsign number is not a valid number (1-7), defaulting number to %d", default_number), parser_name) tankerCallsignConfigJson.number = default_number end else Jtff_log.warn(string.format("Tanker Callsign number is not a number, defaulting number to %d", default_number), parser_name) tankerCallsignConfigJson.number = default_number end return tankerCallsignConfigJson end -- @type TankerConfig -- @field #boolean enable Enable tanker creation. -- @field #string type Tanker type. -- @field #string airbaseName Airbase name. -- @field #boolean autospawn Autospawn tanker. -- @field #RacetrackConfig racetrack Racetrack configuration. -- @field #number nbEscort Number of escort aircraft. -- @field #number missionmaxduration Maximum mission duration in minutes. -- @field #number refuelSystem Refueling system (0=boom, 1=probe). -- @field #tacanConfig tacan TACAN configuration. -- @field #radioConfig radio Radio configuration. -- @field #number fuelLowThreshold Fuel low threshold in %. -- @field #TankerCallsignConfig callsign Callsign configuration. -- @field #SquawkConfig squawk Squawk configuration. --- Parse Tanker config Object. -- @param #JsonObject config Config object to parse -- @return #TankerConfig tankerConfigJson Parsed TankerConfig object function ParseTankersConfigJson(config) local json = require('Scripts/json') local parser_name = "TANKER" local default_autospawn = false local default_nbEscort = 0 local default_missionmaxduration = 180 local default_fuelLowThreshold = 35 local default_refuelSystem = Unit.RefuelingSystem.PROBE_AND_DROGUE -- ************************************************************************** -- enable -- ************************************************************************** local tankerConfigJson = { enable = config.enable or false, } -- ************************************************************************** -- type -- ************************************************************************** if type(config.type) == "string" then tankerConfigJson.type = config.type else Jtff_log.error("Tanker type is not a string, skipping tanker configuration", parser_name) config.enable = false return config end -- ************************************************************************** -- airbaseName -- ************************************************************************** if type(config.airbaseName) == "string" then if type(AIRBASE:FindByName(config.airbaseName)) == 'nil' then Jtff_log.error( string.format( "Airbase %s not found, skipping tanker configuration", config.airbaseName ), parser_name ) config.enable = false return config else tankerConfigJson.airbaseName = config.airbaseName end else Jtff_log.error("Airbase warehouse is not a string", parser_name) config.enable = false return config end -- ************************************************************************** -- autospawn -- ************************************************************************** if type(config.autospawn) == "boolean" then tankerConfigJson.autospawn = config.autospawn else Jtff_log.warn("Tanker %s autospawn not defined : defaulting", parser_name) tankerConfigJson.autospawn = default_autospawn end -- ************************************************************************** -- racetrack -- ************************************************************************** if type(config.racetrack) == "table" then tankerConfigJson.racetrack = ParseRacetrackConfigJson(config.racetrack, parser_name) if next(tankerConfigJson.racetrack) == nil then Jtff_log.error("Tanker racetrack is not a valid Racetrack object, skipping tanker configuration", parser_name) config.enable = false return config end else Jtff_log.error("Tanker racetrack is not a table, skipping tanker configuration", parser_name) config.enable = false return config end -- ************************************************************************** -- nbEscort -- ************************************************************************** if type(config.nbEscort) == "number" then tankerConfigJson.nbEscort = config.nbEscort else Jtff_log.warn(string.format("Tanker nbEscort is not a number, defaulting to %d", default_nbEscort), parser_name) tankerConfigJson.nbEscort = default_nbEscort end -- ************************************************************************** -- missionmaxduration -- ************************************************************************** if type(config.missionmaxduration) == "number" then tankerConfigJson.missionmaxduration = config.missionmaxduration else Jtff_log.warn( string.format("Tanker missionmaxduration is not a number, defaulting to %d", default_missionmaxduration), parser_name) tankerConfigJson.missionmaxduration = default_missionmaxduration end -- ************************************************************************** -- refuelSystem -- ************************************************************************** if type(config.refuelSystem) == "number" then tankerConfigJson.refuelSystem = config.refuelSystem else Jtff_log.warn(string.format("Tanker refuelSystem is not a number, defaulting to %s", default_refuelSystem), parser_name) tankerConfigJson.refuelSystem = default_refuelSystem end -- ************************************************************************** -- tacan -- ************************************************************************** if type(config.tacan) == "table" then tankerConfigJson.tacan = ParseTacanConfigJson(config.tacan, parser_name) if next(tankerConfigJson.tacan) == nil then Jtff_log.error("Tanker tacan is not a valid TACAN object, skipping tanker configuration", parser_name) config.enable = false return config end else Jtff_log.error("Tanker tacan is not a table, skipping tanker configuration", parser_name) config.enable = false return config end -- ************************************************************************** -- radio -- ************************************************************************** if type(config.radio) == "table" then tankerConfigJson.radio = ParseRadioConfigJson(config.radio, parser_name) if next(tankerConfigJson.radio) == nil then Jtff_log.error("Tanker radio is not a valid Radio object, skipping tanker configuration", parser_name) config.enable = false return config end else Jtff_log.error("Tanker radio is not a table, skipping tanker configuration", parser_name) config.enable = false return config end -- ************************************************************************** -- fuelLowThreshold -- ************************************************************************** if type(config.fuelLowThreshold) == "number" then tankerConfigJson.fuelLowThreshold = config.fuelLowThreshold else Jtff_log.warn( string.format("Tanker fuelLowThreshold is not a number, defaulting to %d %%", default_fuelLowThreshold), parser_name) tankerConfigJson.fuelLowThreshold = default_fuelLowThreshold end -- ************************************************************************** -- callsign -- ************************************************************************** if type(config.callsign) == "table" then tankerConfigJson.callsign = ParseTankerCallsignConfigJson(config.callsign, parser_name) if next(tankerConfigJson.callsign) == nil then Jtff_log.error("Tanker callsign is not a valid Callsign object, skipping tanker configuration", parser_name) config.enable = false return config end else Jtff_log.error("Tanker callsign is not a table, skipping tanker configuration", parser_name) config.enable = false return config end -- ************************************************************************** -- squawk -- ************************************************************************** if type(config.squawk) == "table" then tankerConfigJson.squawk = ParseSquawkConfigJson(config.squawk, parser_name) else Jtff_log.warn("Tanker squawk is not a table, defaulting tanker squawk configuration", parser_name) tankerConfigJson.squawk = ParseSquawkConfigJson({}, parser_name) end Jtff_log.debug( string.format( "parsed Tanker config for %s Tanker, resulting config :\n%s", config.type or "", json:encode( tankerConfigJson, { indent = true } ) ), parser_name ) return tankerConfigJson end -- endregion TankerConfigFunctions -- region TankerFunctions --- Trigger a Tanker mission. -- @param #table objTanker Tanker object. function TriggerTankerMission(objTanker) objTanker.airwing:AddMission(objTanker.mission) Jtff_log.info( string.format( 'Tanker %s triggered', objTanker.customconfig.type ), "TANKER" ) end --- Generate a Tanker mission object. -- @param #TankerConfig tankerconfig Tanker configuration object. -- @param #boolean manageEscort Manage escort mission creation. -- @return #AUFTRAG tankermission Tanker mission object, #AIRWING airwing Airwing object. function GenerateTankerMission(tankerconfig, manageEscort) local generateEscortMission = false if manageEscort == true then generateEscortMission = true end local airwing = FindAirwingByAirbaseName(tankerconfig.airbaseName) local tankermission = nil if airwing == nil then Jtff_log.error( string.format( "Airwing not found for Airbase %s, skipping tanker mission generation", tankerconfig.airbaseName ), "TANKER" ) return nil,nil else local function isAnchorCoordValid(MGRSString, autospawn) if autospawn == true then return true end if #(UTILS.Split(MGRSString," ")) ~= 5 then return false end return true end local orbitCoord = {x=0, y=0, z=0} if (isAnchorCoordValid(tankerconfig.racetrack.coordinate, tankerconfig.autospawn) == true) then orbitCoord = COORDINATE:NewFromMGRSString(tankerconfig.racetrack.coordinate) else orbitCoord = AIRBASE:FindByName(tankerconfig.airbaseName):GetCoordinate() end tankermission = AUFTRAG:NewTANKER( orbitCoord, tankerconfig.racetrack.fl * 100, tankerconfig.racetrack.speed, tankerconfig.racetrack.heading, tankerconfig.racetrack.leg, tankerconfig.refuelSystem ) tankermission:SetDuration(tankerconfig.missionmaxduration*60) tankermission:SetReturnToLegion(true) tankermission:SetRequiredAssets(1) tankermission:SetName( string.format( "Tanker-%s", tankerconfig.type ) ) tankermission:SetRadio( tankerconfig.radio.freq, tankerconfig.radio.modulation ) tankermission:SetTACAN( tankerconfig.tacan.channel, tankerconfig.tacan.morse, nil, tankerconfig.tacan.band ) tankermission:SetEPLRS(true) tankermission:SetEmission(false) tankermission:SetVerbosity(JTFF_verbosity_levels[JTFF_LOGLEVEL]) tankermission.additionalData = { minEscort = tankerconfig.nbEscort, maxEscort = tankerconfig.nbEscort, tankerType = tankerconfig.type, tankerDuration = tankerconfig.missionmaxduration, callsign = tankerconfig.callsign, tankerTacan = tankerconfig.tacan, tankerRadio = tankerconfig.radio, tankerRacetrack = tankerconfig.racetrack, tankerFuelLowThreshold = tankerconfig.fuelLowThreshold, } function tankermission:OnAfterScheduled(From, Event, To) local tankerToEscort = self:GetOpsGroups()[1] local airwing = tankerToEscort:GetAirwing() tankerToEscort:SwitchCallsign( self.additionalData.callsign.name, self.additionalData.callsign.number ) tankerToEscort:SetFuelLowThreshold(self.additionalData.tankerFuelLowThreshold) tankerToEscort:SetFuelCriticalThreshold(self.additionalData.tankerFuelLowThreshold-10) tankerToEscort:SetFuelLowRefuel(true) tankerToEscort:SetFuelLowRTB(false) tankerToEscort:SetFuelCriticalRTB(true) if (Jtff_map_marker[tankerToEscort:GetName()]) then COORDINATE:RemoveMark(Jtff_map_marker[tankerToEscort:GetName()]) end if(self.additionalData.tankerTacan) then Jtff_map_marker[tankerToEscort:GetName()] = COORDINATE:NewFromMGRSString(self.additionalData.tankerRacetrack.coordinate):MarkToCoalition( string.format( 'OnDemand Tanker %s - TCN %i\nFL %i at %i knots\nFreq %.2f MHz\nOn station for %i minutes\nRacetrack : %i ° for %i nm', self.additionalData.tankerType, self.additionalData.tankerTacan.channel, UTILS.Round(self.additionalData.tankerRacetrack.fl , 0), self.additionalData.tankerRacetrack.speed, self.additionalData.tankerRadio.freq, self.additionalData.tankerDuration, self.additionalData.tankerRacetrack.heading, self.additionalData.tankerRacetrack.leg ), tankerToEscort:GetCoalition(), true, 'OnDemand Tanker %s is Activated' ) else Jtff_map_marker[tankerToEscort:GetName()] = COORDINATE:NewFromMGRSString(self.additionalData.tankerRacetrack.coordinate):MarkToCoalition( string.format( 'OnDemand Tanker %s\nFL %i at %i knots\nFreq %.2f MHz\nOn station for %i minutes\nRacetrack : %i ° for %i nm', self.additionalData.tankerType, UTILS.Round(self.additionalData.tankerRacetrack.fl , 0), self.additionalData.tankerRacetrack.speed, self.additionalData.tankerRadio.freq, self.additionalData.tankerDuration, self.additionalData.tankerRacetrack.heading, self.additionalData.tankerRacetrack.leg ), tankerToEscort:GetCoalition(), true, 'OnDemand Tanker %s is Activated' ) end function tankerToEscort:OnBeforeRTB(From, Event, To, Airbase, speed, holdspeed) COORDINATE:RemoveMark(Jtff_map_marker[self:GetName()]) end function tankerToEscort:OnAfterDead(From, Event, To) COORDINATE:RemoveMark(Jtff_map_marker[self:GetName()]) end function tankerToEscort:OnAfterElementDestroyed(From, Event, To, Element) COORDINATE:RemoveMark(Jtff_map_marker[self:GetName()]) end if self.additionalData.maxEscort > 0 and generateEscortMission == true then local escortMission = AUFTRAG:NewESCORT( tankerToEscort, {x=250, y=100, z=400}, 35, {'Air'} ) escortMission:SetReturnToLegion(true) escortMission:SetRequiredAssets(self.additionalData.minEscort, self.additionalData.maxEscort) escortMission:SetReinforce(3) escortMission:SetName(string.format("Escort-Tanker-%s", self.additionalData.tankerType)) escortMission:SetEPLRS(true) escortMission:SetEmission(true) escortMission:SetVerbosity(JTFF_verbosity_levels[JTFF_LOGLEVEL]) escortMission:SetFormation(ENUMS.Formation.FixedWing.EchelonRight.Close) escortMission:SetMissionAltitude(20000) escortMission:SetMissionWaypointRandomization(UTILS.NMToMeters(20)) escortMission:SetProhibitAfterburnerExecutePhase() escortMission:SetTime( 300, ( self.additionalData.tankerDuration * 60 ) + 1200 ) function escortMission:OnAfterScheduled(From, Event, To) for _, opsGroup in ipairs(self:GetOpsGroups()) do opsGroup:SetFuelLowThreshold(30) opsGroup:SetFuelCriticalThreshold(20) opsGroup:SetFuelLowRefuel(true) opsGroup:SetFuelLowRTB(false) opsGroup:SetFuelCriticalRTB(true) opsGroup:SetJettisonEmptyTanks(false) opsGroup:SetJettisonWeapons(false) end end airwing:AddMission(escortMission) end end return tankermission,airwing end end -- endregion TankerFunctions TankersArray = {} MenuCoalitionTankers = {} for _k, _coalition in pairs(coalition.side) do MenuCoalitionTankers[UTILS.GetCoalitionName(_coalition)] = MENU_COALITION:New(_coalition, "Tankers", MenuCoalition[UTILS.GetCoalitionName(_coalition)]) end local compteur = #TankersArray for _index, _currentTankerConfigObject in ipairs(TankersConfig) do local tankerconfig = ParseTankersConfigJson(_currentTankerConfigObject) if tankerconfig.enable == true then compteur = compteur + 1 Jtff_log.info( string.format( 'configuration Tanker %s: ', tankerconfig.type ), "TANKER" ) -- local templateGroup = UTILS.DeepCopy(GROUP:FindByName(tankerconfig.templateGroup)) -- templateGroup.name = string.format("Tanker-%s %s", tankerconfig.type, CreateSquawkString(tankerconfig.squawk)) -- FindAirwingByAirbaseName(tankerconfig.airbaseName):AddAsset(templateGroup,2) local tankermission, airwing = GenerateTankerMission(tankerconfig, true) if tankermission == nil or airwing == nil then Jtff_log.error( string.format( "Airwing not found for Airbase %s, skipping tanker Registration", tankerconfig.airbaseName ), "TANKER" ) else TankersArray[compteur] = { customconfig = tankerconfig, mission = tankermission, airwing = airwing, } if tankerconfig.autospawn == true then Jtff_log.info( string.format( 'autospawn Tanker %s', tankerconfig.type ), "TANKER" ) TriggerTankerMission(TankersArray[compteur]) -- TankersArray[compteur].airwing:AddMission(TankersArray[compteur].mission) else Jtff_log.info( string.format( 'OnDemand Tanker %s Registering', tankerconfig.type ), "TANKER" ) end end end end -- ***************************************************************************** -- ** OnDemand Tankers ** -- ********************************************************* function TriggerOnDemandTanker(type, askedDuration, askedFL, askedSpeed, askedAnchorCoord, askedOrbitHeading, askedOrbitLeg) if (TankersConfig) then for index, objTanker in ipairs(TankersArray) do if (objTanker.customconfig.type == type) then Jtff_log.debug( string.format( 'OnDemandTanker : Found type %s Tanker !', type ), "TANKER" ) if (askedAnchorCoord) then objTanker.customconfig.racetrack.coordinate = askedAnchorCoord:ToStringMGRS() end if (askedFL and askedFL > 0) then objTanker.customconfig.racetrack.fl = askedFL end if (askedSpeed and askedSpeed > 0) then objTanker.customconfig.racetrack.speed = askedSpeed end if (askedDuration ~= nil and askedDuration ~= 0) then objTanker.customconfig.missionmaxduration = askedDuration end if (askedOrbitHeading) then if (askedOrbitLeg) then --heading et Leg demandés objTanker.customconfig.racetrack.heading = askedOrbitHeading % 360 objTanker.customconfig.racetrack.leg = math.max(10, askedOrbitLeg) else --heading demandé et leg non demandé objTanker.customconfig.racetrack.heading = askedOrbitHeading % 360 objTanker.customconfig.racetrack.leg = 30 end end local oldMission = objTanker.airwing:GetMissionByID(objTanker.mission.auftragsnummer) if oldMission then Jtff_log.info( string.format( 'Mission %s already registered to Airwing %s, rerouting now', objTanker.mission:GetName(), objTanker.airwing:GetName() ), "TANKER" ) local objOpsGroup = objTanker.mission:GetOpsGroups()[1] Jtff_log.debug( string.format( 'OpsGroup executing previous mission %s ID=%d is %s', oldMission:GetName(), oldMission.auftragsnummer, objOpsGroup:GetName() ), "TANKER" ) objTanker.mission, objTanker.airwing = GenerateTankerMission(objTanker.customconfig, false) Jtff_log.debug( string.format( 'Next Mission generated %s ID=%d for airwing %s', objTanker.mission:GetName(), objTanker.mission.auftragsnummer, objTanker.airwing:GetName() ), "TANKER" ) objOpsGroup:AddMission(objTanker.mission) Jtff_log.debug( string.format( 'Added next Mission %s generated ID=%d to group %s : %d mission(s) active(s)', objTanker.mission:GetName(), objTanker.mission.auftragsnummer, objOpsGroup:GetName(), #(objOpsGroup.missionqueue) ), "TANKER" ) -- objTanker.airwing:AddMission(objTanker.mission) objTanker.mission:AssignLegion(objTanker.airwing) table.insert(objTanker.airwing.missionqueue, objTanker.mission) oldMission:SetTime(nil, 5) Jtff_log.debug( string.format( 'Stopped old Mission %s (ID=%d) from group %s : %d mission(s) active(s)', oldMission:GetName(), oldMission.auftragsnummer, objOpsGroup:GetName() or "unknown", #(objOpsGroup.missionqueue or {}) ), "TANKER" ) else Jtff_log.info( string.format( 'Mission %s not registered to Airwing %s, spawning now', objTanker.mission:GetName(), objTanker.airwing:GetName() ), "TANKER" ) objTanker.mission, objTanker.airwing = GenerateTankerMission(objTanker.customconfig, true) objTanker.airwing:AddMission(objTanker.mission) end end end end end --local RestrToCoal = nil local TankerMarkHandler = {} function TankerMarkHandler:onEvent(event) local CmdSymbol = "-" if event.id == 25 then --trigger.action.outText(" ", 0, true) elseif (event.id == 27 and string.find(event.text, CmdSymbol)) then --if (event.coalition == RestrToCoal or RestrToCoal == nil) then local full local remString local cmd local param1 local param1Start local param2 local param2Start local param3 local param3Start local param4 local param4Start local param5 local param5Start local param6 local param6Start local mcoord = COORDINATE:New(event.pos.x, event.pos.y, event.pos.z) full = string.sub(event.text, 2) if (string.find(full, CmdSymbol)) then param1Start = string.find(full, CmdSymbol) cmd = string.sub(full, 0, param1Start - 1) remString = string.sub(full, param1Start + 1) if (string.find(remString, CmdSymbol)) then param2Start = string.find(remString, CmdSymbol) param1 = string.sub(remString, 0, param2Start - 1) remString = string.sub(remString, param2Start + 1) if string.find(remString, CmdSymbol) then param3Start = string.find(remString, CmdSymbol) param2 = string.sub(remString, 0, param3Start - 1) remString = string.sub(remString, param3Start + 1) if string.find(remString, CmdSymbol) then param4Start = string.find(remString, CmdSymbol) param3 = string.sub(remString, 0, param4Start - 1) remString = string.sub(remString, param4Start + 1) if string.find(remString, CmdSymbol) then param5Start = string.find(remString, CmdSymbol) param4 = string.sub(remString, 0, param5Start - 1) remString = string.sub(remString, param5Start + 1) if string.find(remString, CmdSymbol) then param6Start = string.find(remString, CmdSymbol) param5 = string.sub(remString, 0, param6Start - 1) param6 = string.sub(remString, param6Start + 1) else param5 = remString end else param4 = remString end else param3 = remString end else param2 = remString end else param1 = remString end else cmd = full end if Log_levels[JTFF_LOGLEVEL] <= Log_levels['debug'] then trigger.action.outText("Full Text = " .. full, 10) trigger.action.outText("Command = " .. cmd, 10) if param1 ~= nil then trigger.action.outText("type = " .. param1, 10) end if param2 ~= nil then trigger.action.outText("Duration = " .. param2, 10) end if param3 ~= nil then trigger.action.outText("FlightLevel = " .. param3, 10) end if param4 ~= nil then trigger.action.outText("Speed = " .. param4, 10) end if param5 ~= nil then trigger.action.outText("OrbitHeading = " .. param5, 10) end if param6 ~= nil then trigger.action.outText("OrbitLeg = " .. param6, 10) end end if string.find(cmd, "tanker") then if Log_levels[JTFF_LOGLEVEL] <= Log_levels['debug'] then trigger.action.outText("DEBUG: On Demand Tanker Started!", 10) end TriggerOnDemandTanker( param1, tonumber(param2), tonumber(param3), tonumber(param4), mcoord, tonumber(param5), tonumber(param6) ) end end end world.addEventHandler(TankerMarkHandler)