| Scripts/fasttargetselector.js | "use strict";
this.name	     = "FastTargetSelector";
this.author	     = "phkb";
this.copyright   = "2016 phkb";
this.description = "Allows quick targeting of ships or other entities visible on the scanner. Also highlights current target on the scanner.";
this.license     = "CC BY-NC-SA 4.0";
/* Borrows scanner highlight code from Thargoids Target Autolock OXP */
/* Also borrows "sort by distance calc" from Timer's Targeter OXP */
/* Bg thanks to cag for code optimisations */
this._normalColorsDefault = {color1:"blueColor", color2:"yellowColor"};
this._normalColorsStation = {color1:"blueColor", color2:"greenColor"};
this._normalColorsPolice = {color1:"greenColor", color2:"0.5 0.0 1.0"};
this._normalColorsRock = {color1:"blueColor", color2:"whiteColor"};
this._normalColorsThargoid = {color1:"blueColor", color2:"redColor"};
this._normalColorsMissile = {color1:"cyanColor", color2:"whiteColor"};
this._normalColorsBuoy = {color1:"greenColor", color2:"whiteColor"};
this._battleColorsDefault = {color1:"redColor", color2:"yellowColor"};
this._battleColorsMissile = {color1:"redColor", color2:"cyanColor"};
this._battleColorsPolice = {color1:"redColor", color2:"1.0 0.0 0.5"};
this._npc = [];								// current scan data array
this._index = -1;							// position of players selected target in array
this._dataAge = 0;							// age of current scan data
this._targetTimer = null;					// timer used to update the colors of the currently selected target, so it will stand out on the scanner
this._trueCondition = 0;					// current alert condition of the player. Adjusted slightly so that red alert (3) is limited to when there are hostile ships
this._oldCondition = 0;						// last state of the players alert condition
this._oldTarget = null;						// reference to the players last selected target
this._ignoreRoles = ["btd_dummy_entity"];	// ship roles that will be ignored (ie they won't be selected)
//cag: increasing the length of _ignoreRoles requires chg's to $findNPCShips()
this._active = false;						// flag to indicate the FTS is in operation
this._scanMode = 0;							// mode 0 = normal scan operations, mode 1 = include all objects, even under condition red. Default to 0.
this._removePrimableEquipment = false;		// flag to indicate whether the mode change primable equipment will be removed from the list. Default to false.
this._trueValues = ["yes", "1", 1, "true", true];
this._tapInstalled = false;
this._ED = null;
// configuration settings for use in Lib_Config
this._ftsConfig = {
	Name:this.name, 
	Alias:expandDescription("[fts_config_alias]"), 
	Display:expandDescription("[fts_config_display]"), 
	Alive:"_ftsConfig", 
	Bool:{
		B0:{Name:"_removePrimableEquipment", Def:false, Desc:expandDescription("[fts_remove_changer]")},
		Info:expandDescription("[fts_config_bool_info]")
	},
	EInt:{
		E0:{Name:"_scanMode", Def:0, Desc:expandDescription("[fts_default_mode]").split("|"), Min:0, Max:1},
		Info:expandDescription("[fts_config_eint_info]")
	}
};
//-------------------------------------------------------------------------------------------------------------
this.startUpComplete = function() {
	// register our settings, if Lib_Config is present
	if (worldScripts.Lib_Config) worldScripts.Lib_Config._registerSet(this._ftsConfig);
	if (missionVariables.FTS_RemoveModeChanger) this._removePrimableEquipment = (this._trueValues.indexOf(missionVariables.FTS_RemoveModeChanger) >= 0 ? true : false);
	if (missionVariables.FTS_ScanMode) this._scanMode = parseInt(missionVariables.FTS_ScanMode);
	if (worldScripts.targetAutolock) {
		if (missionVariables.targetAutolock == "TRUE") this._tapInstalled = true;
	}
	if (worldScripts.escortdeck) {
		this._ED = worldScripts.escortdeck;
		this.$findNPCShips = this.$findNPCShips_withEscortDeck;
	} else {
		this.$findNPCShips = this.$findNPCShips_normal;
	}
	// make sure the mode change equipment isn't on board - we only push it into the equip list on launch
	player.ship.removeEquipment("EQ_TARGETSELECTOR_MODECHANGER");
	if (player.ship.equipmentStatus("EQ_TARGETSELECTOR") === "EQUIPMENT_OK") this._active = true;
}
//-------------------------------------------------------------------------------------------------------------
this.playerWillSaveGame = function() {
	missionVariables.FTS_RemoveModeChanger = this._removePrimableEquipment;
	missionVariables.FTS_ScanMode = this._scanMode;
}
//-------------------------------------------------------------------------------------------------------------
this.shipTargetAcquired = function(newTarget) {
	if (this._active) this.$startTimer();
}
//-------------------------------------------------------------------------------------------------------------
this.alertConditionChanged = function(newCondition, oldCondition) {
	if (this._active === false) return;
	if (this._trueCondition !== newCondition && ((newCondition === 3 && player.alertHostiles === true) || newCondition < 3)) {
		this._trueCondition = newCondition;
		this.$startTimer();
	}
}
//-------------------------------------------------------------------------------------------------------------
this.shipTargetLost = this.shipTargetDestroyed = function(target) {
	if (this._active === false) return;
	this.$resetOldTarget();
}
//-------------------------------------------------------------------------------------------------------------
this.shipDockedWithStation = function(station) {
	this.$resetOldTarget();
	// remove the mode changer
	player.ship.removeEquipment("EQ_TARGETSELECTOR_MODECHANGER");
}
//-------------------------------------------------------------------------------------------------------------
this.shipLaunchedFromStation = function(station) {
	// make sure the mode changer is installed, but only if (a) its not disabled, and the fts is not broken
	if (this._removePrimableEquipment === false && player.ship.equipmentStatus("EQ_TARGETSELECTOR") === "EQUIPMENT_OK") {
		player.ship.awardEquipment("EQ_TARGETSELECTOR_MODECHANGER");
	}
}
//-------------------------------------------------------------------------------------------------------------
this.shipDied = this.shipWillEnterWitchspace = function() {
	if (this._active === false) return;
	this.$resetOldTarget();
}
//-------------------------------------------------------------------------------------------------------------
this.playerBoughtEquipment = function(equipmentKey) {
	if (equipmentKey === "EQ_TARGETSELECTOR_REMOVAL") {
		player.ship.removeEquipment(equipmentKey);
		player.ship.removeEquipment("EQ_TARGETSELECTOR_MODECHANGER");
		player.ship.removeEquipment("EQ_TARGETSELECTOR");
		this._active = false;
	}
	if (equipmentKey === "EQ_TARGETSELECTOR") this._active = true;
	// monitor for when TAP is installed so we don't conflict with it
	if (equipmentKey === "EQ_TARGET_AUTOLOCK") this._tapInstalled = true;
}
//-------------------------------------------------------------------------------------------------------------
this.equipmentDamaged = function(equipmentKey) {
	if (equipmentKey === "EQ_TARGETSELECTOR") this._active = false;
	if (equipmentKey === "EQ_SCANNER_SHOW_MISSILE_TARGET") this._tapInstalled = false;
}
//-------------------------------------------------------------------------------------------------------------
this.equipmentRepaired = function(equipmentKey) {
	if (equipmentKey === "EQ_TARGETSELECTOR") this._active = true;
	// if the fts gets repaired in flight, add back the mode changer (if we haven't disabled it)
	if (player.ship.isInSpace && equipmentKey === "EQ_TARGETSELECTOR" && this._removePrimableEquipment === false) player.ship.awardEquipment("EQ_TARGETSELECTOR_MODECHANGER");
	if (equipmentKey === "EQ_TARGET_AUTOLOCK") this._tapInstalled = true;
}
//-------------------------------------------------------------------------------------------------------------
this.alertConditionChanged = function(newCondition, oldCondition) {
	// update our "trueCondition" variable
	this._trueCondition = newCondition;
	// if this new condition is "red alert", see if we're actually under attack
	if (newCondition === 3) {
		if (player.alertHostiles === false) {
			// if not, it's really only yellow alert from this OXP's point of view
			this._trueCondition = 2;
		}
	}
}
//-------------------------------------------------------------------------------------------------------------
// "n" key
this.activated = function () {
	// next target
	var w = worldScripts.FastTargetSelector;
	w.$lookForTarget(1);
}
//-------------------------------------------------------------------------------------------------------------
// "b" key
this.mode = function() {
	// previous target
	var w = worldScripts.FastTargetSelector;
	w.$lookForTarget(-1);
}
//-------------------------------------------------------------------------------------------------------------
// starts the lockon timer, if it isn't already
this.$startTimer = function() {
	if (this._targetTimer && this._targetTimer.isRunning) return;
	this._targetTimer = new Timer(this, this.$lockOn, 0.25, 0);
}
//-------------------------------------------------------------------------------------------------------------
// reset the scanner colors of the previous target
this.$resetOldTarget = function() {
	if (this._targetTimer && this._targetTimer.isRunning) this._targetTimer.stop();
	if (this._oldTarget) {
		if (this._tapInstalled === false) {
			this._oldTarget.scannerDisplayColor1 = null;
			this._oldTarget.scannerDisplayColor2 = null;
		}
		this._oldTarget = null;
	}
}
//-------------------------------------------------------------------------------------------------------------
// updates the scanner colors of the current target to make it more visible
this.$lockOn = function $lockOn() {
	//this.$resetOldTarget();
	var p = player.ship;
	// let's not lock onto ourselves shall we?
	if(p.target === p) p.target = null;
	// do we have a new target
	if (p.target) {
		// no changes, so don't do anything
		if (p.target === this._oldTarget && this._trueCondition === this._oldCondition) return;
		this.$resetOldTarget();
		
		this._oldTarget = p.target;
		this._oldCondition = this._trueCondition;
		var battle = false;
		if (player.alertCondition === 3 && player.alertHostiles === true) {
			// only switch to battle colors on ships that are actually trying to kill us
			if (this._oldTarget.isShip && this._oldTarget.target === p) battle = true;
		}
		var currentColors = (battle === false ? this._normalColorsDefault : this._battleColorsDefault);
		if (this._oldTarget.isValid) {
			if (this._oldTarget.isMissile || this._oldTarget.isMine || this._oldTarget.isWeapon) {
				currentColors = (battle === false ? this._normalColorsMissile : this._battleColorsMissile);
			} else if (this._oldTarget.isPolice && (this._oldTarget.isShip && this._oldTarget.hasRole("RSsatellite") === false)) {
				currentColors = (battle === false ? this._normalColorsPolice : this._battleColorsPolice);
			} else if (this._oldTarget.isThargoid) {
				currentColors = this._normalColorsThargoid;
			} else if (this._oldTarget.isStation) {
				currentColors = this._normalColorsStation;
			} else if (this._oldTarget.isRock || this._oldTarget.isDerelict || this._oldTarget.isCargo || (this._oldTarget.isShip && this._oldTarget.hasRole("RSsatellite") === true)) {
				currentColors = this._normalColorsRock;
			} else if (this._oldTarget.scanClass === "CLASS_BUOY") {
				currentColors = this._normalColorsBuoy;
			}
		}
		if (this._tapInstalled === false) {
			p.target.scannerDisplayColor1 = currentColors.color1;
			p.target.scannerDisplayColor2 = currentColors.color2;
		}
	}
}
//-------------------------------------------------------------------------------------------------------------
this.$lookForTarget = function(direction) {
	
	var currNPC = null;
	var ps = player.ship;
	var valid = false;
	
	this.$refreshArray();
	var npc = this._npc;
	if (npc && npc.length > 0) {
		var lc = 0;
		do {
			this._index += direction;					
			// select the next/previous index value, based on the direction
			if (this._index < 0) 						
				// if we get to the beginning of the array, go back to the end
				this._index = npc.length - 1;
			else if (this._index > npc.length - 1) 
				// if we get to the end of the array, go back to the beginning
				this._index = 0;
			currNPC = npc[this._index];
			valid = currNPC && currNPC.isValid === true && currNPC.isJamming === false 
				&& currNPC.isCloaked === false;
			if (valid && ps.position.distanceTo(currNPC) < ps.scannerRange) {
				// if the item is valid, make it the player's target
				ps.target = currNPC;
				break;
			}
			lc += 1;
		} while ( valid && lc < npc.length ); 	
		// if we didn't get a target, keep looping until we do
	}
}
//-------------------------------------------------------------------------------------------------------------
this.$refreshArray = function() {
	// get the array of ships
	if ((global.clock.adjustedSeconds - this._dataAge) > 1) { // limit to once/sec
		this._npc = this.$findNPCShips();
		this._dataAge = global.clock.adjustedSeconds;
	}
}
//-------------------------------------------------------------------------------------------------------------
// gives every ship a sort order as it spawns
this.shipSpawned = function(ship) {
	ship.fts_sort = 5;
	if (ship.scanClass == "CLASS_NEUTRAL" || ship.isStation) ship.fts_sort = 3;
	if (ship.isThargoid) ship.fts_sort = 0;
	if (ship.bounty > 0 && ship.fts_sort == 5) ship.fts_sort = 2;
	if (ship.bounty >= 50 && ship.fts_sort == 5) ship.fts_sort = 1;
	if (ship.isPolice) ship.fts_sort = 4;
	if (ship.isCargo) ship.fts_sort = 6;
	if (ship.scanClass == "CLASS_BUOY") ship.fts_sort = 7;
	if (ship.isRock && !ship.isStation) ship.fts_sort = 8;
}
//-------------------------------------------------------------------------------------------------------------
// find all npc ships in range
this.$findNPCShips_normal = function() {
	function _isNPC(entity)	{
		//var wF = worldScripts.FastTargetSelector; // shouldn't actually need this, as context should always be "this"
		if (!entity.isShip) return false;
		if (entity.isPlayer) return false;
		if (entity.displayName === "Wreckage") return false;
		if (this._trueCondition === 3 && this._scanMode === 0) {
			// during red alert, only target thargoids, missiles and enemy ships
			if (entity.isThargoid === true && entity.isCargo === false) return true;
			if (entity.target === player.ship && entity.hasHostileTarget) return true;
			if (entity.isMine) return true;
			if (entity.isWeapon) return true;
			return false;
		} 
		if (entity.isVisualEffect) return false ;
		if (this._ignoreRoles.length === 1) {
			if (entity.primaryRole === this._ignoreRoles[0]) return false;
		} else {
			// only do indexof if there is more than one entry in the ignoreroles list
			if (this._ignoreRoles.indexOf(entity.primaryRole) === -1) return false;
		}
		return true;
	}
	return this.$sortList(system.filteredEntities(this, _isNPC, player.ship, player.ship.scannerRange));
}
//-------------------------------------------------------------------------------------------------------------
// find all npc ships in range
this.$findNPCShips_withEscortDeck = function() {
	function _isNPC(entity)	{
		//var wF = worldScripts.FastTargetSelector; // shouldn't actually need this, as context should always be "this"
		if (!entity.isShip) return false;
		if (entity.isPlayer) return false;
		if (entity.displayName === "Wreckage") return false;
		if (this._trueCondition === 3 && this._scanMode === 0) {
			// during red alert, only target thargoids, missiles and enemy ships
			if (entity.isThargoid === true && entity.isCargo === false) return true;
			if (entity.target === player.ship && entity.hasHostileTarget) return true;
			if (entity.isMine) return true;
			if (entity.isWeapon) return true;
			return false;
		} 
		if (entity.isVisualEffect) return false ;
		// thanks to Norby for this escortdeck exclusion code
		var i = this._ED.$EscortDeckShip.indexOf(entity);
		if (i >= 0 && this._ED.$EscortDeckShipPos[i]) //entity is on deck
			return false; //so exclude this entity from target list
		if (this._ignoreRoles.length === 1) {
			if (entity.primaryRole === this._ignoreRoles[0]) return false;
		} else {
			// only do indexof if there is more than one entry in the ignoreroles list
			if (this._ignoreRoles.indexOf(entity.primaryRole) === -1) return false;
		}
		return true;
	}
	return this.$sortList(system.filteredEntities(this, _isNPC, player.ship, player.ship.scannerRange));
}
//-------------------------------------------------------------------------------------------------------------
this.$sortList = function(targets) {
	return targets.sort( function sortByID(a,b) {
		return (a.fts_sort < b.fts_sort) ? -1 : (a.fts_sort > b.fts_sort) ? 1 : 0;
	});
}
//-------------------------------------------------------------------------------------------------------------
// removes any wreckage items from the array, then calls the sorting algorithm
/*this.$sortArray = function() {
	// remove any wreckage items
	for (var i = this._npc.length - 1; i >= 0; i--) {
		if (this._npc[i].displayName === "Wreckage") this._npc.splice(i, 1);
	}
	// then sort
	this.$sortByDistance(this._npc);
}*/
//-------------------------------------------------------------------------------------------------------------
// sort by distance function courtesy of Thargoid
/*this.$sortByDistance = function(targets) {
	var cache = {};
	// sort
	targets.sort( function sortByDist(a,b) {
		// cache distance
		var ai = a.entityPersonality, bi = b.entityPersonality; // use some "uniq" ident
		cache[ai] = cache[ai] || (a.isValid && a.position.distanceTo(player.ship.position));
		cache[bi] = cache[bi] || (b.isValid && b.position.distanceTo(player.ship.position));
		return ((cache[ai] < cache[bi]) ? -1 : ((cache[ai] > cache[bi]) ? 1 : 0));
	});
	// turn off the flag that indicates the data isn't sorted
	this._sortRequired = false;
}
*/ |