--[[ 

	DCS - OBOE - Blind Bombing System V1.06
	by Draken35

	Requires d35Lib v1.01
	
	Requires MIST 4.5 <https://github.com/mrSkortch/MissionScriptingTools/tree/master>
	
	Credit to Davis Claiborne for his math lib for Lua <https://github.com/davisdude/mlib>

   
    Version history
	1.00		09/22/2021	Initial release.
	1.01		09/26/2021	- Fixed 10 mins warning.
							- changed warnigs to target instead of  release.
							- oboe.SelectTarget() returns number of selected target
							- added bomb impact tracking
	1.02		09/28/2021  - added new sounds
							- Modified distance to target signals 
								- 4xA = 10 minutes
								- 4xB = 8 minutes
								- 4xC = 6 minutes
								- 4xD = 3 Minutes
							- Added distance to Cat track	
								- X = 20 miles
								- Y = 10 Miles
								- Z = 5 miles
							- changed default accuracy to 8 meters (~ times 2 = wingspan of the Mosquito)
							- improved sound tutorial
							- improved accuracy and reliability of the signals
							- added optional parameter to SelectTarget to select specific target 
							 (if nill or greater than the number of targets in the list, then the target will be randomly selected)
							- added subtitles to the Morse sounds. Default is off
	1.03		09/30/2021	- replaced variable position polling frequency by a fixed one 
							- reworked sound system
							- added default values for release altitude, ground speed and briefing in the target list.
							  This means, that you only need to supply that target zone name. The script will generate the rest
							- Added track intercept mark
	1.04		10/01/2021	- Restricted Mouse signals to the vicinity of the Cat track
	1.05		10/05/2021	- Requires d35lib v1.01
							- Added historically accurated 7.5 seconds release signal (new sound, ile name 5t7.wav - triggers need to be updated)
							  Thanks Retnek!!!
							- Added headings to the interception and warning points (perpendicular to CAT track) as reference to the briefing
	1.06		10/08/2021	- Changed calculation of Cat track to make the release vector tangential to the track		
							- Improve horizon LOS calculations
							- Added path guidance information for training (overrides sub titles). Can be disabled in the script
								XXX < YYYYm> ZZZ  SIGNAL
								where XXX is the tangential heding to the path at current position
								      YYYY is the distance in meters to the center of the path, max display is +/-999m 
									  ZZZ delta between current heading and tangential . 0 = on path
									  SIGNAL = mouse or cat signal:
										miles 20 | 10 | 5 : Signal from CAT distance to path
										10|8|6|3 mins : Signal from MOUSE, minutes to target
										Release : well...Guess...(remember, pickle at the end of the SOUND signal)
--]]

trigger.action.outText( 'OBOE Script  - v1.06', 2 )

--############################################################################################################################################
oboe  = {}

-- #############   Configuration section   ############# 

oboe.ReleaseSignalDuration = 7.5 		-- seconds. Duration of the sound file for the release warning. It is taken into account in the calculation for the release point
oboe.Cat 		= 'Cat Station'		-- Circular Zone name for the Cat Station
oboe.Mouse 		= 'Mouse Station'	-- Circular Zone name for the Cat Station
oboe.MarkPoints = true				-- Marks in F10 map visible to all. For troubleshoting/testing
oboe.wingspan = 16.5 --meters (FB mk VI)
oboe.TrainingAidsInMenu = true		-- displays the path guidance trainning aid in the settings menu

-- Target List
oboe.TargetList = {}
oboe.TargetList[1] = {zone = 'target1',  -- circular zone name
					 Release_Alt = 6096,   -- in meters 
					 Ground_Speed = 463,  -- in kph
					 Approach= 'CW',  -- CW Clockwise /  CCW Counter Clockwise .. unknows considered CW
					 Briefing = 'Target is an industrial complex, 2 miles east of Douai.\nApproach fron the north. Fly east of Veurne to intercept the path'} -- Briefing text

oboe.TargetList[2] = {zone = 'target1',  -- circular zone name
					 Release_Alt = 6096,   -- in meters 
					 Ground_Speed = 402.34,  -- in kph
					 Approach= 'CCW',  -- CW Clockwise /  CCW Counter Clockwise .. unknows considered CW
					 Briefing = 'Target is an industrial complex, 2 miles east of Douai.\nApproach fron the south. Fly east of Doullens to intercept the path'} -- Briefing text
					 
oboe.TargetList[3] = {zone = 'target2',  -- circular zone name
					 Release_Alt = 5486,   -- in meters 
					 Ground_Speed = 386.24,  -- in kph
					 Approach= 'CW',  -- CW Clockwise /  CCW Counter Clockwise .. unknows considered CW
					 Briefing = 'Target is an industrial complex, 2 miles south east of Bruay-la-Bussiere.\nApproach fron the north. Fly east of Saint-Omer to intercept the path'} -- Briefing text					 

oboe.TargetList[4] = {zone = 'target2',  -- circular zone name
					 Release_Alt = 5486,   -- in meters 
					 Ground_Speed = 386.24,  -- in kph
					 Approach= 'CCW',  -- CW Clockwise /  CCW Counter Clockwise .. unknows considered CW
					 Briefing = 'Target is an industrial complex, 2 miles south east of Bruay-la-Bussiere.\nApproach fron the south. Fly east of Doullens to intercept the path'} -- Briefing text		

oboe.TargetList[5] = {zone = 'target3'  -- circular zone name
					--	,Release_Alt = 3048   -- in meters 
						-- rest of the params as default
					 }


-- Sound files ---------------------------------------------------------------------------------------------------------------------------------------------------------------

			oboe.soundfile_4a = '4a.wav'																							
			oboe.soundfile_4b = '4b.wav'																					
			oboe.soundfile_4c = '4c.wav'
			oboe.soundfile_4d = '4d.wav'
			oboe.soundfile_5t = '5t7.wav'
			oboe.soundfile_x = 'x.wav'			
			oboe.soundfile_y = 'y.wav'
			oboe.soundfile_z = 'z.wav'
			oboe.soundfile_OT = 'on_track.wav'
			oboe.soundfile_dit = 'dit.wav'
			oboe.soundfile_dat = 'dat.wav'
			oboe.soundfile_sil = ''
			
	


-- ############# end Configuration section ############# 	
	
--############################################################################################################################################
-- init
oboe.eventHandler = {}
oboe.PlaySound = {}

oboe.CatTrack = 0 -- in meters (distance from Cat Station to Target
oboe.catZone = {}
oboe.mouseZone = {}
oboe.targetZone = {}

oboe.target = {}
oboe.EarthRadius = 6378137 -- meters


oboe.mouse10_dis = 0 -- meters, 10 minutes from target
oboe.mouse8_dis = 0 -- meters, 8 minutes from target
oboe.mouse6_dis = 0 -- meters, 6 minutes from target
oboe.mouse3_dis = 0 -- meters, 3 minutes from target
oboe.mouseR_dis = 0 -- meters, bomb release
oboe.mouseR_Heading = 0 
oboe.mouse10_pend = {}
oboe.mouse8_pend = {}
oboe.mouse6_pend = {}
oboe.mouse3_pend = {}
oboe.mouseR_pend = {}
oboe.cat20_pend = {}
oboe.cat10_pend = {}
oboe.cat5_pend = {}
oboe.last_pos = {}


oboe.briefing = ''
oboe.accuracy  	= {}  				-- In meters (radius)
oboe.BriefingUnits = {} 			-- 0 = Metric, 1 = Imperial (knots), 2 (or anything else) = Imperial (mph) 
oboe.Results = {}
oboe.subTitles = {}
oboe.trainingAids = {}

oboe.Polling_Int = 0.05 -- seconds 
oboe.currently_PlayingSound = {}

--############################################################################################################################################
function oboe.SelectTarget(_arg)
	--trigger.action.outText( 'OBOE Script  - Selecting Target', 20 )

	local _targetCount = 0
	
	for _,_ in pairs(oboe.TargetList) do
		_targetCount = _targetCount + 1
	end

	local tt
	if _arg == nil or _arg > _targetCount then
		tt = math.random(_targetCount)
	else 
		tt = _arg
	end
	oboe.target = oboe.TargetList[tt]
	

	oboe.catZone = trigger.misc.getZone(oboe.Cat)
	oboe.mouseZone = trigger.misc.getZone(oboe.Mouse)
	oboe.targetZone = trigger.misc.getZone(oboe.target.zone)
	
	local catCenter = {x=oboe.catZone.point.x ,y=oboe.catZone.point.z}
	local mouseCenter = {x=oboe.mouseZone.point.x ,y=oboe.mouseZone.point.z}
	local targetCenter = {x=oboe.targetZone.point.x , y=oboe.targetZone.point.z}
	
	-- Flight profile defaults - GS
	if oboe.target.Ground_Speed == nil then
		 oboe.target.Ground_Speed = 418.94 -- kph = 260mph
	end

	
	local targetHeight =  land.getHeight(targetCenter) -- 66ft = 20m


	-- do the math
	-- bomb flight time
	local h = oboe.target.Release_Alt - targetHeight -- 4552M
	local t = math.sqrt(2*h/9.81)
	local s = oboe.target.Ground_Speed * 1000 / 3600 -- m/sec release speed 
	-- release point & cat track
	local r = s*(t+oboe.ReleaseSignalDuration) -- m  release distance
	local td  = d35lib.getDistance(oboe.catZone.point,oboe.targetZone.point) 
	oboe.CatTrack = math.sqrt((td*td)-(r*r))--686008ft = 209095m 

	-- Flight profile defaults 
	if oboe.target.Release_Alt == nil then
		-- 2000 feet above horizon_distance rounded
		oboe.target.Release_Alt  = math.floor((oboe.CatTrack*oboe.CatTrack)/(2*oboe.EarthRadius)/100)*100 + 2000
	end
	
	if oboe.target.Approach == nil then
		 local a = math.random(2)
		 if a == 1 then
			oboe.target.Approach = 'CCW'
		 else
			oboe.target.Approach = 'CW'
		 end
	end
	

	-- release point
	local releasePoints = oboe.getCircleCircleIntersection(catCenter.x,catCenter.y,oboe.CatTrack,targetCenter.x,targetCenter.y, r)
	
	local releasePoint = oboe.getPoint(oboe.target.Approach, catCenter ,{x=releasePoints[2],y=releasePoints[3]} , {x=releasePoints[4],y=releasePoints[5]} )
	oboe.mouseR_Heading = math.floor(mist.utils.toDegree(mist.utils.getHeadingPoints(mist.utils.makeVec3(releasePoint),mist.utils.makeVec3(targetCenter),true) )+0.5)
	oboe.mouseR_dis = d35lib.getDistance(oboe.mouseZone.point,mist.utils.makeVec3(releasePoint)) 
	oboe.mouseR_HeadingP = math.floor(mist.utils.toDegree(mist.utils.getHeadingPoints(mist.utils.makeVec3(catCenter),mist.utils.makeVec3(releasePoint),true) )+0.5)
		
	if oboe.MarkPoints then
		trigger.action.markToAll(100 , 'target' , mist.utils.makeVec3(targetCenter) , true, '')
		trigger.action.markToAll(101 , 'release point' , mist.utils.makeVec3(releasePoint) , true, '')
	end

	-- 3 minutes warning point
	local w3 = s*180
	local w3Points = oboe.getCircleCircleIntersection(catCenter.x,catCenter.y,oboe.CatTrack,targetCenter.x,targetCenter.y, w3)
	local w3Point = oboe.getPoint(oboe.target.Approach, catCenter ,{x=w3Points[2],y=w3Points[3]} , {x=w3Points[4],y=w3Points[5]} )
	oboe.mouse3_dis = d35lib.getDistance(oboe.mouseZone.point,mist.utils.makeVec3(w3Point)) 
	oboe.w3_Heading = math.floor(mist.utils.toDegree(mist.utils.getHeadingPoints(mist.utils.makeVec3(catCenter),mist.utils.makeVec3(w3Point),true) )+0.5)

	if oboe.MarkPoints then
		trigger.action.markToAll(102 , '3 minutes point' , mist.utils.makeVec3(w3Point) , true, '')
	end
	
	-- 6 minutes warning point
	local w6 = s*360
	local w6Points = oboe.getCircleCircleIntersection(catCenter.x,catCenter.y,oboe.CatTrack,targetCenter.x,targetCenter.y, w6)
	local w6Point = oboe.getPoint(oboe.target.Approach, catCenter ,{x=w6Points[2],y=w6Points[3]} , {x=w6Points[4],y=w6Points[5]} )
	oboe.mouse6_dis = d35lib.getDistance(oboe.mouseZone.point,mist.utils.makeVec3(w6Point)) 
	oboe.w6_Heading = math.floor(mist.utils.toDegree(mist.utils.getHeadingPoints(mist.utils.makeVec3(catCenter),mist.utils.makeVec3(w6Point),true) )+0.5)
	
	if oboe.MarkPoints then
		trigger.action.markToAll(103 , '6 minutes point' , mist.utils.makeVec3(w6Point) , true, '')
	end

	-- 8 minutes warning point
	local w8 = s*480
	local w8Points = oboe.getCircleCircleIntersection(catCenter.x,catCenter.y,oboe.CatTrack,targetCenter.x,targetCenter.y, w8)
	local w8Point = oboe.getPoint(oboe.target.Approach, catCenter ,{x=w8Points[2],y=w8Points[3]} , {x=w8Points[4],y=w8Points[5]} )
	oboe.mouse8_dis = d35lib.getDistance(oboe.mouseZone.point,mist.utils.makeVec3(w8Point)) 
	oboe.w8_Heading = math.floor(mist.utils.toDegree(mist.utils.getHeadingPoints(mist.utils.makeVec3(catCenter),mist.utils.makeVec3(w8Point),true) )+0.5)

	if oboe.MarkPoints then
		trigger.action.markToAll(104 , '8 minutes point' , mist.utils.makeVec3(w8Point) , true, '')
	end
	
	-- 10 minutes warning point
	local w10 = s*600

	local w10Points = oboe.getCircleCircleIntersection(catCenter.x,catCenter.y,oboe.CatTrack,targetCenter.x,targetCenter.y, w10)
	local w10Point = oboe.getPoint(oboe.target.Approach, catCenter ,{x=w10Points[2],y=w10Points[3]} , {x=w10Points[4],y=w10Points[5]} )
	oboe.mouse10_dis =  d35lib.getDistance(oboe.mouseZone.point,mist.utils.makeVec3(w10Point)) 
	oboe.w10_Heading = math.floor(mist.utils.toDegree(mist.utils.getHeadingPoints(mist.utils.makeVec3(catCenter),mist.utils.makeVec3(w10Point),true) )+0.5)

	if oboe.MarkPoints then
		trigger.action.markToAll(105 , '10 minutes point' , mist.utils.makeVec3(w10Point) , true, '')
	end	
	
	
	-- track intercept for briefing
	local wINT = s*720

	local wINTPoints = oboe.getCircleCircleIntersection(catCenter.x,catCenter.y,oboe.CatTrack,targetCenter.x,targetCenter.y, wINT)
	local wINTPoint = oboe.getPoint(oboe.target.Approach, catCenter ,{x=wINTPoints[2],y=wINTPoints[3]} , {x=wINTPoints[4],y=wINTPoints[5]} )
	oboe.wINTPoint_Heading = math.floor(mist.utils.toDegree(mist.utils.getHeadingPoints(mist.utils.makeVec3(catCenter),mist.utils.makeVec3(wINTPoint),true) )+0.5)

	if oboe.MarkPoints then
		trigger.action.markToAll(106 , 'Initial Point' , mist.utils.makeVec3(wINTPoint) , true, '')
	end	
	
	
	-- add magvar
	local magVar = 0--d35lib.getMagVar() until I figure out how to get it based on dates
	local perp = 90
	oboe.mouseR_Heading = oboe.mouseR_Heading - magVar
	if oboe.target.Approach == 'CCW' then
		perp = -90
	end
	oboe.wINTPoint_Heading =  oboe.AngTo360(oboe.wINTPoint_Heading+magVar + perp)
	oboe.w10_Heading =  oboe.AngTo360(oboe.w10_Heading +magVar + perp)
	oboe.w8_Heading =  oboe.AngTo360(oboe.w8_Heading +magVar + perp)
	oboe.w6_Heading =  oboe.AngTo360(oboe.w6_Heading +magVar + perp)
	oboe.w3_Heading =  oboe.AngTo360(oboe.w3_Heading +magVar + perp)
	oboe.mouseR_HeadingP =  oboe.AngTo360(oboe.mouseR_HeadingP +magVar + perp)
	
	-- add corrections for > 360 and < 0
	-- default briefing
	if oboe.target.Briefing == nil then
		local lat, lon =  coord.LOtoLL(wINTPoint)
		local latT, lonT =  coord.LOtoLL(targetCenter)
		oboe.target.Briefing = 'Fly to '..mist.tostringLL(lat,lon,1,true)..' and turn '..oboe.target.Approach..' towards \nthe target at '..mist.tostringLL(latT,lonT,1,true)
	end
	
	return r -- selected target
	
end
--############################################################################################################################################
function oboe.eventHandler:onEvent(_eventDCS)

    if _eventDCS == nil or _eventDCS.initiator == nil then
        return true
    end

    local status, err = pcall(function(_event)


	-- ### player entered unit   _event.id == 15   
    if _event.id == 15 then --player entered unit

		oboe.EnterUnitEvent(_event)
			
	elseif (_event.id == world.event.S_EVENT_SHOT) and _event.initiator:getPlayerName()  then
		
		oboe.BombsDroppedEvent(_event)
				
	end -- if _event.id == 15 then --player entered unit
  -- ### End event processing

        return true
    end, _eventDCS)

    if (not status) then
        env.error(string.format("oboe: Error while handling event %s", err),false)
		trigger.action.outText( string.format("oboe: Error while handling event %s", err), 30 )
    end
end
--############################################################################################################################################
function oboe.EnterUnitEvent(_event)
	_unitName = _event.initiator:getName()
	oboe.BriefingUnits[_unitName] = 2
	oboe.accuracy[_unitName] = 16.5 * 2 -- FB mk VI wingspan
	oboe.Results[_unitName] = {}
	oboe.PlaySound[_unitName] = false
	oboe.subTitles[_unitName] = false 
	oboe.trainingAids[_unitName] = false 

	oboe.currently_PlayingSound[_unitName] = {
						source = 'cat'
					,	sound	= 'pause'
					,	dur_left = 0
					,	duration = 0
	}
	
	missionCommands.addCommandForGroup(_event.initiator:getGroup():getID(),"OBOE: Mission Briefing ", nil,  oboe.missionBriefing, _unitName)
	missionCommands.addCommandForGroup(_event.initiator:getGroup():getID(),"OBOE: toggle on/off ", nil,  oboe.toggle, _unitName)
	missionCommands.addCommandForGroup(_event.initiator:getGroup():getID(),"OBOE: reset approach ", nil,  oboe.ResetApproach, _unitName)
	missionCommands.addCommandForGroup(_event.initiator:getGroup():getID(),"OBOE: Report results ", nil,  oboe.ReportResults, _unitName)
	local settings_root =  missionCommands.addSubMenuForGroup(_event.initiator:getGroup():getID(), "OBOE: Settings")
	missionCommands.addCommandForGroup(_event.initiator:getGroup():getID(),"Toggle sub titles on/off ", settings_root,  oboe.toggleSubTitles,_unitName)
	missionCommands.addCommandForGroup(_event.initiator:getGroup():getID(),"Briefing units: set to Metric ", settings_root,  oboe.SetBriefingUnits, {_unitName,0})
	missionCommands.addCommandForGroup(_event.initiator:getGroup():getID()," -     set to Imperial(knots) ", settings_root,  oboe.SetBriefingUnits, {_unitName,1})
	missionCommands.addCommandForGroup(_event.initiator:getGroup():getID(),"-      set units to Imperial(mph) ", settings_root,  oboe.SetBriefingUnits, {_unitName,2})
	missionCommands.addCommandForGroup(_event.initiator:getGroup():getID(),"Beam width/accuracy: wingspan * 2", settings_root,  oboe.SetAccuracy, {_unitName,oboe.wingspan*2}) -- 108ft
	missionCommands.addCommandForGroup(_event.initiator:getGroup():getID(),"-         wingspan * 4", settings_root,  oboe.SetAccuracy, {_unitName,oboe.wingspan*3 }) -- 216ft
	missionCommands.addCommandForGroup(_event.initiator:getGroup():getID(),"-         wingspan * 6", settings_root,  oboe.SetAccuracy, {_unitName,100}) -- 432ft
	if oboe.TrainingAidsInMenu  then
		missionCommands.addCommandForGroup(_event.initiator:getGroup():getID(),"toggle Training Aids on/off", settings_root,  oboe.toggleTrainingAids,_unitName) 
	end
	local tutotial_root =  missionCommands.addSubMenuForGroup(_event.initiator:getGroup():getID(), "OBOE: Sound 'tutorial' Cat")
	missionCommands.addCommandForGroup(_event.initiator:getGroup():getID(),"Short of path ", tutotial_root,  oboe.tutorial, {_unitName,'dit'})
	missionCommands.addCommandForGroup(_event.initiator:getGroup():getID(),"Long of path ", tutotial_root,  oboe.tutorial, {_unitName,'dat'})
	missionCommands.addCommandForGroup(_event.initiator:getGroup():getID(),"On path ", tutotial_root,  oboe.tutorial, {_unitName,'ontrack'})
	missionCommands.addCommandForGroup(_event.initiator:getGroup():getID(),"20 miles from path ", tutotial_root,  oboe.tutorial, {_unitName,'20track'})
	missionCommands.addCommandForGroup(_event.initiator:getGroup():getID(),"10 miles from path ", tutotial_root,  oboe.tutorial, {_unitName,'10track'})
	missionCommands.addCommandForGroup(_event.initiator:getGroup():getID(),"5 miles from path ", tutotial_root,  oboe.tutorial, {_unitName,'5track'})
	local tutotial_root1 =  missionCommands.addSubMenuForGroup(_event.initiator:getGroup():getID(), "OBOE: Sound 'tutorial' Mouse")
	missionCommands.addCommandForGroup(_event.initiator:getGroup():getID(),"10 minutes warning ", tutotial_root1,  oboe.tutorial, {_unitName,'w10'})
	missionCommands.addCommandForGroup(_event.initiator:getGroup():getID(),"8 minutes warning ", tutotial_root1,  oboe.tutorial, {_unitName,'w8'})
	missionCommands.addCommandForGroup(_event.initiator:getGroup():getID(),"6 minutes warning ", tutotial_root1,  oboe.tutorial, {_unitName,'w6'})
	missionCommands.addCommandForGroup(_event.initiator:getGroup():getID(),"3 minutes warning ", tutotial_root1,  oboe.tutorial, {_unitName,'w3'})
	missionCommands.addCommandForGroup(_event.initiator:getGroup():getID(),"Bomb release signal ", tutotial_root1,  oboe.tutorial, {_unitName,'rel'})
	oboe.ResetApproach(_unitName)

	return true
end
--############################################################################################################################################
function oboe.toggle(_unitName)
	local params = {}

	params.unitName = _unitName
	local _unit = Unit.getByName(_unitName)
	local _groupID = _unit:getGroup():getID()
	 
	
	if not oboe.PlaySound[_unitName] then 
		params.TA_LS = ''
		params.TA_LT = timer.getTime()
		oboe.PlaySound[_unitName] = true
		trigger.action.outTextForGroup(_groupID,'OBOE is On',20,false)
		oboe.last_pos[_unitName]  =  _unit:getPoint()
		timer.scheduleFunction(oboe.Guide, params,  timer.getTime() +oboe.Polling_Int) 
	else
		trigger.action.outTextForGroup(_groupID,'OBOE is off',20,false)
		oboe.PlaySound[_unitName] = false
	end
	return true
end
--############################################################################################################################################
function oboe.Guide(_params,time)	
	local _unitName = _params.unitName
    local _unit = Unit.getByName(_unitName)
	local _groupID = _unit:getGroup():getID()
--	trigger.action.outText( '...'.._params.unitName, 1 )
	local _sound_from_CAT = false
	local _sound_from_Mouse = false
	local _msg = ""
	
	
	if oboe.PlaySound[_unitName]  then
		local _time = 0.50
		local _delta = 0
		local _soundfile = ''
		local _interrupt = false
		local _newSound = {}
		local catCenter = {x=oboe.catZone.point.x ,y=oboe.catZone.point.z}
		local mouseCenter = {x=oboe.mouseZone.point.x ,y=oboe.mouseZone.point.z}
		
		--calculate distance from Cat & Mouse
		local _current_pos = _unit:getPoint()
		local Mouse_distance_cp = d35lib.getDistance(oboe.mouseZone.point,_current_pos)
		local CAT_distance_cp = d35lib.getDistance(oboe.catZone.point,_current_pos)
		local horizon_distance_cat = math.sqrt(2*oboe.EarthRadius*(_unit:getPosition().p.y+land.getHeight(catCenter) + 10))
		local horizon_distance_mouse = math.sqrt(2*oboe.EarthRadius*(_unit:getPosition().p.y+land.getHeight(mouseCenter) + 10))
		local _dist = oboe.CatTrack - CAT_distance_cp
	
		local Mouse_distance_lp = d35lib.getDistance(oboe.mouseZone.point,oboe.last_pos[_unitName])
		local CAT_distance_lp = d35lib.getDistance(oboe.catZone.point,oboe.last_pos[_unitName])
		
		local mouse_closer = (Mouse_distance_cp < Mouse_distance_lp)
		local cat_closer = (CAT_distance_cp < CAT_distance_lp)
		


		if oboe.mouse10_pend[_unitName] and math.abs(_dist) <= 5000 and (
			math.abs(oboe.mouse10_dis - Mouse_distance_cp) <= oboe.accuracy[_params.unitName] 		
		) then
			oboe.mouse10_pend[_unitName] = false
			_msg ="10 minutes"
			_interrupt = true
			_newSound.source = 'mouse'
			_newSound.sound = 'mouse10'
			_newSound.duration = 3
			_newSound.dur_left = _newSound.duration
			_soundfile = oboe.soundfile_4a 																						

		elseif oboe.mouse8_pend[_unitName]and math.abs(_dist) <= 5000	and (
				math.abs(oboe.mouse8_dis - Mouse_distance_cp) <= oboe.accuracy[_params.unitName] 
		) then
			oboe.mouse8_pend[_unitName] = false
			oboe.mouse10_pend[_unitName] = false
			_msg ="8 minutes"
			_interrupt = true
			_newSound.source = 'mouse'
			_newSound.sound = 'mouse8'
			_newSound.duration = 3
			_newSound.dur_left = _newSound.duration
			_soundfile = oboe.soundfile_4b 																				

		elseif oboe.mouse6_pend[_unitName] and math.abs(_dist) <= 5000 	and (
			math.abs(oboe.mouse6_dis - Mouse_distance_cp) <= oboe.accuracy[_params.unitName] 
		) then		

			oboe.mouse6_pend[_unitName] = false
			oboe.mouse8_pend[_unitName] = false
			oboe.mouse10_pend[_unitName] = false
			_msg ="6 minutes"
			_interrupt = true
			_newSound.source = 'mouse'
			_newSound.sound = 'mouse6'
			_newSound.duration = 3
			_newSound.dur_left = _newSound.duration
			_soundfile = oboe.soundfile_4c 			

		elseif oboe.mouse3_pend[_unitName]and math.abs(_dist) <= 5000 and (
			math.abs(oboe.mouse3_dis - Mouse_distance_cp) <= oboe.accuracy[_params.unitName] 
		) then			

			oboe.mouse3_pend[_unitName] = false
			oboe.mouse6_pend[_unitName] = false
			oboe.mouse8_pend[_unitName] = false
			oboe.mouse10_pend[_unitName] = false
			_msg ="3 minutes"
			_interrupt = true
			_newSound.source = 'mouse'
			_newSound.sound = 'mouse3'
			_newSound.duration = 3
			_newSound.dur_left = _newSound.duration
			_soundfile = oboe.soundfile_4d 		

		elseif oboe.mouseR_pend[_unitName]and math.abs(_dist) <= 500 and (
			math.abs(oboe.mouseR_dis - Mouse_distance_cp) <= oboe.accuracy[_params.unitName] 
		) then	

			oboe.mouseR_pend[_unitName] = false
			oboe.mouse3_pend[_unitName] = false
			oboe.mouse6_pend[_unitName] = false
			oboe.mouse8_pend[_unitName] = false
			oboe.mouse10_pend[_unitName] = false
			_msg ="Bomb release!"
			_interrupt = true
			_newSound.source = 'mouse'
			_newSound.sound = 'mouseR'
			_newSound.duration = oboe.ReleaseSignalDuration + 0.2
			_newSound.dur_left = _newSound.duration
			_soundfile = oboe.soundfile_5t 				

		elseif oboe.cat20_pend[_unitName] and (
			(cat_closer and (oboe.CatTrack - 32187 >= CAT_distance_cp and oboe.CatTrack - 32187 <= CAT_distance_lp )) or
			(not cat_closer and (oboe.CatTrack - 32187 >= CAT_distance_lp and oboe.CatTrack - 32187  <=  CAT_distance_cp ) )	
		) then
			oboe.cat20_pend[_unitName] = false
			_msg ="20 miles from track"
			_interrupt = true
			_newSound.source = 'cat'
			_newSound.sound = 'cat20'
			_newSound.duration = 2
			_newSound.dur_left = _newSound.duration
			_soundfile = oboe.soundfile_x 					

		elseif oboe.cat10_pend[_unitName] and (
			(cat_closer and (oboe.CatTrack - 16093 >= CAT_distance_cp and oboe.CatTrack - 16093 <= CAT_distance_lp )) or
			(not cat_closer and (oboe.CatTrack - 16093 >= CAT_distance_lp and oboe.CatTrack - 16093  <=  CAT_distance_cp ) )	
		) then
			oboe.cat10_pend[_unitName] = false
			oboe.cat20_pend[_unitName] = false
			_msg ="10 miles from track"
			_interrupt = true
			_newSound.source = 'cat'
			_newSound.sound = 'cat10'
			_newSound.duration = 2
			_newSound.dur_left = _newSound.duration
			_soundfile = oboe.soundfile_y 
			
		elseif oboe.cat5_pend[_unitName] and (
			(cat_closer and (oboe.CatTrack - 8047 >= CAT_distance_cp and oboe.CatTrack - 8047 <= CAT_distance_lp )) or
			(not cat_closer and (oboe.CatTrack - 8047 >= CAT_distance_lp and oboe.CatTrack - 8047  <=  CAT_distance_cp ) )	
		) then

			oboe.cat5_pend[_unitName] = false
			oboe.cat10_pend[_unitName] = false
			oboe.cat20_pend[_unitName] = false
			_msg ="5 miles from track"
			_interrupt = true
			_newSound.source = 'cat'
			_newSound.sound = 'cat5'
			_newSound.duration = 2
			_newSound.dur_left = _newSound.duration
			_soundfile = oboe.soundfile_z
						
		   
		end
		
		local _pause = math.floor((((math.abs(_dist)/100)*oboe.Polling_Int))*100)/100



		if not _interrupt then
			if math.abs(_dist) <= oboe.accuracy[_params.unitName] then
				_newSound.source = 'cat'
				_newSound.sound = 'catT'
				_newSound.duration = 1.00
				_newSound.dur_left = _newSound.duration
				_msg =""
				_interrupt = false
				_soundfile = oboe.soundfile_OT
			elseif _dist > 0 then
				_newSound.source = 'cat'
				_newSound.sound = 'cat.'
				_newSound.duration = 0.15
				_newSound.dur_left = _newSound.duration
				_msg =""
				_interrupt = false
				_soundfile = oboe.soundfile_dit 
			else
				_newSound.source = 'cat'
				_newSound.sound = 'cat-'
				_newSound.duration = 0.45
				_newSound.dur_left = _newSound.duration
				_msg =""
				_interrupt = false
				_soundfile = oboe.soundfile_dat 
			end
		end -- if not _interrupt then
		-- remove a tick from the currently playing sound
		oboe.currently_PlayingSound[_unitName].dur_left = oboe.currently_PlayingSound[_unitName].dur_left - oboe.Polling_Int
			


				-- training aids
		if oboe.trainingAids[_unitName] then
			local currCatTangH = math.floor(mist.utils.toDegree(mist.utils.getHeadingPoints(mist.utils.makeVec3(catCenter),_current_pos,true) )+0.5)
			local hdg =  mist.utils.toDegree(mist.getHeading(_unit))
			local _d = _dist
			if _d > 999 then
				_d = 999
			elseif _d < -999 then
				_d = -999
			end
			local _s = ''
			
			if (timer.getTime()-_params.TA_LT) >= 5 then
				_s = ''
				_params.TA_LT = timer.getTime()
			else
				_s = _params.TA_LS 
			end
			
			
			if _newSound.sound == 'mouse10' then
				_s = '10 mins'
				_params.TA_LT = timer.getTime()				
			elseif _newSound.sound == 'mouse8' then
				_s = '8 mins'
				_params.TA_LT = timer.getTime()
			elseif _newSound.sound == 'mouse6' then
				_s = '6 mins'
				_params.TA_LT = timer.getTime()
			elseif _newSound.sound == 'mouse3' then
				_s = '3 mins'
				_params.TA_LT = timer.getTime()
			elseif _newSound.sound == 'mouseR' then
				_s = 'Release'
				_params.TA_LT = timer.getTime()
			elseif _newSound.sound == 'cat20' then
				_s = 'miles 20'
				_params.TA_LT = timer.getTime()
			elseif _newSound.sound == 'cat10' then
				_s = 'miles 10'
				_params.TA_LT = timer.getTime()
			elseif _newSound.sound == 'cat5' then
				_s = 'miles 5'
				_params.TA_LT = timer.getTime()
			end
			_params.TA_LS  = _s 
			  
			if oboe.target.Approach == 'CCW' then
				currCatTangH =  oboe.AngTo360(currCatTangH - 90)	
			else
				currCatTangH =  oboe.AngTo360(currCatTangH + 90)
			end
			local delta_hdg = currCatTangH - hdg
			trigger.action.outTextForGroup(_groupID,string.format("%3i <%4im> %4i  %8s",currCatTangH,_d, delta_hdg, _s),1,true)
		end -- training aids

			
		--- play the sound
		local _playSound = false
		
		if _interrupt and oboe.currently_PlayingSound[_unitName].sound ~= _newSound.sound  then
			_playSound= true
			oboe.currently_PlayingSound[_unitName] = _newSound
			--trigger.action.outTextForGroup(_groupID, 'interrupting '.. _newSound.sound..' for '.._newSound.duration , 5 )
		end
		
		if not _interrupt then
			if oboe.currently_PlayingSound[_unitName].dur_left <= 0 then
				-- new sound
				if oboe.currently_PlayingSound[_unitName].sound	== 'pause' then
					oboe.currently_PlayingSound[_unitName] = _newSound
					_playSound = true
					--trigger.action.outTextForGroup(_groupID, 'playing '.. _newSound.sound..' for '.._newSound.duration , 5 )		
				else
					-- start a pause
					_playSound = true
					oboe.currently_PlayingSound[_unitName].source = 'cat'
					oboe.currently_PlayingSound[_unitName].sound = 'pause'
					oboe.currently_PlayingSound[_unitName].duration = _pause
					oboe.currently_PlayingSound[_unitName].dur_left = _pause
					_soundfile = oboe.soundfile_sil
					--trigger.action.outTextForGroup(_groupID, 'Pausing for '.. _pause, 5 )
				end
			
			elseif oboe.currently_PlayingSound[_unitName].sound	== 'catT' and _newSound.sound ~= 'catT' and oboe.currently_PlayingSound[_unitName].dur_left > 0 then
			-- went off track interrupt!
				oboe.currently_PlayingSound[_unitName] = _newSound
				_playSound = true
			--else continue playing whatever is being played
			end
		end-- 	if not _interrupt then
		
		if _playSound then
			if 	(CAT_distance_cp <= horizon_distance_cat and oboe.currently_PlayingSound[_unitName].source == 'cat') or 
				(Mouse_distance_cp <= horizon_distance_mouse and oboe.currently_PlayingSound[_unitName].source == 'mouse')  
			then
				trigger.action.outSoundForGroup(_groupID,_soundfile)
				if oboe.subTitles[_unitName]  and _msg ~= "" then
					trigger.action.outTextForGroup(_groupID, _msg, 5 )	
				end
			end
		end --if _playSound then
		return timer.getTime() + oboe.Polling_Int
	else
		return nil
	end
end
--############################################################################################################################################
function oboe.missionBriefing(_unitName)
	local s 
	local su 
	local a 
	local au 
	local _unit = Unit.getByName(_unitName)
	local _groupID = _unit:getGroup():getID()

	-- Briefing
	-- 0 = Metric, 1 = Imperial (knots), 2 (or anything else) = Imperial ( mph) 
	
	local _msg = oboe.target.Briefing 
	if oboe.BriefingUnits[_unitName] == 0 then
		s = oboe.target.Ground_Speed
		su = 'kph'
		a = oboe.target.Release_Alt
		au = 'm'
	elseif oboe.BriefingUnits[_unitName] == 1 then
		s = math.floor(oboe.target.Ground_Speed * 0.539957+0.5)
		su = 'knts'
		a =  math.floor(oboe.target.Release_Alt * 3.28084+0.5)
		au = 'ft'
	else
		s = math.floor(oboe.target.Ground_Speed * 0.621371+0.5)
		su = 'mph'
		a =  math.floor(oboe.target.Release_Alt * 3.28084+0.5)
		au = 'ft'
	end
	
	_msg = _msg..'\n\nPath tangencial headings:\n'
	_msg = _msg..'  @ Initial Point: '..oboe.wINTPoint_Heading..'°\n'
	_msg = _msg..'  @ 10 mins mark: '..oboe.w10_Heading..'°\n'
	_msg=  _msg..'  @  8 mins mark: '..oboe.w8_Heading..'°\n'
	_msg = _msg..'  @  6 mins mark: '..oboe.w6_Heading..'°\n'
	_msg = _msg..'  @  3 mins mark: '..oboe.w3_Heading..'°\n'
--	_msg = _msg..'  @           RP: '..oboe.mouseR_HeadingP..'°\n'
	
	_msg = string.format("%s \nRelease profile:\n    Speed: %i %s\n    Altitude %i %s\n    Release heading: %s°",_msg, s, su,a,au,  oboe.mouseR_HeadingP)
	
	 trigger.action.outTextForGroup(_groupID, _msg, 20 )
end
--############################################################################################################################################
function oboe.getCircleCircleIntersection( circle1x, circle1y, radius1, circle2x, circle2y, radius2 )
	local length = oboe.getLength( circle1x, circle1y, circle2x, circle2y )
	if length > radius1 + radius2 then return false end -- If the distance is greater than the two radii, they can't intersect.
	if oboe.checkFuzzy( length, 0 ) and oboe.checkFuzzy( radius1, radius2 ) then return {'equal'} end
	if oboe.checkFuzzy( circle1x, circle2x ) and oboe.checkFuzzy( circle1y, circle2y ) then return {'collinear'} end

	local a = ( radius1 * radius1 - radius2 * radius2 + length * length ) / ( 2 * length )
	local h = math.sqrt( radius1 * radius1 - a * a )

	local p2x = circle1x + a * ( circle2x - circle1x ) / length
	local p2y = circle1y + a * ( circle2y - circle1y ) / length
	local p3x = p2x + h * ( circle2y - circle1y ) / length
	local p3y = p2y - h * ( circle2x - circle1x ) / length
	local p4x = p2x - h * ( circle2y - circle1y ) / length
	local p4y = p2y + h * ( circle2x - circle1x ) / length

	if not oboe.validateNumber( p3x ) or not oboe.validateNumber( p3y ) or not oboe.validateNumber( p4x ) or not oboe.validateNumber( p4y ) then
		return {'inside'}
	end

	if oboe.checkFuzzy( length, radius1 + radius2 ) or oboe.checkFuzzy( length, math.abs( radius1 - radius2 ) ) then return {'tangent', p3x, p3y} end
	return {'intersection', p3x, p3y, p4x, p4y}
end
--############################################################################################################################################
function oboe.validateNumber( n )
	if type( n ) ~= 'number' then return false
	elseif n ~= n then return false -- nan
	elseif math.abs( n ) == math.huge then return false
	else return true end
end
--############################################################################################################################################
function oboe.getLength( x1, y1, x2, y2 )
	local dx, dy = x1 - x2, y1 - y2
	return math.sqrt( dx * dx + dy * dy )
end
--############################################################################################################################################
function oboe.checkFuzzy( number1, number2 )
	return ( number1 - .00001 <= number2 and number2 <= number1 + .00001 )
end
--############################################################################################################################################
function oboe.getPoint(_direction, _ref, _p1, _p2)


 local a1 = mist.utils.getHeadingPoints(mist.utils.makeVec3(_ref),mist.utils.makeVec3(_p1)) 
 local a2 = mist.utils.getHeadingPoints(mist.utils.makeVec3(_ref),mist.utils.makeVec3(_p2)) 
 
 if _direction == 'CCW' then
 	if a1 < a2 then
		return _p2
	else	
		return _p1
	end
 else -- CW
	if a1 < a2 then
		return _p1
	else	
		return _p2
	end
 end

end
--############################################################################################################################################
function oboe.ResetApproach(_unitName)
	oboe.mouse10_pend[_unitName] = true
	oboe.mouse8_pend[_unitName] = true
	oboe.mouse6_pend[_unitName] = true
	oboe.mouse3_pend[_unitName] = true
	oboe.mouseR_pend[_unitName] = true
	oboe.cat20_pend[_unitName] = true
	oboe.cat10_pend[_unitName] = true
	oboe.cat5_pend[_unitName] = true
	return true
end
--############################################################################################################################################
function oboe.tutorial(_args)
	local _unitName = _args[1]
	local _sound = _args[2]
	local _unit = Unit.getByName(_unitName)
	local _groupID = _unit:getGroup():getID()
	local _soundfile
	local _msg = ''
	
	if _sound == 'dit' then
		_soundfile = oboe.soundfile_dit
		_msg = 'Short or inside track.\n Short tone. Pause between tones indicates distance to track '
	elseif _sound == 'dat' then
		_msg = 'Long or outside track\n Long tone. Pause between tones indicates distance to track '
		_soundfile = oboe.soundfile_dat
	elseif _sound == 'ontrack' then
		_msg = 'On track'
		_soundfile = oboe.soundfile_OT
	elseif _sound == '20track' then
		_msg = '20 miles to track. Morse X (-..-), 1 time'
		_soundfile = oboe.soundfile_x
	elseif _sound == '10track' then
		_msg = '10 miles to track. Morse Y (-.--), 1 time'
		_soundfile = oboe.soundfile_y
	elseif _sound == '5track' then
		_msg = '5 miles to track. Morse Z (--..), 1 time '
		_soundfile = oboe.soundfile_z
	elseif _sound == 'w10' then
		_msg = '10 minutes to target. Morse A (.-), 4 times '
		_soundfile = oboe.soundfile_4a
	elseif _sound == 'w8' then
		_msg = '8 minutes to target. Morse B (-...), 4 times '
		_soundfile = oboe.soundfile_4b
	elseif _sound == 'w6' then
		_msg = '6 minutes to target. Morse C (-.-.), 4 times '
		_soundfile = oboe.soundfile_4c
	elseif _sound == 'w3' then
		_msg = '3 minutes to target. Morse D (-..), 4 times '
		_soundfile = oboe.soundfile_4d
	else -- 'rel'
		_msg = 'Bomb release point. Morse 5 (.....) and Morse T (-), 1 time each.\n Release the bombs at the end of the T'
		_soundfile = oboe.soundfile_5t
	end
	trigger.action.outTextForGroup(_groupID,_msg,15,false)
	trigger.action.outSoundForGroup(_groupID,_soundfile)
	return true
end
--############################################################################################################################################
function oboe.SetBriefingUnits(_args)
	local _unitName = _args[1]
	local _units = _args[2]
	
	oboe.BriefingUnits[_unitName] = _units
	return true
end
--############################################################################################################################################
function oboe.SetAccuracy(_args)
	local _unitName = _args[1]
	local _accuracy = _args[2]
	local _unit = Unit.getByName(_unitName)
	local _groupID = _unit:getGroup():getID()
	
	oboe.accuracy[_unitName] = _accuracy
	 trigger.action.outTextForGroup(_groupID, 'OBOE Accuracy set to '.._accuracy..'m', 20 )
	return true
end
--############################################################################################################################################
function oboe.BombsDroppedEvent(_event)
	local _weapon = _event.weapon
	local _target = _weapon:getTarget() 
		
	if _target == nil then -- is not a guided weapon	
			local params = {}
			params.unitName = _event.initiator:getName()
			params.releaseHeading =  mist.utils.toDegree(mist.getHeading(_event.initiator)) -- in degrees. Players release heading
			params.bombLastPoint = _weapon:getPoint()
			params.bomb = _weapon
			params.bombName = _weapon:getTypeName()
			timer.scheduleFunction(oboe.TrackBomb, params,  timer.getTime() +  0.005 ) 					
	end --if _target == nil then -- is not a guided weapon
	return true
end
--############################################################################################################################################
function oboe.TrackBomb(params,time)	
	
	 local _status,_bombPos =  pcall(function()
									return params.bomb:getPoint()
									end)
	if _status then
		-- bomb still in fly, update last point
		params.bombLastPoint = _bombPos
		return timer.getTime() + 0.005 -- keep tracking		
	else -- bomb hit
		local _impactDistance = math.floor(d35lib.getDistance(params.bombLastPoint,oboe.targetZone.point)) -- in meters
		local _direction = d35lib.getRelativeDirection(params.releaseHeading ,oboe.targetZone.point,params.bombLastPoint)
		_direction = d35lib.getClockDirection(_direction)
		table.insert(oboe.Results[params.unitName],{bombName=params.bombName,distance=_impactDistance,direction=_direction})
		return nil
	end
end
--############################################################################################################################################
function oboe.ReportResults(_unitName)
	local _unit = Unit.getByName(_unitName)
	local _groupID = _unit:getGroup():getID()
	local _units = ""
	
	for i,r in pairs(oboe.Results[_unitName]) do
	
		if oboe.BriefingUnits[_unitName] == 0 then -- meters 
			_units = 'm'
			_distance = r.distance
		else -- feet
			_units = 'ft'
			_distance = math.floor(r.distance * 3.28084)
		end
	
		local _msg = string.format("%s: %s impact, %s %s @ %s o'clock from target",_unit:getPlayerName(),r.bombName, _distance,_units, r.direction)
		trigger.action.outTextForGroup(_groupID, _msg, 20 )	
	end	
end
--############################################################################################################################################
function oboe.toggleSubTitles(_unitName)
	local _unit = Unit.getByName(_unitName)
	local _groupID = _unit:getGroup():getID()
	 
	if oboe.subTitles[_unitName] then 
		oboe.subTitles[_unitName] = false
		trigger.action.outTextForGroup(_groupID,'Sub titles off',10,false)
	else
		oboe.subTitles[_unitName] = true
		trigger.action.outTextForGroup(_groupID,'Sub titles on',10,false)
	end

	return true
end
--############################################################################################################################################
function oboe.AngTo360(_a)
	local _b = _a
	if _b >= 360 then
		_b = _b - 360
	elseif _b < 0 then
		_b = 360 + _b
	end
	
	return _b
end
--############################################################################################################################################
function oboe.toggleTrainingAids(_unitName)
	local _unit = Unit.getByName(_unitName)
	local _groupID = _unit:getGroup():getID()
	 	
	if oboe.trainingAids[_unitName] then 
		oboe.trainingAids[_unitName] = false
		trigger.action.outTextForGroup(_groupID,'Training Aids off',10,false)
	else
		oboe.trainingAids[_unitName] = true
		trigger.action.outTextForGroup(_groupID,'Training Aids on',10,false)
	end

	return true
end
--############################################################################################################################################



world.addEventHandler(oboe.eventHandler)

