Back to Index Page generated: Dec 20, 2024, 7:22:09 AM

Expansion Fast Target Selector

Content

Manifest

from Expansion Manager's OXP list from Expansion Manifest
Description Primable equipment that allows quick targeting of other ships in scanner range. Primable equipment that allows quick targeting of other ships in scanner range.
Identifier oolite.oxp.phkb.FastTargetSelector oolite.oxp.phkb.FastTargetSelector
Title Fast Target Selector Fast Target Selector
Category Equipment Equipment
Author phkb phkb
Version 1.6 1.6
Tags equipment, scanner, targeting equipment, scanner, targeting
Required Oolite Version
Maximum Oolite Version
Required Expansions
Optional Expansions
Conflict Expansions
Information URL https://wiki.alioth.net/index.php/Fast_Target_Selector_OXP n/a
Download URL https://wiki.alioth.net/img_auth.php/a/ac/FastTargetSelector_1.6.oxz n/a
License CC-BY-NC-SA 4.0 CC-BY-NC-SA 4.0
File Size n/a
Upload date 1700528439

Documentation

Also read http://wiki.alioth.net/index.php/Fast%20Target%20Selector

readme.txt

Fast Target Selector
By Nick Rogers

Overview
========
This OXP aims to simplify the process of targeting ships. In the core game, a pilot must point their crosshairs at a ship in order to target it. However, at the edge of scanner range it is quite hard to move the reticule over the small dot of a ship in order to get a target lock. This seems counter-intuitive as the scanner can certainly identify the ship - why can't a lock be set without having to jiggle the crosshairs over the ship?

This is where Fast Target Selector comes into play. Now you can do what NPC ships do - just cycle through all the available targets by priming the equipment and selecting "n". During red alert conditions when enemies are targeting you, the list of targets cycled through will be reduced to just those who are targeting you. 

The Fast Target Selector is simple to install and use, costing a mere 10cr and available from all tech level 5 systems and above. The reason for the small cost is to make it a reasonable piece of equipment for new commanders to install even before they leave Lave. 

Usage
=====
Ideally the Fast Target Selector will be set as the "Fast Defensive" equipment item, using the "0" key on the keyboard to switch to the next target. However, you can also prime the equipment using "Shift-N", and use the "n" key to go to the next target and "b" to go back to the previous target.

When a ship or object is targeted it will be highlighted on the scanner with an alternating color, depending on the type of ship. 

Ships are grouped together by type when you cycle through available targets (and you aren't in condition Red). The sorting priority is:
    Thargoids
    Fugitives
    Offenders
    "Neutral" ships (eg traders, non-aggressive ships, stations)
    Police
    Any others ships that aren't rocks or cargo
    Cargo containers
    Rocks (splinters, boulders, asteroids)

If you find yourself in a situation where you are in Condition Red, but you need to target something other than enemy ships, prime the "FTS Mode Changer" equipment (this becomes available during flight), and press the "n" key to change the mode between "Standard target selection" (which is the default mode where only enemy ships are targeted in Condition Red) and "Include all targets" (which essentially ignores Condition Red and targets all entities in range).

Note: Wormholes will not be targeted with this system. They must be targeted in the traditional way.
Note: This equipment is incompatible with the "Targeter" and "Military Targeting System" equipment items. You will not be able to purchase this equipment with those items already installed.

License
=======
This work is licensed under the Creative Commons Attribution-Noncommercial-Share Alike 4.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/

Thanks for Thargoid for his "Target Autolock Plus" OXP, from which I borrowed the scanner highlighting code, and for his "sort by distance" calculation (although I can't remember where I sourced this from).
Thanks to cag for his speed improvements and finding of bugs.

Version History
===============
1.6
- Sorted targets by type.

1.5
- Bug fixes.
- Code refactoring.

1.4
- Removed incompatible behaviour when "Target Autolock Plus" OXP is active.

1.3
- Removed hard-coded scanner range value.
- Improved integration with EscortDesk, thanks to Norby and cag.

1.2
- Code optimisations with thanks to cag.

1.1
- Small tweak to the method of identifying hostile ships.

1.0
- Initial release.

Equipment

Name Visible Cost [deci-credits] Tech-Level
Fast Target Selector yes 100 5+
FTS Mode Changer no 100 5+
Remove Fast Target Selector no 10 3+

Ships

This expansion declares no ships.

Models

This expansion declares no models.

Scripts

Path
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:"Fast Target Selector", Display:"Config", Alive:"_ftsConfig", 
	Bool:{
		B0:{Name:"_removePrimableEquipment", Def:false, Desc:"Removes the FTS Mode Changer"},
		Info:"0 - Removes the FTS Mode Changer from primable equipment."},
	EInt:{
		E0:{Name:"_scanMode", Def:0, Desc:["Def. scan mode","Def. scan mode"], Min:0, Max:1},
		Info:"Default scan mode: 0=Standard (red alert limited). 1=All entities (even in red alert)"}
};

//-------------------------------------------------------------------------------------------------------------
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.isRock && !ship.isStation) ship.fts_sort = 7;
}

//-------------------------------------------------------------------------------------------------------------
// 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;
}
*/
Scripts/fasttargetselector_conditions.js
"use strict";
this.name	     = "FastTargetSelector_Conditions";
this.author	     = "phkb";
this.copyright   = "2016 phkb";
this.description = "Condition script for equipment";
this.licence     = "CC BY-NC-SA 4.0";

//-------------------------------------------------------------------------------------------------------------
this.allowAwardEquipment = function(equipment, ship, context) {
	if (equipment === "EQ_TARGETSELECTOR_MODECHANGER" && context != "scripted") return false;
	return true;
}
Scripts/fasttargetselector_modechanger.js
"use strict";
this.name	     = "FastTargetSelector_ModeChanger";
this.author	     = "phkb";
this.copyright   = "2016 phkb";
this.description = "Changes the way the FTS works in Condition Red.";
this.licence     = "CC BY-NC-SA 4.0";

//-------------------------------------------------------------------------------------------------------------
this.activated = function() {
	var w = worldScripts.FastTargetSelector;
	if (w) {
		if (player.ship.equipmentStatus("EQ_TARGETSELECTOR") != "EQUIPMENT_OK") {
			player.consoleMessage(expandDescription("[fts_damaged]"));
			return;
		}
		w._scanMode += 1;
		if (w._scanMode >= 2) w._scanMode = 0;
		player.consoleMessage(expandDescription("[fts_scan_mode_" + w._scanMode + "]"));
	}
}