| Scripts/shiplib.js | "use strict";
this.name        = "shiplib";
this.author      = "Norby";
this.copyright   = "2013 Norbert Nagy";
this.licence     = "CC BY-NC-SA 3.0";
this.description = "Contains data about player ships.";
this.version     = "1.0";
//please configure these to your custom ship if not in the list below
this.$ShipLibViewPosition = { //must be equal with view_position_* of your ship in the shipdata.plist
	aft:[0.0, 7.5, -32.5], forward:[0.0, 7.25, 16.25], port:[-30.0, 4.75, 0.0], starboard:[30.0, 4.75, 0.0]};
	
//internal data, should not touch
this.$ShipLibVP = [
	{k:"default", a:this.$ShipLibViewPosition.aft, f:this.$ShipLibViewPosition.forward,
		p:this.$ShipLibViewPosition.port, s:this.$ShipLibViewPosition.starboard},
	//original player ships
	{k:"adder", a:[0.0, 2.5, -22.5], f:[0.0, 2.5, 14.0], p:[-12.0, 1.5, -1.0], s:[12.0, 1.5, -1.0]},
	{k:"anaconda", a:[0.0, -4.0, -80.0], f:[0.0, -0.5, 70.0], p:[-2.15, -0.5, 70.0], s:[2.15, -0.5, 70.0]},
	{k:"asp", a:[0.0, 5.0, -35.0], f:[0.0, 5.31, 17.5], p:[-27.0, 5.0, 0.0], s:[27.0, 5.0, 0.0]},
	{k:"boa", a:[0.0, 20.5, -50.75], f:[0.0, 16.25, 23.9], p:[-7.3, 16.25, -23.9], s:[7.3, 16.25, -23.9]},
	{k:"boa-mk2", a:[0.0, 22.875, -47.375], f:[0.0, 12.5, 19.4375], p:[-20.67, 11.17, -14.67], s:[20.67, 11.17, -14.67]},
	{k:"cobra", a:[0.0, 7.5, -32.5], f:[0.0, 7.25, 16.25], p:[-30.0, 4.75, 0.0], s:[30.0, 4.75, 0.0]},
	{k:"cobra3", a:[0.0, 7.5, -32.5], f:[0.0, 7.25, 16.25], p:[-30.0, 4.75, 0.0], s:[30.0, 4.75, 0.0]},
	{k:"cobramk1", a:[0.0, 3.75, -27.5], f:[0.0, 2.925, 15.125], p:[-13.0, 3.75, 0.0], s:[13.0, 3.75, 0.0]},
	{k:"ferdelance", a:[0.0, 5.0, -32.5], f:[0.0, 0.0, 9.0], p:[-16.875, 2.0, 3.5], s:[16.875, 2.0, 3.5]},
	{k:"moray", a:[0.0, 8.875, 5.5], f:[0.0, 4.4, 22.5], p:[-22.5, 5.25, 11.875], s:[22.5, 5.25, 11.875]},
	{k:"morayMED", a:[0.0, 8.875, 5.5], f:[0.0, 4.4, 22.5], p:[-22.5, 5.25, 11.875], s:[22.5, 5.25, 11.875]},
	{k:"python", a:[0.0, 15.0, -49.5], f:[0.0, 10.0, 31.5], p:[-16.0, 4.0, 15.2], s:[16.0, 4.0, 15.2]},
	//custom ships
	{k:"base", a:[0.0, 500.0, -400.0], f:[0.0, 3.0, 320.0], p:[-60.0, 3.0, 0.0], s:[60.0, 3.0, 0.0]},
	{k:"corvette", a:[0.0, 6.0, -16.5], f:[0.0, 3.0, 16.5], p:[-5.0, 2.0, 0.0], s:[5.0, 2.0, 0.0]},
	{k:"cruiser", a:[0.0, 3.0, -120.0], f:[0.0, 3.0, 120.0], p:[-60.0, 3.0, 0.0], s:[60.0, 3.0, 0.0]},
	{k:"drone", a:[0.0, 5.0, -6.5], f:[0.0, 3.0, 9.5], p:[-2.5, 2.0, 0.0], s:[2.5, 2.0, 0.0]},
	{k:"freighter", a:[0.0, 3.0, -120.0], f:[0.0, 3.0, 120.0], p:[-60.0, 3.0, 0.0], s:[60.0, 3.0, 0.0]},
	{k:"frigate", a:[0.0, 30.0, -50.0], f:[0.0, 3.0, 50.0], p:[-30.0, 0.0, 0.0], s:[30.0, 0.0, 0.0]},
	{k:"fighter", a:[0.0, 6.0, -16.5], f:[0.0, 3.0, 16.5], p:[-5.0, 2.0, 0.0], s:[5.0, 2.0, 0.0]},
	{k:"gunship", a:[0.0, 6.0, -16.5], f:[0.0, 3.0, 16.5], p:[-5.0, 2.0, 0.0], s:[5.0, 2.0, 0.0]},
	{k:"miner", a:[0.0, 30.0, -50.0], f:[0.0, 3.0, 50.0], p:[-30.0, 0.0, 0.0], s:[30.0, 0.0, 0.0]},
	{k:"runner", a:[0.0, 6.0, -16.5], f:[0.0, 3.0, 16.5], p:[-5.0, 2.0, 0.0], s:[5.0, 2.0, 0.0]},
	];
 | 
                
                    | Scripts/telescope.js | "use strict";
this.name        = "telescope";
this.author      = "Norby";
this.copyright   = "2013 Norbert Nagy";
this.licence     = "CC BY-NC-SA 4.0";
this.description = "Telescope mark all visible ships, show vitrual model, sniper ring and more.";
//customizable properties
this.$TelescopeAutoScan = true; //check continually for new isVisible isPiloted target and scan if found
this.$TelescopeAutoScanMaxRange = 1000000; //meters, how far targets will be reported
this.$TelescopeAutoLock = 1; //if no target and something in crosshairs with max. this degree diff., 1=lightball size, 0=off
this.$TelescopeFarStatus = false; //red ball reveal pirates over normal scanner if true
this.$TelescopeGravLock = 20; //gravity scanner relock in this degree cone (0-180, 20=about the screen)
this.$TelescopeIdentLock = 180; //if ident pressed or target lost then lock in this degree (0-180, 180=the whole sphere)
this.$TelescopeLargeLightBalls = false; //lightballs are increasing depending on the distance or remains small
this.$TelescopeLightBalls = true; //turn on or off all lightballs, but markes on the scanner will be remain
this.$TelescopeMassLockBorders = true; //coloured circles around ships and planets in green alert
this.$TelescopeBrightMassLockBorders = false; //brighter circles around ships and planets in green alert
this.$TelescopeRedAlertDist = 30000; //show lollipops in red alert within this distance only
this.$TelescopeRedAlertLimiter = false; //a bit more FPS in dogfight if true but show all marks with weapons off only
this.$TelescopeRing = true; //show a ring around the visual target
this.$TelescopeShipLightBalls = true; //turn on or off the lightballs with scanner markers of the ships, but cargo, etc. remain
this.$TelescopeShowVisualQuestionMark = false; //if a ship has no visual model in effecdata.plist show a big "?" model
this.$TelescopeShowVisualStation = true; //show or not show the 3D model of the targeted station
this.$TelescopeShowVisualTarget = true; //show 3D model of target if weapons are on (except stations if VisualStation false)
this.$TelescopeSniperMinRange = 10000; //meters, show sniper ring if the target is over this distance
this.$TelescopeSniperRange = 30000; //meters, if the target is inside then show sniper ring and large lightball
this.$TelescopeSniperRingSize = 2; //size of the sniper ring (between 1 and 5, default: 2)
this.$TelescopeSteering = 2; //auto steering if lock nearest or step in the target list with activate, 1:nearest only
this.$TelescopeTargets = 200; //limitable to reduce FPS drop in systems with many ships, min. 4, max. 200 due to VFCBM0-VFCBM49
this.$TelescopeThargoids = false; //you will get some aliens right after undock to test Telescope
this.$TelescopeVMarkMinDist = 3000; //meters, if target is inside then remove the lightball marker
this.$TelescopeVMarkShipMinDist = 5000; //meters, if target is ship and inside this range then remove the lightball marker
this.$TelescopeVPosHUD = [0, 0, 0]; //position shift for your HUD's built-in visual target screen if any
this.$TelescopeVSize = 4; //size of the visual target with online weapons (between 0 and 10, default: 4)
this.$TelescopeVZoomSize = 6; //zoomed size of the visual target with offline weapons (between 0 and 10, default: 6)
//internal properties, should not touch
this.$TelescopeAttackers = []; //ships attacked player regardless of success
this.$TelescopeAttackeri = 0; //store the sequential number of the last attacker in the target list
this.$TelescopeBuyMsg = true; //flag to show the buy message once
this.$TelescopeCMFD = null; //store the worldScripts pointer of Combart MFD
this.$TelescopeDataKeys77 = ["adder", //dataKeys with 77 suffix in effectdata.plist for Oolite 1.77
	"alloy", "anaconda", "arc-detail", "asp", "asp-cloaked", "asteroid", "asteroid-alternative",
	"oolite-cinder", "oolite-cinder-alternative", "oolite-cinder-small", "oolite-cinder-small-alternative",
	"barrel","boa", "boa-mk2", "boulder", "boulder-alternative", "buoy", "buoy-witchpoint",
	"cloaking-device", "cobra3-trader", "cobra3-alternate", "cobra3-pirate", "cobramk1",
	"cobramk1-alt", "cobramk1-miner", "constrictor", "coriolis-station", "dock",
	"dock-flat", "oolite-dock-virtual", "dodecahedron-station", "ecm-proof-missile",
	"escape-capsule", "ferdelance", "gecko", "hermit-docking-slit", "hermitage", "icosahedron-station",
	"krait", "mamba", "mamba-escort", "missile", "moray", "morayMED", "oolite-unknown-ship",
	"python", "python-blackdog", "python-trader", "qbomb", "rock-hermit", "scarred-alloy",
	"shuttle", "sidewinder", "sidewinder-escort", "splinter", "splinter-alternative", "strut",
	"tharglet", "thargoid", "transporter", "transporter-miner", "viper", "viper-interceptor",
	"viper-pursuit", "worm", "worm-miner", "wreckage-component", "more-wreckage2",
	"more-wreckage3", "more-wreckage4", "more-wreckage5", "ballturret"];
this.$TelescopeFixedTel = 0; //cheaply fixed Telescope with drawbacks
this.$TelescopeFixedGS = 0; //cheaply fixed Gravity Scanner with drawbacks
this.$TelescopeFixedSD = 0; //cheaply fixed Small Dish with drawbacks
this.$TelescopeFixedLD = 0; //cheaply fixed Large Dish with drawbacks
this.$TelescopeGSC = 0; //Gravity Scan counter to call aliens
this.$TelescopeGSP = 0; //Gravity Scan percent counter store the progress of the gravity scan
this.$TelescopeIdentUnLock = false; //flag to steering started by ident, next press will unlock
this.$TelescopeList = []; //list of the scanned ships
this.$TelescopeListFar = []; //list of the scanned far ships
this.$TelescopeListFarPos = []; //positions of the scanned far ships at the moment of the scan
this.$TelescopeListGD = []; //gravity scanner maximal detection distances of the scanned ships
this.$TelescopeListPos = []; //positions of the scanned ships at the moment of the scan
this.$TelescopeListi = 0; //sequential number of the currently selected ship in the list
this.$TelescopeMaxRange = 1000000000000000; //10^15m, usable part of double precision for filteredEntities
this.$TelescopeMMarks = []; //visual effects to mark the masslocking fields around NPC ships
this.$TelescopeMRings = []; //mark the border of masslock fields
this.$TelescopeNearestd = this.$TelescopeMaxRange; //store the distance of the nearest ship in the list (updated in TimedVMC)
this.$TelescopeNearesti = 0; //store the sequential number of the nearest ship in the list (updated in TimedVMC)
this.$TelescopePlanetNames = []; //cache of names
this.$TelescopePrevHeading = []; //heading vector of the player ship in the previous frame to detect manual steering
this.$TelescopePrevNewTarget = null; //bugfix against repeated scan if a ship can not get into the list by any reason
this.$TelescopePrevMFDTarget = null; //support for Combat MFD
this.$TelescopePrevTargetAcq = null; //bugfix against repeated scan if a ship can not get into the list by any reason
this.$TelescopePrevWho = null; //bugfix against non-lockable Military Jammer awarded by ShipVersion to avoid repeated scan
this.$TelescopePrevPlanet = null; //bugfix against target switch but name remain old if new planet is within scanner range
this.$TelescopeReds = []; //store red ships detected earlier
this.$TelescopeSoundScan = null; //scan soundsource
this.$TelescopeStaionNearby = true; //store a station is nearby for gravity scanner
this.$TelescopeSteerFCB = null; //auto steering framecallback
//this.$TelescopeSniperRing = null; //if this ring guided around the crosshair then the far target is lined up correctly
this.$TelescopeSVSync = false; //flag to synhronize the visual effect during auto steering
this.$TelescopeTarget = null; //the targeted entity, needed when target is far and player target set to the markership
this.$TelescopeTargetSet = false; //flag scanning to avoid double scan
this.$TelescopeGravLock2 = this.$TelescopeGravLock; //ident press will switch this between 1 and GravLock
this.$TelescopeTimerA = null; //set player target a bit later to avoid relock the ship in crosshair (bugfix)
this.$TelescopeTimerF = null; //delayed launch of FCBs, must after FarPlanets FCB
this.$TelescopeTimerS = null; //AutoScan timer get targets from normal scanner and do scan if new far target in the front view
this.$TelescopeTWS = null; //store the worldScripts pointer of Towbar
//this.$TelescopeV = null; //visual effect to show the selected target
//this.$TelescopeVDataKey = null; //key of the visual effect
this.$TelescopeVFCB = null; //visual model and ring positions updated in these framecallbacks
this.$TelescopeVFCB2 = null; //visual model and ring positions updated in these framecallbacks part 2 to avoid timelimit
this.$TelescopeVFCB3 = null; //visual model and ring positions updated in these framecallbacks part 3 to avoid timelimit
this.$TelescopeVFCBM = []; //visual marker positions updated in these framecallbacks
this.$TelescopeVMark = null; //visual effect to mark the target
this.$TelescopeVMarks = []; //visual effects to mark the non-current targets
//this.$TelescopeVMCC = 0; //counter to make colour of the visual marks, used to do once in a second within a 0.25s timer
this.$TelescopeVMCs = []; //colour of the visual marks
this.$TelescopeVP = { //default view_positions if shiplib.js is missing
	aft:[0.0, 7.5, -32.5], forward:[0.0, 7.25, 16.25], port:[-30.0, 4.75, 0.0], starboard:[30.0, 4.75, 0.0]};
this.$TelescopeVPos = null; //position of the visual effect
//this.$TelescopeVRing = null; //a ring around the visual effect target
//this.$TelescopeVShrinkC = 0; //visual effect shrink counter - shrink code is not ready, leave these at 0
//this.$TelescopeVShrinkDelay = 0; //in sec after the larger visual target start shrinkig to normal size (0: instant)
//this.$TelescopeVShrinkLength = 0; //in sec, shrinking faster if smaller (0: instant)
//this.$TelescopeVUp = 0; //visual effect align to top modifier
//this.$TelescopeWps = true; //the previous state of the player weapons
//this.$TelescopeZoom = 1; //store the original maximal zoom level of the visual effect
//world script events
this.startUp = function() {
	var h = worldScripts.hudselector;
	if( h && h.$HUDSelectorAddMFD ) h.$HUDSelectorAddMFD(this.name);
	var ps = player.ship;
	if( ps.setMultiFunctionText )
		ps.setMultiFunctionText(this.name, "No Telescope Target", false);
	this.$TelescopeSoundScan = new SoundSource();
	this.$TelescopeSoundScan.sound = "ScanSound.ogg"; //sound of the Gravity Scanner
	this.$TelescopeSoundScan.loop = false;
	this.$TelescopeSoundScan.repeatCount = 1;
	this.$TelescopeBuyMsg = true; //show again after reload
	this.$TelescopePlanetNames = []; //cache of names
	this.$TelescopeReds = []; //store red ships detected earlier
	this.$TelescopeCMFD = worldScripts.combat_MFD; //store the worldScripts pointer of Combart MFD
	this.$TelescopeTWS = worldScripts.towbar; //store the worldScripts pointer of Towbar
	
	var subitem = missionVariables.$TelescopeMenuLightballs;
	if( subitem > 0 ) this.$Telescope_SetLightballs( subitem );
	subitem = missionVariables.$TelescopeMenuSniper;
	if( subitem > 0 ) this.$Telescope_SetSniper( subitem );
	subitem = missionVariables.$TelescopeMenuSteering;
	if( subitem > 0 ) this.$Telescope_SetSteering( subitem );
	subitem = missionVariables.$TelescopeMenuTargets;
	if( subitem > 0 ) this.$Telescope_SetTargets( subitem );
	subitem = missionVariables.$TelescopeMenuVisual;
	if( subitem > 0 ) this.$Telescope_SetVisual( subitem );
	subitem = missionVariables.$TelescopeMenuVisualSize;
	if( subitem > 0 ) this.$Telescope_SetVisualSize( subitem );
	
	var ws = worldScripts.telescope;
	ws._FBC_closures();
 	if( ws.$FBC_closures )								// once done
		delete ws._FBC_closures;						// remove ability to create a 2nd ones!
	else
		log('telescope', 'startUp, failed to create $FBC_closures!' );
}
this.$FBC_closures = false;
this._FBC_closures = function() {	// create closure & expose functions
	var ws = worldScripts.telescope;
	ws.$Telescope_TimedS = ws._TimedS_closure();
	ws.$Telescope_VFCBVisualTarget = ws._VFCBVisualTarget_closure();
	
	var vc = ws._VFCB_closure();
	ws.$Telescope_VClearS = vc.clear;
	ws.$Telescope_VShow = vc.show;
	ws.$Telescope_VFCB = vc.fcb;
	vc = ws._VFCB2_closure();
	ws._clearSniperRing = vc.clear;
	ws.$Telescope_VFCB2 = vc.fcb;
	ws.$FBC_closures = true;
}
this.equipmentDamaged = this.equipmentDestroyed = function(equipment) {
	if( equipment === "EQ_TELESCOPE" || equipment === "EQ_GRAVSCANNER"
		|| equipment === "EQ_GRAVSCANNER2" || equipment === "EQ_SMALLDISH" || equipment === "EQ_LARGEDISH" )
		worldScripts.telescope.$Telescope_NewList(); //remove lost targets, autoscan will scan again
}
this.equipmentRepaired = function(equipment) {
	//Do not use "false" in missionVariables! Will return true! Use 0 and 1 instead!
	if(equipment === ("EQ_TELESCOPE"))
		this.$TelescopeFixedTel = missionVariables.$TelescopeFixedTel = 0;
	else if(equipment === ("EQ_GRAVSCANNER"))
		this.$TelescopeFixedGS = missionVariables.$TelescopeFixedGS = 0;
	else if(equipment === ("EQ_GRAVSCANNER2"))
		this.$TelescopeFixedGS = missionVariables.$TelescopeFixedGS = 0;
	else if(equipment === ("EQ_SMALLDISH"))
		this.$TelescopeFixedLD = missionVariables.$TelescopeFixedSD = 0;
	else if(equipment === ("EQ_LARGEDISH"))
		this.$TelescopeFixedLD = missionVariables.$TelescopeFixedLD = 0;
}
this.playerBoughtEquipment = function(equipmentKey) {
	var ps = player.ship;
	if(equipmentKey === ("EQ_TELESCOPE"))
		this.$TelescopeFixedTel = missionVariables.$TelescopeFixedTel = 0;
	else if(equipmentKey === ("EQ_GRAVSCANNER"))
		this.$TelescopeFixedGS = missionVariables.$TelescopeFixedGS = 0;
	else if(equipmentKey === ("EQ_GRAVSCANNER2"))
		this.$TelescopeFixedGS = missionVariables.$TelescopeFixedGS = 0;
	else if(equipmentKey === ("EQ_SMALLDISH"))
		this.$TelescopeFixedSD = missionVariables.$TelescopeFixedSD = 0;
	else if(equipmentKey === ("EQ_LARGEDISH"))
		this.$TelescopeFixedLD = missionVariables.$TelescopeFixedLD = 0;
	else if(equipmentKey === ("EQ_TELESCOPE_REPAIR"))      {
		ps.setEquipmentStatus("EQ_TELESCOPE", "EQUIPMENT_OK");
		ps.removeEquipment("EQ_TELESCOPE_REPAIR");
		clock.addSeconds("3600");
		this.$TelescopeFixedTel = missionVariables.$TelescopeFixedTel = 1; //with drawback (lightballs only)
	}
	else if(equipmentKey === ("EQ_TELESCOPE_FULLREPAIR"))      {
		ps.removeEquipment("EQ_TELESCOPE_FULLREPAIR");
		clock.addSeconds("3600");
		this.$TelescopeFixedTel = missionVariables.$TelescopeFixedTel = 0;
	}
	else if(equipmentKey === ("EQ_GRAVSCANNER_REPAIR"))      {
		ps.setEquipmentStatus("EQ_GRAVSCANNER", "EQUIPMENT_OK");
		ps.removeEquipment("EQ_GRAVSCANNER_REPAIR");
		clock.addSeconds("3600");
		this.$TelescopeFixedGS = missionVariables.$TelescopeFixedGS = 1; //with drawback (misjump)
	}
	else if(equipmentKey === ("EQ_GRAVSCANNER2_REPAIR"))      {
		ps.setEquipmentStatus("EQ_GRAVSCANNER2", "EQUIPMENT_OK");
		ps.removeEquipment("EQ_GRAVSCANNER2_REPAIR");
		clock.addSeconds("3600");
		this.$TelescopeFixedGS = missionVariables.$TelescopeFixedGS = 1; //with drawback (misjump)
	}
	else if(equipmentKey === ("EQ_GRAVSCANNER_FULLREPAIR") || equipmentKey === ("EQ_GRAVSCANNER2_FULLREPAIR") ) {
		ps.removeEquipment(equipmentKey);
		clock.addSeconds("3600");
		this.$TelescopeFixedGS = missionVariables.$TelescopeFixedGS = 0;
	}
	else if(equipmentKey === ("EQ_SMALLDISH_REPAIR"))      {
		ps.setEquipmentStatus("EQ_SMALLDISH", "EQUIPMENT_OK");
		ps.removeEquipment("EQ_SMALLDISH_REPAIR");
		clock.addSeconds("3600");
		this.$TelescopeFixedSD = missionVariables.$TelescopeFixedSD = 1; //with drawback (break during jump)
	}
	else if(equipmentKey === ("EQ_LARGEDISH_REPAIR"))      {
		ps.setEquipmentStatus("EQ_LARGEDISH", "EQUIPMENT_OK");
		ps.removeEquipment("EQ_LARGEDISH_REPAIR");
		clock.addSeconds("3600");
		this.$TelescopeFixedLD = missionVariables.$TelescopeFixedLD = 1; //with drawback (break during jump)
	}
	else if(equipmentKey === ("EQ_SMALLDISH_FULLREPAIR"))      {
		ps.removeEquipment("EQ_SMALLDISH_FULLREPAIR");
		clock.addSeconds("3600");
		this.$TelescopeFixedSD = missionVariables.$TelescopeFixedSD = 0;
	}
	else if(equipmentKey === ("EQ_LARGEDISH_FULLREPAIR"))      {
		ps.removeEquipment("EQ_LARGEDISH_FULLREPAIR");
		clock.addSeconds("3600");
		this.$TelescopeFixedLD = missionVariables.$TelescopeFixedLD = 0;
	}
	else if( equipmentKey.substr(-7,7) === "_REFUND" ) { 
		//any eq which end with _REFUND means full refund the eq named before
		ps.removeEquipment( equipmentKey ); //remove the "bought" refund eq
		var eq = equipmentKey.substr( 0, equipmentKey.length-7 );//the real eq
		if( ps.equipmentStatus( eq ) === "EQUIPMENT_OK" )
			worldScripts.telescope.$Telescope_RefundEQ( eq );
		else if( ps.equipmentStatus( eq ) === "EQUIPMENT_DAMAGED" ) //need check to avoid message at refund
			player.consoleMessage("Need repair first"); //do not get full price back for a damaged eq
		return;
	}
}
this.shipBeingAttacked = function(whom) {
	if( whom && whom.isValid ) {
		var ws = worldScripts.telescope;
		if( ws._index_in_list( whom, ws.$TelescopeAttackers ) === -1 )
			ws.$TelescopeAttackers.push(whom); //add
		var i = ws._index_in_list( whom, ws.$TelescopeList );
		if( i > -1 ) ws.$TelescopeAttackeri = i + 1; //save last attacker
	}
}
this.shipBeingAttackedUnsuccessfully = function(whom) {
	if( whom && whom.isValid ) {
		var ws = worldScripts.telescope;
		if( ws._index_in_list( whom, ws.$TelescopeAttackers ) === -1 )
			ws.$TelescopeAttackers.push(whom); //add
		var ai = ws.$TelescopeAttackeri;
		if( ai > 0 ) {
			var a = ws.$TelescopeList[ ai - 1 ]; //if previous attacker is destroyed or too far
			if( !a || !a.isValid || !a.isVisible ) ai = ws.$TelescopeAttackeri = 0;
		}
		if( ai === 0 ) {
			var i = ws._index_in_list( whom, ws.$TelescopeList );
			if( i > -1 ) ws.$TelescopeAttackeri = i + 1; //save attacker
		}
	}
}
this.shipKilledOther = function(whom /*,damageType*/) {
	var ws = worldScripts.telescope;
	var i = ws._index_in_list( whom, ws.$TelescopeList );
	if( i > -1 ) ws.$Telescope_Scan();//forced rescan to remove from the list
}
this.shipLaunchedFromStation = function() {
	var ps = player.ship;
	this.$TelescopeFixedTel = missionVariables.$TelescopeFixedTel;
//	if(this.$TelescopeFixedTel) log("Telescope", "FixedTel "+this.$TelescopeFixedTel);
	this.$TelescopeFixedGS = missionVariables.$TelescopeFixedGS;
//	if(this.$TelescopeFixedGS) log("Telescope", "FixedGS "+this.$TelescopeFixedGS);
	this.$TelescopeFixedSD = missionVariables.$TelescopeFixedSD;
//	if(this.$TelescopeFixedSD) log("Telescope", "FixedSD "+this.$TelescopeFixedSD);
	this.$TelescopeFixedLD = missionVariables.$TelescopeFixedLD;
//	if(this.$TelescopeFixedLD) log("Telescope", "FixedLD "+this.$TelescopeFixedLD);
	this.$TelescopeGSC = missionVariables.$TelescopeGSC; //Gravity Scan counter to call aliens
	if( !this.$TelescopeGSC ) this.$TelescopeGSC = 0; //start to count gravity scans
	this.$TelescopeGSP = 0; //begin new gravity detection process
	var ws = worldScripts.telescope;
	ws.$Telescope_NewList();
	ws.$TelescopeAttackers = [];
//	if( ps.equipmentStatus("EQ_TELESCOPE") == "EQUIPMENT_OK" ) {
//		if( ps && ps.weaponsOnline 
//			&& ps.equipmentStatus("EQ_GRAVSCANNER") == "EQUIPMENT_OK" )
//			player.consoleMessage("Turn off weapons for Gravity Scan.",10);
////		ws.$Telescope_Scan();//forced rescan
//	}
//	if( ws.$TelescopeV && ws.$TelescopeV.isValid ) {
//;		log("Telescope", "shipLaunchedFromStation V:"+ws.$TelescopeV);//debug
		ws.$Telescope_VClear();
//		ws.$TelescopeV.remove();
//		var i = system.allVisualEffects.indexOf( this.$TelescopeV );
//		if( i > -1 ) system.allVisualEffects[i].remove();
//	}
	if( ps.viewPositionForward ) { //from oolite v1.79
		ws.$TelescopeVPos = ps.viewPositionForward;
	} else  {
		var p = new Vector3D(0,0,0);
		if( worldScripts.shiplib && worldScripts.shiplib.$ShipLibVP ) {
			var l = worldScripts.shiplib.$ShipLibVP; //view_position
			var d = 0; //0. line contains the default data
			for( var i = 1; i < l.length; i++ )
				if( ps.dataKey && l && l[i]
					&& ws._index_in_list( l[i].k, ps.dataKey ) > -1 ) {
					d = i; //found
					i = l.length; //end of for
				}
//			player.consoleMessage(l[d].f[0]+" "+l[d].f[1]+" "+l[d].f[2]);//debug
			p = new Vector3D( l[d].f[0], l[d].f[1], l[d].f[2] );
		} else {
			var p1 = ws.$TelescopeVP;
			p = new Vector3D( p1.forward[0], p1.forward[1], p1.forward[2] );
		}
//		player.consoleMessage(p,10);//debug
		ws.$TelescopeVPos = p; //save corrected position
	}
//	if( worldScripts["genericHUDswitch - MilHUD.js"] ) { //was in MilHUD 0.9, removed in 0.92
//		ws.$TelescopeVPos = ws.$TelescopeVPos.add([20,-25,70]);
//		ws.$TelescopeRing = false; //do not show the ring around visual target
//		ws.$TelescopeVSize = 1; //zoomed size of the visual target
//		ws.$TelescopeVZoomSize = 1; //zoomed size of the visual target
//	} else
	ws.$TelescopeVPos = ws.$TelescopeVPos.add(ws.$TelescopeVPosHUD); //custom HUD position
	ws.$Telescope_Scan(); //need to avoid list all ship name as newship
	ws.$Telescope_StartTimer( 0 ); //no delay
}
this.shipScoopedOther = function(whom) {
//      var w = worldScripts["telescope"].$TelescopeList [worldScripts["telescope"].$TelescopeListi - 1 ];
//      log("Telescope", "Scooped "+w.isValid+w.name+" VMark:"+worldScripts["telescope"].$TelescopeVMark); //debug
	var ws = worldScripts.telescope;
	if( ws.$TelescopeVMark && whom === ws.$TelescopeList [ ws.$TelescopeListi - 1 ] ) { //remove shadowmarker if targeted
		ws.$TelescopeVMark.remove();
		ws.$TelescopeVMark = null;
	}
}
this.shipSpawned = function(ship) { //detect missile launch immediately
//	ship.setScript("oolite-default-ship-script.js");//debug
	var ps = player.ship;
	if( !ps || !ps.isValid || ps.docked //player died or docked
		|| !worldScripts.telescope.$TelescopeTimerS //no timer means in witchspace
		|| !ship || !ship.isValid || ship.isVisualEffect //invalid or effect
		|| !ship.dataKey || ship.dataKey === "telescopemarker" //planet or marker
		|| !ship.isWeapon ) //missile and mine only
		return; //means no scan needed
	var distance = ps.position.distanceTo( ship.position );
	if( distance < ps.scannerRange ) //ship in range
		worldScripts.telescope.$Telescope_Scan();//forced rescan to see launched missiles
}
this.shipTargetAcquired = function(target) { //if locked target by hand then set as the actual item in the list
//		player.ship.targetSystem = 147;
//	player.consoleMessage(target.name+" "+target.isBeacon);//debug
//	var a = system.allShips; var s = "";//debug
//	for( var i = 0; i < a.length; i++) s+=a[i]+"\n";//debug
//;	log("Telescope", a.length+" Ships "+s);//debug
//	a = system.allVisualEffects; s = "";//debug
//	for( var i = 0; i < a.length; i++) s+=a[i]+"\n";//debug
//	log("Telescope", a.length+" VEffs "+s);//debug
//	a = system.filteredEntities(this, this.$Telescope_True, player.ship, player.ship.scannerRange); s = "";//debug
//	for( var i = 0; i < a.length; i++) { s+=a[i]+"\n"; if(!a[i].isPlayer && !a[i].isStation) a[i].remove(); }//debug
//	log("Telescope", a.length+" Nears: "+s);//debug
	if(!target || !target.isValid || this.$TelescopeTargetSet === true ) return;
		//no target or just set in $Telescope_Show() or list is empty
//	log("Telescope", "TargetAcq: "+target);//debug
	var i = -1;
	var ws = worldScripts.telescope;
	if( target && target.dataKey && target.dataKey === "telescopemarker" ) { //planet marker?
		i = ws.$TelescopeListi;
		ws.$TelescopeTarget = ws.$TelescopeList [ i - 1 ];
	} else ws.$TelescopeTarget = target; //save the real target
	if( i < 0 && ws.$TelescopeList && ws.$TelescopeList.length > 0 )
		i = ws._index_in_list( target, ws.$TelescopeList ); //find target in list
	if( i < 0 && target && target.isValid //not found, refresh the list (usually the target spawned after the last scan)
		&& target !== ws.$TelescopePrevTargetAcq ) { //Military Jammer bugfix
//;		log("Telescope", "ScanTA: "+target+" - "+ws.$TelescopePrevTargetAcq);//debug
		ws.$TelescopePrevTargetAcq = target; //store to avoid repeated scan
		ws.$Telescope_Scan(); //rescan
//		ws.$Telescope_List3(0, true, true); //free refresh from the normal scanner - need fix, removed
		i = ws._index_in_list( target, ws.$TelescopeList ); //find target in list
		if( i < 0 ) ws.$TelescopeListi = 0; //target not in list
	}
	if( ( ws.$TelescopeShowVisualTarget || !player.ship.weaponsOnline )
		&& ws.$TelescopeFixedTel !== 1 ) {
		//player.consoleMessage(i);//debug
		if( i >= 0) { //found
			ws.$TelescopeListi = i + 1;
			ws.$Telescope_Show2( false );
		} else {
//;			log("Telescope","shipTargetAcquired i:"+i+" ti:"+ws.$TelescopeListi);
			ws.$Telescope_VShow();
		}
	}
}
this.shipTargetLost = function(losttarget) {
//;	log("Telescope", "Losttarget: "+losttarget);//debug
	var ws = worldScripts.telescope;
	ws.$TelescopeTarget = null; //must to clear
	if( ws.$TelescopeTargetSet === true //set by script or disabled
		|| ws.$TelescopeIdentLock === 0 ) return;
	var lostvalid = false; //target destroyed
	if( losttarget && losttarget.isValid ) lostvalid = true; //target not destroyed but lost by ident keypress
	ws.$Telescope_MostCentered( null, "ident", lostvalid ); //lock-unlock target
}
this.shipWillEnterWitchspace = function() {
	var ps = player.ship;
	this.$TelescopeReds = []; //store red ships detected earlier
	if( this.$TelescopeFixedGS === 1 && Math.random() > 0.5 ) {
		ps.scriptedMisjump = true; //meet Thargoids due to the cheap Grav.Sc. repair
		player.consoleMessage("Gravity Scanner caused misjump!");
	}
	if( this.$TelescopeFixedSD === 1 && Math.random() > 0.2 ) {
		ps.setEquipmentStatus("EQ_SMALLDISH", "EQUIPMENT_DAMAGED");
		player.consoleMessage("Small Dish damaged during hyperjump!", 10);
	}
	if( this.$TelescopeFixedLD === 1 && Math.random() > 0.2 ) {
		ps.setEquipmentStatus("EQ_LARGEDISH", "EQUIPMENT_DAMAGED");
		player.consoleMessage("Large Dish damaged during hyperjump!", 10);
	}
	this.shipWillDockWithStation();
}
this.shipWillDockWithStation = function() {
	var ws = worldScripts.telescope;
	ws.$Telescope_NewList();
	ws.$TelescopeAttackers = [];
	ws.$Telescope_VClear();
	if( isValidFrameCallback( ws.$TelescopeVFCB ) )
		removeFrameCallback( ws.$TelescopeVFCB );
	if( isValidFrameCallback( ws.$TelescopeVFCB2 ) )
		removeFrameCallback( ws.$TelescopeVFCB2 );
	if( isValidFrameCallback( ws.$TelescopeVFCB3 ) )
		removeFrameCallback( ws.$TelescopeVFCB3 );
	for(var i = 0; i < ws.$TelescopeVFCBM.length; i++) {
		if( isValidFrameCallback( ws.$TelescopeVFCBM[i] ) )
			removeFrameCallback( ws.$TelescopeVFCBM[i] );
		ws.$TelescopeVFCBM[i] = null; //cag: converting to static arrays
	}
	if( ws.$TelescopeTimerS ) {
		ws.$TelescopeTimerS.stop();
		ws.$TelescopeTimerS = null;
	}
}
this.shipWillExitWitchspace = function() { //use this event due to shipExitedWitchspace is not working in v1.77
	var ws = worldScripts.telescope;
	ws.$TelescopePlanetNames = []; //cache of names
	ws.$TelescopeGSP = 0; //begin new gravity detection process
	ws.$Telescope_NewList();
	ws.$Telescope_AddShips(); //do not call shipLaunchedFromStation() to aovid a bug
	this.$TelescopeTimerF = new Timer(this, ws.$Telescope_TimedF, 1, 0); //1s delay to launch FCBs after FarPlanets FCB!
}
this.shipWillLaunchFromStation = function( /*station*/ ) {
//	player.consoleMessage(player.ship.targetSystem+" "+player.ship.galaxyCoordinatesInLY.x+" "+player.ship.galaxyCoordinatesInLY.y);//debug
//	player.ship.targetSystem = 147;//debug
	var ws = worldScripts.telescope;
	ws.$TelescopeStaionNearby = false; //to send gravscanner message after launch
	ws.$Telescope_AddShips();
}
this._index_in_list = function( item, list ) { // for arrays only; faster than indexOf 
	var k = list.length;
	while( k-- ) {
		if( list[ k ] === item ) return k;
	}
	return -1;
}
//Telescope methods
this.$Telescope_AddShips = function() { 
	if( !system.isInterstellarSpace ) { //need check due to called from shipWillExitWitchspace also
		system.addShips("shuttle", 1, system.mainStation.position, 50000);//demo visible target
		system.addShips("trader", 1, system.mainStation.position, 20000);//demo near target
		
//		system.addShips("asteroid", 10, system.mainStation.position, 20000);//test rock target
//		system.addShips("rescue_station", 1, system.mainStation.position, 20000);//test custom station
//		system.addShips("rescue_blackbox", 1, system.mainStation.position, 10000);//test custom ship
//		system.addShips("rescue_blackbox_generic", 1, system.mainStation.position, 10000);//test custom ship
//		system.addShips("stealth_base", 1, system.mainStation.position, 20000);//test custom station
//		system.addShips("stealth_barracuda", 1, system.mainStation.position, 10000);//test stealth ship
//		system.addShips("stealth_mine", 1, system.mainStation.position, 20000);//test stealth mine
//		system.addShips("vector_areidisAlpha", 1, system.mainStation.position, 20000);//test custom station
//		system.addShips("vector_arn", 1, system.mainStation.position, 10000);//test custom ship
//		system.addShips("griff_NPC_prototype_boa_decals_from_red_channel",
//			1, system.mainStation.position, 10000);//test visual effect shader uniforms, need Griff Boa OXP
		
		if( worldScripts.telescope.$TelescopeThargoids ) { //test Telescope in instant action
			system.addShips("tharglet", 4, system.mainStation.position, 30000);
			system.addShips("thargoid", 4, system.mainStation.position, 30000);
//			system.addShips("police", 4, system.mainStation.position, 30000);
			player.ship.scriptedMisjump = true; //meet Thargoids in the next hyperjump also
		}
	}
}
this.$Telescope_Angle = function( angle ) { //show the side and up viewing angle of the target
	var c = 90;
	if( angle < 0 ) c = -90;
	return( Math.round( angle * 180 / Math.PI - c ) + "°" );
}
this.$Telescope_Attacker = function(lock) { //lock on the last attacker
	var ws = worldScripts.telescope;
	var ai = ws.$TelescopeAttackeri;
	var who = ws.$TelescopeList[ ai - 1 ];
//;	log("Telescope", "Attacker ai:"+ai+" who:"+who);//debug
	if( who && who.isValid && who.isVisible ) { //remove if destroyed or flyed too far
		ws.$TelescopeListi = ai;
		if( lock ) ws.$Telescope_Show(); //change player target to the nearest
		return( who );
	} else {
		ws.$TelescopeAttackeri = 0; //clear to get another attacker
		return false;
	}
}
this.$Telescope_From = function(ship, whomposition) { //direction mark
//	log("Telescope_From1", "ship:"+ship+" wp:"+whomposition);//debug
	if( !ship || !ship.isValid || !whomposition || !whomposition.dot ) return ("");
	var v = ship.position.subtract(whomposition);
	var fw = ship.vectorForward.angleTo(v);
	var ri = ship.vectorRight.angleTo(v);
	var up = ship.vectorUp.angleTo(v);
	var s = ""; // refine if out of front 1 degree cone
	if( ri < 1.56 ) s += "<";
	if( up > 1.58 ) s += "^";
	else if( up < 1.56 ) s += "v";
	if( ri > 1.58 ) s += ">";
//	log("Telescope_From2", "v:"+v+" fw:"+fw+" ri:"+ri+" up:"+up+" s:"+s);//debug
	if( s.length > 0 || fw < 1 ) //do not exclude the aft 1 degree cone
		s = Math.round( 180 * ( 1 - fw / Math.PI ) ) + "° " + s;
	return( s ); //show forward angle in degrees
}
this.$Telescope_GetDetect = function(who) { //read detection from the script_info telescope entry
	var good = true; var d;
	if( who.scriptInfo || who.isStation ) {//can give custom detection distance to the target ship
		if( who.scriptInfo ) d = parseInt( who.scriptInfo.telescope );
		if( d === 0 ) good = false;
		else if( d > 0 || d < 0 ) good = true; //show custom station
		else if( who.isStation && who.primaryRole !== "station" //d is NaN due to undefined scriptInfo key
			&& who.primaryRole !== "coriolis"
			&& who.primaryRole !== "dodo"
			&& who.primaryRole !== "dodec"
			&& who.primaryRole !== "dodecahedron"
			&& who.primaryRole !== "ico"
			&& who.primaryRole !== "icosa"
			&& who.primaryRole !== "icosahedron"
			&& who.primaryRole !== "rockhermit" ) {
			var ps = player.ship;
			if(ps && ps.isValid) { //hide custom station over 4x scanner range
				var distance = ps.position.distanceTo(who.position);
				if( distance > 4 * ps.scannerRange ) good = false;
			}
		}
	}
	//stealth ships and non-standard stations detected in normal scanner range only, requested by Svengali
	if( good && ( who.dataKey && ( who.dataKey.indexOf("stealth") > -1 
		|| who.dataKey === "vector_arn" ) //mission ship in Vector OXP
		|| who.primaryRole && ( who.primaryRole.indexOf("stealth") > -1
		|| who.primaryRole.indexOf("rescue_blackbox") > -1 ) ) ) //mission ships in Rescue Stations OXP
		good = false;
	return( good );
}
this.$Telescope_GetMass = function(entity) { //read mass from the script_info telescope_mass entry
	var em = entity.mass;
	if( entity.scriptInfo ) { //can give custom mass to the ship
		var e = parseInt(entity.scriptInfo.telescope);
		if( e >= 0 ) em = e;
		else if( e < 0 ) em += e; //substract the mass instead of overwrite it
		//else telescope key not present, return the original mass
//; 		log("Telescope", "scriptInfo "+entity.name+":"+entity.scriptInfo.telescope+" e:"+e+" mass:"+em);//debug
	}
	return( em * Math.min( 1, worldScripts.telescope.$TelescopeGSP ) ); //reduce if GS not in full detection yet
}
this.$Telescope_GravOK = function() { //in the range of the Gravity Scanner if installed
	if( player.ship && player.ship.equipmentStatus("EQ_GRAVSCANNER") == "EQUIPMENT_OK"
		&& worldScripts.telescope.$TelescopeStaionNearby //near a station or baseship
		&& !player.ship.weaponsOnline ) return true; //grav scan without weapons only to need force it
	return false;
}
this.$Telescope_InRange = function(entity) { //Visible or in normal Scanner means in Telescope range
	if( player.ship && player.ship.equipmentStatus("EQ_TELESCOPE") === "EQUIPMENT_OK" &&
		( entity.isPlanet || entity.isSun || !entity.isCloaked && !entity.isVisualEffect
		&& ( entity.dataKey //sun and planets has no dataKey
			&& entity.dataKey !== "telescopemarker" ) //virtual target of far targets
		&& ( !entity.isRock || entity.isStation || entity.isPiloted ) && //there are piloted "rocks" in lave.oxp
		( ( entity.isVisible && player.ship.equipmentStatus("EQ_TELESCOPEEXT") === "EQUIPMENT_OK" )
		|| entity.isBeacon || entity.beaconCode //beacons in the whole system
		|| worldScripts.telescope.$Telescope_IsScannerTarget(entity)
		|| worldScripts.telescope.$Telescope_InGravRange(entity) ) ) ) //or in grav.scanner
		return true;
	else return false;
}
this.$Telescope_InRangeFast = function(entity, listi) {
	//less check than InRange due to checked before and speed is important
	if( entity.isCloaked ) return false;
	var ps = player.ship;
	if( entity.isVisible && ps.equipmentStatus("EQ_TELESCOPEEXT") === "EQUIPMENT_OK"
		|| entity.isBeacon || entity.beaconCode || entity.isPlanet || entity.isSun )
		return true;//beacons in the whole system
	var distance = ps.position.distanceTo(entity.position);
	if( distance < ps.scannerRange ) return true; //target in the normal scanner
	var ws = worldScripts.telescope;
	if( !ws.$TelescopeListGD[ listi ] ) { //store max. grav. distance to next time check faster
		var em = ws.$Telescope_GetMass( entity );
		var gd = Math.pow( 100 * em, 1/3 );//reverse calculation of InGravRange
		if( ps.equipmentStatus("EQ_LARGEDISH") === "EQUIPMENT_OK" ) {
			gd = gd * 2; //large dish double range
			var pm = ws.$Telescope_GetMass( ps );
			if( pm > 1000000 ) gd = gd * 2; //huge player ship double range another time
			if( pm > 100000000 ) gd = gd * 2; //baseship scan double range third time
		} else if( ps.equipmentStatus("EQ_SMALLDISH") === "EQUIPMENT_OK" )
			gd = gd * 1.33; //small dish 1.33x range
		gd = gd * 500; //max. grav. distance of this ship in meters
		ws.$TelescopeListGD[ listi ] = gd; //store
//;		log("Telescope", "InRangeFast ship:"+entity.name+" em:"+Math.round(em)+"kg gd:"+Math.round(gd)+"m");//debug
	}
	if( distance < ws.$TelescopeListGD[ listi ] ) return true; //compare with the stored value
	return false;
}
this.$Telescope_InGravRange = function(entity) { //in the range of the Gravity Scanner if installed
	var ws = worldScripts.telescope;
	if( ws.$Telescope_GravOK()
		&& entity.isValid && !entity.isCloaked //can not detect if cloaked
		&& entity.dataKey && entity.dataKey !== "telescopemarker" //virtual target of far targets
		&& ( entity.isPiloted || entity.forwardWeapon ) //need to detect hostile drones from HardShips OXP
		&& ( !entity.isRock || entity.isStation )
		&& !entity.isVisualEffect ) {
		var ps = player.ship;
		var distance = ps.position.distanceTo(entity.position);
		if( distance < ps.scannerRange ) return false; //near ships listed before
		if( entity.isBeacon || entity.beaconCode || entity.isPlanet || entity.isSun )
			return true; //beacons in the whole system
		var d2 = distance / 500; //in half km, = 2 * distance / 1000, keep it in sync with InRangeFast
		if( ps.equipmentStatus("EQ_LARGEDISH") === "EQUIPMENT_OK" ) {
			var pm = ws.$Telescope_GetMass( ps );
			if( pm.mass > 100000000 ) d2 = d2 / 2; //baseship scan double range
			if( pm.mass > 1000000 ) d2 = d2 / 2; //huge player ship
			d2 = d2 / 2; //others detected at double range (with huge 4x)
		} else if( ps.equipmentStatus("EQ_SMALLDISH") === "EQUIPMENT_OK" )
			d2 = d2 / 1.33; //small dish 1.33x range
		var em = ws.$Telescope_GetMass( entity );
		if( em > d2*d2*d2/100 ) return true; //mass scaled to third power of distance
	}
	return false;
}
this.$Telescope_IsCargo = function(entity) { //Cargo, Escape Pod, Station in visible range and Beacon anywhere
	if( entity && entity.isValid && !entity.isVisualEffect ) {
		if( entity.isBeacon || entity.beaconCode ) return true; //beacons identifyed by active radio broadcast
		if( !entity.isPiloted && entity.isCargo || entity.isStation
			|| entity.primaryRole && entity.primaryRole === "escape-capsule" ) {
			if( worldScripts.telescope.$Telescope_IsVisible( entity ) )
				return true; //visible
		}
	}
	return false;
}
this.$Telescope_IsHostile = function(entity) {
	var ps = player.ship;
	var ws = worldScripts.telescope;
	if( entity && entity.isValid && !entity.isVisualEffect
		&& ( entity.isPiloted || entity.forwardWeapon ) //need to detect hostile drones from HardShips OXP
		&& ws.$Telescope_InRange( entity )
		&& !entity.isStation && !entity.isCloaked &&
		( entity.target && entity.target === ps //target is hostile if targeting back
		   //or in the defenseTargets
		|| ps.defenseTargets && ws._index_in_list( entity, ps.defenseTargets ) > -1
		   //or this ship in the defenseTargets of the other
		|| entity.defenseTargets && ws._index_in_list( ps, entity.defenseTargets ) > -1 )
		|| ws._index_in_list( entity, ws.$TelescopeReds ) > -1 ) //pirate or thargoid
		return true;
	else return false;
}
/* this.$Telescope_IsNonHostileStaion = function(entity) {
	if( entity && entity.isValid && entity.isStation && !entity.isVisualEffect && !entity.isCloaked
		&& entity.mass > 10000000 //skip ships with docking port (except baseships), rockhermit with 53508t must fit in
		&& entity.target !== player.ship //target is hostile if targeting back
		   //or player is in the defenseTargets
		&& ( !entity.defenseTargets || entity.defenseTargets.indexOf(player.ship) === -1 ) )
		return true;
	else return false;
}
 */
 this.$Telescope_IsNotInTList = function(entity) { //asteroids, etc.
	var ws = worldScripts.telescope;
	if( entity && entity.isValid && !entity.isVisualEffect && !entity.isCloaked //else excluded from this list also
		&& !entity.isPiloted && !entity.forwardWeapon && !entity.isCargo //approximately not in list for speedup
		&& ws._index_in_list( entity, ws.$TelescopeList ) === -1 ) //surely not in list but slow alone
		return( true );
	else return( false );
}
this.$Telescope_IsPilotedShip = function(entity) { //non-hostile piloted ships
	return( entity.isValid && !entity.isVisualEffect && !entity.isStation && !entity.isPlanet && !entity.isSun
		&& ( entity.isPiloted || entity.forwardWeapon ) //need to detect drones from HardShips OXP
		&& worldScripts.telescope.$Telescope_InRange( entity )
		&& !entity.isCloaked && !this.$Telescope_IsHostile(entity) );
}
/* this.$Telescope_IsPilotedVisible = function(entity) {
	if( entity && entity.isValid && !entity.isVisualEffect && !entity.isCloaked
		&& ( entity.isPiloted || entity.forwardWeapon ) //need to detect drones from HardShips OXP
//		&& ( !entity.isRock || entity.isStation ) //too much asteroids, lock only in scannerRange
		&& (( worldScripts.telescope.$Telescope_GetDetect( entity )
			&& entity.isVisible && player.ship.equipmentStatus("EQ_TELESCOPEEXT") === "EQUIPMENT_OK" )
			|| worldScripts.telescope.$Telescope_IsScannerTarget( entity ))
	  	&& ( !entity.dataKey || entity.dataKey !== "telescopemarker" ) )
		return true;
	return false;
}
 */
// there is now a copy of this inside _TimedS_closure
this.$Telescope_IsScannerTarget = function(entity) { //call to surely detect all small targets in scanner
	var ps = player.ship;
	if( entity && entity.isValid && !entity.isVisualEffect && entity.dataKey //sun and planets has no dataKey
		&& !entity.isCloaked && ps && ps.isValid ) {
		var distance = ps.position.distanceTo(entity.position);
		if( distance < ps.scannerRange ) return true; //target in the normal scanner
	}
	return false;
}
this.$Telescope_IsValid = function(entity) { //help to find most centered
	if( !entity || !entity.isValid || ( !entity.dataKey && !entity.isPlanet && !entity.isSun ) )
		return false; //Sun and Planets has no dataKey
//	var angle = player.ship.vectorForward.angleTo(player.ship.position.subtract(entity.position));
//	log("Telescope","angle:"+angle+" entity:"+entity.name);//debug
//	if( angle < worldScripts.telescope.$TelescopeAutoLockRad ) { //in auto degree
		if( !entity.isVisualEffect || entity.dataKey && entity.dataKey.indexOf("telescope-") > -1 )
			return true;
//	}
	return false;
}
this.$Telescope_IsVisible = function(entity) {
	if( !entity.isVisualEffect && ( entity.dataKey || entity.isPlanet || entity.isSun ) //sun and planets has no dataKey
		&& ( !entity.isRock || entity.isStation ) //too much asteroids, lock only in scannerRange
		&& !entity.isCloaked
		&& ( ( entity.isVisible && player.ship.equipmentStatus("EQ_TELESCOPEEXT") === "EQUIPMENT_OK" )
			|| worldScripts.telescope.$Telescope_IsScannerTarget(entity) ) ) return true;
	return false;
}
this.$Telescope_List = function(step) {
	//no scan if there are a list already, show again the message only
	worldScripts.telescope.$Telescope_List2(step, false);
}
this.$Telescope_List2 = function(step, forcedscan) {
	worldScripts.telescope.$Telescope_List3(step, forcedscan, false);
}
this.$Telescope_List3 = function(step, forcedscan, free) {
	var ps = player.ship;
	if( !ps || !ps.isValid ) return; //if player died
	var ws = worldScripts.telescope;
	var list = ws.$TelescopeList;
	var len = list.length;
	var ti = ws.$TelescopeListi;
	if(ps.equipmentStatus("EQ_TELESCOPE") === "EQUIPMENT_DAMAGED") {
		player.consoleMessage("Telescope damaged");
		ws.$Telescope_NewList();
	} else if(ps.equipmentStatus("EQ_TELESCOPE") !== "EQUIPMENT_OK") {
		if( ws.$TelescopeBuyMsg ) {
			player.consoleMessage("Buy Telescope! (x)");
			ws.$TelescopeBuyMsg = false;
		}
		ws.$Telescope_NewList(); //needed when destroyed
		//else silent exit (no Telescope when called from shipTargetLost)
	} else {
		ws.$TelescopeTargetSet = true;
		var forcedship = null;
		if( ti > 0 )
			forcedship = list[ti - 1];
//		var who = null;
		if(step < 0) ws.$TelescopeListi--; //step backward in list
		else if(step > 0) ws.$TelescopeListi++; //step forward in list
		ti = ws.$TelescopeListi;
//		player.consoleMessage(step+" "+ws.$TelescopeListi);//debug
		if( forcedscan || !list
			|| ti < 1
			|| len < ti ) {
			//if forced to scan or no list yet or listed all items then rescan
			if(ps.energy < 64)
				player.consoleMessage("Not enough energy for Telescope");
			else {
				ws.$Telescope_NewList();
				//detect hostile ships in normal scanner
				var st = system.filteredEntities(this, this.$Telescope_IsHostile,
								 ps, ps.scannerRange);
//				var st1 = st.length;
				if(st && st[0]) ws.$Telescope_ListAdd(st); //add to the list if any
				//detect non-hostile piloted ships in normal scanner
				st = system.filteredEntities(this, this.$Telescope_IsPilotedShip,
								 ps, ps.scannerRange);
//				var st2 = st.length;
				if(st && st[0]) ws.$Telescope_ListAdd(st); //add to the list if any
				//detect visible cargoes, stations and all beacons
				st = system.filteredEntities(this, this.$Telescope_IsCargo,
								 ps, 2*ps.scannerRange);
//				var st3 = st.length;
				if(st && st[0]) ws.$Telescope_ListAdd(st); //add to the list
				var st4 = 0;
				if( free ) { //add the previously scanned far ships
					ws.$Telescope_VFCBShrinkVMarks();//new far marks needed
					ws.$Telescope_ListAdd2(
						ws.$TelescopeListFar,
						ws.$TelescopeListFarPos ); //old positions
				} else {
					ps.energy -= 2; //use a little energy to scan the whole sky
					var sc = "Telescope";
					if( ws.$Telescope_GravOK() ) {
						ps.energy -= 6; //need 4x energy with Gravity Scanner
						ws.$TelescopeSoundScan.play();//GS scan sound
						sc = "Gravity scan";
						if( ws.$TelescopeGSP < 1 )
							sc += " "+Math.round(ws.$TelescopeGSP*100)
								+"% done,";
						//Gravity Scan counter to bring aliens
						missionVariables.$TelescopeGSC = ++ws.$TelescopeGSC;
						var p = 200; //at every 200. scan
						if( ws.$TelescopeFixedGS === 1 ) p = 100; //every 100. scan
						var g = ws.$TelescopeGSC / p;
						if( g === Math.floor( g ) ) {
							var n = Math.ceil( Math.pow( player.score, 0.5 ) / 10 );
							if( n > 0 ) { //with 0 score do not get any
								player.consoleMessage("Aliens detected your Gravity Scan!",10);
								system.addShips("thargoid", n, ps.position, 50000);
							}
						}
					}
					ws.$Telescope_NewFarList();
					//detect ships NOT in normal scanner
					st = system.filteredEntities(this, this.$Telescope_InRange, ps,
								     ws.$TelescopeMaxRange);//to avoid bugs
					st4 = st.length;
					if(st && st[0]) ws.$Telescope_ListFarAdd(st); //add to the list
					//finally planets
					st = system.planets;
					if(st && st[0]) ws.$Telescope_ListFarAdd(st); //add to the list
					if( !system.isInterstellarSpace && system.sun && system.sun.isValid )
						ws.$Telescope_ListFarAdd(system.sun); //add sun to the end of the list
					//send message about scan
					len = ws.$TelescopeList.length;
					if( !forcedscan ) {
						var msg = "No";
						if( len > 0 ) msg = sc+" found "+len;
						var s = "";
						if( len > 1 ) s = "s";
						player.consoleMessage(msg+" target"+s, 5);
					}
				}
//;		 		log("Telescope", "filteredEntities: "+st1+", "+st2+", "+st3+", "+st4+" list:"+ws.$TelescopeList.length+" allShips:"+system.allShips.length);  //debug
				ws.$Telescope_TimedVMC(); //make colours to the new list
			}
		}
		if( ps.target && ps.target.isValid ) {
			ti = 0;
			list = ws.$TelescopeList;
			len = list.length;
			if( forcedship && ps.target.dataKey
				&& ps.target.dataKey === "telescopemarker" ) //far target
				ti  = ws._index_in_list( forcedship, list ) + 1;
			else ti = ws._index_in_list( ps.target, list ) + 1;
			if( ti > 0 ) {
				if( step < 0 ) {
					ti--; //step backward in list
					if( ti <= 0 ) ti = len;
				} else if( step > 0 ) {
					ti++; //step forward in list
					if( ti > len ) ti = 1;
				}
			} else ti = 0;
			if( ti > 0 ) {
				var who = list[ ti - 1 ];
				var limit = len;
				while( ws._is_ignored_ship( ws, who ) && limit > 0 ) {
					ti += step;
					limit--;
					if( ti <= 0 ) ti = len;
					if( ti > len ) ti = 1;
					who = list[ ti - 1 ];
				}
				if( limit === 0 ) ti = 0;
			}
			ws.$TelescopeListi = ti;
//			if( forcedscan ) ws.$Telescope_Show2( false ); //do not show name - removed due to a target changer bug reported by Bogatyr
			if( !forcedscan ) ws.$Telescope_Show(); //target name with list number
		} //else leave listi on 0 to do not nock anything, show lightballs only
//; 		log("Telescope", "List step:"+step+" forcedscan:"+forcedscan+" listi:"+ws.$TelescopeListi+" ship:"+ws.$TelescopeList[ws.$TelescopeListi-1]);//debug
		ws.$TelescopeTargetSet = false;
	}
}
this.$Telescope_ListAdd = function(newitems) { //add to the main list
	worldScripts.telescope.$Telescope_ListAdd2(newitems, null); //with fresh positions
}
this.$Telescope_ListAdd2 = function(newitems, pos) { //can append the old scanned far list to the new free list
	var ws = worldScripts.telescope;
	for(var i = 0; i < newitems.length && ws.$TelescopeList.length <
		ws.$TelescopeTargets; i++) { //limited number of targets to save CPU
		var who = newitems[i]; var p;
		if( who && who.isValid &&
			ws._index_in_list( who, ws.$TelescopeList ) === -1 ) {
			if( ws.$Telescope_GetDetect( who )
				|| ws.$Telescope_IsScannerTarget( who ) ) {
				if( pos ) p = pos[i]; //old saved position
				else p = who.position; //fresh position
				ws.$TelescopeListPos.push( p );
				ws.$TelescopeList.push(who);
			}
		}
	}
}
this.$Telescope_ListFarAdd = function(newitems) { //add to the far and main list also
	var ws = worldScripts.telescope;
	for(var i = 0; i < newitems.length; i++) {
		var who = newitems[i];
		if( who && who.isValid &&
			ws._index_in_list( who, ws.$TelescopeListFar ) === -1 ) {
			ws.$TelescopeListFarPos.push(who.position);
			ws.$TelescopeListFar.push(who);
		}
	}
	ws.$Telescope_ListAdd(newitems); //add to the main list also
}
/* this.$Telescope_MFDTarget = function(ws, ps, who) { //helper function for building MFD in TimedS
	if( who && who.isValid ) {
		var v = ws.$Telescope_ShowName2(who, who.position);
		if( v && v.length > 0 && v.indexOf("(Lost ") === -1 ) {
			if( who === ps.target || //current target
				ps.target && ps.target.dataKey
				&& ps.target.dataKey === "telescopemarker" //get the original target
				&& who === ws.$TelescopeList[ ws.$TelescopeListi - 1 ] )
				v = "[ "+v+" ]"; //mark the current target
			return( v+"\n" );
		}
	}
	return false;
}
 */
this.$Telescope_MostCentered = function( skiptarget, mode, lostvalid ) {
	var ws = worldScripts.telescope;
//;	log("Telescope", "MostCentered "+skiptarget+" "+mode+" "+lostvalid+" "+ws.$TelescopeIdentUnLock);//debug
	var mct = null;
	var ps = player.ship;
	//in red alert find most centered hostile first if not supressed with offline weapons
	if( player.alertCondition > 2 && ps.weaponsOnline && mode === "mode" ) {
//		mct = ws.$Telescope_Attacker(false); //fallback to last attacker - old method
//;		if(mct) log("Telescope", "Ident lock on attacker: "+mct.name);//debug
		mct = ws.$Telescope_MostCentered2( skiptarget, mode, true, true );//ships attacking player
//		log("Telescope", "mct1:"+mct);  //debug
		if(!mct) {
			mct = ws.$Telescope_MostCentered2( skiptarget, mode, true, false );//ships targeting player
//			log("Telescope", "mct2:"+mct);  //debug
		}
	}
	//if no hostile then fallback to normal check
	if(!mct) mct = ws.$Telescope_MostCentered2( skiptarget, mode, false, false );
	if( mct ) {
		if( mct.dataKey && mct.dataKey === "telescopemarker" ) {//markership
			mct = ws.$TelescopeTarget; //the real target behind the markership
		}
		var mi = ws._index_in_list( mct, ws.$TelescopeList );
		if( mi > -1 ) {
			//ident mode do unlock if pressed second time with the same most centered target
			if( mode !== "ident" || mi + 1 !== ws.$TelescopeListi ) {
				
//;				log("Telescope", "MostCenteredTarget: "+mct.name+" mi:"+mi);//debug
				if( !ps.weaponsOnline && mode === "ident" ) {//restore GravLock if ident lock target
					ws.$TelescopeGravLock2 = ws.$TelescopeGravLock;
					player.consoleMessage("Panorama targeting turned ON.");
				}
				if(  ws.$TelescopeListi !== mi + 1 ) {
					ws.$TelescopeListi = mi + 1;
					ws.$Telescope_Show2( false ); //lock target
				}
			} else {
				if( mode === "ident" && ws.$TelescopeIdentUnLock ) { //unlock target after steer (3. ident press)
					ws.$TelescopeIdentUnLock = false;
//;					log("Telescope", "MCT ident unlock");//debug
					if( !ps.weaponsOnline && mode === "ident" && lostvalid ) {
						//shrink GravLock if ident unlock target by keypress and not by target destroyed
						ws.$TelescopeGravLock2 = ws.$TelescopeAutoLock;
						player.consoleMessage("Panorama targeting turned OFF.");
					}
					ws.$TelescopeListi = 0;
//the following lines cause crash to desktop in 1.77 after some fight when a targeted Tharglet is exploding
//					ws.$Telescope_VClear();
					if( ps.target ) {
//						log("Telescope", "MostCenteredTarget unlock: "+mct.name+" mi:"+mi);//debug
						ws.$TelescopeTargetSet = true;//to avoid doubled message
						ps.target = null;
						ws.$TelescopeTargetSet = false;
					}
					ws.$TelescopeTarget = null; //clear after(!) player target cleared
				} else if( mode === "ident") { //second ident press start steering to the target
					ws.$TelescopeIdentUnLock = true; //next time will do unlock
					ws.$TelescopeListi = mi + 1;
					ws.$Telescope_Show2( false ); //lock target
 					if( ws.$TelescopeSteering > 0 )
						if( ps.velocity.magnitude() < ps.maxSpeed * 1.1 )
							//prevent unwanted steer when lost marker at high speeds
							ws.$Telescope_Steer();//turn to the target
				}
			}
		}
	}
}
this.$Telescope_MostCentered2 = function( skiptarget, mode, red, attacker ) {
	var ps = player && player.ship;
	if( !ps || !ps.isValid ) return false;
	var psp = ps.position;
	var ws = worldScripts.telescope;
	function closest_to( target_vector ) {
		var min_a = rad;
		var best = -1; 		// ie. not init'd
		var best_d = ws.$TelescopeMaxRange;
		var angle, distance, i;
		for( i = 0; i < list.length; i++ ) { //search target near the center
			var test = list[ i ];
			if( !test || !test.isValid ) continue; // brought $Telescope_MostCenteredCheck inline
			if( test === skiptarget ) continue;
			if( red && mode === 'ident' && test.target !== ps ) continue;
				// in red alert w/ mode 'ident', check only hostiles
			angle = target_vector.angleTo( test.position.subtract( psp ) );
			if( angle > min_a ) continue;
			distance = psp.distanceTo( test.position );
			if( min_a === rad ) { // 1st target found
				best_d = distance;
				min_a = angle;
				best = i;
				continue;
			}
			if( Math.abs( angle - min_a ) < Math.PI / 360 ) { // for ships within a half degree, pick the closer one
				if( distance > best_d ) continue;
			}
			best_d = distance;
			min_a = angle;
			best = i;
		}
		return best;
	}
	function find_target() {
		var dir = ps.viewDirection;
			 if( dir === "VIEW_FORWARD" ) 	return closest_to( ps.vectorForward );
		else if( dir === "VIEW_AFT" )		return closest_to( ps.vectorForward.multiply(-1) );
		else if( dir === "VIEW_STARBOARD" ) return closest_to( ps.vectorRight );
		else if( dir === "VIEW_PORT" ) 		return closest_to( ps.vectorRight.multiply(-1) );
		return -1;
	}
	var list = ws.$TelescopeList; //search in the list only (stable)
	var rad = ws.$TelescopeIdentLock * Math.PI / 180; ///angle in radians
	var result = -1;
	if( attacker ) { 
		list = ws.$TelescopeAttackers; 
		red = false; 
	} else if( player.alertCondition > 2 && ps.weaponsOnline ) {//in red alert find most centered hostile if not supressed with offline weapons
		rad = Math.PI; //in Red Alert lock from the whole sphere who target you
		red = true;
	}
	if( mode === "ident" ) {									//button "r" pressed or target lost
		if( player.alertCondition < 3 ) { //do not target asteroids before ships in red alert
//	log("Telescope", "MCT calling filteredEntities");  //debug
//	var list = system.filteredEntities(this, this.$Telescope_IsValid, ps, ps.scannerRange); - cause bug
			list = system.filteredEntities(ws, ws.$Telescope_IsNotInTList, ps, ps.scannerRange);
//	log("Telescope", "MCT filteredEntities returned "+list.length);  //debug
			rad = ws.$TelescopeIdentLock * Math.PI / 180; ///angle in radians
			result = find_target();
			if( result > -1 ) { //found, target it
				return( list[ result ] ); //priority to targets not in list but in crosshairs for asteroid hunting
			}
		} //if no asteroid in crosshairs then do normal ident to a ship in telescope list
		list = ws.$TelescopeList; 
		red = false;
	} else 	if( mode === "auto" ) { //lock in the crosshairs only
		if( ws.$TelescopeAutoLock <= 0 ) return false;	//if disabled
		rad = ws.$TelescopeAutoLock * Math.PI / 180; ///angle in radians
	} else 	if( mode === "grav" ) { //panorama targeting or lock in the crosshairs
		rad = ws.$TelescopeGravLock2 * Math.PI / 180; ///angle in radians
	} else 	if( mode === "mode" ) { //button "b" pressed after primed Telescope equipment with Shift+N
		if( red ) rad = Math.PI;//in Red Alert lock from the whole sphere who target you
	}
//;	log("Telescope", "MostCentered2 "+mode+" "+l);//debug
//	if( ps && ps.isValid && ps.target ) skiptarget = ps.target; //search another target
	result = find_target();
	if( result > -1 ) { //found, target it
		return( list[ result ] );
	}
	return false;
}
/* this.$Telescope_Nearest = function() { //lock the nearest target
//	var attacker = false;
	var ws = worldScripts.telescope;
	var ps = player.ship;
	var i, nd, nai, who, ni = 0, d = null;
	//in Red Alert lock the last attacker if any and weapons are online
	if( player.alertCondition > 2 && ps.weaponsOnline ) {
//		attacker = ws.$Telescope_Attacker( true ); //last attacker - old method
		nd = ws.$TelescopeMaxRange; //reset to max.
		nai = -1;
		for( i = 0; i < ws.$TelescopeAttackers.length; i++ ) {
			who = ws.$TelescopeAttackers[ i ];
			ni = ws._index_in_list( who, ws.$TelescopeList );
			if( who && who.isValid && ni > -1 && !who.isDerelict ) {
				d = ps.position.distanceTo( who.position ); //distance to the attacker
				if( d < nd ) { //nearer than the last nearest
					nd = d; //save the new nearest distance
					nai = ni; //save the index in the main list
				}
			} else { //remove if destroyed, derelict or not in TelescopeList
				ws.$TelescopeAttackers.splice( i, 1 ); //remove from the array
				i--; //stay on this position due to the array is shorter
			}
		}
		if( nai > -1 ) { //change player target to the nearest attacker
			ws.$TelescopeListi = nai + 1;
			ws.$Telescope_Show();
		}
	} else { //lock the nearest target
//	if( !attacker ) {
		ni = ws.$TelescopeNearesti;
		who = ws.$TelescopeList[ ni - 1 ];
		if( ps && ( !who || !who.isValid ) ) {
			ws.$Telescope_Scan(); //forced scan
			ni = ws.$TelescopeNearesti;
			who = ws.$TelescopeList[ ni - 1 ];
		}
//;		log("Telescope", "Nearest ni:"+ni+" who:"+who);//debug
		if( ps && who && who.isValid ) {
			ws.$TelescopeListi = ni;
			ws.$Telescope_Show(); //change player target to the nearest
		}
	}
}
 */
 this.$Telescope_Nearest = function() { //lock the nearest target
//	var attacker = false;
	var ws = worldScripts.telescope;
	var ps = player.ship;
	var i, nd, nai, who, ni = 0, d = null;
	function nearest( list ) {
		nd = ws.$TelescopeMaxRange; //reset to max.
		let near = -1;
		let len = list.length;
		for( i = 0; i < len; i++ ) {
			who = list[ i ];
			if( ws._is_ignored_ship( ws, who ) ) continue;
			ni = list === ws.$TelescopeList ? i : ws._index_in_list( who, ws.$TelescopeList );
			if( who && who.isValid && ni > -1 && !who.isDerelict ) {
				d = ps.position.distanceTo( who.position ); //distance to the attacker
				if( d < nd ) { //nearer than the last nearest
					nd = d; //save the new nearest distance
					near = ni; //save the index in the main list
				}
			} else { //remove if destroyed, derelict or not in TelescopeList
				ws.$TelescopeAttackers.splice( i, 1 ); //remove from the array
				i--; //stay on this position due to the array is shorter
			}
		}
		return near;
	}
	//in Red Alert lock the last attacker if any and weapons are online
	if( player.alertCondition > 2 && ps.weaponsOnline ) {
//		attacker = ws.$Telescope_Attacker( true ); //last attacker - old method
		nai = nearest( ws.$TelescopeAttackers );
		if( nai > -1 ) { //change player target to the nearest attacker
			ws.$TelescopeListi = nai + 1;
			ws.$Telescope_Show();
			return;
		}
	}
	nai = nearest( ws.$TelescopeList );
	if( nai > -1 ) { //change player target to the nearest attacker
		ws.$TelescopeListi = nai + 1;
		ws.$Telescope_Show();
	}
}
this.$Telescope_NewFarList = function() {
//	log("Telescope","NewFarList");
	var ws = worldScripts.telescope;
	ws.$TelescopeListFar = [];
	ws.$TelescopeListFarPos = [];
}
this.$Telescope_NewList = function() {
//;	log("Telescope","NewList");
	var ws = worldScripts.telescope;
	ws.$Telescope_NewFarList();
	ws.$TelescopeAttackeri = 0;
	ws.$TelescopeList = [];
	ws.$TelescopeListGD = [];
	ws.$TelescopeListPos = [];
	ws.$TelescopeListi = 0;
	ws.$TelescopeNearestd = ws.$TelescopeMaxRange;
	ws.$TelescopeNearesti = 0;
	var m = ws.$TelescopeMMarks;
	var i = 0;
	for( i = 0; i < m.length; i++ ) {
		if( m[i] ) m[i].remove();
		ws.$TelescopeMMarks[i] = null;
	}
	ws.$TelescopeMMarks = [];
	m = ws.$TelescopeMRings;
	for( i = 0; i < m.length; i++ ) {
		if( m[i] ) m[i].remove();
		ws.$TelescopeMRings[i] = null;
	}
	ws.$TelescopeMRings = [];
	m = ws.$TelescopeVMarks;
	for( i = 0; i < m.length; i++ ) {
		if( m[i] ) m[i].remove();
		ws.$TelescopeVMarks[i] = null;
	}
	ws.$TelescopeVMarks = [];
	ws.$TelescopeVMCs = [];
}
this.$Telescope_PlanetName = function(who) { //cached names
	if( !system || !system.planets || system.planets.length < 1 || !who || (!who.isPlanet && !who.isSun ))
		return("");
	var ws = worldScripts.telescope;
	var name = '';
	if( who.isSun ) {
		name = ws.$TelescopePlanetNames.sun;
		if( !name || name.length < 1 ) {
			name = ws.$Telescope_PlanetName2(who);
			ws.$TelescopePlanetNames.sun = name;
		}
	} else {
		var i = ws._index_in_list( who, system.planets );
		if( i === -1 ) return("");
		name = ws.$TelescopePlanetNames[i];
		if( !name || name.length < 1 ) {
			name = ws.$Telescope_PlanetName2(who);
			ws.$TelescopePlanetNames[i] = name;
		}
	}
	return( name );
}
this._isNotPlanet = function (entity) {return(!entity.isPlanet);}
this._allPlanets  = function (entity) {return entity.isPlanet;}
this.$Telescope_PlanetName2 = function(who) {
	var ws = worldScripts.telescope;
	var name = "Planet";
	var p = null;
	if( system && who && who.isValid ) {
		if( who.isSun ) return("Sun of "+system.name);
		if( !who.isPlanet ) return("Non-Planet");
		var wn = worldScripts.planetnames;
		if( wn ) {
			name = wn.$PlanetNames_GetPlanetName( who );
			if( name && name.length > 0 ) return( name );
		}
		if( worldScripts["planetaryCompass_worldScript.js"] ) {
			p = system.filteredEntities(this, this._isNotPlanet, who, 10);
//;			log("Telescope","PlanetName2 "+p);
			if( p && p[0] && p[0].isValid && p[0].isVisualEffect && p[0].displayName )
				return( p[0].displayName );
		}
		name = system.name;
		if( who.isMainPlanet ) name += " Prime (Planet)";
		else {
			p = system.filteredEntities(this, this._allPlanets, system.sun); //order by distance from sun
			if( who.hasAtmosphere ) {
				var n = [ "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", 
					"XI", "XII", "XIII", "XIV", "XV", "XVI", "XVII", "XVIII", "XIX", "XX" ];
				var no = 0;
				var i = ws._index_in_list( who, p );
				if( i < n.length ) no = n[i];
				else no = i;
				name += " "+no+" (Planet)";
			} else name = "Moon "+(ws._index_in_list( who, p )+1);
		}
	}
	return( name );
}
this.$Telescope_RefundEQ = function( eq ) {
	var ws = worldScripts.telescope;
	var ps = player.ship;
	if( ws._index_in_list( eq, ps.equipment ) ) {
		if( ps.equipmentStatus( eq ) === "EQUIPMENT_DAMAGED" )
			player.consoleMessage("Need repair first"); //do not get back full price for a damaged eq
		else if( ps.equipmentStatus( eq ) === "EQUIPMENT_OK" ) {
			ps.removeEquipment( eq );
			clock.addSeconds ("3600");
			var refund = EquipmentInfo.infoForKey( eq ).price / 10; ///
			player.credits += refund;
			player.consoleMessage("Refunded "+refund+" credits for "+EquipmentInfo.infoForKey( eq ).name);
			return( refund );
		}
	}
	return( 0 );
}
this.$Telescope_Scan = function() {
	worldScripts.telescope.$Telescope_List2( 0, true ); //forced to scan
}
this.$Telescope_SetLightballs = function( subitem ) { //set config variables from telescopeeq.js also
	var ws = worldScripts.telescope;
//	if( subitem == 1 ) { //off
	ws.$TelescopeLightBalls = false;
	ws.$TelescopeShipLightBalls = false;
	ws.$TelescopeMassLockBorders = false;
    ws.$TelescopeBrightMassLockBorders = false;
	ws.$TelescopeLargeLightBalls = false;
	if( subitem >= 2 ) ws.$TelescopeLightBalls = true; //ship off
	if( subitem >= 3 ) ws.$TelescopeShipLightBalls = true; //small
	if( subitem >= 4 ) ws.$TelescopeMassLockBorders = true; //25.6km but in green alert only
	if( subitem >= 5 ) ws.$TelescopeBrightMassLockBorders = true; //use brighter borders
	if( subitem >= 6 ) ws.$TelescopeLargeLightBalls = true;//large
}
this.$Telescope_SetSniper = function( subitem ) {
	var ws = worldScripts.telescope;
	if( subitem === 1 ) { //off
		ws.$TelescopeSniperRange = 10000;
		ws.$TelescopeSniperMinRange = 10000;
	} else {
		var minitem = subitem;
		if( subitem < 5 ) ws.$TelescopeSniperRange = 25600;
		else { minitem = subitem - 3; ws.$TelescopeSniperRange = 30000;}
		ws.$TelescopeSniperMinRange = 5000 * (minitem-1); //5, 10 or 15km
	}
}
this.$Telescope_SetSteering = function( subitem ) {
	worldScripts.telescope.$TelescopeSteering = subitem - 1;
}
this.$Telescope_SetTargets = function( subitem ) {
	var ws = worldScripts.telescope;
	if( subitem === 1 ) { //20 and limitation in red alert
		ws.$TelescopeRedAlertLimiter = true;
		ws.$TelescopeTargets = 20;
	} else {
		ws.$TelescopeRedAlertLimiter = false;
		if( subitem === 2 ) ws.$TelescopeTargets = 50;
		else if( subitem === 3 ) ws.$TelescopeTargets = 100;
		else ws.$TelescopeTargets = 200;
	}
}
this.$Telescope_SetVisual = function( subitem ) {
	var ws = worldScripts.telescope;
	if( subitem === 1 ) {
		ws.$TelescopeShowVisualTarget = false; //off
		ws.$TelescopeVZoomSize = 0; //off without weapons also
	}
	if( subitem <= 2 ) ws.$TelescopeShowVisualTarget = false; //weapons off
		else ws.$TelescopeShowVisualTarget = true;
	if( subitem <= 3 ) {
		if( subitem > 1 && ws.$TelescopeRing ) {
			ws.$TelescopeRing = false; //no ring
			ws.$TelescopeVSize += 3; //due to ring turned off
			ws.$TelescopeVZoomSize = ws.$TelescopeVSize;
		}
	} else {
		if( subitem > 1 && !ws.$TelescopeRing ) {
			ws.$TelescopeRing = true;
			ws.$TelescopeVSize -= 3; //due to ring turned on
			ws.$TelescopeVZoomSize = ws.$TelescopeVSize + 3;
		}
	}
	if( subitem <= 4 ) ws.$TelescopeShowVisualStation = false; //no station
	else ws.$TelescopeShowVisualStation = true;
	if( subitem <= 5 ) ws.$TelescopeShowVisualQuestionMark = false; //no "?"
	else ws.$TelescopeShowVisualQuestionMark = true;
}
this.$Telescope_SetVisualSize = function( subitem ) {
	var ws = worldScripts.telescope;
	if( ws.$TelescopeRing ) ws.$TelescopeVSize = subitem; //1-8
	else ws.$TelescopeVSize = subitem + 3; //without ring is equal with zoomed
	ws.$TelescopeVZoomSize = subitem + 3; //3-10
}
this.$Telescope_Show = function() { //inputs: $TelescopeList array, $TelescopeListi item index to show
	worldScripts.telescope.$Telescope_Show2( true );
}
this.$Telescope_Show2 = function( showname ) { //call with showname=true if eq step pressed
	var ws = worldScripts.telescope;
	//must use ws.$Telescope* and not this.$Telescope* to do not crash the game
	var ti = ws.$TelescopeListi;
	if( ws.$TelescopeList && ti > 0
		&& ws.$TelescopeList.length >= ti ) {
		var who = ws.$TelescopeList[ ti - 1 ];
		if( who ) {
			if( worldScripts.detectors && who.isValid && who.script && who.script.$Detectors_Origname )
				who.displayName = who.script.$Detectors_Origname;
				//show short name in automatic lock message
//			log("Telescope", "Show pt:"+player.ship.target+" who:"+who+" tt:"+ws.$TelescopeTarget);//debug
			if( player.ship.target !== who //to avoid repeated ident lock sound and message
				&& ws.$TelescopeTarget !== who) { //far target
				if( ws.$TelescopeTimerA ) { //restart timer if already running
					ws.$TelescopeTimerA.stop();
					ws.$TelescopeTimerA = null;
				}
				if( showname )
					ws.$TelescopeTimerA = new Timer(this, ws.$Telescope_TimedA, 0.05, 0);
				//return; //if the wait time of the timer set to 0.01 then buggy on Intel Atom netbook
				//but good on i3: if see coriolis then can not step to the next target (relock the base)
				//without timer the same bug happen on desktop i3
				else ws.$Telescope_TimedA();
				//if called from shipTargetLost then must to lock new target instantly to avoid
				//ident system active message
			}
		}
		if( showname ) ws.$Telescope_ShowName(ti, who,
			ws.$TelescopeListPos[ ti - 1 ] ); //fallback pos if lost target
		if( ( ws.$TelescopeShowVisualTarget || !player.ship.weaponsOnline )
			&& ws.$TelescopeFixedTel !== 1 ) {//no visual mode if cheaply fixed
//;			log("Telescope","Show2 ti:"+ti+" who:"+who);
			ws.$Telescope_VShow();
		}
	}
}
this.$Telescope_ShowName = function(ti, who, position) {
	var ws = worldScripts.telescope;
	var msg = ws.$Telescope_ShowName2(who, position);
	if( !msg || msg.length <= 0 ) return;
	
	var idlast = "";
	if( ti > 0 && ws.$TelescopeList.length <= ti ) idlast = " last";
	var tis = "";
	if( ti ) tis = " ("+ti+"."+idlast+")";
	
//	msg+=" ("+Math.round( ws.$TelescopeZoom )+"x)"; //debug
	if( ws.$TelescopeCMFD ) { //show in Combat MFD instead of console
		ws.$TelescopePrevMFDTarget = who; //store for MFD
		if(msg && msg.length > 0) ws.$TelescopeCMFD.$TelescopeLine = msg;
	} else player.consoleMessage( msg+tis, 4.5 ); //fallback to console, showtime matched to ident message
//;	log("Telescope", msg+tis); //debug
}
this.$Telescope_ShowName2 = function(who, position) {
	var ws = worldScripts.telescope;
	var name = "";
	if( who && who.isValid ) {
		if( who.name && ( who.name === "Railgun Projectile"
                        || who.name === "Debris" //do not show launched bullets
			|| who.name.indexOf("customshields") > -1 ) ) return; //nor customshields parts
		if( ws.$Telescope_InRange( who ) )
			position = who.position; //got fresh data from the normal scanner or the telescope
		if( who.script && who.script.$Detectors_Origname )
			name = who.script.$Detectors_Origname;
		else if( who.displayName && who.displayName.length > 0 )
			name = who.displayName;
		else if( who.isPlanet || who.isSun ) name = ws.$Telescope_PlanetName(who);
		else name = who.name;
	}
	if( name.indexOf("Exhibition]") > -1 ) return; //do not show ships in exhibition of Gallery OXP
	if( !name || name.length === 0 ) name = "(Lost target)";
	else if( !who || !who.isPlanet && !who.isSun && ( !who.isValid || !ws.$Telescope_InRange( who ) ) )
//		name = "(Lost "+name+")";
		return;
	var direction = ws.$Telescope_From(player.ship, position);
//	log(ws.name, name+" d:"+direction+" p:"+position); //debug
	var cr = 0;
	if( who.collisionRadius > 0) cr = who.collisionRadius;
	var rng = Math.floor((player.ship.position.distanceTo(position)-cr) / 1000);
	if( rng >= 1000000 ) rng = Math.floor(rng/1000000)+"M";
	if( who.isDerelict ) {
		if(ws.$TelescopeTWS && who.script) { //Towbar status
			if(who.script.$TowbarUsableShip) name = "Usable "+name;
			else if(who.script.$TowbarMinedShip) name = "Mined "+name;
			if(who.script.$TowbarEmptyShip) name = "Empty "+name;
			else name = "Derelict "+name;
		} else name = "Derelict "+name;
	} else if( who.target === player.ship ) rng = "! "+rng; //hostile
	else if( ws._index_in_list( who, ws.$TelescopeReds ) > -1 ) rng = "* "+rng; //pirate
	return( rng+"km "+name+" "+direction );
}
this.$Telescope_ShowFoundTargetNumber = function() {
	var ws = worldScripts.telescope;
	var len = ws.$TelescopeList.length;
	var s = "";
	if( len > 1 ) s = "s";
	var msg = " found "+len+" target"+s;
	if( ws.$TelescopeStaionNearby ) {
		var p = "";
		if( ws.$TelescopeGSP < 1 ) p = Math.round(ws.$TelescopeGSP*100)+"% ";
		msg = "Gravity scan"+msg+", "+p+"done"; //need online gravity scanner
	} else msg = "Telescope"+msg;
	player.consoleMessage( msg, 5 );
}
this.$Telescope_StartTimer = function( delay ) {
	if( player.ship.equipmentStatus("EQ_TELESCOPE") === "EQUIPMENT_OK" ) {
		var ws = worldScripts.telescope;
		if( !isValidFrameCallback( ws.$TelescopeVFCB ) )
			ws.$TelescopeVFCB = addFrameCallback( this.$Telescope_VFCB.bind(this) );
		if( !isValidFrameCallback( ws.$TelescopeVFCB2 ) )
			ws.$TelescopeVFCB2 = addFrameCallback( this.$Telescope_VFCB2.bind(this) );
		if( !isValidFrameCallback( ws.$TelescopeVFCB3 ) )
			ws.$TelescopeVFCB3 = addFrameCallback( this.$Telescope_VFCBVisualTarget.bind(this) );
		for(var i = 0; i < ws.$TelescopeVFCBM.length; i++) {
			if( isValidFrameCallback( ws.$TelescopeVFCBM[i] ) )
				removeFrameCallback( ws.$TelescopeVFCBM[i] );
			ws.$TelescopeVFCBM[i] = null; //cag: converting to static arrays
		}
//		ws.$TelescopeVFCBM = [];
		ws.$TelescopeVFCBM[0] = addFrameCallback( ws.$Telescope_VFCBM0.bind(ws) );
//cag: deleted 49 static FCBs to replace with dynamic ones
		//AutoScan timer get targets from normal scanner and do scan if a new target is visible
		//need at least 1 sec delay when called from shipWillExitWitchspace to avoid many gray balls
		this.$TelescopeTimerS = new Timer(this, ws.$Telescope_TimedS, delay, 0.25);
	}
}
this._is_ignored_ship = function( ws, who ) {
	var we = worldScripts.escortdeck;
	if( who.status === 'STATUS_BEING_SCOOPED' || who.status === 'STATUS_IN_HOLD' ) 
		return true;											// a problem when mining!
	if( we ) { 
		let i = ws._index_in_list( who, we.$EscortDeckShip );	//escortdeck oxp is present
		if( i >= 0 && we.$EscortDeckShipPos[i] )
			return true; 										//who is on deck so exclude this who from target list
	}
	var wt = worldScripts.towbar;
		if( wt && who === wt.$TowbarShip )
			return true;										//skip the towed ship
	return false;
}
this.$Telescope_Steer = function() {//turn to the target
	var ws = worldScripts.telescope;
	var ti = ws.$TelescopeListi;
	var who = ws.$TelescopeList[ ti - 1 ];
	if( ws._is_ignored_ship( ws, who ) ) return;
	if( player.ship && who && who.isValid && player.ship.viewDirection === "VIEW_FORWARD" //working in forward view only
		&& ws.$TelescopeFixedTel !== 1 //no steering if cheaply fixed
		&& !isValidFrameCallback( ws.$TelescopeSteerFCB ) ) {
		ws.$TelescopePrevHeading = player.ship.heading;
		ws.$TelescopeSteerFCB = addFrameCallback( ws.$Telescope_SteerFCB.bind(ws) );
	}
}
/*
(function() {
	var ws = worldScripts.telescope;
	if( isValidFrameCallback( ws.$TelescopeSteerFCB ) )
		removeFrameCallback( ws.$TelescopeSteerFCB );
	ws.$TelescopeSteerFCB = addFrameCallback( ws.$Telescope_SteerFCB.bind(ws) );
	console.clearConsole();
})()
*/
this.$Telescope_SteerFCB = function( delta ) {
	var ws = worldScripts.telescope;
	var ps = player.ship;
//	sum += delta;	log("Telescope", "Time elapsed: " + sum + " (delta: " + delta + ")");
	var ti = ws.$TelescopeListi;
	var who = ws.$TelescopeList[ ti - 1 ];
	if( ps && ps.isValid && who && who.isValid && !ws._is_ignored_ship( ws, who ) ) {
		var position = who.position; //got fresh data from the normal scanner or the telescope
		if( !ws.$Telescope_InRangeFast( who, ti ) )
			position = ws.$TelescopeListPos[ ti - 1 ];
		if( !position ) {
			if( isValidFrameCallback( ws.$TelescopeSteerFCB ) )
				removeFrameCallback( ws.$TelescopeSteerFCB );
		} else {
			var v = position.subtract( ps.position ).direction();
			var angle = ps.heading.angleTo(v);
                	var ato = ps.heading.angleTo(ws.$TelescopePrevHeading);
			if( ato < 0.005 && angle > 0.015 ) { //steer if no manual steering and not in 1 degree
				//if the above ato value lower then can not start steering in my intel atom netbook
//				player.consoleMessage(ws.$TelescopePrevHeading +" vs "+ ps.heading);
				var a = Math.min(ps.maxPitch*delta, angle/12);//half max turn/step and not too accutate
				var wstow = ws.$TelescopeTWS;  //slowed steering from towbar oxp if there are towed ship
				if( wstow && wstow.$TowbarShip && wstow.$TowbarShip.isValid ) {
					var ma = Math.min( 2, ps.mass / wstow.$TowbarShip.mass ); ///small ship max. 2x
					a = a * ma / 3; // 1/3 of the original rate with same mass, min. 1/5 max. 2/3
//					player.consoleMessage("Telescope slow steering with towed ship "+ato);//debug
				}
				var c = ps.heading.cross(v).direction(); //set the plane where we should rotate in
				var q = ps.orientation.rotate( c, -a );
				ps.orientation = q;
			} else { //end of steering
//      	                player.consoleMessage(ato+" a"+angle+" p"+position);//debug
				if( isValidFrameCallback( ws.$TelescopeSteerFCB ) )
					removeFrameCallback( ws.$TelescopeSteerFCB );
			}
		}
		ws.$TelescopePrevHeading = ps.heading;
	} else { //end of steering
		if( isValidFrameCallback( ws.$TelescopeSteerFCB ) )
			removeFrameCallback( ws.$TelescopeSteerFCB );
	}
	if( isValidFrameCallback( ws.$TelescopeVFCB ) ) {
		ws.$TelescopeSVSync = true;
		ws.$Telescope_VFCB( delta );//must be run surely after the SteerFCB
		ws.$TelescopeSVSync = false;
	}
}
this.$Telescope_TimedA = function() { //to avoid backstep if a target in the crosshairs
//	log("Telescope", "TimedA start");//debug
	var ws = worldScripts.telescope;
	var ti = ws.$TelescopeListi;
	var who = ws.$TelescopeList[ ti - 1 ];
	var ps = player.ship;
//	log("Telescope", "TimedA ti:"+ti+" who:"+who);//debug
	if( ps && ps.isValid && who && who.isValid ) {
		var d = ps.position.distanceTo( who.position );
		if( d < ps.scannerRange && !who.isVisualEffect && !who.isPlanet && !who.isSun ) {
			if( who && who.isValid && ps.target !== who ) {
				ws.$TelescopeTargetSet = true;//to avoid doubled list item message
//				log("Telescope", "New player target: "+who.displayName+" who:"+who);//debug
				if( !who.isPlanet && !who.isSun ) ps.target = who;
				if( ps.target !== who && //yes, it is possible! Hohoho showed when a ship jumped!
				//bugfix against non-lockable Military Jammer awarded by ShipVersion to avoid repeated scan
					ws.$TelescopePrevWho !== who ) {
					ws.$TelescopePrevWho = who; //store to scan only once
//					player.consoleMessage("Hohoho!");//debug
//;				log("Telescope", "ScanTW: "+who+" - "+ws.$TelescopePrevWho);//debug
					ws.$Telescope_Scan(); //forced scan to remove the invalid ball
				}
				ws.$TelescopeTarget = ps.target; //save real target after(!) set
//				log("Telescope", "New player target set to "+who.displayName);//debug
				ws.$TelescopeTargetSet = false;
			}
		} else { //far target or planet, lock the marker
			var vm = ws.$TelescopeVMark;
//			log("Telescope", "ti:"+ti+" pt:"+ps.target+" VMark:"+vm);//debug
			if( !ps.target || ps.target !== vm ) {
//				log("Telescope", "TimedA pt:"+ps.target+" vm:"+vm);//debug
				ws.$Telescope_VFCBVisualTarget(); //get new name
				vm = ws.$TelescopeVMark;
//				log("Telescope", "TimedA got new target name for vm:"+vm);//debug
				if( vm && vm.isValid && !vm.isVisualEffect && ps.target !== vm ) {
//					log("Telescope", "TimedA will set displayName:"+vm.displayName);//debug
					var vd = vm.displayName;
					if( who.isPlanet || who.isSun ) vm.displayName = ws.$Telescope_PlanetName(who);
					else vm.displayName = who.displayName; //remove km from ident message
					ws.$TelescopeTargetSet = true;//avoid doubled list item message
//					log("Telescope", "New player target: "+vm.displayName+" vm:"+vm+" who:"+who);//debug
					ps.target = vm;
					ws.$TelescopeTarget = who; //save the real target after(!) set
//					log("Telescope", "New player target set to "+vm.displayName);//debug
					ws.$TelescopeTargetSet = false;
					vm.displayName = vd; //restore km
				}
			}
		}
//		log("Telescope", "Player target: "+ps.target+" VMark:"+vm);//debug
//		ws.$Telescope_ShowName();//removed, show in target box
	}
	if( ws.$TelescopeTimerA ) {
		ws.$TelescopeTimerA.stop();
		ws.$TelescopeTimerA = null;
	}
//	log("Telescope", "TimedA end");//debug
}
this.$Telescope_TimedF = function() { //delayed launch of FCBs, must after FarPlanets FCB
	var ws = worldScripts.telescope;
	ws.$Telescope_StartTimer( 1 ); //1 sec delay to avoid unwanted gray balls
	if( ws.$TelescopeTimerF ) {
		ws.$TelescopeTimerF.stop();
		ws.$TelescopeTimerF = null;
	}
}
// before w/ TelescopeVMCC=0, Total time: 48.901 ms JavaScript: 10.732 ms, native: 38.155 ms
//  after w/ TelescopeVMCC=0, Total time: 34.331 ms JavaScript: 9.057 ms, native: 25.257 ms
this._TimedS_closure = function() {
	// 'constant' variables
	var ws = worldScripts.telescope;
	var telescopeAutoScan = ws.$TelescopeAutoScan;
	var telescopeAutoScanMaxRange = ws.$TelescopeAutoScanMaxRange;
	var telescopeRedAlertLimiter = ws.$TelescopeRedAlertLimiter;
	var telescopeAutoLock = ws.$TelescopeAutoLock;
	var telescopeGravLock = ws.$TelescopeGravLock;
	var telescopeCMFD = ws.$TelescopeCMFD;
	// function references
	var player_consoleMessage = player.consoleMessage;
	var telescope_Scan = ws.$Telescope_Scan;
	var telescope_ShowFoundTargetNumber = ws.$Telescope_ShowFoundTargetNumber;
	var system_filteredEntities = system.filteredEntities;
	var telescope_ShowName = ws.$Telescope_ShowName;
	var telescope_ShowName2 = ws.$Telescope_ShowName2;
	var telescope_MostCentered = ws.$Telescope_MostCentered;
	var telescope_GetDetect = ws.$Telescope_GetDetect;
	var telescope_VClear = ws.$Telescope_VClear;
	var telescope_TimedVMC = ws.$Telescope_TimedVMC;
	var index_in_list = ws._index_in_list;
	// local variables
	var telescopeVMCC = 0; //counter to make colour of the visual marks, used to do once in a second within a 0.25s timer
	var ps, pst, psp, scannerRange, telescopeGSP, telescopeList, telescopeListi, telescopeStaionNearby;
	function _IsPilotedVisible(entity) {
		if( entity && entity.isValid && !entity.isVisualEffect && !entity.isCloaked
			&& ( entity.isPiloted || entity.forwardWeapon ) //need to detect drones from HardShips OXP
	//		&& ( !entity.isRock || entity.isStation ) //too much asteroids, lock only in scannerRange
			&& (( telescope_GetDetect( entity )
				&& entity.isVisible && ps.equipmentStatus("EQ_TELESCOPEEXT") === "EQUIPMENT_OK" )
				|| // brought code inline // ws.$Telescope_IsScannerTarget( entity )
				( entity.dataKey && ps && ps.isValid && psp.distanceTo(entity.position) < scannerRange )
				)
			&& ( !entity.dataKey || entity.dataKey !== "telescopemarker" ) )
			return true;
		return false;
	}
	function _IsNonHostileStaion(entity) {
		if( entity && entity.isValid && entity.isStation && !entity.isVisualEffect && !entity.isCloaked
			&& entity.mass > 10000000 //skip ships with docking port (except baseships), rockhermit with 53508t must fit in
			&& entity.target !== ps //target is hostile if targeting back
			   //or player is in the defenseTargets
			&& ( !entity.defenseTargets || ws._index_in_list( ps, entity.defenseTargets ) === -1 ) )
			return true;
		else return false;
	}
	function _MFDTarget(ws, ps, who) { //helper function for building MFD in TimedS
		if( who && who.isValid ) {
			var v = telescope_ShowName2(who, who.position);
			if( v && v.length > 0 && v.indexOf("(Lost ") === -1 ) {
				if( who === pst || //current target
					pst && pst.dataKey
					&& pst.dataKey === "telescopemarker" //get the original target
					&& who === telescopeList[ telescopeListi - 1 ] )
					v = "[ "+v+" ]"; //mark the current target
				return( v+"\n" );
			}
		}
		return false;
	}
	function _TimedS() { //check for most centered 4 times/second, new targets and colour update once/second
	//	log("Telescope", "TimedS start"); //debug
		ps = player.ship;
		pst = ps.target;
		psp = ps.position;
		scannerRange = ps.scannerRange;
		telescopeStaionNearby = ws.$TelescopeStaionNearby;
	//;	if( ps.target === null ) log("Telescope","TimedS notarget1");
		if( !ps || !ps.isValid || ps.equipmentStatus("EQ_TELESCOPE") !== "EQUIPMENT_OK" ) {
			if(ps.setMultiFunctionText) //clear MFD if Telescope damaged
				ps.setMultiFunctionText(this.name, "No Telescope Target", false);
			return; //stop scan when damaged
		}
	//	var gravdone = false;
		var gravok = false;
		if( ps.equipmentStatus("EQ_GRAVSCANNER") === "EQUIPMENT_OK" ) gravok = true;
		telescopeGSP = ws.$TelescopeGSP;
		if( gravok && telescopeStaionNearby ) {
			if( telescopeGSP < 1 ) {
				var gsm = 1;//gravity scanner multiplyer
				if( ps.speed === 0 ) gsm = 4; //4 times faster if stopped
				if( ps.equipmentStatus("EQ_GRAVSCANNER2") === "EQUIPMENT_OK" )
					gsm *= 2; //half time with 2 working grav.scanner
				ws.$TelescopeGSP = telescopeGSP = telescopeGSP + gsm * 1/960; //normal gravity scan need 4 minutes
				if( telescopeGSP >= 1 ) {
					ws.$TelescopeGSP = telescopeGSP = 1;
					if( ps.weaponsOnline )
						player_consoleMessage("Gravity scan done, turn off weapons to see results", 5);
					else {
						telescope_Scan(); //forced scan to insert new gravity targets
						telescope_ShowFoundTargetNumber();
					}
				}
			}
		} else if( telescopeGSP > 0 ) 
			ws.$TelescopeGSP = telescopeGSP = telescopeGSP - 1/240; //degrading from 100% to 0% in 2 minute if away from stations
		var newtarget = null;
		var st = [];
		var ascan = false;
		var i = 0, j = 0, who = 0, s = '';
		telescopeList = ws.$TelescopeList;
		telescopeListi = ws.$TelescopeListi;
		if( telescopeAutoScan //enabled
			&& telescopeVMCC < 1 //and every 4. call
			&& telescopeList.length < ws.$TelescopeTargets ) { //and list is not full
			ascan = true;
			st = system_filteredEntities(this._TimedS_closure, _IsPilotedVisible, ps,
							 telescopeAutoScanMaxRange); //maybe avoid bugs if maxed
	// 		log("Telescope", "TimedS filteredEntities returned "+st.length);  //debug
			var mfd = ""; //build Telescope MFD
			for( i = 0; i < st.length && j < 10; i++ ) { //list the nearest ships first
				s = _MFDTarget(ws, ps, st[i]);
				if( s ) { mfd += s; j++; }
			}
			for( i = 0; i < telescopeList.length && j < 10; i++ ) { //then other targets
				who = telescopeList[i];
				if( who && ws._index_in_list( who, st ) === -1 ) { //not in the filtered ships array
					s = _MFDTarget(ws, ps, who);
					if( s ) { mfd += s; j++; }
				}
			}
			if( j < 1 ) mfd = "No Telescope Target";
			if(ps.setMultiFunctionText) ps.setMultiFunctionText(this.name, mfd, false);
			//check a station is nearby for gravity scanner
			if( gravok ) {
				if( ps.mass < 100000000 ) { //baseships can perform gravity scan anywhere
					var sg = system_filteredEntities(this._TimedS_closure, _IsNonHostileStaion, ps, 5000);
					if( !telescopeStaionNearby && sg && sg.length > 0 ) {
	//					var w = "";
						telescopeStaionNearby = true; //store the result
						if( ps.weaponsOnline )  //show when arrived near a station
							player_consoleMessage("Gravity scan need offline weapons", 5);
						else {
							telescope_Scan();//forced rescan
							telescope_ShowFoundTargetNumber();
						}
					} else {
						if( telescopeStaionNearby && ( !sg || sg.length === 0 ) ) { //too far or become hostile
							player_consoleMessage("Gravity scan need friendly station in 5km", 5);
							telescopeStaionNearby = false;
							telescope_Scan();//forced rescan
						}
					}
				} else telescopeStaionNearby = true;//baseship
			} else telescopeStaionNearby = false;
		}
		for( i = 0; i < st.length; i++ ) { //search new target
			if( ws._index_in_list( st[i], telescopeList ) === -1 &&
				!st[i].isPlayer ) { //must, bugfix
				var p = st[i].position;
				telescope_ShowName("", st[i], p);
	//			var m = Math.round(ps.position.distanceTo( p ));
	//			var dir = ws.$Telescope_From(ps, p);
	//			log("Telescope", "New visible: " + m+"m "+dir+" "+st[i]);//debug
	//			system.addVisualEffect("telescope-whitemarker", p);//debug
				newtarget = st[i]; //but do not jump out of the cycle, keep to print all new name
			}
		}
		if( !newtarget && telescopeCMFD ) { //CombatMFD support
			var pt = ws.$TelescopePrevMFDTarget;
			if( !pt || !pt.isValid || ascan && index_in_list( pt, telescopeList ) === -1 ) {
				//need ascan check also, else cause blinking names in CombatMFD
	//			player.consoleMessage(pt);//debug
				ws.$TelescopePrevMFDTarget = null;
				telescopeCMFD.$TelescopeLine = ""; //clear the line in MFD
			} else telescope_ShowName("", pt, pt.position); //update the direction
		}
	//;	if( ps.target == null ) log("Telescope","TimedS notarget2");
		if( newtarget && newtarget !== ws.$TelescopePrevNewTarget
			|| pst && !pst.isValid //target jumped or so
			&& !pst.isPlanet && !pst.isSun
			&& ( !pst.dataKey || pst.dataKey !== "telescopemarker" )) {
	//;		log("Telescope", "ScanNT: "+newtarget+" - "+ws.$TelescopePrevNewTarget);//debug
			ws.$TelescopePrevNewTarget = newtarget; //store to scan only once
			telescope_Scan(); //forced scan to insert new one(s)
		}
	//	else ws.$Telescope_List3(0, true, true); //forced free scan to refresh from the normal scanner - need fix, removed
	//;	if( ps.target == null ) log("Telescope","TimedS notarget3");
		if( !isValidFrameCallback( ws.$TelescopeSteerFCB ) ) { //no retarget during autosteering
			if( ps.weaponsOnline ) {//in auto mode
				if( pst === null //no target and autolock not disabled
					&& telescopeAutoLock !== 0 )
					telescope_MostCentered( null, "auto", false );
			} else if( telescopeGravLock !== 0 ) //in grav mode and not disabled
				telescope_MostCentered( null, "grav", false );
		}
		
		if( pst === null ) {
	//;		log("Telescope","TimedS notarget");
			var ti = telescopeListi;
			who = telescopeList[ ti - 1 ];
			if( !who || ( !who.isPlanet && !who.isSun ) ) {
	//;			log("Telescope","TimedS VClear");
				telescope_VClear(); //cleanup needed in some cases
			}
		}
		
		if( !telescopeRedAlertLimiter //small help to slow computers:
			|| telescopeVMCC < 1 ) //update in every 4. call only
			telescope_TimedVMC(); //update the colours of the lightball markers
		if( telescopeVMCC < 1 ) { //check colour making counter
			telescopeVMCC = 3; //do once in a second when reach 0
		} else telescopeVMCC--;
	//	log("Telescope", "TimedS end"); //debug
		ws.$TelescopeStaionNearby = telescopeStaionNearby;
	}
	return _TimedS;
}
this.$Telescope_TimedVMC = function() { //make colours to visual markers, check targets are flyed out of range
	if( !player.ship || !player.ship.isValid ) return;
	var ws = worldScripts.telescope;
	var ps = player.ship;
	var ball = null;
	var col = null;
	var dt = null;
//	var dir = null;
//	var scr = ps.scannerRange;
	var tcr = 0;
	var tl = ws.$TelescopeList;
	var tfs = ws.$TelescopeFarStatus;
	var t = null;
//	var v = null;
	ws.$TelescopeNearestd = ws.$TelescopeMaxRange; //reset to max.
	var prepa = ws.$TelescopeRedAlertLimiter; //run much faster with local variables
	var prepb = ws.$TelescopeLightBalls;
	var prepc = ws.$TelescopeVMCs;
	var prepd = ws.$TelescopeNearestd;
	var prepi = ws.$TelescopeNearesti;
	var prepl = ws.$TelescopeLargeLightBalls;
	var prepm = ws.$TelescopeSniperMinRange;
	var prepp = ws.$TelescopeListPos;
	var prepr = ws.$TelescopeSniperRange;
	var preps = ws.$TelescopeShipLightBalls;
	var prept = null; if( ws.$TelescopeTWS ) prept = ws.$TelescopeTWS.$TowbarShip; //skip the towed ship
	var prepv = ws.$TelescopeVMarkMinDist;
	var prepvs = ws.$TelescopeVMarkShipMinDist;
	var reds = ws.$TelescopeReds; //pirate detected earlier
	var telescope_InRangeFast = ws.$Telescope_InRangeFast;
	var telescopeListPos = ws.$TelescopeListPos;
	var psp = ps.position;
	var scannerRange = ps.scannerRange;
	var tpos, dataKey, isPlanet, isThargoid, isSun, mass;
	//following part is very CPU consuming, with 100 ships need 400 cycle/sec, so code wisely
	for( var i = 0; i < tl.length; i++ ) {
		t = tl[i];
		if( !t || !t.isValid ) col = null; //invalid target
		else if( !telescope_InRangeFast( t, i ) ) {
			//less check than InRange due to checked before and speed is important
			col = "gray"; //last known position only
			dt = psp.distanceTo( prepp[ i ] );
			//distance to the last known position
		} else {
			tpos = t.position;
			dataKey = t.dataKey;
			isPlanet = t.isPlanet;
			isThargoid = t.isThargoid;
			isSun = t.isSun;
			mass = t.mass;
			dt = psp.distanceTo( tpos ); //distance to the target
			telescopeListPos[ i ] = tpos; //update until InRange
			if( t.isStation || ( dataKey && dataKey.indexOf("buoy") > -1 ) )
				col = "green"; //base or buoy, do not use isBeacon to exclude ships with beaconCode
			else if( isPlanet ) col = "lightgray"; //planets
			else if( isThargoid && dataKey !== "tharglet" //warship is red but tharglet is pink or white
				|| !t.isDerelict && prepc[i] && prepc[i].indexOf("red") > -1 )
				col = "red"; //pirate detected earlier
			else if( t.isVisible || dt < scannerRange ) {
				if( t.isPolice ) col = "purple"; //police
				else if( t.isWormhole || t.isDerelict ) col = "blue"; //wormhole from Oolite v1.79
				else if( !t.isPiloted && t.forwardWeapon && t.bounty > 0 //active tharglet or drone
//					&& ( ( t.owner && t.owner.isValid || t.speed > 0 ) //can fire after owner gone until moving
						//&& !t.owner.isDerelict - still fire
					&& ( isThargoid //will be like a rock when inactive
						|| dataKey !== "tharglet" ) ) col = "pink";
				else if( t.isCargo || t.primaryRole === "escape-capsule" //cargo, esc.pod, rock or sun
					|| isSun || t.isRock || dataKey === "tharglet" ) col = "white";
				else if( t.bounty > 0 && ( tfs || dt < scannerRange || isThargoid ) ) {
					col = "red"; //pirate in scanner or FarStatus=true or thargoid
					if( ws._index_in_list( t, reds ) === -1 ) reds.push(t); //save
				}
				else if( t.isWeapon ) col = "cyan"; //missile or mine
				else if( ws._index_in_list( t, reds ) > -1 ) col = "red"; //pirate detected earlier
				else col = "yellow"; //other ship with clean status
			} else if( mass >= 130000 ) col = "orange"; //large ship over the visible range
			else col = "brown"; //small ship over the visible range
		}
		if( col ) { //check to avoid invalid target
			if( dt < prepd && t !== prept ) { //find the nearest target but skip the towed ship
				prepd = dt;
				prepi = i + 1; //store the nearest
			}
			tcr = 0;
			if( t && t.isValid && !isPlanet && !isSun ) tcr = t.collisionRadius;
			if( prepb && prepl ) {
				if( isPlanet && !t.hasAtmosphere ) ball = "moon";
				else if( dt < prepm + tcr ) ball = "largeball"; //xl size
				else if( dt < prepr + tcr ) ball = "ball"; //large size
				else ball = "marker"; //average size
			}
			if( dt < prepv + tcr //too near
				|| ( ( dt < prepvs + tcr ) && col !== "cyan" && col !== "white" && col !== "pink" )
				//in red alert show ball marked targets only to save CPU and clean scanner
				|| prepa && player.alertCondition > 2 && ps.weaponsOnline && ball !== "ball" )
				col = null;
			else {
				if( !prepb || !preps //if ship lightballs are disabled then show others only
					&& col !== "blue" && col !== "cyan" && col !== "green" && col !== "white" )
					col = "telescope-"+col+"_flag";//lollipop without lightball
				else if( isPlanet || isSun ) {
					if( t.hasAtmosphere ) { //only lightrgray has moon in effectdata.plsit
						if( t.isVisible && dt < 10000000 )
							col = "telescope-lightgray_moonflag"; //no dot
						else col = "telescope-lightgray_moon";
					} else if( t.isVisible && dt < 10000000 )
						col = "telescope-"+col+"_flag"; //no dot
					else col = "telescope-"+col+"_dotmarker";
				} else if( prepl ) { //large balls
					if( dt > 1000000 ) col += "_tiny"; //over 1000km show tiny ball
					else if( dt > 100000 ) col += "_small"; //over 100km show smaller ball
					col = "telescope-"+col+"_"+ball;
				} else if( dt > 1000000 ) col = "telescope-"+col+"_dotmarker"; //over 1000km
				else if( mass < 400000 ) col = "telescope-"+col+"_tinymarker"; //escort ship
				else col = "telescope-"+col+"_smallmarker"; //large ship (Cobra3 and over)
			}
		}
		ws.$TelescopeVMCs[ i ] = col;
	}
	ws.$TelescopeNearestd = prepd;
	ws.$TelescopeNearesti = prepi;
}
this.$Telescope_True = function(/*target*/) { //system.filteredEntities can get all ship with this
	return true;
}
this.$Telescope_VClear = function() { //Clear Visual Effect Ship Model and Visual Marker also
	var ws = worldScripts.telescope;
//;	log("Telescope","VClear");
	ws.$Telescope_VClearM();
	ws.$Telescope_VClearS();
}
/*
(function() {
	var ws = worldScripts.telescope;
	if( isValidFrameCallback( ws.$TelescopeVFCB ) ) removeFrameCallback( ws.$TelescopeVFCB );
	var vc = ws._VFCB_closure();
	ws.$Telescope_VClearS = vc.clear;
	ws.$Telescope_VShow = vc.show;
	ws.$Telescope_VFCB = vc.fcb;
	if( !isValidFrameCallback( ws.$TelescopeVFCB ) )
		ws.$TelescopeVFCB = addFrameCallback( ws.$Telescope_VFCB.bind(ws) );
})()
*/
this._VFCB_closure = function() {
	// 'constant' variables
	var ws = worldScripts.telescope;
	var w_shiplib = worldScripts.shiplib;
	var gameWindow = oolite.gameSettings.gameWindow;
	// function references
	var system_addVisualEffect = system.addVisualEffect;
	var telescope_Scan = ws.$Telescope_Scan;
	var telescope_ShowFoundTargetNumber = ws.$Telescope_ShowFoundTargetNumber;
	var telescope_InRange = ws.$Telescope_InRange;
	var telescope_VClear = ws.$Telescope_VClear;
	var index_in_list = ws._index_in_list;
	// local variables
	var weaps = true; //the previous state of the player weapons
	var vRing = null; //a ring around the visual effect target
	var vShip = null; //visual effect to show the selected target
	var vDataKey = null; //key of the visual effect
	var vShrinkC = 0; //visual effect shrink counter - shrink code is not ready, leave these at 0
	var vShrinkDelay = 0; //in sec after the larger visual target start shrinkig to normal size (0: instant)
	var vShrinkLength = 0; //in sec, shrinking faster if smaller (0: instant)
	var vAlighUp = 0; //visual effect align to top modifier
	var vZoom = 1; //store the original maximal zoom level of the visual effect
	var ps, wide;
//	function getVShip() { return vShip; }
	function setVShip( ship, up, delay, length ) {
		vShip = ship;
		vAlighUp = up;
		vShrinkDelay = delay;
		vShrinkLength = length;
		vShrinkC = vShrinkDelay + vShrinkLength; //shrink after delay
		return ship;
	}
	function clearVShip() { //Clear Visual Effect Ship Model and large visual ring
		if( vShip ) {
	//; 		log("Telescope", "Remove VModel:"+ws.$TelescopeV); //debug
			vShip.remove();
	// 		log("Telescope", "Removed VModel."); //debug
			vShip = null;
			vShrinkC = 0;
			vAlighUp = 0;
			vDataKey = null;//need to show again when reident
		}
		if( vRing ) {//remove large visual ring
	//; 		log("Telescope", "Remove VRing:"+vRing); //debug
			vRing.remove();
	// 		log("Telescope", "Removed VRing."); //debug
			vRing = null;
		}
		return null;
	}
	function showVShip() { //Show Visual Effect
		ps = player.ship;
		if( !ws.$TelescopeShowVisualTarget ) return;
	//cag: added to fix bug where loading game or altering its size caused effect to re-appear when turned off
		if( !ws.$TelescopeAutoLock && !ps.target ) return;
		if( ps.weaponsOnline ) {
			if( ws.$TelescopeVSize <= 0 ) return;
		} else if( ws.$TelescopeVZoomSize <= 0 ) return;
		var ti = ws.$TelescopeListi;
		var who = ws.$TelescopeList[ ti - 1 ];
		if( !who || !who.isValid && !who.isPlanet && !who.isSun || !telescope_InRange( who ) ) {
	//;		log("Telescope","VShow ti:"+ti+" who:"+who);
			telescope_VClear();
			return;
		}
		var dk = who.dataKey;
		var v = vShip;
	//	log("Telescope","VShow "+dk+" "+v+" "+who.isStation+" "+ws.$TelescopeShowVisualStation);//debug
		if( who.isPlanet || who.isSun || who.isStation && !ws.$TelescopeShowVisualStation )
			clearVShip();
		else if( !v || vDataKey !== dk ) {
			vDataKey = dk;
			if( v ) v.remove();
			v = null;
					
			if( 0 < oolite.compareVersion("1.79") ) { //before Oolite v1.79
				if( index_in_list( dk, ws.$TelescopeDataKeys77 ) > -1 ) {
					dk += "77"; //use dataKeys with 77 suffix in effectdata.plist
				}
			}
			var p = ps.position;
			if( dk ) v = system_addVisualEffect( dk, p );
	//		log("Telescope","VShow2 "+dk+" "+v);//debug
			if( !v ) { //maybe the dataKey contains partially a name from the shiplib
				if( w_shiplib && w_shiplib.$ShipLibVP ) {
					var l = w_shiplib.$ShipLibVP;
					var d = 0; //0. line contains the default data
					for( var i = l.length; i >= 0 ; i-- ) //search backward (from orig.ships)
						if( dk && l && l[i] && dk.indexOf( l[i].k ) > -1 ) {
							d = i; //found
							i = -1; //exit from for
						}
					v = system_addVisualEffect( l[d].k, p );
				}
				if( !v && ws.$TelescopeShowVisualQuestionMark )
					v = system_addVisualEffect("oolite-unknown-ship", p); //fallback
				else clearVShip(); //silent fallback
			}
	//		player.consoleMessage(v+" "+dk);//debug
			if( v ) { //must to check it (bugfix)
	//			log("Telescope","VShow3 "+dk+" "+v);//debug
					var z = v.collisionRadius / 6; ///
				vZoom = z; //save zoom value
				if( z > 0 ) v.scale( 1 / z ); //shrink to the largest size
				v.scannerDisplayColor1 = null; //hide from the scanner
				v.scannerDisplayColor2 = null; //hide from the scanner
	//			ws.$TelescopeV = v; //save
	//			ws.$TelescopeVUp = 0;
	//			ws.$TelescopeVShrinkC = ws.$TelescopeVShrinkDelay + ws.$TelescopeVShrinkLength; //shrink after delay
				setVShip( v, 0, 0, 0 ); // vAlighUp, vShrinkDelay, vShrinkLength
				if( !isValidFrameCallback( ws.$TelescopeVFCB ) )
					ws.$TelescopeVFCB = addFrameCallback( ws.$Telescope_VFCB.bind( ws ) );
				if( !isValidFrameCallback( ws.$TelescopeVFCB2 ) )
					ws.$TelescopeVFCB2 = addFrameCallback( ws.$Telescope_VFCB2.bind( ws ) );
				if( !isValidFrameCallback( ws.$TelescopeVFCB3 ) )
					ws.$TelescopeVFCB3 = addFrameCallback( ws.$Telescope_VFCBVisualTarget.bind( ws ) );
			}
		}
	}
	function _VFCB( delta ) { //Visual FrameCallBack
	//	log("Telescope", "VFCB start"); //debug
		ps = player.ship
		if( !ps || !ps.isValid ) return; //if player died
		if( isValidFrameCallback( ws.$TelescopeSteerFCB )
			&& !ws.$TelescopeSVSync ) return; //call after Steering
		wide = gameWindow.height / gameWindow.width; ///widescreen correction
		//full size virtual target (and ring also) if weapons offline
		var vsize = ws.$TelescopeVZoomSize / 10; ///
		var vsizechanged = false;
		if( ps.weaponsOnline ) { //gravity scan if weapons turned off
			if( !ws.$TelescopeShowVisualTarget //remove model if weapons changed back to on
				&& vShip ) { //and model showing is disabled
				vShip.remove();
				vShip = null;
			}
			vsize = ws.$TelescopeVSize / 10; ///
			if( !weaps ) { //state changed to on
				vsizechanged = true;
				vShrinkC = 0; //restart counter to rescale
				weaps = true; //save state
				telescope_Scan(); //perform visible scan
			}
		} else if( weaps ) { //state changed to off
			vsizechanged = true;
			vShrinkC = 0; //restart counter to rescale
			weaps = false; //save state
			telescope_Scan(); //perform gravity scan
			telescope_ShowFoundTargetNumber();
			if( ps.target //repaint visual target and ring if show in weapons off mode only
				&& ws.$TelescopeFixedTel !== 1 ) {//no visual mode if cheaply fixed
				clearVShip();
				showVShip();
			}
	//		player.consoleMessage(ps.target);//debug
		}
		//visual target zoom
		var vp = ws.$TelescopeVPos;
		var pos = ps.position.add( ps.vectorForward.multiply( 50 + vp.z ) ); //check effectdata.plist if this line give TypeError: vp is null
		pos = pos.add( ps.vectorRight.multiply( vp.x ) );
	//	var srp = pos; //sniper ring position
		if( !vShip || !vShip.isValid ) {
			if( vShip || vRing ) {
	//;			log("Telescope","VFCB remove V:"+ws.$TelescopeV);
				clearVShip();
			}
		} else {
			var slen = vShrinkLength; //do shrink after delay
			if( ( vShrinkC >= 0 || vsizechanged )
				&& vShrinkC <= slen ) {
				vShrinkC -= delta;
				var d = 1;
				if( slen > 0 ) d = ( slen - vShrinkC ) / slen;
				var z = ( 1 - d * ( 1 - vsize ) ) / vZoom;
				if( z > 0 ) vShip.scale( z ); //shrink
				vAlighUp = 3 * d * ( 1 - vsize ) * 1.5; //align to top
	//			player.consoleMessage(vShrinkC+" "+z);//debug
			}
			var up = 12.5;
			if( !ws.$TelescopeRing )
				//|| vsize == ws.$TelescopeVZoomSize / 10 ) ///no ring if zoomed - removed since the ring is narrow
				up += 1; //a bit more higher if no ring
			else up += 1.25 - 2.5 * vsize; //correction with ring
	//		player.consoleMessage(up);//debug
			pos = pos.add( ps.vectorUp.multiply( up - 24 * ( 0.75 - wide ) + vp.y
				+ vAlighUp ) );
			vShip.position = pos;
		}
		//orient vship model
		var ti = ws.$TelescopeListi;
		var who = ws.$TelescopeList[ ti - 1 ];
		var wp = null;
		if( who && who.isValid && ( who.isPlanet || who.isSun || ws.$Telescope_InRangeFast( who, ti ) ) ) {
			wp = who.position;
		} else 
			wp = ws.$TelescopeListPos[ ti - 1 ]; //last known position
		if( wp ) {
			var vd = wp.subtract( ps.position ).direction(); // unit vector towards wp
			if( vShip && vShip.isValid && !vShip.isPlanet && !vShip.isSun ) {
				if( who.isVisible ) { //orientation is known only if visible, Grav.Scanner can give position only
					let ps_heading = ps.heading;
					var a = ps_heading.angleTo(vd);
					var c = ps_heading.cross(vd).direction();
					var o = who.orientation.rotate( c, -a );
	//	var v2 = ps.position.subtract(wp);
	//	var fw = ship.vectorForward.angleTo(v2);
	//	var ri = ship.vectorRight.angleTo(v2);
	//	var up = ship.vectorUp.angleTo(v2);
	//	var o = who.orientation;
	//	var o = who.orientation.multiply( ps.orientation ); //depends on player ori only
	//	o = o.rotateZ(fw);
	//	o = o.rotateY(up);
	//	o = o.rotateX(ri);
					vShip.orientation = o;
	// 		( who.orientation.rotate( ps.position.subtract( who.position ) )
	//			).multiply( ps.orientation );
			//who.orientation.multiply( ps.orientation );
				} else { //nonvisible, fixed view only
					var r = Math.PI / 2 + 0.22; ///ships viewed from top (90 degree plus a bit)
					let who_isValid = who.isValid;
					if( who_isValid && who.isStation ) r = Math.PI + 0.22; //stations facing
					vShip.orientation = ps.orientation.rotate( ps.vectorRight, r );
					if( who_isValid && who.isMainStation ) //rotate to horizontal dock position
						vShip.orientation = vShip.orientation.rotate( vShip.vectorForward, Math.PI / 2 );
	//				var cr = who.collisionRadius;
	//				log("Telescope", "VFCB who:"+who.name+" cr:"+cr);//debug
	//				if( cr > 0 ) vShip.scale( 2.5 / cr ); //smaller
				}
			}
		}
		
		//large visual target ring
		if( vShip && vShip.isValid && ws.$TelescopeRing //if not disabled
			&& ws.$TelescopeFixedTel !== 1 //no ring (nor target model) if cheaply fixed
			//&& ps.weaponsOnline //and not in zoomed size - removed since the ring is narrow
			&& vShrinkC <= 0 ) { //and not zooming
			if( !vRing ) {
				vRing = system_addVisualEffect("telescope-sniper", pos);
				vRing.scale( 0.166 * vsize ); //shrink
	//			vRing.scaleZ = vRing.scaleZ * 0.1; //make flatter
			} else {
				vRing.position = pos;
				if( vsizechanged ) { //if weapons switched on/off set new size (small/large)
					vRing.scale( 0.166 * vsize ); //shrink
	//				vRing.scaleZ = vRing.scaleZ * 0.1; //make flatter
				}
			}
			vRing.orientation = ps.orientation;//.rotate( ps.vectorRight, 0 );
		} else if( vRing ) {
			vRing.remove();
			vRing = null;
		}
	//	ws.$Telescope_VFCBVisualTarget(); //called from separated FCB to avoid timeLimit
	//	the following can reach timeLimit if placed here so must be called from separatd FCB
	//	for(var i = 0; i < ws.$TelescopeList.length; i+=10; )
	//		ws.$Telescope_VFCBMarks(Math.floor(i/10)); 
	}
	return { clear: clearVShip,
			  show: showVShip,
			   fcb: _VFCB };
}
this.$Telescope_VClearM = function() { //Clear Visual Marker and small sniper ring
	var ws = worldScripts.telescope;
	if( ws.$TelescopeVMark ) {
//; 		log("Telescope", "Remove VMark:"+ws.$TelescopeVMark); //debug
		ws.$TelescopeVMark.remove();
// 		log("Telescope", "Removed VMark."); //debug
		ws.$TelescopeVMark = null;
	}
	ws._clearSniperRing();
}
/*
(function() {
	var ws = worldScripts.telescope;
	if( isValidFrameCallback( ws.$TelescopeVFCB2 ) ) removeFrameCallback( ws.$TelescopeVFCB2 );
	var vc = ws._VFCB2_closure();
	ws._clearSniperRing = vc.clear;
	ws.$Telescope_VFCB2 = vc.fcb;
	if( !isValidFrameCallback( ws.$TelescopeVFCB2 ) )
		ws.$TelescopeVFCB2 = addFrameCallback( ws.$Telescope_VFCB2.bind(ws) );
})()
*/
this._VFCB2_closure = function() {
	// 'constant' variables
	var ws = worldScripts.telescope;
	var gameWindow = oolite.gameSettings.gameWindow;
	var TelescopeSniperRingSize = ws.$TelescopeSniperRingSize;
	// function references
	var system_addVisualEffect = system.addVisualEffect;
	// local variables
	var sniperRing = null; //if this ring guided around the crosshair then the far target is lined up correctly
	var ps, wide;
	function clearVRing() { //Clear small sniper ring
		if( sniperRing ) {//remove the small sniper ring
	//; 		log("Telescope", "Remove SniperRing:"+ws.$TelescopeSniperRing); //debug
			sniperRing.remove();
	// 		log("Telescope", "Removed SniperRing."); //debug
			sniperRing = null;
		}
	}
	function _VFCB2( /*delta*/ ) { //Visual FrameCallBack for the small sniper ring
		ps = player.ship;
		var d = 0;
		var snipertarget = false;
		var psp, psVF, psVR, psVU;
		var pst = ps.target;
		if( ps && pst && pst.isValid
			&& ws.$TelescopeFixedTel !== 1 ) { //no sniper ring if cheaply fixed
			if( pst.dataKey && pst.dataKey === "telescopemarker" ) { //get the original target
				var ti = ws.$TelescopeListi;
				pst = ws.$TelescopeList[ ti - 1 ];
			}
			if( pst && pst.isValid && !pst.isPlanet && !pst.isSun ) {
				psp = ps.position;
				d = psp.distanceTo( pst.position );
				var tcr = pst.collisionRadius;
				if( d - tcr < ws.$TelescopeSniperRange
					&& d - tcr > ws.$TelescopeSniperMinRange ) {
					var m = psp.subtract( pst.position );
					psVF = ps.vectorForward;
					if( psVF.angleTo( m ) > 3 ) { //to exclude aft line-up
	//					player.consoleMessage(ps.vectorForward.angleTo( m ));//debug
						var vp = ws.$TelescopeVPos;
						var srp = psp.add( psVF.multiply( 50 + vp.z ) ); //check effectdata.plist if this line give TypeError: vp is null
						psVR = ps.vectorRight;
						psVU = ps.vectorUp;
						srp = srp.add( psVR.multiply( vp.x ) );
						wide = gameWindow.height / gameWindow.width; ///widescreen correction
						//distant and smaller target tracked with larger ring movement
						var ax = -2 * d / tcr * ( psVR.angleTo( m ) - Math.PI / 2 );
						var ay = -2 * d / tcr * ( psVU.angleTo( m ) - Math.PI / 2 );
	//					player.consoleMessage(Math.round(ax*100)/100+" "+Math.round(ay*100)/100);//debug
						if( Math.abs(ax) < 5 &&  Math.abs(ay) < 6 * wide ) { //ay<4 if 4:3, <3.4 if 16:9
							srp = srp.add( psVU.multiply( vp.y ) );//center of the view_position
							srp = srp.add( psVR.multiply( ax ) );//show misalignment
							srp = srp.add( psVU.multiply( ay ) );
							if( sniperRing ) sniperRing.position = srp;
							else {
								sniperRing = system_addVisualEffect("telescope-ring", srp);
								sniperRing.scale( 0.01 * TelescopeSniperRingSize ); //shrink
							}
							sniperRing.orientation = ps.orientation;
							snipertarget = true;
						}
					}
				}
			}
		}
		if( !snipertarget && sniperRing ) {
			sniperRing.remove();
			sniperRing = null;
		}
	}
	return { clear: clearVRing,
			   fcb: _VFCB2 };
}
//call these from separated FCBs to avoid timeLimit
//this.$Telescope_VFCBM0 = function( delta ) { this.$Telescope_VFCBMarks(0); }
//...
this.$Telescope_VFCBM0 = function() { this.$Telescope_VFCBMarks('zero'); }
//cag:  deleted 49 static FCB fns to replace with dynamic FCBs
/*
(function() {
	var ws = worldScripts.telescope;
	for(var i = 1; i < ws.$TelescopeVFCBM.length; i++) {
		if( isValidFrameCallback( ws.$TelescopeVFCBM[i] ) )
			removeFrameCallback( ws.$TelescopeVFCBM[i] );
		ws.$TelescopeVFCBM[i] = null; //cag: converting to static arrays
	}
	console.clearConsole();
})()
*/
this.$Telescope_fcb_Startfrom = 0;
this.$Telescope_VFCBMarks = function(starting) { //create and update target marker lightballs - new fast code
//cag: implement dynamic FCBs, as unused ones impact framerate significantly.  
// 	var $num_fcbs = 1;  			// we initiate w/ FCB in [ 0 ] // for *debug msgs* only
	var $fcbs_removed = 0;
	function _SetUpFCBs() { // count # FCBs needed and adjust list
	//cag: implement dynamic FCBs, as unused ones impact framerate significantly
		var ws = worldScripts.telescope;
		var TelescopeVFCBM = ws.$TelescopeVFCBM;
		var Telescope_VFCBMarks = ws.$Telescope_VFCBMarks;
		var list = ws.$TelescopeList;
		var i = 4;		// start checking on 2nd group of 4 from TelescopeList
		var fnum = 1;	// $Telescope_VFCBMarks() is started w/ an FCB in [ 0 ]
		var fcb;
//var chg = false; // to reduce # degug msgs
		do {
			fcb = TelescopeVFCBM[ fnum ]; // the next FCB in the lists
			if( list[ i ] ) { // have at least one more, will need next FCB slot
//				if( !isValidFrameCallback( fcb ) ) {
// not good enough, as it can take a number of frames for a callback to become registered (ie. isValidFrameCallback to become true)!!!
				if( fnum > TelescopeVFCBM.length - 1 || fcb === null ) {
					TelescopeVFCBM[ fnum ] = addFrameCallback( Telescope_VFCBMarks.bind(ws) );
//				log('telescope', '_SetUpFCBs adding FCB in slot ' + fnum + ', list.len is ' + list.length ); $num_fcbs++; chg = true;
				}
			} else if( isValidFrameCallback( fcb ) ) { // have run out of items in TelescopeList, remove any remaining FCBs
					removeFrameCallback( fcb );
					TelescopeVFCBM[ fnum ] = null;
					$fcbs_removed = 5;	// takes 5 frames (on my pc) to complete removal
//				log('telescope', '_SetUpFCBs removing FCB in slot ' + fnum + ', list.len is ' + list.length ); $num_fcbs--; chg = true;
			}
			i += 4;	// each call of $Telescope_VFCBMarks handles 4 items from list
			fnum++; // the # of the next FCB in the array
		} while( i < list.length || isValidFrameCallback( fcb ) ); // end of list or have extra FCB to delete
//	if( chg ) log('telescope', "# of FCBs: " + $num_fcbs + ', List.len is ' + ws.$TelescopeList.length );
	}
//	log("Telescope", "VFCBMarks start from "+startfrom); //debug
	//need call from FrameCallBack to compensate player movement
	var ws = worldScripts.telescope;
	var startfrom = starting;	// never modify function args
	var prept = ws.$TelescopeList; // local
// - insert - ////////////////////////////////////////////////////////
	if( startfrom === 'zero' ) { // is 'zero' on 1st call, delta on the rest
		_SetUpFCBs();
		ws.$Telescope_fcb_Startfrom = 0;
	} else { 
		ws.$Telescope_fcb_Startfrom++;
	}
	startfrom = ws.$Telescope_fcb_Startfrom;
// - end - ///////////////////////////////////////////////////////////
	var co = 4*startfrom, cl = prept.length; // set loop conditions from local instead
//	if( co > cl ) return; //early exit over the end of TelescopeList
// - insert - ////////////////////////////////////////////////////////
	if( cl && co > cl ) { // happens when decr # of FCBs, as changes are not applied until next frame (or later?) so don't panic
		if( $fcbs_removed > 0 ) {
			$fcbs_removed--;
		} else if( prept.length > ws.$TelescopeVFCBM * 4 ) {
			log('telescope', 'do not have enough FCBs!!!!!!  List.length: ' + prept.length + ' TelescopeVFCBM: ' + ws.$TelescopeVFCBM );
		} else {										// ok, now you can panic
			log('telescope', 'should never happen anymore!!!!!!  co > cl: ' + co + ' > ' + cl );
		}
		return; //early exit over the end of TelescopeList
	}
// - end - ///////////////////////////////////////////////////////////
	var ce = co+4; //end of the loop, 4 item only in one FCB to avoid timelimit
	var ps = player.ship;
	if( !ps || !ps.isValid ) return; //exit if player died
	var dir = null, dt = null, md = null, pos = null, tpos = null;
	var prepc = ws.$TelescopeVMCs; // local, colour calculated in timer to save CPU
// 	var prepd = ws.$TelescopeRedAlertDist; //show lollipops in red alert within this distance only
//	var prepm = ws.$TelescopeMMarks; // removed
	var prepp = ws.$TelescopeListPos; // local
	var prepr = ws.$TelescopeMRings; // local
	var prepv = ws.$TelescopeVMarks; // local
	var pla = player.alertCondition;
	var psf = ps.vectorForward; // local
	var pso = ps.orientation; //local
	var psp = ps.position; //local
	var scr = ps.scannerRange; //local
	var angle = 0; //thin masslock border outside this angle
	var bl = false; //black lollipops in red alert
	if( pla > 1 && ps.weaponsOnline ) bl = true;
	var bla = [0,0,0]; //the color of black lollipops is black in red alert
	if( pla === 2 ) bla = [0.1, 0.1, 0.1]; //and very dark grey in yellow alert
	var cm = null, cmf = null, cmt = null; //dataKey holders of normal, far and thin masslock borders
	var fardist = 300000; //far masslock border over this distance (station or gravity scanner target)
	var farplanet = 30; //far masslock border distance multiplier (an average 5000km planet over 1500km distance)
	var mb = ws.$TelescopeMassLockBorders && ( pla === 1 || !ps.weaponsOnline );
	var mb2 = "";
	if( ws.$TelescopeBrightMassLockBorders ) mb2 = "2";
	var mrs = 41.8; //masslock ring scale, used in var rsc and below for planets also
	var rsc = scr / mrs; //masslock ring size
	var scr2 = 2 * scr; //maximal distance of thin border
	var ti = ws.$TelescopeListi - 1; //exclude current target from black lollipops
	
	//following part is very CPU consuming, with 100 ships 60fps need 6000 cycle/sec, so code wisely
	for(var i=co; i<cl && i<ce; i++){ //process 5 marker only at once to avoid timeLimit
		var t = prept[i], c = prepc[i], r = prepr[i], v = prepv[i]; // local //m = prepm[i],
		if( !c || !t || !t.isValid ) {
			tpos = null;
			if( v ) {
				v.remove(); //without this red ball remain after destroyed pirates
				prepv[i] = null;
			}
			if( r ) {
				r.remove();
				prepr[i] = null;
			}
//			if( m ) {
//				m.remove();
//				ws.$TelescopeMMarks[i] = null;
//			}
		} else if( c.indexOf("gray") > -1 ) tpos = prepp[i]; //last known position only
		else tpos = t.position;
		if( tpos ) { //check to avoid invalid target and if ListPos[i] is null
			dir = tpos.subtract( psp ).direction();
			dt = psp.distanceTo( tpos ); //distance to the target (or to last known pos)
			if( dt > scr ) { //target is out of scanner range
				md = scr - 600; //marker distance
				//do not set closer to the range to do not left behind aft markers during torus travel
				if( mb && !t.isSun ) { //masslock borders allowed, except for the sun
					cm = c.substr(0, c.indexOf("_"))+mb2+"_ml"; //dataKey with colour
					cmf = cm+"f"; //dataKey of coloured far masslock border
					cmt = cm+"t"; //dataKey of coloured thin masslock border
//					log("Telescope", cm );//debug
//					var ml = false;  //masslock field - removed
					if( r && r.dataKey !== cm && r.dataKey !== cmf && r.dataKey !== cmt ) {
						r.remove(); //colour changed, forced to recreate
						prepr[i] = r = null;
					}
					//following conditions determine the thickness of ring, need thinner from close
					//if( t.isSun ) cm = cmt; //set thin masslock border, the sun always get thin border
					//else 
					if( dt > fardist ) { //change to far
						if( t.isPlanet ) {
							if( dt > farplanet * t.radius ) {
								cm = cmf; //set far masslock border
								if( r && r.dataKey.substr(-1) !== "f" ) {
									r.remove();
									prepr[i] = r = null;
								}
							} else if( dt < 3 * t.radius ) {
								cm = cmt; //set thin masslock border
								if( r && r.dataKey.substr(-1) !== "t" ) {
									r.remove();
									prepr[i] = r = null;
								}
							} else {
//								cm = cm; //set normal masslock border (default)
								if( r && r.dataKey.substr(-1) !== "l" ) {
									r.remove();
									prepr[i] = r = null;
								}
							}
						} else { //is not planet but far
							cm = cmf; //set far masslock border
							if( r && r.dataKey.substr(-1) !== "f" ) {
								r.remove();
								prepr[i] = r = null;
							}
						}
					} else { //check for thin border within 2x scanner range
						//and over 45 degree mean ship is near and out of sight
						//so if ring is in screen then can be close so need thin border
						if( dt < scr2 //45 degree = 2.9 radian
							&& (angle = psf.angleTo(psp.subtract(tpos))) < 2.9
							//and crosshairs points out of ring which is closer
							&& Math.sin( angle ) > scr / dt ) {
							cm = cmt; //set thin masslock border
							if( r && r.dataKey.substr(-1) !== "t" ) {
								r.remove();
								prepr[i] = r = null;
							}
						} else {
//							cm = cm; //set normal masslock border (default)
							if( r && r.dataKey.substr(-1) !== "l" ) {
								r.remove();
								prepr[i] = r = null;
							}
						}
					}
					if( r ) { //move masslock ring
						r.position = tpos;
//						if( cm == cmt ) {//stop further rotating if crosshairs points outside of ring
//							r.orientation = (psp.subtract(tpos)).rotationTo([0, 0, 1]).normalize();
//							r.orientation = psp.subtract(tpos).rotationTo(psf, angle).normalize();
//							var psu = ps.vectorUp;
//							var ua = psu.angleTo(psp.subtract(tpos));//up angle
							//var borderangle = Math.asin( scr / dt );
							//r.orientation = pso.rotate([1, 1, 0], Math.asin(scr / dt) );
//						} else 
							r.orientation = pso;
					} else { //create masslock ring
						if( t.isPlanet  ) { //|| t.isSun ) {
							r = system.addVisualEffect( cm, tpos );
							if( r ) r.scale( ( t.radius + Math.max( t.radius, scr ) ) / mrs ); ///
//							log("Telescope", "Planet radius: "+t.radius );
						} else if( !t.isRock && !t.isDerelict
							&& t.scanClass !== "CLASS_BUOY" && t.scanClass !== "CLASS_CARGO"
							&& !t.isWormhole && !t.isCloaked ) {
//							ml = true;  //target
							r = system.addVisualEffect( cm, tpos ); //cm is set above
							if( r ) r.scale( rsc ); //ring radius=scanner range
						}
						if( r ) {
							r.orientation = pso;
							prepr[i] = r;
						}
					}
//					if( m ) m.position = tpos; //huge lightball - replaced with MRings
//					else {
//						if( ml ) {
//							ws.$TelescopeMMarks[i] = //show masslockfield
//								system.addVisualEffect( "telescope-masslockfield", tpos );
//						} else ml = false;
//					}
				} else { //masslock borders turned off with primable menu or weapons on and alert is not green
					if( r ) {
						r.remove();
						prepr[i] = null;
					}
//					if( m ) {
//						m.remove();
//						ws.$TelescopeMMarks[i] = null;
//					}
				}
			} else { //target is within scanner range (circle is surely out of view)
				md = scr + 300; //show the visual marker only without shadow lolipop
				if( r ) {
					r.remove();
					prepr[i] = null;
				}
//				if( m ) { //remove masslockfield
//					m.remove();
//					ws.$TelescopeMMarks[i] = null;
//				}
			}
			pos = psp.add( dir.multiply( md - i ) ) //anti-flicker difference
				.add( ps.heading.direction().multiply( ps.speed / 25 ) ); ///torus speed need correction
			if( !v ) {
				ws.$TelescopeVMarks[i] = system.addVisualEffect( c, pos );
//				log("Telescope", "VMarks["+i+"]:"+ws.$TelescopeVMarks[i]+" c:"+c+" pos:"+pos); //debug
			} else {
				if( v.dataKey !== c ) { //colour or size changed
					v.remove();
					prepv[i] = system.addVisualEffect( c, pos );
				} else {
					v.position = pos;
				}
				if( bl && i !== ti ) //black lollipops exccept the current target
					v.scannerDisplayColor1 = bla;
				else v.scannerDisplayColor1 = null; //original color from effectdata.plist
			}
		}
	}	
//	log("Telescope", "VFCBMarks end"); //debug
}
this.$Telescope_VFCBMarksOld = function() { //create and update target marker lightballs - old slow code
	var ps = player.ship;
	if( !ps || !ps.isValid ) return; //exit if player died
	var ws = worldScripts.telescope;
	var tl = ws.$TelescopeList, i, md, v = 0, ball = '';
	//following part is very CPU cunsuming, with 100 ships 60fps need 6000 cycle/sec, so code wisely
	for( i = 0; i < tl.length; i++ ) {
		var col = "pink"; //invalid target
		var tpos = null;
		var t = tl[i];
		if( !t || !t.isValid || !t.dataKey ) { //sun and planets has no dataKey
			v = ws.$TelescopeVMarks[i];
			if( v ) {
				v.remove(); //without this red ball remain after destroyed pirates
				ws.$TelescopeVMarks[i] = null;
			}
		} else if( !t.isBeacon && //buoy in the whole system, others in range
			!ws.$Telescope_InRange( t ) ) {
			tpos = ws.$TelescopeListPos[ i ];
			col = "gray"; //last known position only
		} else {
			ws.$TelescopeListPos[ i ] = t.position; //update until InRange
			if( t.isStation || t.dataKey.indexOf("buoy") > -1 )
				col = "green"; //base or buoy, do not use isBeacon to exclude ships with beaconCode
			else if( t.isVisible || ps.position.distanceTo( t.position ) < ps.scannerRange ) {
				if( t.isPolice ) col = "purple"; //police
				else if( t.isCargo || t.primaryRole === "escape-capsule" //cargo, esc.pod,
					|| t.isRock ) col = "white"; //or rock
				else if( t.isWormhole || t.isDerelict ) col = "blue"; //wormhole from Oolite v1.79
				else if( t.bounty > 0 ) col = "red"; //pirate or thargoid with bounty
				else if( t.isWeapon ) col = "cyan"; //missile or mine
				else col = "yellow"; //other ship with clean status
			} else if( t.mass >= 130000 ) col = "orange"; //large ship over the visible range
			else col = "brown"; //small ship over the visible range
			if( ws.$TelescopeLightBalls //if ship lightballs are disabled
				|| col === "blue" || col === "cyan" || col === "green" || col === "white" )
				tpos = t.position; //then show others only
		}
		if( tpos ) { //check to avoid invalid target and if ListPos[i] is null
			var dir = tpos.subtract( ps.position ).direction();
			var dt = ps.position.distanceTo( tpos ); //distance to the target (or to last known pos)
			if( dt > ps.scannerRange ) md = ps.scannerRange - 100; //marker distance
			//do not set closer to the range to do not left behind aft markers during torus travel
			else md = ps.scannerRange + 100; //show the visual marker only without shadow lolipop
			var pos = ps.position.add( dir.multiply( md + 50 * Math.random() ) ) //anti-flicker randomness
				.add( ps.heading.direction().multiply( ps.speed / 60 ) ); ///torus speed need correction
			var tcr = 0;
			if( t && t.isValid ) tcr = t.collisionRadius;
			if( dt < ws.$TelescopeSniperRange + tcr ) ball = "ball"; //large size
			else ball = "marker"; //average size
			if( dt < ws.$TelescopeVMarkMinDist + tcr
				//in red alert show nearest large ball marked targets only to save CPU and clean scanner
				|| player.alertCondition > 2 && ps.weaponsOnline && ball !== "ball" ) {
				var vmi = ws.$TelescopeVMarks[i];
				if( vmi ) vmi.remove();
				ws.$TelescopeVMarks[i] = null;
			} else {
				if( dt > 150000 ) col += "small"; //over 150km show smaller ball
				v = ws.$TelescopeVMarks[i];
				if( !v ) {
					v = ws.$TelescopeVMarks[i] =
						system.addVisualEffect("telescope-"+col+ball, pos);
				} else if( v.dataKey !== "telescope-"+col+ball ) { //colour or size changed
					v.remove();
					v = ws.$TelescopeVMarks[i] =
						system.addVisualEffect("telescope-"+col+ball, pos);
				} else v.position = pos;
			}
		}
	}
//	ws.$Telescope_VFCBShrinkVMarks();//should not needed
}
this.$Telescope_VFCBShrinkVMarks = function() {
	var ws = worldScripts.telescope;
	var i = ws.$TelescopeList.length;
	while( i < ws.$TelescopeVMarks.length) { //shrink VMarks to match List
		if( ws.$TelescopeVMarks[i] ) ws.$TelescopeVMarks[i].remove();
		ws.$TelescopeVMarks[i] = null;
		i++;
	}
	i = ws.$TelescopeList.length;
	while( i < ws.$TelescopeMMarks.length) { //shrink VMarks to match List
		if( ws.$TelescopeMMarks[i] ) ws.$TelescopeMMarks[i].remove();
		ws.$TelescopeMMarks[i] = null;
		i++;
	}
	i = ws.$TelescopeList.length;
	while( i < ws.$TelescopeMRings.length) { //shrink VMarks to match List
		if( ws.$TelescopeMRings[i] ) ws.$TelescopeMRings[i].remove();
		ws.$TelescopeMRings[i] = null;
		i++;
	}
}
this._VFCBVisualTarget_closure = function() { //orient vship model, make and update shadow and vmarkship
	// 'constant' variables
	var ws = worldScripts.telescope;
	var ps, ps_scannerRange, ps_target, ps_heading, ps_position, who_isPlanet, who_isSun, who_isValid;
	var vm, mp, wdn, who;
	// function references
	var system_addShips = system.addShips;
	var system_addVisualEffect = system.addVisualEffect;
	var telescope_VClear = ws.$Telescope_VClear;
	var telescope_VClearM = ws.$Telescope_VClearM;
	var telescope_InRangeFast = ws.$Telescope_InRangeFast;
	var telescope_PlanetName = ws.$Telescope_PlanetName;
	var telescope_Scan = ws.$Telescope_Scan;
		
	function _VFCBVisualTargetMarker() {
		if( vm ) vm.remove();
		vm = system_addShips("telescopemarker", 1, mp, 1)[0];
		ws.$TelescopeVMark = vm;
		if( wdn && vm && vm.isValid ) vm.displayName = wdn;
	//;	log("Telescope", "VMarkShip:"+vm);//debug
		if( vm && vm.isValid && (ps_target === who || who_isPlanet || who_isSun) && ps_target !== vm ) {
	//		var v = ws.$TelescopeVMarks[ws.$TelescopeListi-1];
	//		if( v && v.isValid ) 
			vm.scannerDisplayColor1 = vm.scannerDisplayColor2 = [0.2, 0.2, 0.2];
			//change the lock to the markership when step over normal scanner range
			ws.$TelescopeTargetSet = true;//to avoid doubled list item message
	//;		log("Telescope", "Vtarget change n:"+n+"km from:"+vm+" to:"+who);//debug
			ps.target = ps_target = vm;
			ws.$TelescopeTarget = who; //save the real target after(!) set
	//;		log("Telescope", "Vtarget changed n:"+n+"km from:"+vm+" to:"+who);//debug
			ws.$TelescopeTargetSet = false;
		}
		return(vm);
	}
	function _VFCBVisualTarget() { // make and update shadow and vmarkship
		ps = player.ship;	// need this here unless we export a fn to reset value when player changes ship
		if( !ps || !ps.isValid ) return; //if player died
		ps_scannerRange = ps.scannerRange; // needed in case scannerRange can chg over course of a game (new equipment?)
		ps_target = ps.target; 
		if( !ws.$TelescopeAutoLock && !ps_target ) return;
	//	ws.$TelescopeV.orientation = ps.orientation;
		var ti = ws.$TelescopeListi;
		who = ws.$TelescopeList[ ti - 1 ];
		var wp = null;
		if( !who ) {
	//;		log("Telescope","VFCBVisualTarget ti:"+ti+" who:"+who);
			telescope_VClear();
		} else {
			who_isPlanet = who.isPlanet;
			who_isSun = who.isSun;
			who_isValid = who.isValid;
			wdn = "";
			if( who_isValid && ( who_isPlanet || who_isSun || telescope_InRangeFast( who, ti ) ) ) {
				wp = who.position;
				if( who_isPlanet || who_isSun ) wdn = telescope_PlanetName(who);
				else wdn = who.displayName; //remove km from ident message
			} else wp = ws.$TelescopeListPos[ ti - 1 ]; //last known position
			if( !wp ) return; //bugfix after jump
			ps_heading = ps.heading; 
			ps_position = ps.position;
			var vd = wp.subtract( ps_position ).direction(); //used below for target marker also		
			
			//current target marker
			var md = ps_scannerRange - 499.6; //shadow lolipop nearer than others to reduce flickering
				//do not set nearer to do not left behind aft markers during torus travel
			mp = ps_position.add(vd.multiply(md))
				.add(ps_heading.direction().multiply(ps.speed/25));
			vm = ws.$TelescopeVMark;
			var n = ps_position.distanceTo( wp );
			if( who_isPlanet || who_isSun ) n -= who.radius;
			else n += who.collisionRadius;
			if( n < ps_scannerRange ) { //too near, replace the markership with visuall effect shadow lollipop
				if( who_isPlanet || who_isSun ) { //stay with marker
					mp = ps_position.add(vd.multiply(Math.min(md, n-299.6)
						)).add(ps_heading.direction().multiply(ps.speed/25));
					if( !vm || !vm.isValid || ws.$TelescopePrevPlanet !== who ) {
						ws.$TelescopePrevPlanet = who;
						vm = _VFCBVisualTargetMarker();
	//;					log("Telescope", "Markwho: "+who+" "+n+" m target:"+ps.target+" "+vm);
					} else {
						vm.position = mp;
					}
				} else {
					if( vm && !ps_target ) //must check player target to remove shadow marker after scooped
						telescope_VClearM();
					else if( who && who_isValid && vm && ps_target === vm && ps_target !== who ) {
						//change the lock to the real target when reached normal scanner range
						ws.$TelescopeTargetSet = true;//to avoid doubled list item message
	//;					log("Telescope", "Vtarget change n:"+n+"km from:"+vm+" to:"+who);//debug
						if( !who_isPlanet && !who_isSun ) ps.target = ps_target = who;
						if( ps.target !== who && //yes, it is possible! Hohoho showed when a ship jumped!
	//						player.consoleMessage("Hohoho!");//debug
					//bugfix against non-lockable Military Jammer awarded by ShipVersion to avoid repeated scan
							ws.$TelescopePrevWho !== who ) {
	//						player.consoleMessage("Hohoho!");//debug
	//;					log("Telescope", "ScanV: "+who+" - "+ws.$TelescopePrevWho);//debug
							ws.$TelescopePrevWho = who; //store to scan only once
							telescope_Scan(); //forced scan to remove the invalid ball
						}
						ws.$TelescopeTarget = ps_target; //save the real target after(!) set
	//;					log("Telescope", "Vtarget changed n:"+n+"km from:"+vm+" to:"+who);//debug
						ws.$TelescopeTargetSet = false;
					}
					if( vm && !vm.isVisualEffect ) { vm.remove(); vm = null; } //remove markership
					if( !vm && ps_target ) { //check player target to remove shadow marker after scooped
						vm = system_addVisualEffect("telescope-shadow", mp);
						ws.$TelescopeVMark = vm;
					} else if( vm ) vm.position = mp;
				}
			} else if( !vm || vm.isVisualEffect ) { //markership to can virtually target far ships
				vm = _VFCBVisualTargetMarker();
			} else vm.position = mp;
			if( vm && vm.isValid && !vm.isVisualEffect ) {
				if( n >= ps_scannerRange ) {
					n -= ps.collisionRadius;
					if( !who_isPlanet && !who_isSun ) n -= 2 * who.collisionRadius;
					var km = Math.floor( n / 1000 );
					if( km < 100 ) {
						var m = Math.floor( n - km * 1000 ); //rest after km
						var lz = ""; //leading zeros
						if( m < 10 ) lz = "00";
						else if( m < 100 ) lz = "0";
						//ws.$TelescopeVMark.name =
						ws.$TelescopeVMark.displayName =
	//						formatInteger( Math.round( n ) ) + //missing a dot after km
							km + "." + lz + m + " km " + wdn;
					} else {
						if( km < 1000000 ) ws.$TelescopeVMark.displayName = km + " km " + wdn;
						else ws.$TelescopeVMark.displayName = Math.floor( km / 1000000 ) + " Mkm " + wdn;
					}
				} else ws.$TelescopeVMark.displayName = wdn; //clear previous km
				ws.$TelescopeVMark.velocity = ps.velocity;//keep target over Torus speeds in FarPlanets OXP
			}
		}
	//;	log("Telescope", "VFCB end, target:"+ps.target); //debug
	}
	return _VFCBVisualTarget;
}
 |