Scripts/telescope.js |
this.name = "telescope";
this.author = "Norby, cag";
this.copyright = "2018 Norbert Nagy, cag";
this.license = "CC BY-NC-SA 4.0";
this.description = "Telescope mark all visible ships, show vitrual model, sniper ring and more.";
this.version = "2.0.2";
/* jshint elision: true, shadow: true, esnext: true, curly: false, maxerr: 1000, asi: true,
laxbreak: true, undef: true, unused: true, evil: true, forin: true, eqnull: true,
noarg: true, eqeqeq: true, boss: true, loopfunc: true, strict: true, nonew: true, noempty: false
*/
/*jslint indent: 4, white: true, debug: true, continue: true, sub: true, css: false, todo: true,
on: false, fragment: false, vars: true, nomen: true, plusplus: true, bitwise: true,
regexp: true, newcap: true, unparam: true, sloppy: true, eqeq: true, stupid: true
*/
/* global addFrameCallback, clock, EquipmentInfo, isValidFrameCallback, log,
missionVariables, oolite, player, Quaternion, removeFrameCallback, SoundSource,
system, Timer, worldScripts, Vector3D, Script
*/
(function(){
/* validthis: true */
"use strict";
/*
* customizable subset of property values also available in-game as primable equipment
*/
// NB: editting these values here WILL NOT take effect! Provided as illistration only.
// Use the in-station option facility (F4). If you insist on editing this
// file, make sure your changes are made in:
// this._load_missionVariables()
// Default values get assigned there in the absence of missionVariables
// 'config' page
this.$AutoScan = true; //check continually for new isVisible isPiloted target and scan if found
this.$AutoScanMaxRange = 1e6; //meters, how far targets will be reported
this.$AutoLock = 1; //degrees, if no target and something in crosshairs within this diff. from center, 1=lightball size, 0=off
this.$GravLock = 20; //degrees, navigation scanner relock in this center cone ( 0-180, 20=about the screen height)
this.$IdentLock = 180; //degrees, if ident pressed or target lost then lock in this center cone ( 0-180, 90=anything fwd,180=the whole sphere )
this.$IdentDelay = 4; //(new) quarter seconds, time targeting is suspended following Ident unlock, default: 4 (1 sec)
// otherwise it *could* immediately re-acquire same target!
// - this comes down to a pilot's style, whether or not you move the aim point before/after unlocking
this.$FarStatus = false; //red ball reveal pirates over normal scanner if true
this.$MaxTargets = 200; //limitable to reduce FPS drop in systems with many ships, min. 4, max. 200
this.$RedAlertDist = 30000; //meters, show lollipops in red alert within this distance only
this.$Steering = 0; //auto steering if lock nearest or each step in the target list with activate, 2: each, 1: nearest only, 0 off;
this.$LightBalls = true; //turn on or off all lightballs, but markes on the scanner will be remain
this.$ShipLightBalls = true; //turn on or off the lightballs with scanner markers of the ships, but cargo, etc. remain
this.$LargeLightBalls = false; //lightballs are increasing depending on the distance or remains small
this.$LightBallMinDist = 1000; //meters, if target is inside then remove the lightball marker
this.$LightBallShipMinDist = 5000; //meters, if target is ship and inside this range then remove the lightball marker
this.$DEFAULT_ML_RINGS = 23; // as per 1.15, in green Alert or weapons off-line
this.$MassLockRings = 23; //coloured circles around ships and planets in green alert or weapons off-line
// - (new) now are bit flags for when to show
this.$MassLockViewDirn = 1; //(new) bit flags for in which view masslock rings are shown
this.$BrightMassLockRings = false; //brighter circles around ships and planets
this.$SniperRingSize = 2; //size of the sniper ring ( between 1 and 5, default: 2 )
this.$SniperRingActive = 42; //states when sniper ring is active (6: 3 alerts * 2 weapons states)
this.$SniperRange = 25600; //meters, if the target is inside then show sniper ring
this.$SniperMinRange = 10000; //meters, show sniper ring if the target is over this distance
this.$SniperRingColor = [0.3, 0.3, 0.3];//(new) colour of the sniper ring, default is lightGrayColor
this.$ShowVisualTarget = 0; //show 3D model of target, 2: on, 1: only when weaps off-line, 0 off;
this.$VisualTargetNormalSize = 6; //zoomed size of the visual target with off-line weapons ( between 0 and 8, default: 6 )
this.$VisualTargetCombatSize = 4; //size of the visual target with online weapons ( between 0 and 8, default: 4 )
this.$VisualTargetRing = true; //show a ring around the visual target
this.$ShowVisualStation = true; //show or not show the 3D model of the targeted station
this.$ShowVisualQuestionMark = false; //if a ship has no visual model in effecdata.plist show a big "?" model
this.$ModelRingColor = [0.3, 0.3, 0.3]; //(new) colour of ring around 3D model, default is lightGrayColor
this.$VTarget_HUD_shift = [0, 0, 0]; //position shift for your HUD's built-in visual target screen if any
// 'UI_and_docs' page
this.$ConsoleMsgDurn = 5; // duration in sec for console messages
this.$GravScanMsgFreq = 3; // bit flags for frequency of gravity scanner update msgs
this.$IdentMessages = true; // flag for displaying/suppressing Ident key messages
this.$ShowSummary = true; // display a summary of changes when exiting station options
// - first conditionally displayed option
// 'experimental' page // new options/features
this.$TargetOnlyHostile = false; // ignore targeting cargo, pods and rocks in Red Alert
this.$RemoveInFlight = false; // cut in half # of entries in equipment's mode cycle
// constants for MFD(s) filtering
this.$MFD_DYNAMIC_ALLSET = 127; // highest bit = 64, Number('0x007f')
this.$MFD_STATIC_ALLSET = 4095; // highest bit = 2048, Number('0x0fff')
this.$MFDFiltering = false; // toggle for filtering MFD output
this.$MFDPrimaryStatic = this.$MFD_STATIC_ALLSET;// bit flags for filtering MFD using static properties
this.$MFDPrimaryDynamic = this.$MFD_DYNAMIC_ALLSET;// bit flags for filtering MFD using dynamic properties
this.$SeparateMFDs = false; // toggle for adding an auxiliary MFD
this.$MFDAuxStatic = this.$MFD_STATIC_ALLSET; // bit flags for filtering MFD using static properties
this.$MFDAuxDynamic = this.$MFD_DYNAMIC_ALLSET;// bit flags for filtering MFD using dynamic properties
this.$Thargoids = false; //you will get some aliens right after undock to test Telescope
// Beta licence feature in station options (testing dynamics)
this.$BetaLicence = ''; // choice for licence of experimental options
this.$BetaLicenceTimestamp = ''; // date of player accepting license agreement for experimental options
this.$BetaLicenceSystem = ''; // system where this occured; preserved in missionVariables (_reloadFromStn)
this.$DebugMessages = false; // flag for logging debug messages
///////////////////////////////////////////////////////////////////////////////////////////////////
// internal properties, should not touch //////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
this.$PrimaryMFD_name = 'TelescopeMFD';
this.$AuxilaryMFD_name = 'TelescopeAuxMFD';
this.$are_Steering = false; // flag as to whether ship is auto-steering
this.$DamageMsg = true; // flag to show messgage less frequently
this.$FixedTel = 0; //cheaply fixed Telescope with drawbacks
this.$FixedGS = 0; //cheaply fixed Gravity Scanner with drawbacks
this.$FixedSD = 0; //cheaply fixed Small Dish with drawbacks
this.$FixedLD = 0; //cheaply fixed Large Dish with drawbacks
this.$GravScanCount = 0; //Gravity Scan counter to call aliens
this.$IdentKeyPress = 0; // count for 'ident' key presses: 1st to lock target, 2nd to steer (if turned on), next press will unlock
// IdentKeyPress values
this.$IDENT_READY = 0;
this.$IDENT_LOCKED = 1;
this.$IDENT_STEERING = 2;
this.$IDENT_UNLOCK = 3;
this.$IDENT_STEER_DELAY = 4;
this.$IDENT_STEP_DELAY = 5;
this.$MaxRange = 1e15; //10^15m, usable part of double precision for filteredEntities
this.$MASSLOCK_RING_SCALE = 41.8; //masslock ring scale, used for .scale() calc's
// to abort the resultant events shipTargetAcquired & shipTargetLost
this.$SoundScan = null; //scan soundsource
this.$Timer_auto_updates = null; //AutoScan timer get targets from normal scanner and do scan if new far target in the front view
this.$extenderActive = null; // maintained for _condition statements in Station Options
// values from activate/mode in flight changes
// missionVariables - are VERY slow, so only use on load/save game
this.$TelescopeMenuSteering = 1; // defaults low so as to not overwhelm initiates
this.$TelescopeMenuLightballs = 3;
this.$TelescopeMenuMasslockRings= 1;
this.$TelescopeMenuSniper = 2;
this.$TelescopeMenuTargets = 3;
this.$TelescopeMenuVisual = 1;
this.$TelescopeMenuVisualSize = 4;
this.$UserChangedSettings = 0; // bit flags to signal changes in-game; see _SetLightballs et. al.
this.$SightingsMap = []; // persistent array of Sightings that comprise all the telescope sees
this.$curr_Sighting = { map: null, ent: null, marker: null, marker_type: null, name: null }; // info on current Sighting
this.$Sighting_events_FCB = null; // store frame callback for _Sighting_events()
this.$fps_closure = null; // closure for fps_monitor
this.$Telescope_not_in_use = true; // flag set in startUpComplete where it's determined if player has a telescope, used to stop event handlers
///////////////////////////////////////////////////////////////////////////////////////////////////
// legacy properties for oxp support //////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// new: a simpler way to get the entity a far target marker is refering to is a property
// of the telescope marker: ps.target.$TelescopeTarget
// eg. var target = ps.target;
// if( target.dataKey === 'telescopemarker' )
// target = target.$TelescopeTarget;
// OR var target = ps.target.dataKey === 'telescopemarker' ? ps.target.$TelescopeTarget : ps.target;
this.$fakeTelescopeList = function() {
this[ 0 ] = null;
};
this.$fakeTelescopeList.prototype.length = 1;
this.$fakeTelescopeList.prototype.indexOf = function indexOf( ent ) {
var that = indexOf;
var ws = (that.ws = that.ws || worldScripts.telescope);
var mapping = (that.mapping = that.mapping || ws.$SightingsMap);
var index = ws._Sighting_index( ent );
if( index < 0 ) return -1;
this[ 0 ] = mapping[ index ].ent;
return 0;
};
this.$TelescopeList = new this.$fakeTelescopeList();
this.$TelescopeListi = 0;
// changing variable & fn names breaks all external oxp references
// - external refs usually just ws.$TelescopeList[ ws.$TelescopeListi - 1 ], ie. current far target
// so we'll maintain TelescopeList as a one element array and set TelescopeListi to 0 or 1 accordingly
this.$TelescopeVPos = [0, 0, 0]; // maintain for Carriers oxp (position of the visual effect)
this.$TelescopeVPosHUD = [0, 0, 0]; // maintain for Carriers oxp (position shift for your HUD's built-in visual target screen if any)
this.$TelescopeSteerFCB = null; // maintain for Towbar oxp
this.$TelescopeTargetSet = false; // used by Telescope and EscortDeck
this.$TelescopeRing = null; // maintain for VimanaHUD oxp
this.$TelescopeVSize = null; // maintain for VimanaHUD oxp
this.$TelescopeVZoomSize = null; // maintain for VimanaHUD oxp
//flag scanning to avoid double scan; it's like a write lock: when we set ps.target, we use this
///////////////////////////////////////////////////////////////////////////////////////////////////
// world script events ////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///
this.startUp = function startUp() {
var ws = worldScripts.telescope;
///
if( worldScripts.NShields ) // too much logging
worldScripts.NShields._logging = false;
///
try {
ws.$SoundScan = new SoundSource();
ws.$SoundScan.sound = "ScanSound.ogg"; //sound of the Gravity Scanner
ws.$SoundScan.loop = false;
ws.$SoundScan.repeatCount = 1;
var hud = worldScripts.hudselector;
if( hud ) {
//hud.startUpComplete = ws.$HUDStartUpComplete;
//hud.$HUDSelectorSetMFDs = ws.$HUDSelectorSetMFDs;
ws._registerHUDSelector();
}
// create sightings closure
ws._init_Sightings_closure();
ws._load_missionVariables();
if( ws.$DebugMessages ) {
ws._debug_Sightings_closure();
}
ws._reload_config( ws.$DebugMessages ); // if DebugMessages, will call _report_config
} catch( err ) {
log( ws.name, ws._reportError( err, startUp, ws.$SoundScan, 1 ) );
if( ws.$DebugMessages )
throw err;
}
}
this.startUpComplete = function startUpComplete() {
var ws = worldScripts.telescope;
var ps = player && player.ship;
var fps = worldScripts.telescope_fps_monitor;
if( fps ) {
var fm = ws.$fps_closure = fps._fps_monitor_closure; // not called as it self-initiates
// _init_fps_monitor( oxp_name, paused, no_fcb )
fm._init_fps_monitor( 'telescope', true );
// _setup_fps_report( minutes, shortterm, longterm, filelog, console, duration )
fm._setup_fps_report( 1, 2, 5, ws.$DebugMessages, false );
// _setup_fps_calc( cut_low, cut_high, harmonic, fps_only, median, mode, mean, high, low )
fm._setup_fps_calc( 2, 0, true, false, false, false, false, true, true );
}
ws._initOxpVars(); // moved from startUp as some oxp's will load after us
// extenderActive is used in station options' _condition statements
ws.$extenderActive = ps.equipmentStatus( 'EQ_TELESCOPEEXT' ) === 'EQUIPMENT_OK';
ws._startStationOptions();
if( ps.equipmentStatus( 'EQ_TELESCOPE' ) === 'EQUIPMENT_UNAVAILABLE' ) {
ws.$Telescope_not_in_use = true; // ship has no telescope, prevent world script handlers from running
} else {
ws.$Telescope_not_in_use = false;
}
}
// load & save events /////////////////////////////////////////////////////////////////////////////
this._load_missionVariables = function _load_missionVariables() {
var ws = worldScripts.telescope;
var item = null, bool = [false, true];
// remember menu values loaded so can switch 1.15 <-> 2.0 repeatedly
var menuLightballs = false, menuSniper = false, menuSteering = false,
menuTargets = false, menuVisual = false, menuVisualSize = false;
// TelescopeVisualTargetRing is unique to ver.2
var savedIsOrig = missionVariables.$TelescopeVisualTargetRing === null;
/// original telescope missionVariables
// inflight options
// - the original used these to store option values; here we store options on their own
// thus, we apply these first so inflight config is in sync
if( savedIsOrig ) {
item = missionVariables.$TelescopeMenuLightballs;
if( item !== null ) {
ws._oldSetLightballs( item );
menuLightballs = true; // preserve LightBalls, ShipLightBalls, MassLockRings, BrightMassLockRings & LargeLightBalls
}
} else {
item = missionVariables.$TelescopeMenuLightballs;
if( item !== null ) ws._SetLightballs( item );
item = missionVariables.$TelescopeMenuMasslockRings;
if( item !== null ) ws._SetMasslockRings( item );
}
item = missionVariables.$TelescopeMenuSniper;
if( item !== null ) {
ws._SetSniper( item );
menuSniper = savedIsOrig; // preserve SniperRange, SniperMinRange
}
item = missionVariables.$TelescopeMenuSteering;
if( item !== null ) {
ws._SetSteering( item );
menuSteering = savedIsOrig; // preserve Steering
}
item = missionVariables.$TelescopeMenuTargets;
if( item !== null ) {
ws._SetTargets( item );
menuTargets = savedIsOrig; // preserve MaxTargets
}
item = missionVariables.$TelescopeMenuVisual;
if( item !== null ) {
ws._SetVisual( item );
menuVisual = savedIsOrig; // preserve ShowVisualTarget, VisualTargetRing, TelescopeRing, ShowVisualStation, ShowVisualQuestionMark
}
item = missionVariables.$TelescopeMenuVisualSize;
if( item !== null ) {
ws._SetVisualSize( item );
menuVisualSize = savedIsOrig; // preserve VisualTargetCombatSize, TelescopeVSize, VisualTargetNormalSize, TelescopeVZoomSize
}
// state of equipment
// - these missionVariables are common to both the original and this version
item = missionVariables.$TelescopeFixedTel;
ws.$FixedTel = item !== null ? item : 0;
item = missionVariables.$TelescopeFixedGS;
ws.$FixedGS = item !== null ? item : 0;
item = missionVariables.$TelescopeFixedSD;
ws.$FixedSD = item !== null ? item : 0;
item = missionVariables.$TelescopeFixedLD;
ws.$FixedLD = item !== null ? item : 0;
// renamed from original $TelescopeGSC -> $TelescopeGravScanCount
item = savedIsOrig ? missionVariables.$TelescopeGSC : missionVariables.$TelescopeGravScanCount;
ws.$GravScanCount = item !== null ? item : 0;
/// new station options
// light balls
if( savedIsOrig ) {
if( !menuLightballs ) { // 1.15 defaults
ws.$LightBalls = true;
ws.$ShipLightBalls = true;
ws.$MassLockRings = this.$DEFAULT_ML_RINGS; // default green alert or weapons off-line
ws.$BrightMassLockRings = false;
ws.$LargeLightBalls = false;
} // else set via _SetLightballs above
} else {
let isBetaSaveGame = !missionVariables.hasOwnProperty( '$TelescopeMassLockRings' );
item = missionVariables.$TelescopeMassLockBorders;
if( item !== null ) { // renamed from beta
delete missionVariables.$TelescopeMassLockBorders;
if( isBetaSaveGame ) {
missionVariables.TelescopeMassLockRings = item;
}
}
item = missionVariables.$TelescopeBrightMassLockBorders;
if( item !== null ) { // renamed from beta
delete missionVariables.$TelescopeBrightMassLockBorders;
if( isBetaSaveGame ) {
missionVariables.TelescopeBrightMassLockRings = item;
}
}
item = missionVariables.$TelescopeLightBalls;
ws.$LightBalls = item !== null ? bool[ item ] : true; // default on
item = missionVariables.$TelescopeShipLightBalls;
ws.$ShipLightBalls = item !== null ? bool[ item ] : true;
item = missionVariables.$TelescopeLargeLightBalls;
ws.$LargeLightBalls = item !== null ? bool[ item ] : false; // default off
item = missionVariables.$TelescopeMassLockRings;
ws.$MassLockRings = item !== null ? item : this.$DEFAULT_ML_RINGS; // default green alert or weapons off-line
item = missionVariables.$TelescopeShowMassLock;
if( item !== null ) { // deprecated from beta (using MassLockRings flags only)
delete missionVariables.$TelescopeShowMassLock;
if( item === 0 && isBetaSaveGame ) {
ws.$MassLockRings = 0; // were turned off
}
}
item = missionVariables.$TelescopeBrightMassLockRings;
ws.$BrightMassLockRings = item !== null ? bool[ item ] : false; // default off
}
item = savedIsOrig ? null : missionVariables.$TelescopeLightBallMinDist;
ws.$LightBallMinDist = item !== null ? item : 1000;
item = savedIsOrig ? null : missionVariables.$TelescopeLightBallShipMinDist;
ws.$LightBallShipMinDist = item !== null ? item : 5000;
item = savedIsOrig ? null : missionVariables.$TelescopeMassLockViewDirn;
ws.$MassLockViewDirn = item !== null ? item : 1; // default forward view only
// sniper ring
item = savedIsOrig ? null : missionVariables.$TelescopeSniperRingSize;
ws.$SniperRingSize = item !== null ? item : 2;
item = savedIsOrig ? null : missionVariables.$TelescopeSniperRingActive;
ws.$SniperRingActive = item !== null ? item : 42;
item = savedIsOrig ? null : missionVariables.$TelescopeSniperRingColor;
ws.$SniperRingColor = item !== null ? JSON.parse( item ) : [0.3, 0.3, 0.3];
if( savedIsOrig ) {
if( !menuSniper ) { // 1.15 defaults
ws.$SniperRange = 25600;
ws.$SniperMinRange = 10000;
} // else set via _SetSniper above
} else {
item = missionVariables.$TelescopeSniperRange;
ws.$SniperRange = item !== null ? item : 25600;
item = missionVariables.$TelescopeSniperMinRange;
ws.$SniperMinRange = item !== null ? item : 10000;
}
// visual target
if( savedIsOrig ) {
if( !menuVisual ) { // 1.15 defaults
ws.$ShowVisualTarget = 0;
ws.$VisualTargetRing = true;
ws.$TelescopeRing = true; // maintain for oxps
ws.$ShowVisualStation = true;
ws.$ShowVisualQuestionMark = false;
} // else set via _SetVisual above
} else {
item = missionVariables.$TelescopeShowVisualTarget;
ws.$ShowVisualTarget = item !== null ? item : 0; // default is choice 'Off'
item = missionVariables.$TelescopeVisualTargetRing;
ws.$VisualTargetRing = item !== null ? bool[ item ] : true;
ws.$TelescopeRing = ws.$VisualTargetRing; // maintain for oxps
item = missionVariables.$TelescopeShowVisualStation;
ws.$ShowVisualStation = item !== null ? bool[ item ] : true;
item = missionVariables.$TelescopeShowVisualQuestionMark;
ws.$ShowVisualQuestionMark = item !== null ? bool[ item ] : false;
}
if( savedIsOrig ) {
if( !menuVisualSize ) { // 1.15 defaults
ws.$VisualTargetNormalSize = 6;
ws.$TelescopeVZoomSize = 6; // maintain for oxps
ws.$VisualTargetCombatSize = 4;
ws.$TelescopeVSize = 4; // maintain for oxps
} // else set via _SetVisualSize above
} else {
item = missionVariables.$TelescopeVisualTargetNormalSize;
ws.$VisualTargetNormalSize = item !== null ? item : 6;
ws.$TelescopeVZoomSize = ws.$VisualTargetNormalSize; // maintain for oxps
item = missionVariables.$TelescopeVisualTargetCombatSize;
ws.$VisualTargetCombatSize = item !== null ? item : 4;
ws.$TelescopeVSize = ws.$VisualTargetCombatSize; // maintain for oxps
}
item = savedIsOrig ? null : missionVariables.$TelescopeModelRingColor;
ws.$ModelRingColor = item !== null ? JSON.parse( item ) : [0.3, 0.3, 0.3];
item = savedIsOrig ? null : missionVariables.$TelescopeVTarget_HUD_shift;
ws.$VTarget_HUD_shift = item !== null ? JSON.parse( item ) : [0, 0, 0];
ws.$TelescopeVPosHUD = ws.$VTarget_HUD_shift; // maintain for oxps
// miscellaneous
if( savedIsOrig ) {
if( !menuSteering ) { // 1.15 defaults
ws.$Steering = 0;
} // else set via _SetSteering above
} else {
item = missionVariables.$TelescopeSteering;
ws.$Steering = item !== null ? item : 0; // default is choice 'Off'
}
if( savedIsOrig ) {
if( !menuTargets ) { // 1.15 defaults
ws.$MaxTargets = 200;
} // else set via _SetTargets above
} else {
item = missionVariables.$TelescopeMaxTargets;
ws.$MaxTargets = item !== null ? item : 200;
}
item = savedIsOrig ? null : missionVariables.$TelescopeRemoveInFlight;
ws.$RemoveInFlight = item !== null ? bool[ item ] : false;
item = savedIsOrig ? null : missionVariables.$TelescopeAutoScan;
ws.$AutoScan = item !== null ? bool[ item ] : true;
item = savedIsOrig ? null : missionVariables.$TelescopeAutoScanMaxRange;
ws.$AutoScanMaxRange = item !== null ? item : 1e6;
item = savedIsOrig ? null : missionVariables.$TelescopeFarStatus;
ws.$FarStatus = item !== null ? bool[ item ] : false;
item = savedIsOrig ? null : missionVariables.$TelescopeAutoLock;
ws.$AutoLock = item !== null ? item : 1; // default cone of radius 1 degree
item = savedIsOrig ? null : missionVariables.$TelescopeGravLock;
ws.$GravLock = item !== null ? item : 20; // default cone of radius 20 degrees
item = savedIsOrig ? null : missionVariables.$TelescopeIdentLock;
ws.$IdentLock = item !== null ? item : 180; // default cone of radius 180 degrees, ie. whole sky
item = savedIsOrig ? null : missionVariables.$TelescopeIdentDelay;
ws.$IdentDelay = item !== null ? item : 4; // time in 0.25 seconds, ie. 1 second
item = savedIsOrig ? null : missionVariables.$TelescopeRedAlertDist;
ws.$RedAlertDist = item !== null ? item : 30000; // default to max range of military laser
// - not a cheat, really, as just showing lollipop where shot came from, cannot target w/o extender
// UI & docn
item = savedIsOrig ? null : missionVariables.$TelescopeConsoleMsgDurn;
ws.$ConsoleMsgDurn = item !== null ? item : 5; // time in seconds
item = savedIsOrig ? null : missionVariables.$TelescopeGravScanMsgFreq;
ws.$GravScanMsgFreq = item !== null ? item : 3; // default is choices 'progress endpoints' & 'progress quarterly update'
item = savedIsOrig ? null : missionVariables.$TelescopeIdentMessages;
ws.$IdentMessages = item !== null ? bool[ item ] : true;
item = savedIsOrig ? null : missionVariables.$TelescopeShowSummary;
ws.$ShowSummary = item !== null ? bool[ item ] : true;
item = savedIsOrig ? null : missionVariables.$TelescopeDebugMessages;
ws.$DebugMessages = item !== null ? bool[ item ] : false;
// MFD(s) & filtering
item = savedIsOrig ? null : missionVariables.$TelescopeMFDFiltering;
ws.$MFDFiltering = item !== null ? bool[ item ] : false;
item = savedIsOrig ? null : missionVariables.$TelescopeMFDfilterStatic;
ws.$MFDPrimaryStatic = item !== null ? item : ws.$MFD_STATIC_ALLSET;
item = savedIsOrig ? null : missionVariables.$TelescopeMFDfilterDynamic;
ws.$MFDPrimaryDynamic = item !== null ? item : ws.$MFD_DYNAMIC_ALLSET;
item = savedIsOrig ? null : missionVariables.$TelescopeSeparateMFDs;
ws.$SeparateMFDs = item !== null ? bool[ item ] : false;
item = savedIsOrig ? null : missionVariables.$TelescopeMFDAuxStatic;
ws.$MFDAuxStatic = item !== null ? item : ws.$MFD_STATIC_ALLSET;
item = savedIsOrig ? null : missionVariables.$TelescopeMFDAuxDynamic;
ws.$MFDAuxDynamic = item !== null ? item : ws.$MFD_DYNAMIC_ALLSET;
// $Thargoids was never saved in original so it remains a one-off switch from a station dock
if( missionVariables.$TelescopeOptionsSaveGameReminder ) // never implemented
delete missionVariables.$TelescopeOptionsSaveGameReminder;
item = savedIsOrig ? null : missionVariables.$TelescopeBetaLicence;
if( item !== null ) {
ws.$BetaLicence = item;
ws.$BetaLicenceTimestamp = missionVariables.$TelescopeBetaLicenceTimestamp;
ws.$BetaLicenceSystem = missionVariables.$TelescopeBetaLicenceSystem;
}
ws.$UserChangedSettings = 0; // clear flags as _initOxpVars will updateMenuVars
}
this.playerWillSaveGame = function playerWillSaveGame( /*message*/ ) {
var that = playerWillSaveGame;
var ws = (that.ws = that.ws || worldScripts.telescope);
if( missionVariables.hasOwnProperty( '$TelescopeRedAlertLimiter' ) )// removed from beta
delete missionVariables.$TelescopeRedAlertLimiter;
// inflight options
missionVariables.$TelescopeMenuLightballs = ws._getOldLightballs(); // set to conform with 1.15
missionVariables.$TelescopeMenuMasslockRings = ws.$TelescopeMenuMasslockRings; // 1.15 will ignore
missionVariables.$TelescopeMenuSniper = ws.$TelescopeMenuSniper;
missionVariables.$TelescopeMenuSteering = ws.$TelescopeMenuSteering;
missionVariables.$TelescopeMenuTargets = ws.$TelescopeMenuTargets;
missionVariables.$TelescopeMenuVisual = ws.$TelescopeMenuVisual;
missionVariables.$TelescopeMenuVisualSize = ws.$TelescopeMenuVisualSize;
// state of equipment
missionVariables.$TelescopeGravScanCount = ws.$GravScanCount;
missionVariables.$TelescopeGSC = ws.$GravScanCount;
// - added to ensure reversion to original is complete
missionVariables.$TelescopeFixedTel = ws.$FixedTel;
missionVariables.$TelescopeFixedGS = ws.$FixedGS;
missionVariables.$TelescopeFixedSD = ws.$FixedSD
missionVariables.$TelescopeFixedLD = ws.$FixedLD;
// NB: boolean values must be stored as 0 or 1, else loads false, true as strings(!) which are always true
// light balls
missionVariables.$TelescopeLightBalls = ws.$LightBalls ? 1 : 0;
missionVariables.$TelescopeShipLightBalls = ws.$ShipLightBalls ? 1 : 0;
missionVariables.$TelescopeLargeLightBalls = ws.$LargeLightBalls ? 1 : 0;
missionVariables.$TelescopeLightBallMinDist = ws.$LightBallMinDist;
missionVariables.$TelescopeLightBallShipMinDist = ws.$LightBallShipMinDist;
// masslock rings
missionVariables.$TelescopeMassLockRings = ws.$MassLockRings;
missionVariables.$TelescopeBrightMassLockRings = ws.$BrightMassLockRings ? 1 : 0;
missionVariables.$TelescopeMassLockViewDirn = ws.$MassLockViewDirn ? ws.$MassLockViewDirn : 1;
// sniper ring
missionVariables.$TelescopeSniperRingSize = ws.$SniperRingSize;
missionVariables.$TelescopeSniperRingActive = ws.$SniperRingActive;
missionVariables.$TelescopeSniperRange = ws.$SniperRange;
missionVariables.$TelescopeSniperMinRange = ws.$SniperMinRange;
missionVariables.$TelescopeSniperRingColor = JSON.stringify( ws.$SniperRingColor );
// visual target
missionVariables.$TelescopeShowVisualTarget = ws.$ShowVisualTarget;
missionVariables.$TelescopeVisualTargetNormalSize = ws.$VisualTargetNormalSize;
missionVariables.$TelescopeVisualTargetCombatSize = ws.$VisualTargetCombatSize;
missionVariables.$TelescopeShowVisualStation = ws.$ShowVisualStation ? 1 : 0;
missionVariables.$TelescopeShowVisualQuestionMark = ws.$ShowVisualQuestionMark ? 1 : 0;
missionVariables.$TelescopeVisualTargetRing = ws.$VisualTargetRing ? 1 : 0;
missionVariables.$TelescopeModelRingColor = JSON.stringify( ws.$ModelRingColor );
missionVariables.$TelescopeVTarget_HUD_shift = JSON.stringify( ws.$VTarget_HUD_shift );
// option available on station
missionVariables.$TelescopeRemoveInFlight = ws.$RemoveInFlight ? 1 : 0; // new
missionVariables.$TelescopeSteering = ws.$Steering;
missionVariables.$TelescopeMaxTargets = ws.$MaxTargets;
missionVariables.$TelescopeAutoScan = ws.$AutoScan ? 1 : 0;
missionVariables.$TelescopeAutoScanMaxRange = ws.$AutoScanMaxRange;
missionVariables.$TelescopeFarStatus = ws.$FarStatus ? 1 : 0;
missionVariables.$TelescopeAutoLock = ws.$AutoLock;
missionVariables.$TelescopeGravLock = ws.$GravLock;
missionVariables.$TelescopeIdentLock = ws.$IdentLock;
missionVariables.$TelescopeIdentDelay = ws.$IdentDelay;
missionVariables.$TelescopeRedAlertDist = ws.$RedAlertDist;
// new options - UI_and_docs
missionVariables.$TelescopeConsoleMsgDurn = ws.$ConsoleMsgDurn;
missionVariables.$TelescopeGravScanMsgFreq = ws.$GravScanMsgFreq;
missionVariables.$TelescopeIdentMessages = ws.$IdentMessages ? 1 : 0;
missionVariables.$TelescopeShowSummary = ws.$ShowSummary ? 1 : 0;
missionVariables.$TelescopeDebugMessages = ws.$DebugMessages ? 1 : 0;
// new options - experimental
missionVariables.$TelescopeMFDFiltering = ws.$MFDFiltering ? 1 : 0;
missionVariables.$TelescopeMFDfilterStatic = ws.$MFDPrimaryStatic;
missionVariables.$TelescopeMFDfilterDynamic = ws.$MFDPrimaryDynamic;
missionVariables.$TelescopeSeparateMFDs = ws.$SeparateMFDs ? 1 : 0;
missionVariables.$TelescopeMFDAuxStatic = ws.$MFDAuxStatic;
missionVariables.$TelescopeMFDAuxDynamic = ws.$MFDAuxDynamic;
// Thargoids was never saved in original so it remains a one-off switch from a station dock
// $TelescopeBetaLicenceTimestamp & $TelescopeBetaLicenceSystem are saved in _reloadFromStn(), maybe
missionVariables.$TelescopeBetaLicence = ws.$BetaLicence;
}
// station & witchspace events ////////////////////////////////////////////////////////////////////
this.shipWillLaunchFromStation = function shipWillLaunchFromStation( /*station*/ ) {
var that = shipWillLaunchFromStation;
var ws = (that.ws = that.ws || worldScripts.telescope);
var ps = player && player.ship;
if( ws.$Telescope_not_in_use ) return; // no telescope, nothing to do
ws._set_vShip_posn( ps.viewPositionForward, ws.$VTarget_HUD_shift );
ws._AddShips();
}
this.shipExitedWitchspace =
this.shipLaunchedFromStation = function shipLaunchedFromStation( /*station*/ ) {
var that = shipLaunchedFromStation;
var ws = (that.ws = that.ws || worldScripts.telescope);
if( ws.$DebugMessages && global.console && console.writeJSMemoryStats )
console.writeJSMemoryStats();
if( !ws.$GravScanCount ) ws.$GravScanCount = 0; //start to count gravity scans (a missionVariables)
if( !ws._init_player_vars() ) { // equipment damaged, nothing to do
ws._shutdown_Sightings();
if( ws.$DebugMessages && player.ship.equipmentStatus( 'EQ_TELESCOPE' ) !== 'EQUIPMENT_UNAVAILABLE' )
log(ws.name, 'shipLaunchedFromStation, _init_player_vars failed, quitting before making mapping or starting Timer!' );
return;
}
ws._restart_after_shutdown();
ws._create_Sightings();
ws._StartTimer( 1 ); // delay to allow _create_Sightings to finish -curr'ly takes ~20 frames
}
this.shipWillDockWithStation = function shipWillDockWithStation( /*station*/ ) { // called by shipWillEnterWitchspace
var that = shipWillDockWithStation;
var ws = (that.ws = that.ws || worldScripts.telescope);
ws._set_curr_Sighting( null, 'shipWillDockWithStation' ); // no parms clears it
ws._StopTimer();
ws._shutdown_Sightings();
if( ws.$DebugMessages && global.console && console.writeJSMemoryStats )
console.writeJSMemoryStats();
}
this.shipWillEnterWitchspace = function shipWillEnterWitchspace( /*cause, destination*/ ) {
var that = shipWillEnterWitchspace;
var ws = (that.ws = that.ws || worldScripts.telescope);
var random = (that.random = that.random || Math.random);
// log(ws.name, 'in shipWillEnterWitchspace, arguments:'+arguments );
var ps = player && player.ship;
if( ws.$FixedGS === 1 && random() > 0.5 ) {
ps.scriptedMisjump = true; //meet Thargoids due to the cheap Grav.Sc. repair
player.consoleMessage("Gravity Scanner caused misjump!");
}
if( ws.$FixedSD === 1 && random() > 0.2 ) {
ps.setEquipmentStatus("EQ_SMALLDISH", "EQUIPMENT_DAMAGED");
player.consoleMessage("Small Dish damaged during hyperjump!", 10);
}
if( ws.$FixedLD === 1 && random() > 0.2 ) {
ps.setEquipmentStatus("EQ_LARGEDISH", "EQUIPMENT_DAMAGED");
player.consoleMessage("Large Dish damaged during hyperjump!", 10);
}
ws.shipWillDockWithStation();
}
this.shipWillExitWitchspace = function shipWillExitWitchspace() { //use this event due to shipExitedWitchspace is not working in v1.77
var that = shipWillExitWitchspace;
var ws = (that.ws = that.ws || worldScripts.telescope);
if( ws.$Telescope_not_in_use ) return; // no telescope, nothing to do
ws._AddShips(); //do not call shipLaunchedFromStation() to avoid a bug
}
// ship events ////////////////////////////////////////////////////////////////////////////////////
this.shipBeingAttackedUnsuccessfully =
this.shipBeingAttacked = function shipBeingAttacked( whom ) {
var that = shipBeingAttacked;
var ws = (that.ws = that.ws || worldScripts.telescope);
var mapping = (that.mapping = that.mapping || ws.$SightingsMap);
if( ws.$Telescope_not_in_use ) return; // no telescope, nothing to do
if( !whom || !whom.isValid ) return;
if( player.ship.equipmentStatus( 'EQ_TELESCOPE' ) !== 'EQUIPMENT_OK' ) return;
var found = ws._Sighting_index( whom, 'shipBeingAttacked' );
if( found < 0 ) { // not registered
found = ws._add_Sighting( whom, false, true, 'shipBeingAttacked' );
if( found < 0 ) {
log( ws.name, 'shipBeingAttacked, Yikes! _add_Sighting returned "'
+ ws.$add_Sighting_errors[ found ] + '" trying to add ' + whom );
}
}
if( found < 0 ) return; // failed to add!
var map = mapping[ found ];
map.rank = 'bad';
}
this.shipDied = // used instead of shipKilledOther, as catches more cases
this.shipScoopedOther = function shipScoopedOther( whom ) { // NB: scooped objects become hostile, ie. they target ps (even splinters!)
var that = shipScoopedOther;
var ws = (that.ws = that.ws || worldScripts.telescope);
if( ws.$Telescope_not_in_use ) return; // no telescope, nothing to do
ws._delete_Sighting( whom, 'shipScoopedOther' ); // will reset target lock, clear HUD, if it's player's target
}
this.shipSpawned = function shipSpawned( ship ) { //detect missile launch immediately
var that = shipSpawned;
var ws = (that.ws = that.ws || worldScripts.telescope);
if( ws.$Telescope_not_in_use ) return; // no telescope, nothing to do
// - not testing scanClass, as conflicts w/ some oxp's
if( ship.isVisualEffect ) return;
if( ship.dataKey == 'telescopemarker' ) return; // dataKey == 'telescope-shadow' caught by isVisualEffect
var ps = player && player.ship;
if( !ps || !ps.isValid || ps.alertCondition === 0 ) //player died or docked (alertCondition === 0)
return;
if( !ws.$Timer_auto_updates ) return; //no timer means in witchspace
if( ws._Sighting_index( ship ) >= 0 ) return; // already in mapping! sometimes, check_if_new_targets can get there 1st
let index = ws._add_Sighting( ship, false, false, 'shipSpawned' );
if( index < -1 && index > -6 && ws.$DebugMessages ) {
let reason = ws.$add_Sighting_errors[ index ];
log(ws.name, 'shipSpawned, isVisible = ' + ship.isVisible
+ ', distance = ' + ship.position.distanceTo( ps ).toFixed() + ', mass = ' + ship.mass
+ ', w/ ship = ' + ship + '\n\t Yikes! _add_Sighting returned: ' + reason );
}
/*
///testing if isVisible bug still present; 1.92 (Jan/22) set record: wreckage @ 31,733,066 m!
/// - problem is that .isVisible is always true when an ent is spawned and it may not be
/// properly set before we try to add it to the list of sightings
/// - new solution is to ignore anything spawned w/i last ??? second; here we try to get a feel
/// for what a good interval should be for use in grow_new_list, _add_Sighting: SPAWN_DELAY
if( ws.$DebugMessages ) {
let dist = ps.position.distanceTo( ship ), spawn = ship.spawnTime;
let now = clock.absoluteSeconds;
if( dist > 5e6 && ship.isVisible && ship.scanClass !== 'CLASS_NO_DRAW'
&& ship.status !== 'STATUS_LAUNCHING' ) {
log(ws.name, 'shipSpawned, ship "' + ship.entityPersonality + '" at ' + dist.toFixed() + ' has isVisible true! ####, spawnTime: '
+ spawn.toFixed(4) + ': , diff from now: ' + (spawn > 0 ? (now - spawn).toFixed(4) : 'n/a') + ', ship:' + ship );
let timr = new Timer( ws, ws._isVisMonitor, 0.05, 0.25 );
timr.$spawnedShip = ship;
ws.$isVisTimers.push( timr );
}
}
*/
}
///
this.$isVisTimers = [];
this._isVisMonitor = function _isVisMonitor() {
function suicide() {
if( timeRef.isRunning ) {
timeRef.stop();
}
let idx = ws.$isVisTimers.indexOf( timeRef );
if( idx < 0 ) {
log('_isVisMonitor, timeRef ('+timeRef+') not found in $isVisTimers: ' + ws.$isVisTimers);
} else {
ws.$isVisTimers.splice( idx, 1 ); // modify array be removing 1 item at idx
}
timeRef = that.timeRef = null;
}
var that = _isVisMonitor;
var ws = (that.ws = that.ws || worldScripts.telescope);
var timeRef = (that.timeRef = that.timeRef || ws.$isVisTimers[ws.$isVisTimers.length - 1]);
var ship = timeRef.$spawnedShip;
if( !ship || ship.inValid ) {
log('_isVisMonitor, failed to capture ship: ' + ship );
suicide();
return;
}
let now = clock.absoluteSeconds;
if( ship.isVisible ) {
log('_isVisMonitor, timeRef ('+timeRef+'), ship "' + ship.entityPersonality + '" still isVisible after ' + (now - ship.spawnTime).toFixed(4) + ': ' + ship );
} else {
log('_isVisMonitor, timeRef ('+timeRef+'), ship "' + ship.entityPersonality + '" NO LONGER isVisible after ' + (now - ship.spawnTime).toFixed(4) + ': ' + ship );
suicide();
}
}
///
this.$add_Sighting_errors = { '-1': '!mappingReady', '-2': 'maplen >= MaxTargets', '-3': '!ent.isValid',
'-4': 'player died or docked', '-5': 'already in mapping', '-6': '!_has_good_status',
'-7': '!notable_ent', '-8': 'rank === ukn', '-9': 'wreckage', '-10': 'younger than SPAWN_DELAY' };
this.shipTargetAcquired = function shipTargetAcquired( target ) { //if locked target by hand then set as the actual item in the list
var that = shipTargetAcquired;
var ws = (that.ws = that.ws || worldScripts.telescope);
var mapping = (that.mapping = that.mapping || ws.$SightingsMap);
var curr_S = (that.curr_S = that.curr_S || ws.$curr_Sighting);
var IDENT_READY = (that.IDENT_READY = that.IDENT_READY || ws.$IDENT_READY);
var IDENT_STEER_DELAY = (that.IDENT_STEER_DELAY = that.IDENT_STEER_DELAY || ws.$IDENT_STEER_DELAY);
if( ws.$Telescope_not_in_use ) return; // no telescope, nothing to do
if( !target || !target.isValid || ws.$TelescopeTargetSet ) //no target or we have just set ps.target
return;
if( ws.$DebugMessages && target === curr_S.marker )
log(ws.name, 'shipTargetAcquired, re-acquired same target, should we bail out?');
if( target === curr_S.marker ) { // marker for target outside scannerRange
target = curr_S.ent || null;
}
var isNewTarget = target !== curr_S.ent;
var index = ws._Sighting_index( target, 'shipTargetAcquired' ); // already scanned?
if( index < 0 ) { // try adding it (should only fail if $MaxTargets reached)
index = ws._add_Sighting( target, false, false, 'shipTargetAcquired' );
if( index === -2 ) {
player.consoleMessage( (mapping.length >= ws.$MaxTargets
? 'Telescope memory is full.' : 'Telescope unable to lock target.'), ws.$ConsoleMsgDurn );
ws._set_curr_Sighting( null, 'shipTargetAcquired' ); // no parms resets
if( ws.$DebugMessages )
log(ws.name, 'shipTargetAcquired, maplen (' + mapping.length + ') >= MaxTargets (' + ws.$MaxTargets
+ '), curr_Sighting being reset! ' + target );
return;
}
}
if( ws.$DebugMessages ) log(ws.name, 'shipTargetAcquired, new target (' + target.entityPersonality + '), index: ' + index
+ ', IdentKeyPress: ' + ws.$IdentKeyPress + ': ' + target );
if( isNewTarget ) {
let identKeyPress = ws.$IdentKeyPress;
if( identKeyPress > IDENT_READY && identKeyPress < IDENT_STEER_DELAY ) {// it was 'locked', not in delay
if( ws.$IdentMessages )
player.consoleMessage( 'Telescope lock released', ws.$ConsoleMsgDurn );
ws.$IdentKeyPress = IDENT_READY;
// if( ws.$DebugMessages ) log('shipTargetAcquired, identKeyPress = IDENT_READY');
}
}
ws._manage_marker( mapping[ index ], false, 'shipTargetAcquired' );
}
this.shipTargetCloaked = function shipTargetCloaked() {
var that = shipTargetCloaked;
var ws = (that.ws = that.ws || worldScripts.telescope);
var curr_S = (that.curr_S = that.curr_S || ws.$curr_Sighting);
var IDENT_READY = (that.IDENT_READY = that.IDENT_READY || ws.$IDENT_READY);
if( ws.$Telescope_not_in_use ) return; // no telescope, nothing to do
var target = curr_S.ent || null;
if( target && target.isCloaked ) {
ws._delete_Sighting( target, 'shipTargetCloaked' );
if( ws.$IdentKeyPress > IDENT_READY ) { // it was 'locked'
if( ws.$IdentMessages )
player.consoleMessage( 'Telescope lock released', ws.$ConsoleMsgDurn );
ws.$IdentKeyPress = IDENT_READY;
// if( ws.$DebugMessages ) log('shipTargetCloaked, identKeyPress = IDENT_READY');
}
}
}
this.shipTargetLost = function shipTargetLost( target ) { // used to re-purpose ident key fn
var that = shipTargetLost;
var ws = (that.ws = that.ws || worldScripts.telescope);
var curr_S = (that.curr_S = that.curr_S || ws.$curr_Sighting);
var _has_bad_status = (that._has_bad_status = that._has_bad_status || ws._has_bad_status);
if( ws.$Telescope_not_in_use ) { // no telescope, nothing to do
return;
}
var ps = player && player.ship;
if( !ps || !ps.isValid || ps.alertCondition === 0 ) { //player died or docked
return;
}
if( ws.$TelescopeTargetSet || ws.$IdentLock === 0 ) { //set by script OR disabled by user
return;
}
// if( ws.$DebugMessages ) log('shipTargetLost, ws.$IdentKeyPress: ' + ws.$IdentKeyPress );
if( target === curr_S.marker ) { // telescopemarker was last target
target = curr_S.ent;
} else if( !target || target !== curr_S.ent ) {
target = curr_S.ent || null;
}
var target_dead = !target //target destroyed, jumped, docked else lost by ident key press
|| _has_bad_status( target ) // _has_bad_status now checks .isValid, isWormhole
|| target.energy <= 0; // !isValid no longer enough, as not always set before this event
/*
if( ws.$DebugMessages ) log(ws.name, 'shipTargetLost, ship target was '
+ (target === curr_S.marker ? ' (' + curr_S.marker_type + ') ':'')
+ (target ? ' @' + Math.floor(target.position.distanceTo(ps)) + ', ' + target : 'null' )
+ '\n\t marker was ' + (curr_S.marker ? '@' + Math.floor(curr_S.marker.position.distanceTo(ps))
+ (curr_S.marker_type === 'marker' ? ', a marker' : ', a shadow') : 'empty' )
+'\n\t ent was '+ (target ? '@' + Math.floor(target.position.distanceTo(ps)) + ', ' + target : 'null')
+ '\n\t IdentKeyPress = ' + ws.$IdentKeyPress
+ ', target.isValid = ' + (target ? target.isValid : '<target is null>')
+ ', target_dead = ' + target_dead + ', ps.target = ' + ps.target
+ (curr_S.lightball ? '\nlightball (' + curr_S.map.ve_colour + ') @'
+ Math.floor(curr_S.lightball.position.distanceTo(ps)) : '')
);
// if( ws.$DebugMessages && worldScripts.telescope_debug ) worldScripts.telescope_debug._curr_S_report();
*/
if( ws.$IdentKeyPress < ws.$IDENT_STEER_DELAY ) // IdentDelay timer not running
// when target_dead is true, _mostCentered won't allow an 'ident' lock
ws._mostCentered( "ident", !target_dead ); //lock-steer?-unlock target
}
this.weaponsSystemsToggled = function weaponsSystemsToggled( /* state */ ) {
var that = weaponsSystemsToggled;
var ws = (that.ws = that.ws || worldScripts.telescope);
if( player && player.ship ) { // reset state (esp. for Navigation mode)
ws.$IdentKeyPress = ws.$IDENT_READY;
// if( ws.$DebugMessages ) log('weaponsSystemsToggled, identKeyPress = IDENT_READY');
}
}
// equipment events ///////////////////////////////////////////////////////////////////////////////
this.$telescopeEquipment = [
'EQ_TELESCOPE', 'EQ_TELESCOPEEXT',
'EQ_GRAVSCANNER', 'EQ_GRAVSCANNER2',
'EQ_SMALLDISH', 'EQ_LARGEDISH' ];
this.equipmentDestroyed =
this.equipmentDamaged = function equipmentDamaged( equipment ) {
var that = equipmentDamaged;
var ws = (that.ws = that.ws || worldScripts.telescope);
var telEq = (that.telEq = that.telEq || ws.$telescopeEquipment);
if( telEq.indexOf( equipment ) === -1 ) {
return;
}
if( equipment === 'EQ_TELESCOPE' ) {
ws._StopTimer();
ws._shutdown_Sightings();
return;
}
if( equipment === 'EQ_TELESCOPEEXT' ) {
ws.$extenderActive = false; // only used in station options
}
ws._init_player_vars(); // update status of equipment vars
ws._create_Sightings(); //remove lost targets, autoscan will scan again
}
this.equipmentRepaired = function equipmentRepaired( equipment ) {
var that = equipmentRepaired;
var ws = (that.ws = that.ws || worldScripts.telescope);
var telEq = (that.telEq = that.telEq || ws.$telescopeEquipment);
if( telEq.indexOf( equipment ) === -1 ) { // not a relevant repair - thanks Milo
return;
}
var ps = player && player.ship;
if( equipment === 'EQ_TELESCOPE' ) {
ws.$FixedTel = 0;
if( ws._init_player_vars() ) {
ws._restart_after_shutdown();
if( ps && ps.isInSpace ) { // emulate launch if fixed in space
ws._StartTimer( 1 ); // delay to allow _create_Sightings to finish -curr'ly takes 20+ frames
}
} else {
ws._StopTimer();
ws._shutdown_Sightings();
return;
}
} else if( equipment === 'EQ_TELESCOPEEXT' ) {
ws.$extenderActive = true;
} else if( equipment === 'EQ_GRAVSCANNER' ) {
ws.$FixedGS = 0;
} else if( equipment === 'EQ_GRAVSCANNER2' ) {
ws.$FixedGS = 0;
} else if( equipment === 'EQ_SMALLDISH' ) {
ws.$FixedSD = 0;
} else if( equipment === 'EQ_LARGEDISH' ) {
ws.$FixedLD = 0;
}
ws._init_player_vars(); // update status of equipment vars
ws._create_Sightings(); //remove lost targets, autoscan will scan again
}
this.playerBoughtEquipment = function playerBoughtEquipment( equipment ) {
var that = playerBoughtEquipment;
var ws = (that.ws = that.ws || worldScripts.telescope);
var telEq = (that.telEq = that.telEq || ws.$telescopeEquipment);
var random = (that.random = that.random || Math.random);
var round = (that.round = that.round || Math.round);
var floor = (that.floor = that.floor || Math.floor);
var restock = (that.restock = that.restock || {}); // dictionary of stations' restocking fee
var ps = player && player.ship;
var actualEq = equipment,
endsWith = '',
parsed = equipment.split( '_' );
if( parsed.length === 3 ) {
actualEq = parsed[ 0 ] + '_' + parsed[ 1 ];
endsWith = parsed[ 2 ];
}
if( telEq.indexOf( actualEq ) === -1 ) {
return;
}
if( endsWith === '' ) { // bought actual equipment
if( equipment === 'EQ_TELESCOPE' ) {
ws.$FixedTel = 0;
ws.$Telescope_not_in_use = false;
ws._registerHUDSelector();
ps.setMultiFunctionText( ws.$PrimaryMFD_name, '' ); // make core aware now for other oxp's that play with MFDs
ps.setMultiFunctionText( ws.$AuxilaryMFD_name, '' );
} else if( equipment === 'EQ_TELESCOPEEXT' ) {
ws.$extenderActive = true;
} else if( equipment === 'EQ_GRAVSCANNER'
|| equipment === 'EQ_GRAVSCANNER2' ) {
ws.$FixedGS = 0;
} else if( equipment === 'EQ_SMALLDISH' ) {
ws.$FixedSD = 0;
} else if( equipment === 'EQ_LARGEDISH' ) {
ws.$FixedLD = 0;
}
return;
}
if( endsWith === 'REFUND' ) { // sold actual equipment
ps.removeEquipment( equipment ); //remove the 'bought' refund eq
if( ps.equipmentStatus( actualEq ) === 'EQUIPMENT_OK' ) {
if( actualEq === 'EQ_TELESCOPEEXT' ) {
ws.$extenderActive = false;
}
ps.removeEquipment( actualEq ); // the refund voucher
clock.addSeconds( ( actualEq[ 3 ] === 'G' ? 1800 : 4500 ) );// dish work takes longer
let infoForKey = EquipmentInfo.infoForKey( actualEq );
let rate, refund = infoForKey.price / 10; // .plist price is in tenths of credits
let station = player.dockedStation;
if( restock.hasOwnProperty( station ) ) // fee cached to be consistent
rate = restock[ station ];
else // random fee 1-5%
rate = that.restock[ station ] = floor( (random() * (0.051 - 0.01) + 0.01) * 100 );
// rate = that.restock[ station ] = random() < 0.5 ? 0.05 : 0.1;
let fee = round( refund * rate );
refund -= fee;
player.credits += refund;
player.consoleMessage( 'Refunded ' + refund + ' credits (less '
+ (rate * 100) + '% commission) for '
+ infoForKey.name, ws.$ConsoleMsgDurn * 2 );
}
} else if( endsWith === 'REPAIR' || endsWith === 'FULLREPAIR') {
// remainder deals with repairs (this function is called after equipmentRepaired)
ps.setEquipmentStatus( actualEq, 'EQUIPMENT_OK' );
ps.removeEquipment( equipment ); // the repair voucher
clock.addSeconds( ( actualEq[ 3 ] === 'G' ? 3600 : 9000 ) );// dish work takes longer
if( actualEq === 'EQ_TELESCOPE' ) {
ws.$FixedTel = endsWith === 'REPAIR' ? 1 : 0; //with drawback (lightballs only)
} else if( equipment === 'EQ_TELESCOPEEXT' ) {
ws.$extenderActive = true; // has no cheap repair option
} else if( actualEq === 'EQ_GRAVSCANNER'
|| actualEq === 'EQ_GRAVSCANNER2' ) {
ws.$FixedGS = endsWith === 'REPAIR' ? 1 : 0; //with drawback (misjump)
} else if( actualEq === 'EQ_SMALLDISH' ) {
ws.$FixedSD = endsWith === 'REPAIR' ? 1 : 0; //with drawback (break during jump)
} else if( actualEq === 'EQ_LARGEDISH' ) {
ws.$FixedLD = endsWith === 'REPAIR' ? 1 : 0; //with drawback (break during jump)
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// station options ////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
this._startStationOptions = function _startStationOptions() {
var ws = worldScripts.telescope;
var so = worldScripts.station_options;
try {
if( so ) { // pass callback functions and initialize station options
let missionKeys;
if( ws.$BetaLicence === 1 ) { // 'experimental' page is active, set keys
missionKeys = {
'telescope_BetaLicenceTimestamp': ws.$BetaLicenceTimestamp,
'telescope_BetaLicenceSystem': ws.$BetaLicenceSystem,
};
} else { // replace 'experimental' page with licence agreement
missionKeys = {
'telescope_optionPages': "[telescope_optionPages_licence]",
'telescope_optionTabStops': "[telescope_optionTabStops_licence]",
'telescope_licence_summary': '[telescope_licence_undeclared]',
'telescope_licence_short': '[telescope_licence_asking]',
// set temporal strings to present tense in case player accepts
'telescope_licenceAcceptance': expandDescription( '[telescope_experimental_accepts]' ),
'telescope_licenceRegistered': expandDescription( '[telescope_experimental_registers]' ),
// - not sure why but these won't expand otherwise (but telescope_licence_summary & telescope_licence_short do work???)
// 'telescope_licenceAcceptance': '[telescope_experimental_accepts]',
// 'telescope_licenceRegistered': '[telescope_experimental_registers]',
};
}
// _initStationOptions( hostOxp, keyPrefix, optionsAllowedCallback, callPWSG, notifyCallback, suppressSummary, missionKeys )
let okay = so.$O_initStationOptions( ws, 'telescope_', ws._stnOptionsAllowed, true, ws._reloadFromStn, false, missionKeys );
if( !okay )
return;
if( so.$O_getReminder4Oxp ) { // absent from version 1.0
let rmdr = so.$O_getReminder4Oxp( 'telescope_' );
if( rmdr ) {
ws.$ShowSummary = rmdr.reportSummary;
} else {
log( ws.name, '_startStationOptions, station_options _getReminder4Oxp returned: "' + rmdr + '"' );
}
}
} else {
log( ws.name, '_startStationOptions, station_options oxp is missing!' );
}
} catch( err ) {
log( ws.name, ws._reportError( err, _startStationOptions ) );
if( ws.$DebugMessages )
throw err;
}
}
this._BetaLicenceAnswered = function _BetaLicenceAnswered( response ) { // _execute fn for station_options
var ws = worldScripts.telescope;
var so = worldScripts.station_options;
// once licence is accepted, expiramental page is added. The licence page remains
// available until next station or next ship/equipment change or the player tries
// to alter the acceptance. These all result in a call to _setLicenceMissionVar()
// which removes the licence page.
var missionKeys;
if( response === 1 ) {
// preserve licence agreement info; .length > 0 && !missionVariables => first time
// - all subsequent values will be skipped, preserving the original
// NB: here we're ASSUMING timestamp & system are set as a pair, ie. at the same time
if( ws.$BetaLicenceTimestamp.length > 0 && !missionVariables.$TelescopeBetaLicenceTimestamp ) {
missionVariables.$TelescopeBetaLicenceTimestamp = ws.$BetaLicenceTimestamp;
missionVariables.$TelescopeBetaLicenceSystem = ws.$BetaLicenceSystem;
// insert experimental page, update text
missionKeys = {
'telescope_BetaLicenceTimestamp': ws.$BetaLicenceTimestamp,
'telescope_BetaLicenceSystem': ws.$BetaLicenceSystem,
'telescope_optionPages': "[telescope_optionPages_licence_accepted]",
'telescope_optionTabStops': "[telescope_optionTabStops_licence_accepted]",
'telescope_licence_summary': '[telescope_licence_accept]',
'telescope_licence_short': '[telescope_licence_answered]',
};
} else if( ws.$BetaLicenceTimestamp.length > 0 ) { // set a 2nd time??? reset missionKeys
// player tries to undo licence acceptance, which we do not allow
ws._setLicenceMissionVar()
return;
}
} else {
// rejection is only allowed when !$BetaLicence; once accepted, it cannot be changed
// - see telescope_BetaLicence_assign in missiontext.plist
missionKeys = {
'telescope_licence_summary': "[telescope_licence_reject]",
};
}
so.$O_updateMissionKeys( 'telescope_', missionKeys );
}
this._stnOptionsAllowed = function _stnOptionsAllowed() { // callback fn for station_options
var ws = worldScripts.telescope;
var ps = player && player.ship;
ws._setLicenceMissionVar()
return ps && ps.equipmentStatus( 'EQ_TELESCOPE' ) === 'EQUIPMENT_OK';
}
this._setLicenceMissionVar = function _setLicenceMissionVar() { // if accepted, change tense for environmental summary
var ws = worldScripts.telescope;
var so = worldScripts.station_options;
// called from _BetaLicenceAnswered, _stnOptionsAllowed & _reloadFromStn, text will
// (text will remain unchanged until this function is called)
if( ws.$BetaLicence === 1 // licence accepted, ensure tense correct
&& missionVariables.$TelescopeBetaLicence === null ) {
// player entering station_options having accepted licence on previous visit
so.$O_updateMissionKeys( 'telescope_',
{
'telescope_licenceAcceptance': expandDescription( '[telescope_experimental_has_accepted]' ),
'telescope_licenceRegistered': expandDescription( '[telescope_experimental_has_registered]' ),
// - not sure why but these won't expand otherwise (but telescope_licence_summary & telescope_licence_short do work???)
// 'telescope_licenceAcceptance': '[telescope_experimental_has_accepted]',
// 'telescope_licenceRegistered': '[telescope_experimental_has_registered]',
'telescope_optionPages': '[telescope_optionPages_experimental]',
'telescope_optionTabStops': '[telescope_optionTabStops_experimental]',
} );
missionVariables.$TelescopeBetaLicence = ws.$BetaLicence;
}
}
this._reloadFromStn = function _reloadFromStn( names, pages ) { // callback fn for station_options
var ws = worldScripts.telescope;
ws._setLicenceMissionVar()
if( pages && pages.length > 0 ) {
ws._reload_config();
if( names.indexOf( 'DebugMessages' ) >= 0 ) {
ws._debug_Sightings_closure();
}
}
if( ws.$DebugMessages ) {
log(ws.name, '_reloadFromStn, pages = ' + pages + '\n\t names = ' + names );
ws._report_config();
}
/* ShowSummary is now an option
var so = worldScripts.station_options;
// retrieve summary reporting status to support conditional option ShowSummary
if( so && so.$O_getReminder4Oxp ) {
var rmdr = so.$O_getReminder4Oxp( 'telescope_' );
if( rmdr ) {
ws.$ShowSummary = rmdr.reportSummary;
log('_reloadFromStn, saving ShowSummary: ' + ws.$ShowSummary );
}
}
*/
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// oxp support ////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
this.$Telescope_List = function $Telescope_List( step ) { // not used here, ?for other oxp's (was used in telescopeeq.js)
var that = $Telescope_List;
var ws = (that.ws = that.ws || worldScripts.telescope);
if( step )
ws._chg_curr_Sighting( step ); // user steps fwd/back through list of Sightings
else
ws._auto_updates( true ); // user performs 'rescan'
}
this.$Telescope_Scan = function _Scan() { // not used here, ?for other oxp's
var that = _Scan;
var ws = (that.ws = that.ws || worldScripts.telescope);
if( ws.$DebugMessages ) log(ws.name, 'Telescope_Scan, FORCED new scan ' );
ws._auto_updates( true ); // true forces a completely new mapping to be built
}
this.$Telescope_Show = function _Show() { // not used here, ?for other oxp's
var that = _Show;
var ws = (that.ws = that.ws || worldScripts.telescope);
ws.$Telescope_Show2( true );
}
this.$Telescope_Show2 = function _Show2( showname ) { // not used here, for other oxp's: EscortDeck
var that = _Show2;
var ws = (that.ws = that.ws || worldScripts.telescope);
var mapping = (that.mapping = that.mapping || ws.$SightingsMap);
var curr_S = (that.curr_S = that.curr_S || ws.$curr_Sighting);
var map = null,
index = ws.$TelescopeListi;
if( index === 0 ) {
index = curr_S.index;
} else {
index -= 1; // TelescopeListi is always index + 1
}
map = index >= 0 && index < mapping.length ? mapping[ index ] : null;
ws._manage_marker( map, showname || false, '_Show' );
}
// HUDSelector ////////////////////////////////////////////////////////////////////////////////////
this.$HUDStartUpComplete = function() {
if( !this.$HUDSelectorDefaultMFDs || this.$HUDSelectorDefaultMFDs.length === 0 ) {
//set default MFDs first time in order of $HUDSelectorMFDs array
this.$HUDSelectorDefaultMFDs = [];
for(var i = 0; i < this.$HUDSelectorMFDs.length; i++) {
if(this.$HUDSelectorMFDs[i] && this.$HUDSelectorMFDs[i][0]) {
// var w = this.$HUDSelectorMFDs[i][0]; //worldScripts name
// if( worldScripts[w] ) {
// this.$HUDSelectorDefaultMFDs[i] = w;
// }
// $HUDSelectorSetMFDs assumes $HUDSelectorDefaultMFDs entries will be
// [ worldScripts name, mfd name (maybe) ]
let [w, m] = this.$HUDSelectorMFDs[i];
if( worldScripts[w] ) {
this.$HUDSelectorDefaultMFDs[i] = m || w;
}
}
}
}
log(this.name, "HUDs: "+this.$HUDSelectorHUDs);//debug
if (this.$debug)
for (var i=0; i<this.$HUDSelectorHUDs.length; i++)
log(this.name, i+": "+this.$HUDSelectorHUDs[i]);
this.$HUDSelectorRestoreHUD();
this.$setInterface();
}
this.$HUDSelectorSetMFDs = function(h) {
var dLen = h.$HUDSelectorDefaultMFDs.length,
mLen = player.ship.multiFunctionDisplayList.length;
// must not exceed mLen else could fill empty slots when we wrap
// for(var i = 0; i < h.$HUDSelectorDefaultMFDs.length; i++) {
for(var i = 0; i < dLen && i < mLen; i++) {
// log(h.name, i+". MFD: "+h.$HUDSelectorDefaultMFDs[i]);//debug
if(h.$HUDSelectorDefaultMFDs[i]) {
var w = h.$HUDSelectorDefaultMFDs[i]; //worldScripts or MFD name
var mfd = -1;
if( w && w.length > 0 && w != "undefined" ) {
for(var j = 0; j < h.$HUDSelectorMFDs.length; j++) {
if( h.$HUDSelectorMFDs[j][0] == w
|| h.$HUDSelectorMFDs[j][1] == w ) mfd = j;
}
}
var m = null;
if( mfd > -1) m = h.$HUDSelectorMFDs[mfd][1]; //MFD name
if( !m ) m = w; //mfd name is equal with worldScripts name
if( m && worldScripts[w] && w != "undefined" )
player.ship.setMultiFunctionDisplay(i, m);
else if( w && w.length > 0 ) player.ship.setMultiFunctionDisplay(i, w);
else player.ship.setMultiFunctionDisplay(i, "");
// log(h.name, i+". MFD: "+w+" "+m+" "+worldScripts[w]);//debug
}
}
}
/*
this.$HUDstartUpComplete = function() {
var hud = worldScripts.hudselector,
defaults = hud.$HUDSelectorDefaultMFDs,
mfdDB = hud.$HUDSelectorMFDs;
if( !defaults ) {// should never happen
hud.$HUDSelectorDefaultMFDs = defaults = [];
log( this.name, '$HUDstartUpComplete, WARNING: hud.$HUDSelectorDefaultMFDs array got deleted!' )
}
if( defaults.length === 0 ) {
//set default MFDs first time in order of $HUDSelectorMFDs array
for( let idx = 0, len = mfdDB.length; idx < len; idx++ ) {
let [wsName, mfdName] = mfdDB[ idx ]; // an array of [worldScripts.name, mfdName], mfdName may be absent
if( wsName && worldScripts.hasOwnProperty( wsName ) ) {
defaults[ idx ] = mfdName ? mfdName : wsName;
}
}
}
log( hud.name, "HUDs: "+hud.$HUDSelectorHUDs );//debug
if( hud.$debug ) {
for( let idx=0, len = hud.$HUDSelectorHUDs.length; idx < len; idx++ )
log( hud.name, idx + ": " + hud.$HUDSelectorHUDs[ idx ] );
}
hud.$HUDSelectorRestoreHUD();
hud.$setInterface();
log('HUDstartUpComplete, exit, MFDs: ' + hud.$HUDSelectorMFDs );
log('HUDstartUpComplete, DefaultMFDs: ' + hud.$HUDSelectorDefaultMFDs );
log('HUDstartUpComplete, MFDisplayList: ' + player.ship.multiFunctionDisplayList );
}
// : ' + + '
this.$HUDSelectorSetMFDs = function( hud ) {
if( !hud ) return;
var mfdDB = hud.$HUDSelectorMFDs; // nested array of MFDs registered with hudselector
if( !mfdDB ) return;
var ps = player && player.ship,
defaults = hud.$HUDSelectorDefaultMFDs;
var slot = 0, numSlots = ps.multiFunctionDisplays,
dLen = defaults.length, mLen = mfdDB.length;
for( let dx = 0; dx < dLen; dx++ ) { // && slot < numSlots
let wsName, mfdName,
defName = defaults[ dx ]; //worldScripts name or MFD name
if( defName && defName != "undefined" ) {
wsName = mfdName = null;
for( let mx = 0; mx < mLen; mx++ ) {
[wsName, mfdName] = mfdDB[ mx ];
if( mfdName == defName || wsName == defName ) { // check MFD name first
break;
}
}
let key = mfdName || wsName;
if( key && defName != "undefined" && worldScripts.hasOwnProperty( defName ) ) {
ps.setMultiFunctionDisplay( slot++, key );
} else if( defName && defName.length > 0 ) {
ps.setMultiFunctionDisplay( slot++, defName );
} else {
ps.setMultiFunctionDisplay( slot, "" );
}
log( hud.name, dx + ". MFD: " + defName + " " + key + " " + worldScripts[ defName ] );//debug
}
}
log('HUDSelectorSetMFDs, exit, MFDs: ' + hud.$HUDSelectorMFDs );
log('HUDSelectorSetMFDs, DefaultMFDs: ' + hud.$HUDSelectorDefaultMFDs );
log('HUDSelectorSetMFDs, MFDisplayList: ' + player.ship.multiFunctionDisplayList );
}
*/
// : ' + + '
this._registerHUDSelector = function _registerHUDSelector() { // called in startUp
var ws = worldScripts.telescope;
var hud = worldScripts.hudselector;
if( !hud ) return;
var mfdDB = hud.$HUDSelectorMFDs; // nested array of MFDs registered with hudselector
var telName = worldScripts.telescope.name;
for( let idx = 0, len = mfdDB.length; idx < len; idx++ ) {
// there is no $HUDSelectorRemoveMFD, so ...
let [hudscript, mfdName] = mfdDB[ idx ];
if( hudscript === telName && !mfdName ) { // set for telescope <= 1.15
for( let mvi = idx; mvi < len - 1; mvi++ ) { // remove from mfdDB
mfdDB[ mvi ] = mfdDB[ mvi + 1 ];
}
mfdDB.length = --len;
}
}
hud.$HUDSelectorAddMFD( telName, ws.$PrimaryMFD_name )
hud.$HUDSelectorAddMFD( telName, ws.$AuxilaryMFD_name )
}
/*
this._registerHUDSelector = function _registerHUDSelector() { // called in startUp
var ws = worldScripts.telescope;
var hud = worldScripts.hudselector;
if( !hud ) return;
var mfdDB = hud.$HUDSelectorMFDs; // nested array of MFDs registered with hudselector
if( !mfdDB ) return;
var telName = worldScripts.telescope.name;
var isOld = false, ver = hud.version.split( '.' );
if( ver.length > 1 && parseInt( ver[ 1 ], 10 ) < 18 )
isOld = true;
if( isOld ) {
let changed = false, primary = false, auxilary = false;
for( let idx = 0, len = mfdDB.length; idx < len; idx++ ) {
let [hudscript, mfdName] = mfdDB[ idx ];
if( hudscript === telName ) {
if( !mfdName || mfdName === telName ) { // old telescope in saved game
mfdDB[ idx ] = [ telName, ws.$PrimaryMFD_name ]; // replace (keep initial position)
primary = changed = true;
} else if( mfdName === 'telescopeAux' ) { // old telescope in saved game
mfdDB[ idx ] = [ telName, ws.$AuxilaryMFD_name ]; // replace (keep initial position)
auxilary = changed = true;
} else { // detect new MFDs
if( mfdName === ws.$PrimaryMFD_name )
primary = true;
else if( mfdName === ws.$AuxilaryMFD_name )
auxilary = true;
}
}
}
if( !primary )
mfdDB.push( [ telName, ws.$PrimaryMFD_name ] );
if( !auxilary )
mfdDB.push( [ telName, ws.$AuxilaryMFD_name ] );
if( changed || !primary || !auxilary ) {
hud.$HUDSelectorSetMFDs( hud );
}
} else {
/// once working, add some old version names & see what needs doing
for( let idx = 0, len = mfdDB.length; idx < len; idx++ ) {
let [hudscript, mfdName] = mfdDB[ idx ];
if( hudscript === telName
&& ( !mfdName || mfdName === hudscript // set for telescope <= 1.15
|| mfdName === 'telescopeAux' ) ) { // old telescope in saved game
for( let mvi = idx; mvi < len - 1; mvi++ ) { // remove from mfdDB
mfdDB[ mvi ] = mfdDB[ mvi + 1 ];
}
mfdDB.length = --len;
}
}
hud.$HUDSelectorAddMFD( telName, ws.$PrimaryMFD_name )
hud.$HUDSelectorAddMFD( telName, ws.$AuxilaryMFD_name )
}
log('_registerHUDSelector, exit, MFDs: ' + hud.$HUDSelectorMFDs );
log('_registerHUDSelector, DefaultMFDs: ' + hud.$HUDSelectorDefaultMFDs );
log('_registerHUDSelector, MFDisplayList: ' + player.ship.multiFunctionDisplayList );
}
*/
///////////////////////////////////////////////////////////////////////////////////////////////////
// Telescope methods //////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// inialization ///////////////////////////////////////////////////////////////////////////////////
this._init_Sightings_closure = function _init_Sightings_closure() { // initialize closures & expose functions
var ws = worldScripts.telescope;
var sc = ws._Sightings_closure();
ws.$Sighting_closure = sc;
ws._initOxpVars = sc._initOxpVars;
ws._init_player_vars = sc._init_player_vars;
ws._reload_config = sc._reload_config;
ws._adjustMLFlags = sc._adjustMLFlags;
ws._getShowState = sc._getShowState;
ws._getShowStateText = sc._getShowStateText;
ws._currMLFlags = sc._currMLFlags;
ws._shutdown_Sightings = sc._shutdown_Sightings;
ws._restart_after_shutdown = sc._restart_after_shutdown;
ws._has_bad_status = sc._has_bad_status;
ws._Sighting_index = sc._Sighting_index;
ws._set_curr_Sighting = sc._set_curr_Sighting;
ws._add_Sighting = sc._add_Sighting;
ws._delete_Sighting = sc._delete_Sighting;
ws._nearest_Sighting = sc._nearest_Sighting;
ws._chg_curr_Sighting = sc._chg_curr_Sighting;
ws._reposition_effects = sc._reposition_effects;
ws._update_Sightings = sc._update_Sightings;
ws._newList = sc._newList;
ws._call_pending = sc._call_pending;
ws._create_Sightings = sc._create_Sightings;
ws._update_target_marker = sc._update_target_marker;
ws._manage_marker = sc._manage_marker;
ws._mostCentered = sc._mostCentered;
ws._auto_updates = sc._auto_updates;
ws._resetIdentDelay = sc._resetIdentDelay;
ws._steerFCB = sc._steerFCB;
ws._clear_HUD_Effects = sc._clear_HUD_Effects;
ws._showVShip = sc._showVShip;
ws._set_vShip_posn = sc._set_vShip_posn;
ws._hud_effects = sc._hud_effects;
ws._relativeDirection = sc._relativeDirection;
ws._report_config = sc._report_config;
ws._report_autovars = sc._report_autovars;
}
this._debug_Sightings_closure = function _debug_Sightings_closure() { // expose debug functions
var ws = worldScripts.telescope;
var sc = ws.$Sighting_closure;
if( !sc || !ws.$DebugMessages ) return;
ws.reset_common_vars = sc.reset_common_vars;
ws.index_in_list = sc.index_in_list;
ws.getDetected = sc.getDetected;
ws.is_hostile = sc.is_hostile;
ws.grav_scan_dist = sc.grav_scan_dist;
ws.check_Sightings = sc.check_Sightings;
ws.select_Sightings = sc.select_Sightings;
ws.add_lt_ball = sc.add_lt_ball;
ws.lb_effect_size = sc.lb_effect_size;
ws.update_lt_ball = sc.update_lt_ball;
ws.add_ml_ring = sc.add_ml_ring;
ws.ml_effect_size = sc.ml_effect_size;
ws.update_ml_ring = sc.update_ml_ring;
ws.proc_stealthy = sc.proc_stealthy;
ws.update_one_Sighting = sc.update_one_Sighting;
ws.update_some = sc.refresh_Sightings;
ws.classify_ship = sc.classify_ship;
ws.is_ignored_ship = sc.is_ignored_ship;
ws.process_new_targets = sc.process_new_targets;
ws.fns_are_pending = sc.fns_are_pending;
ws.set_fn_pending = sc.set_fn_pending;
ws.clear_all_pending = sc.clear_all_pending;
ws.show_pending = sc.show_pending;
ws.grow_new_list = sc.grow_new_list;
ws.notable_ent = sc.notable_ent;
ws.check_if_new_targets = sc.check_if_new_targets;
ws.update_MFDs = sc.update_MFDs;
ws.qualifyMFD = sc.qualifyMFD;
ws.set_displayName = sc.set_displayName;
ws.showTargetName = sc.showTargetName;
ws.showShipReport = sc.showShipReport;
ws.entityIsNamed = sc.entityIsNamed;
ws.planetIsNamed = sc.planetIsNamed;
ws.sunName = sc.sunName;
ws.orbName = sc.orbName;
ws.planetNameString = sc.planetNameString;
ws.report_scan_progress = sc.report_scan_progress;
}
this._StartTimer = function _StartTimer( delay ) {
var that = _StartTimer;
var ws = (that.ws = that.ws || worldScripts.telescope);
var ps = player && player.ship;
if( !ps ) return;
if( ps.equipmentStatus('EQ_TELESCOPE') === 'EQUIPMENT_OK' ) {
//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
if( ws.$Timer_auto_updates || ws.$Sighting_events_FCB ) {
ws._StopTimer();
}
ws.$Timer_auto_updates = new Timer( ws, ws._auto_updates, delay, 0.25 );
ws.$Sighting_events_FCB = addFrameCallback( ws._Sighting_events.bind(ws) );
}
}
this._StopTimer = function _StopTimer() {
var that = _StopTimer;
var ws = (that.ws = that.ws || worldScripts.telescope);
let timer = ws.$Timer_auto_updates;
if( timer ) {
if( timer.isRunning ) {
timer.stop();
}
ws.$Timer_auto_updates = null; // discard Timer as timer == null => docked or in witchspace
}
let fcb = ws.$Sighting_events_FCB;
if( fcb ) {
if( isValidFrameCallback( fcb ) ) {
removeFrameCallback( fcb );
}
ws.$Sighting_events_FCB = null;
}
}
/* (function () { // IIFE for re-loading _Sighting_events
if( isValidFrameCallback( ws.$Sighting_events_FCB ) )
removeFrameCallback( ws.$Sighting_events_FCB );
ws.$Sighting_events_FCB = addFrameCallback( ws._Sighting_events.bind( ws ) );
})()
//*/
this._Sighting_events = function _Sighting_events( delta ) { //delta is the time since the last frame
function fps_missisng() { return -1 }
var that = _Sighting_events;
var ws = (that.ws = that.ws || worldScripts.telescope);
var short_term_fps = (that.short_term_fps = that.short_term_fps || ws.$fps_closure ? ws.$fps_closure._short_term_fps : fps_missisng);
var reset_fps_mon = (that.reset_fps_mon = that.reset_fps_mon || ws.$fps_closure ? ws.$fps_closure._reset_fps_monitor : fps_missisng);
if( that.speedup_tried === undefined ) that.speedup_tried = false;// persistent flag for speed-up attempt
if( that.speedup_fps === undefined ) that.speedup_fps = -1; // persistent variable for short_term_fps value
if( that.tasks === undefined ) that.tasks = 1; // persistent # of pending tasks processed each frame
if( !ws._init_player_vars() ) return; // equipment damaged, nothing to do
var speedup_tried = that.speedup_tried,
speedup_fps = that.speedup_fps,
tasks = that.tasks;
ws._update_target_marker(); // must be 1st, as set variables used by followng (eg. target_vector)
if( ws.$are_Steering ) { // steering must preceed _hud_effects
ws._steerFCB( delta );
}
ws._hud_effects( delta );
ws._reposition_effects();
if( !speedup_tried ) { // reduce overhead if tried & failed
let fps = short_term_fps();
if( fps > 0 ) { // takes 2 minutes to get 1st report
if( ws.$DebugMessages ) log(ws.name, '_Sighting_events, got fps of ' + fps );
reset_fps_mon( delta, true ); // restart & wipe data to have another 2 minute wait
if( speedup_fps < 0 ) { // 1st time through
if( fps > 65 ) { // candidate for processing 2 tasks (2.38+ ms/frame diff)
that.speedup_fps = fps;
that.tasks = 2; // try faster rate
if( ws.$DebugMessages ) log(ws.name, '_Sighting_events, trying out '
+ that.tasks + ' tasks/frame' );
} else { // 1st reading was low, shut down checking on slower machines
that.tasks = 1;
that.speedup_tried = true;
if( ws.$DebugMessages ) log(ws.name, '_Sighting_events, running too slow (' + fps
+ ') for trial of higher tasks/frame' );
}
} else { // back after another 2 minutes
if( fps <= 60 ) { // extra task caused frame rate to fall too much
that.tasks = 1; // revert to single task each frame
that.speedup_tried = true;
if( ws.$DebugMessages ) log(ws.name, '_Sighting_events, fps cost too high('
+ speedup_fps + ' -> ' + fps + '), reverting to '
+ that.tasks + ' tasks/frame' );
} else { // still over 60, keep new rate & shut down checking
that.speedup_tried = true;
if( ws.$DebugMessages ) log(ws.name, '_Sighting_events, fps still over 60 (' + fps
+ '), continuing with ' + that.tasks + ' tasks/frame' );
}
}
}
}
ws._call_pending( tasks );
}
// mode/activate methods //////////////////////////////////////////////////////////////////////////
this.$SET_LIGHTBALLS = 1; // bit flags to reinit cached $UserChangedSettings
this.$SET_MASSLOCKRINGS = 2;
this.$SET_SNIPER = 4;
this.$SET_STEERING = 8;
this.$SET_TARGETS = 16;
this.$SET_VISUAL = 32;
this.$SET_VISUAL_SIZE = 64;
this._getOldLightballs = function _getOldLightballs() { // return values expected by 1.15
var that = _getOldLightballs;
var ws = ( that.ws = that.ws || worldScripts.telescope );
/*
subitem: 1 2 3 4 5 6
version 1.15
[ "Lightballs:", "off", "navigation only", "ships", "masslock borders", "bright masslock borders", "large" ],
version 2
[ "Lightballs:", "off", "navigation only", "include ships", "large" ],
[ "Masslock rings:", "current alert/weapons state: off", "current alert/weapons state: on", "brighter" ],
*/
var subitem = 1; //off
if( ws.$LightBalls ) subitem = 2;
if( ws.$ShipLightBalls ) subitem = 3;
if( ws.$MassLockRings && ws.$LightBalls && ws.$ShipLightBalls ) {// don't turn on LightBalls & ShipLightBalls if MassLockRings
// lesser of evils: it's an inperfect mapping from 1.15's list of bools to v2's situational masslock rings
subitem = 4;
}
if( ws.$BrightMassLockRings ) subitem = 5;
if( ws.$LargeLightBalls ) subitem = 6; // will turn on (bright) masslock rings <meh>
return subitem;
}
// : ' + + '
this._oldSetLightballs = function _oldSetLightballs( subitem ) { // handle as 1.15 would; 2.0 changes will overwrite
var that = _oldSetLightballs;
var ws = ( that.ws = that.ws || worldScripts.telescope );
// if( subitem === 1 ) { //off
ws.$LightBalls = subitem >= 2; //ship off
ws.$ShipLightBalls = subitem >= 3; //small
ws.$MassLockRings = subitem >= 4 ? this.$DEFAULT_ML_RINGS : 0;
ws.$BrightMassLockRings = subitem >= 5; //use brighter borders
ws.$LargeLightBalls = subitem >= 6; //large
}
this._SetLightballs = function _SetLightballs( subitem ) { //set config variables from telescopeeq.js also
var that = _SetLightballs;
var ws = (that.ws = that.ws || worldScripts.telescope);
ws.$UserChangedSettings |= ws.$SET_LIGHTBALLS; // set bit flag to reinit _Sightings_closure cached vars
ws.$DamageMsg = true;
ws.$LightBalls = subitem >= 2; //ship off
ws.$ShipLightBalls = subitem >= 3; //small
ws.$LargeLightBalls = subitem >= 4; //large
if( ws._update_Sightings )
ws._update_Sightings( true );
}
this._SetMasslockRings = function _SetMasslockRings( subitem ) { //set config variables from telescopeeq.js also
var that = _SetMasslockRings;
var ws = (that.ws = that.ws || worldScripts.telescope);
ws.$UserChangedSettings |= ws.$SET_MASSLOCKRINGS; // set bit flag to reinit _Sightings_closure cached vars
ws.$DamageMsg = true;
// ws.$MassLockRings = subitem >= 2;
// MassLockRings is no longer a boolean but a set of bitflags
if( subitem >= 2 ) { // enabling masslock rings
ws._adjustMLFlags( true ); // add current state to flags
} else { // disabling masslock rings
ws._adjustMLFlags( false ); // remove current state from flags
}
ws.$BrightMassLockRings = subitem >= 3; //use brighter borders
if( ws._update_Sightings )
ws._update_Sightings( true );
}
this._SetSniper = function _SetSniper( subitem ) {
var that = _SetSniper;
var ws = (that.ws = that.ws || worldScripts.telescope);
ws.$UserChangedSettings |= ws.$SET_SNIPER; // set bit flag to reinit _Sightings_closure cached vars
ws.$DamageMsg = true;
if( subitem === 1 ) { //off
ws.$SniperRange = 10000;
ws.$SniperMinRange = 10000;
} else {
var minitem = subitem;
if( subitem < 5 )
ws.$SniperRange = 25600;
else {
minitem = subitem - 3;
ws.$SniperRange = 30000;
}
ws.$SniperMinRange = 5000 * ( minitem - 1 ); //5, 10 or 15km
}
}
this._SetSteering = function _SetSteering( subitem ) {
var that = _SetSteering;
var ws = (that.ws = that.ws || worldScripts.telescope);
ws.$UserChangedSettings |= ws.$SET_STEERING; // set bit flag to reinit _Sightings_closure cached vars
ws.$DamageMsg = true;
ws.$Steering = subitem - 1;
}
this._SetTargets = function _SetTargets( subitem ) {
var that = _SetTargets;
var ws = (that.ws = that.ws || worldScripts.telescope);
ws.$UserChangedSettings |= ws.$SET_TARGETS; // set bit flag to reinit _Sightings_closure cached vars
ws.$DamageMsg = true;
if( subitem === 1 ) { //20 and limitation in red alert
ws.$MaxTargets = 20;
} else {
if( subitem === 2 )
ws.$MaxTargets = 50;
else if( subitem === 3 )
ws.$MaxTargets = 100;
else
ws.$MaxTargets = 200;
}
}
this._SetVisual = function _SetVisual( subitem ) {
var that = _SetVisual;
var ws = (that.ws = that.ws || worldScripts.telescope);
ws.$UserChangedSettings |= ws.$SET_VISUAL; // set bit flag to reinit _hud_effects_closure cached vars
ws.$DamageMsg = true;
if( subitem === 1 ) {
ws.$ShowVisualTarget = 0; // always off
} else if( subitem === 2 ) {
ws.$ShowVisualTarget = 1; // on only when weapons off-line
} else {
ws.$ShowVisualTarget = 2; // always on
}
ws.$VisualTargetRing = subitem > 3;
ws.$TelescopeRing = subitem > 3; // maintain for oxps
ws.$ShowVisualStation = subitem > 4; //no station
ws.$ShowVisualQuestionMark = subitem > 5; //no "?"
}
this._SetVisualSize = function _SetVisualSize( subitem ) {
var that = _SetVisualSize;
var ws = (that.ws = that.ws || worldScripts.telescope);
ws.$UserChangedSettings |= ws.$SET_VISUAL_SIZE; // set bit flag to reinit _hud_effects_closure cached vars
ws.$DamageMsg = true;
if( ws.$VisualTargetCombatSize !== 0 ) { // not disabled in station options
ws.$VisualTargetCombatSize = subitem; //1-8
ws.$TelescopeVSize = subitem; // maintain for oxps
}
if( ws.$VisualTargetNormalSize !== 0 ) { // not disabled in station options
ws.$VisualTargetNormalSize = subitem; //1-8
ws.$TelescopeVZoomSize = subitem; // maintain for oxps
}
}
// miscellaneous methods //////////////////////////////////////////////////////////////////////////////
this._AddShips = function _AddShips() { // add ships/aliens for debugging
var that = _AddShips;
var ws = (that.ws = that.ws || worldScripts.telescope);
var wop = (that.wop = that.wop || worldScripts[ "oolite-populator" ]);
if( ws.$DebugMessages && !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
wop._addFreighter( system.mainStation );
wop._addMediumHunterReturn( system.mainStation );
/* for specific ship model, enclose model name in [], eg. addShips('[vector_geek]', ...)
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( ws.$Thargoids ) { //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._reportError = function _reportError( err, func, parms, depth, goDeep ) {
// constants - adjust as needed
var FILE_LEN = 100; // cut-off len for file spec.
var FNAME_LEN = 40; // cut-off len for function name
var ARGS_LEN = 60; // cut-off len for arguments string
var STRING_LEN = 120; // cut-off for long strings
var IPAD = ' '; // inside padding, eg. after array open bracket, before close bracket
function trim_str( str ) {
var result, len = str.length;
if( len === 0 )
return '<empty string>';
result = str.replace( /[\u180e\u2000-\u200a\u202f\u205f\u3000]+/g, ' ' );
result = result.replace( /[\n]+/g, '\\n' ).replace( /[\t]+/g, '\\t' )
result = '"' + (len > STRING_LEN ? result.substr(0, STRING_LEN) + ' ...' : result) + '"';
return result
}
var padding = [];
function mkSpacePad( count ) {
if( typeof count === 'number' ) {
padding.length = count + 1;
return padding.join(' ');
}
return ' ';
}
function countObjKeys( obj, deep ) { // Object.keys( obj ).length only counts hasOwnProperty ones
var count = 0; // deep overrides goDeep
if( goDeep || deep ) {
for( let prop in obj )
if( prop ) // this is just to silence JSLint
count++;
} else {
count = Object.keys( obj ).length;
}
return count;
}
function rptType( obj ) {
if( Array.isArray( obj ) ) {
let len = obj.length;
return len > 0 ? '<array of ' + len + '>' : '[]';
} else if( obj instanceof Script ) {
return '[Script "' + obj.name + '" version ' + obj.version + ']';
} else if( typeof obj === 'object' ) {
let len = countObjKeys( obj, true ); // ignore goDeep when counting
return len > 0 ? '<object of ' + len + '>' : '{}';
} else {
return obj;
}
}
function hasComplex( obj ) {
for( let prop in obj ) {
if( goDeep || obj.hasOwnProperty( prop ) ) {
let item = obj[ prop ];
if( Array.isArray( item ) || (typeof item === 'object' && item !== null) )
return true;
}
}
return false;
}
function showComplex( obj, recurse ) {
var isArray = Array.isArray( obj );
var len = isArray ? obj.length : countObjKeys( obj );
if( len === 0 ) return isArray ? '[]' : '{}';
var index = 0,
str = (isArray ? '[' : '{') + IPAD,
strLen = str.length;
var recursable = recurse > 0 && hasComplex( obj );
for( let prop in obj ) {
if( goDeep || obj.hasOwnProperty( prop ) ) {
let item = obj[ prop ];
let propStr = isArray ? '' :
(goDeep && !obj.hasOwnProperty( prop ) ? '^' : '') + prop + ': ';
let propLen = propStr.length;
str += propStr;
if( recursable ) {
if( index === 0 ) {
outStarts.push( (outStarts.length > 0
? outStarts[outStarts.length-1] + propLen + strLen
: strLen + propLen + strLen) );
}
str += fmt_parm( item, recurse );
if( index < len - 1 ) { // not the last one
let inset = outStarts.length > 1 ? outStarts[outStarts.length-2] : strLen;
str += ',\n' + mkSpacePad( indentLen + inset );
} else {
str += IPAD;
}
} else {
str += hasComplex( item ) ? rptType( item ) : fmt_parm( item, 0 );
str += index < len - 1 ? ', ' : IPAD;
}
index++;
}
}
if( recursable && index ) outStarts.pop();
return str + (isArray ? ']' : '}');
}
var outStarts = []; // stack of running total of recursed insets
var parents = []; // check parm not in parents to avoid endless recursion
function fmt_parm( parm, recurse ) {
if( parents.indexOf( parm ) < 0 ) {
parents.push( parm );
} else {
return parm;
}
var type = typeof parm;
var str = '';
if( parm === null ) {
str += 'null';
} else if( type === 'undefined' ) {
str += 'undefined';
} else if( type === 'string' ) {
str += trim_str( parm );
} else if( type === 'boolean' ) {
str += (parm ? 'true' : 'false');
} else if( type === 'function' ) {
str += 'function ' + parm.name + '()';
} else if( parm instanceof Script ) {
str += '[Script "' + parm.name + '" version ' + parm.version + ']';
} else if( parm instanceof Vector3D ) {
str += 'Vector3D: (' + parm.x.toFixed() + ', '
+ parm.y.toFixed() + ', ' + parm.z.toFixed() + ')';
} else if( parm instanceof Quaternion ) {
str += 'Quaternion: (' + parm.w.toFixed() + ' + ' + parm.x.toFixed() + 'i + '
+ parm.y.toFixed() + 'j + ' + parm.z.toFixed() + 'k)';
} else if( Array.isArray( parm ) ) {
str += showComplex( parm, recurse <= 1 ? 0 : recurse - 1 );
} else if( type === 'object' && parm ) {
str += showComplex( parm, recurse <= 1 ? 0 : recurse - 1 );
} else {
str += rptType( parm );
}
parents.pop();
return str;
}
var funcProps = {};
function propsNotName( obj ) {
if( typeof obj !== 'function' ) return 0; // backwards compatibity
for( let key in funcProps ) { // reset object
if( funcProps.hasOwnProperty( key ) )
delete funcProps[ key ];
}
for( let key in obj ) {
if( key !== 'name' )
funcProps[ key ] = obj[ key ];
}
return Object.keys( funcProps ).length;
}
var parmsLabel = '\n parameters: ';
var indentLen = parmsLabel.length - 1; // -1 for \n
var fnName = typeof func === 'function' ? func.name : func; // backwards compatibity
var rpt, parmMax, propMax,
bonus = Array.isArray( parms ) ? 1 : 0; // don't count parms being an array as recursion (+ 1)
if( Array.isArray( depth ) ) {
parmMax = (depth.length > 0 && typeof depth[ 0 ] === 'number' ? ~~(depth[ 0 ]) : 1) + bonus;
propMax = (depth.length > 1 && typeof depth[ 1 ] === 'number' ? ~~(depth[ 1 ]) : 1) + bonus;
} else {
parmMax = propMax = (typeof depth === 'number' ? ~~(depth) : 1) + bonus;
}
if( err instanceof Error ) {
rpt = '\nfunction ' + fnName + '() \t caught: \t' + err.name + ': ' + err.message;
} else { // for thrown strings (user defined errors)
rpt = '\nfunction ' + fnName + '() \t caught: \t' + err;
}
if( parms ) {
rpt += parmsLabel + fmt_parm( parms, parmMax );
}
if( propsNotName( func ) ) {
parmsLabel = '\n properties: ';
indentLen = parmsLabel.length - 1; // -1 for \n
rpt += parmsLabel + fmt_parm( funcProps, propMax + 1 ); // + 1 as funcProps is an object
}
// err is the stack object with properties: message, fileName, lineNumber, stack, name
// - stack is a long string containing <function call>@<filename>:<line #> separated by
// '\n' for each call in the stack
if( err && err.stack ) {
var lastFile, parsed, frame, fnCall, args, file, line, pad;
var stk = err.stack.split( /[\n\r]+/ ); // split on line breaks
for( let idx = 0, len = stk.length; idx < len; idx ++ ) {
// stack line format: fn(parms)@../AddOns/.../script.js:123
parsed = stk[ idx ].match( /^\s*(\w+)\((.*?)\)@(.*?):(.*?)$/ );
if( !parsed || parsed.length < 5 ) break;
[frame, fnCall, args, file, line] = parsed;
if( file && file !== lastFile ) { // suppress repeat of same filename
if( file.length > FILE_LEN )
file = file.substring( file.length - FILE_LEN ) + '...';
rpt += '\n file: ' + file;
lastFile = file;
}
pad = line < 10 ? ' ' : line < 100 ? ' ' : line < 1000 ? ' ' : '' ;
rpt += '\n line: ' + pad + line + ', ';
if( fnCall.length > FNAME_LEN ) fnCall = fnCall.substring(0, FNAME_LEN) + '...';
if( args.length > ARGS_LEN ) args = args.substring(0, ARGS_LEN) + '...';
if( args.length ) // add spaces inside function's parenthices
args = ' ' + args.replace( /,/g, ', ' ) + ' ';
rpt += fnCall + '(' + args + ')';
}
}
return rpt;
}
/* profiling in debug console
ws.set_profiling()
ws.clear_profiling()
ws._delete_Sighting( PS.target )
:time ws._delete_Sighting( PS.target )
ws._add_Sighting( PS.target )
:time ws._add_Sighting( PS.target )
ws._create_Sightings();
:time ws.grow_new_list( 'start', false )
:time ws.update_some( 2 )
:time ws._call_pending( 1 )
ws.time_create()
ws._report_config()
ws._report_autovars()
*/
// (function () {ws.$VisualTargetRing = true; ws.$UserChangedSettings = 63;})()
// (function () {ws.$VisualTargetRing = false; ws.$UserChangedSettings = 63;})()
///////////////////////////////////////////////////////////////////////////////////////////////////
// Telescope closure //////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
/* (function () { // telescope IIFE for reloading new _Sightings_closure
var ws = worldScripts.telescope;
console.clearConsole();
ws._StopTimer();
ws._shutdown_Sightings();
ws._init_Sightings_closure();
// for debugging Sighting
if (ws.$DebugMessages) {
ws._debug_Sightings_closure();
}
//ws.time_create = sc.time_create; ws.time_update = sc.time_update; //cagiife
//ws.time_refresh = sc.time_refresh; ws.profile_create = sc.profile_create; //cagiife
//ws.profile_update = sc.profile_update; ws.profile_refresh = sc.profile_refresh; //cagiife
//ws.set_profiling = sc.set_profiling; ws.clear_profiling = sc.clear_profiling; //cagiife
ws._initOxpVars();
log("calling _set_vShip_posn(" + ps.viewPositionForward + ", " + ws.$VTarget_HUD_shift + ")");
ws._set_vShip_posn( ps.viewPositionForward, ws.$VTarget_HUD_shift );
log("after calling _set_vShip_posn(" + ps.viewPositionForward + ", " + ws.$VTarget_HUD_shift + ")");
// NB: these are basically shipLaunchedFromStation, so comment out if testing involves launch
ws._init_player_vars( true );
ws._restart_after_shutdown();
ws._create_Sightings();
ws._StartTimer(1);
})() // */
// ^gui screen.*?[\w\s]+(?=[\n]^[^g])
// : ' + + '
this._Sightings_closure = function _Sightings_closure() {
// oxp 'constant' variables
var ws = worldScripts.telescope;
var AstroLibrary, Carriers, Combat_MFD, Escortdeck, FarPlanets,
GalacticAlmanac, GalNavy, ILS, Navigation_MFD,
SniperLock, SniperLockPlus, SpicyHermits, TorusToSun, Towbar,
VariableMassLock, VimanaHUD, WarpDrive,
add_Sighting_errors,
short_term_fps, long_term_fps, current_fps,
turn_off_fps_monitor, turn_on_fps_monitor, realtime_fps;
// must be set after all oxp's loaded, ie. startUpComplete, not startUp where this closure is created
// game 'constant' variables
var gameSettings = oolite.gameSettings,
gameWindow = gameSettings.gameWindow, // can be changed via options menu
fov = gameSettings.fovValue, // can be changed via options menu
sin_fov2, cos_fov2,
strFontLen = defaultFont.measureString,
SpaceLen = strFontLen( ' ' );
// math function references
var floor = Math.floor, round = Math.round, ceil = Math.ceil,
sqrt = Math.sqrt, pow = Math.pow, ln = Math.log,
sin = Math.sin, cos = Math.cos, acos = Math.acos,
asin = Math.asin, random = Math.random, abs = Math.abs;
const LOG10E = Math.LOG10E;
function log10( num ) { // base 10 logarithm of num
return ln( num ) * LOG10E;
}
// function references
var entitiesWithScanClass = system.entitiesWithScanClass,
addVisualEffect = system.addVisualEffect,
addShips = system.addShips,
consoleMessage = player.consoleMessage,
isArray = Array.isArray;
// user settable 'constant' variables
var AutoScan = ws.$AutoScan,
AutoScanMaxRange = ws.$AutoScanMaxRange,
GravLock = ws.$GravLock,
AutoLock = ws.$AutoLock,
IdentLock = ws.$IdentLock,
IdentDelay = ws.$IdentDelay,
FarStatus = ws.$FarStatus,
MaxTargets = ws.$MaxTargets, // can config in flight
RedAlertDist = ws.$RedAlertDist,
Steering = ws.$Steering, // can config in flight
LightBalls = ws.$LightBalls, // can config in flight
ShipLightBalls = ws.$ShipLightBalls, // can config in flight
LargeLightBalls = ws.$LargeLightBalls, // can config in flight
LightBallMinDist = ws.$LightBallMinDist,
LightBallShipMinDist = ws.$LightBallShipMinDist,
MassLockRings = ws.$MassLockRings, // can config in flight
MassLockViewDirn = ws.$MassLockViewDirn,
BrightMassLockRings = ws.$BrightMassLockRings, // can config in flight
SniperRingSize = ws.$SniperRingSize,
SniperRingActive = ws.$SniperRingActive, // new
SniperRange = ws.$SniperRange, // can config in flight
SniperMinRange = ws.$SniperMinRange, // can config in flight
SniperRingColor = ws.$SniperRingColor,
ShowVisualTarget = ws.$ShowVisualTarget, // can config in flight
VisualTargetNormalSize = ws.$VisualTargetNormalSize, // can config in flight
VisualTargetCombatSize = ws.$VisualTargetCombatSize, // can config in flight
VisualTargetRing = ws.$VisualTargetRing, // can config in flight
ShowVisualStation = ws.$ShowVisualStation, // can config in flight
ShowVisualQuestionMark = ws.$ShowVisualQuestionMark, // can config in flight
ModelRingColor = ws.$ModelRingColor,
// VTarget_HUD_shift is not used in closure but passed in during shipWillLaunchFromStation
// new/ui
ConsoleMsgDurn = ws.$ConsoleMsgDurn,
GravScanMsgFreq = ws.$GravScanMsgFreq,
IdentMessages = ws.$IdentMessages,
// ShowSummary is not used in closure; it's the reportSummary bool from station_options
debug = ws.$DebugMessages,
// new/experimental
TargetOnlyHostile = ws.$TargetOnlyHostile,
RemoveInFlight = ws.$RemoveInFlight,
MFDFiltering = ws.$MFDFiltering,
MFDPrimaryStatic = ws.$MFDPrimaryStatic,
MFDPrimaryDynamic = ws.$MFDPrimaryDynamic,
SeparateMFDs = ws.$SeparateMFDs,
MFDAuxStatic = ws.$MFDAuxStatic,
MFDAuxDynamic = ws.$MFDAuxDynamic;
// flags for variables modifiable via activated event - see init_player_vars
const SET_LIGHTBALLS = ws.$SET_LIGHTBALLS,
SET_MASSLOCKRINGS = ws.$SET_MASSLOCKRINGS,
SET_SNIPER = ws.$SET_SNIPER,
SET_STEERING = ws.$SET_STEERING,
SET_TARGETS = ws.$SET_TARGETS,
SET_VISUAL = ws.$SET_VISUAL,
SET_VISUAL_SIZE = ws.$SET_VISUAL_SIZE;
// player's alertCondition
const DOCKED = 0, GREEN_ALERT = 1, YELLOW_ALERT = 2, RED_ALERT = 3;
// gravity scan state
const GS_NONE = 0, GS_STOPPED = 1, GS_RUNNING = 2, GS_DEGRADING = 3, GS_COMPLETE = 4;
// index used to calc bitflags for masslock view direction
const VIEWS_LIST = [ 'VIEW_FORWARD', 'VIEW_AFT', 'VIEW_PORT', 'VIEW_STARBOARD' ];
// identKeyPress values
const IDENT_READY = 0, IDENT_LOCKED = 1, IDENT_STEERING = 2, IDENT_UNLOCK = 3, IDENT_STEER_DELAY = 4, IDENT_STEP_DELAY = 5;
// MFD names
const PrimaryMFD_name = ws.$PrimaryMFD_name,
AuxilaryMFD_name = ws.$AuxilaryMFD_name;
// constant values (really!)
const MASSLOCK_RING_SCALE = ws.$MASSLOCK_RING_SCALE,
PRECISION = 1E-8, // standard for equality: a - b < 1E-8 => essentially equal
QUARTER_SECS_OF_4MIN = 1/960,
QUARTER_SECS_OF_2MIN = 1/480,
PI = Math.PI,
RADIANS_TO_DEGREES = 180 / PI,
//DEGREES_TO_RADIANS = PI / 180,
QUARTER_ARC = PI / 2,
FORTYFIVE_DEGREES = PI / 4,
ONE_DEGREE = PI / 180,
REL_DIR_HALF_PLUS = QUARTER_ARC + ONE_DEGREE * 2, // reduce ambiguity in some cases by excluding an axis close match
REL_DIR_HALF_MINUS = QUARTER_ARC - ONE_DEGREE * 2, // for easier nav, eg. to port & up a small bit is 90 <, not 90<^
REL_DIR_STRESS = 2, // ratio of horiz/vert angle to produce a double direction mark
VECTOR_ALL_ZEROS = [0, 0, 0],
VECTOR_ALL_ONES = [1, 1, 1];
const SPAWN_DELAY = 0.25; // fix (?) for .isVisible bug (freshly spawned ships have .isVisible == true)
// - ignore spawned ship until 1/2 second has passed
// globally local variables, 'glocals'
var TelescopeList = ws.$TelescopeList, // cached ref to back-compatible telescope object for oxp support
MaxRange = ws.$MaxRange,
mapping = ws.$SightingsMap, maplen = 0, // persistent array of Sightings
mappingReady = false, // map of size 0 can exist in interstellar -thanks Milo
curr_S = ws.$curr_Sighting, // cached ref to permantent telescope object
selected_Sightings = [], // for return value of select_Sightings fn
BuyMsg = true, //flag to show the buy message once
ps = player && player.ship,
scannerRange, scannerRange_X_2, scannerRange_X_4, scannerRange_X_10;
var curr_target, viewDirection, viewHasMLRings, identKeyPress,
viewIsStandard, // see _reposition_effects (used to decide if to alter masslock orientation)
headingView = [], // to calc heading for a view, need perpendicular horizontal vector
eq_status, equip_ok, ext_ok, grav_eq_ok, grav_eq2_ok, large_ok, small_ok, scanFilter_ok,
gravScanProgress, gs_mult, gs_state = GS_NONE, stationNearby,
alertCondition, weaponsOnline, show_on_Alert, show_on_Weapons,
ps_collisionRadius, ps_injectorsEngaged,ps_mass, ps_maxSpeed, ps_orientation,
ps_position, prev_psp, ps_speed, ps_torusEngaged, ps_velocity, moving_fast,
ps_vectorForward, ps_vectorRight, ps_vectorUp;
// - these are all set in init_player_vars()
var using_common_vars, hasAtmosphere, isBeacon, isBuoy, is_cargo, isCloaked, is_drone, isFrangible,
isHostile, is_ignored, isJamming, is_minable, isPiloted, isPlanet, isStation, isSun, isThargoid,
isVisible, isWormhole, dataKey, distance, mass, primaryRole, radius, scanClass,
script_mass, shipClassName, status, collisionRadius, gs_curr, gs_max, lb_size,
ml_size, rank, ve_colour, position = [], ent_vector = [], target_vector = [],
bounty, has_targets, targeting_ps, in_ents_Targets, in_ps_Targets, dynamicMFD, staticMFD;
// - these var.s are set as needed by local fns and are shared by all - see reset_common_vars
var prevMFDTarget = null; //support for Combat MFD
var distanceUnits = 'm', // support for navi_mfd, RandomStationNames & Stranger's world
baseDistance = 1000;
var cd = worldScripts.telescope_debug;
// : ' + + '
//debug = ws.$DebugMessages = false; // for profiling
/* turn off for profiling!!*/
function _initOxpVars() { // closure is created in startUp but some values may not be know
// until startUpComplete (order of loading oxp's is unpredictible)
try {
AstroLibrary = worldScripts.AstroLibrary;
Combat_MFD = worldScripts.combat_MFD;
Carriers = worldScripts.carriers;
Escortdeck = worldScripts.escortdeck;
FarPlanets = worldScripts.farplanets;
ILS = worldScripts.ils;
GalacticAlmanac = worldScripts.RandomStationNames;
GalNavy = worldScripts.GalNavy;
Navigation_MFD = worldScripts.navi_mfd;
PlanetaryCompass = worldScripts[ 'planetaryCompass_worldScript.js' ];
PlanetNames = worldScripts.planetnames;
SpicyHermits = worldScripts.spicy_hermits_abandoned;
SniperLock = worldScripts.sniperlock;
SniperLockPlus = worldScripts.sniperlock_plus;
TorusToSun = worldScripts.torustosun;
Towbar = worldScripts.towbar;
VariableMassLock = worldScripts.variablemasslock;
VimanaHUD = worldScripts.VimanaHUD;
WarpDrive = worldScripts.WarpDrive;
if( VimanaHUD ) {
// VimanaHUD sets TelescopeVSize & TelescopeVZoomSize in its startUp
VisualTargetCombatSize = ws.$VisualTargetCombatSize = ws.$TelescopeVSize;
VisualTargetNormalSize = ws.$VisualTargetNormalSize = ws.$TelescopeVZoomSize;
// vsizechanged = true; // force update of current model
}
updateMenuVars();
add_Sighting_errors = ws.$add_Sighting_errors;
var fps = ws.$fps_closure;
if( fps ) {
short_term_fps = fps._short_term_fps;
long_term_fps = fps._long_term_fps;
current_fps = fps._current_fps;
realtime_fps = fps._realtime_fps;
turn_on_fps_monitor = fps._turn_on_fps_monitor;
turn_off_fps_monitor = fps._turn_off_fps_monitor;
}
} catch( err ) {
log( ws.name, ws._reportError( err, '_initOxpVars' ) );
if( debug ) throw err;
}
}
function init_player_statics( ps ) { // init var that do not differ over frames
ps_maxSpeed = ps.maxSpeed;
ps_collisionRadius = ps.collisionRadius;
scannerRange = ps.scannerRange;
scannerRange_X_2 = scannerRange * 2;
scannerRange_X_4 = scannerRange * 4;
scannerRange_X_10 = scannerRange * 10;
ws.$extenderActive = ps.equipmentStatus( 'EQ_TELESCOPEEXT' ) === 'EQUIPMENT_OK';
// - other cases of changed status are handles in equipment world event handlers
}
function init_player_vars( report ) {
try {
return _init_player_vars( report );
} catch( err ) {
log( ws.name, ws._reportError( err, 'init_player_vars', report ) );
if( debug ) throw err;
}
}
function _init_player_vars( report ) { // init var that may differ from one frame to the next
let curr_ps = player && player.ship;
if( !ps || ps !== curr_ps || !ps_maxSpeed ) { // 1st time or diff ship
init_player_statics( curr_ps );
}
ps = curr_ps;
eq_status = ps.equipmentStatus( 'EQ_TELESCOPE' );
equip_ok = eq_status === 'EQUIPMENT_OK';
if( !equip_ok ) {
_shutdown_Sightings()
return false;
}
if( !ps_position ) {
ps_position = alloc_array();
prev_psp = alloc_array();
} else { // ps_position = ps.position; is faster but ensuring it's an array now will streamline any future use
if( ps_position.length )
copy_vector( ps_position, prev_psp );
copy_vector( ps.position, ps_position );
}
identKeyPress = ws.$IdentKeyPress;
let ps_target = ps.target;
if( identKeyPress === IDENT_READY ) { // else target is 'locked' on curr_S, Steering or in IdentDelay
curr_target = ps_target || null;
if( curr_target ) {
if( curr_target === curr_S.marker ) { // far target, am targeting marker
curr_target = curr_S.ent || null; // fetch real target's ent
}
} else if( curr_S.ent ) { // ensure kept in sync
_set_curr_Sighting( null, '_init_player_vars (curr_S.ent but no target)' );
}
}
ps_mass = ps.mass;
ps_speed = ps.speed;
if( WarpDrive ) {
moving_fast = ps_speed > WarpDrive.$basicMaxSpeed;
} else {
moving_fast = ps_speed > ps_maxSpeed; // injectors or torus
}
if( moving_fast && ShowVisualTarget !== 0) {
/*
* NB: setting ps_torusEngaged & ps_torusEngaged only occurs when 3D model is used
* as that's only place where they're used (this save 2 ship property get's/frame)
* If used elsewhere, remove ShowVisualTarget test
*/
ps_torusEngaged = ps.torusEngaged;
ps_injectorsEngaged = ps_torusEngaged ? false : ps.injectorsEngaged;
} else {
ps_injectorsEngaged = ps_torusEngaged = false;
}
if( !ps_velocity ) ps_velocity = alloc_array();
copy_vector( ps.velocity, ps_velocity );
if( !ps_orientation ) ps_orientation = alloc_array();
copy_quaternion( ps.orientation, ps_orientation );
basis_vectors_from_quaternion( ps_orientation )
viewDirection = ps.viewDirection;
let index = index_in_list( viewDirection, VIEWS_LIST );
viewHasMLRings = index >= 0 && index < 4 // VIEWS_LIST.length
? MassLockViewDirn & pow( 2, index ) : false; // bitflag is high
viewIsStandard = false; // used to decide orientation of masslock rings in _reposition_effects
if( viewDirection === "VIEW_FORWARD" ) {
copy_vector( ps_vectorForward, view_vector );
viewIsStandard = true;
} else if( viewDirection === "VIEW_AFT" ) {
scale_vector( ps_vectorForward, -1, view_vector );
viewIsStandard = true;
} else if( viewDirection === "VIEW_STARBOARD" ) {
copy_vector( ps_vectorRight, view_vector );
viewIsStandard = true;
} else if( viewDirection === "VIEW_PORT" ) {
scale_vector( ps_vectorRight, -1, view_vector );
viewIsStandard = true;
}
let ext_status = ps.equipmentStatus( 'EQ_TELESCOPEEXT' ) === 'EQUIPMENT_OK';
if( ext_status !== ext_ok ) { // only update when it changes
ws.$extenderActive = ext_status;
}
ext_ok = ext_status;
let grav_eq2 = ps.equipmentStatus( 'EQ_GRAVSCANNER2' );
grav_eq2_ok = grav_eq2 === 'EQUIPMENT_OK';
grav_eq_ok = ps.equipmentStatus( 'EQ_GRAVSCANNER' ) === 'EQUIPMENT_OK'
&& grav_eq2 !== 'EQUIPMENT_DAMAGED'; // vs EQUIPMENT_OK || EQUIPMENT_UNKNOWN || EQUIPMENT_UNAVAILABLE
small_ok = ps.equipmentStatus( 'EQ_SMALLDISH' ) === 'EQUIPMENT_OK';
large_ok = ps.equipmentStatus( 'EQ_LARGEDISH' ) === 'EQUIPMENT_OK';
if( report && debug ) {
log( ws.name, '_init_player_vars, ext_ok is ' + ext_ok + ', grav_eq_ok is ' + grav_eq_ok
+ ', grav_eq2_ok is ' + grav_eq2_ok + ', small_ok is ' + small_ok + ', large_ok is ' + large_ok
+ '\n player ship is ' + ps
+ '\n curr_target is ' + curr_target );
}
let mult = 1; // gravity scan + other equipment extends its range
if( large_ok ) {
mult = 2;
if( ps_mass > 1e8 ) mult = 8; //baseship scan double range third time
else if( ps_mass > 1e6 ) mult = 4; //huge player ship double range another time
} else if( small_ok ) {
mult = 1.33333;
}
gs_mult = mult;
scanFilter_ok = ps.equipmentStatus( 'EQ_MILITARY_SCANNER_FILTER' ) === 'EQUIPMENT_OK';
alertCondition = player.alertCondition;
weaponsOnline = ps.weaponsOnline;
_set_GS_state(); // uses stationNearby, grav_eq_ok, weaponsOnline & gravScanProgress
wide = gameWindow.height / gameWindow.width; ///widescreen correction
fov = gameSettings.fovValue; // player may change it
sin_fov2 = sin( fov/2 );
cos_fov2 = cos( fov/2 );
setShowFlags();
let userChanges = ws.$UserChangedSettings;
if( userChanges === 0 ) return true; // reload only when user's been busy w/ mode/activate fns
if( userChanges & SET_LIGHTBALLS ) {
LightBalls = ws.$LightBalls;
ShipLightBalls = ws.$ShipLightBalls;
LargeLightBalls = ws.$LargeLightBalls;
ws.$UserChangedSettings &= ~SET_LIGHTBALLS;
}
if( userChanges & SET_MASSLOCKRINGS ) {
MassLockRings = ws.$MassLockRings;
BrightMassLockRings = ws.$BrightMassLockRings;
ws.$UserChangedSettings &= ~SET_MASSLOCKRINGS;
}
if( userChanges & SET_SNIPER ) {
SniperMinRange = ws.$SniperMinRange;
SniperRange = ws.$SniperRange;
ws.$UserChangedSettings &= ~SET_SNIPER;
}
if( userChanges & SET_STEERING ) {
Steering = ws.$Steering;
ws.$UserChangedSettings &= ~SET_STEERING;
}
if( userChanges & SET_TARGETS ) {
MaxTargets = ws.$MaxTargets;
ws.$UserChangedSettings &= ~SET_TARGETS;
}
if( userChanges & SET_VISUAL ) {
ShowVisualTarget = ws.$ShowVisualTarget;
VisualTargetRing = ws.$VisualTargetRing;
ShowVisualStation = ws.$ShowVisualStation;
ShowVisualQuestionMark = ws.$ShowVisualQuestionMark;
VisualTargetNormalSize = ws.$VisualTargetNormalSize;
VisualTargetCombatSize = ws.$VisualTargetCombatSize;
vsizechanged = true; // force update of current model
ws.$UserChangedSettings &= ~SET_VISUAL;
}
if( userChanges & SET_VISUAL_SIZE ) {
VisualTargetNormalSize = ws.$VisualTargetNormalSize;
VisualTargetCombatSize = ws.$VisualTargetCombatSize;
vsizechanged = true; // force update of current model
ws.$UserChangedSettings &= ~SET_VISUAL_SIZE;
}
updateMenuVars();
return true;
}
function reset_common_vars() { // reset all var's that are shared by various fn's
// we don't know if we're set for curr. entity, so all set to -1 & 1st fn that needs it, sets it accordingly
// we do this to minimize the # of property gets, which are a lot more expensive than testing local vars < 0
bounty = -1;
collisionRadius = -1;
dataKey = -1;
distance = -1;
dynamicMFD = 0;
ent_vector.length = 0; // re-use array
gs_curr = -1;
gs_max = -1;
has_targets = -1;
hasAtmosphere = -1;
in_ents_Targets = -1;
in_ps_Targets = -1;
isBeacon = -1;
isBuoy = -1;
is_cargo = -1;
isCloaked = -1;
is_drone = -1;
isFrangible = -1;
isHostile = -1;
is_ignored = -1;
isJamming = -1;
is_minable = -1;
isPiloted = -1;
isPlanet = -1;
isStation = -1;
isSun = -1;
isThargoid = -1;
isVisible = -1;
isWormhole = -1;
lb_size = -1;
mass = -1;
ml_size = -1;
position.length = 0; // re-use array
primaryRole = -1;
radius = -1;
rank = -1;
scanClass = -1;
script_mass = undefined; // scriptInfo: telescope can be 0, 1, any +/- integer
shipClassName = -1;
staticMFD = 0;
status = -1;
targeting_ps = -1;
target_vector.length = 0; // re-use arrays
target_direction.length = 0;
ve_colour = -1;
using_common_vars = true;
}
function _reload_config( report ) { // reload config options chg'd on station
try {
AutoScan = ws.$AutoScan;
AutoScanMaxRange = ws.$AutoScanMaxRange;
AutoLock = ws.$AutoLock;
GravLock = ws.$GravLock;
IdentLock = ws.$IdentLock;
IdentDelay = ws.$IdentDelay;
FarStatus = ws.$FarStatus;
MaxTargets = ws.$MaxTargets;
RedAlertDist = ws.$RedAlertDist;
Steering = ws.$Steering;
LightBalls = ws.$LightBalls;
ShipLightBalls = ws.$ShipLightBalls;
LargeLightBalls = ws.$LargeLightBalls;
LightBallMinDist = ws.$LightBallMinDist;
LightBallShipMinDist = ws.$LightBallShipMinDist;
MassLockRings = ws.$MassLockRings;
MassLockViewDirn = ws.$MassLockViewDirn;
BrightMassLockRings = ws.$BrightMassLockRings;
SniperRingSize = ws.$SniperRingSize;
SniperRingActive = ws.$SniperRingActive;
SniperRange = ws.$SniperRange;
SniperMinRange = ws.$SniperMinRange;
SniperRingColor = ws.$SniperRingColor;
ShowVisualTarget = ws.$ShowVisualTarget;
VisualTargetNormalSize = ws.$VisualTargetNormalSize;
VisualTargetCombatSize = ws.$VisualTargetCombatSize;
VisualTargetRing = ws.$VisualTargetRing;
ShowVisualStation = ws.$ShowVisualStation;
ShowVisualQuestionMark = ws.$ShowVisualQuestionMark;
ModelRingColor = ws.$ModelRingColor;
//VTarget_HUD_shift = ws.$VTarget_HUD_shift; // not used in closure; gets applied in shipWillLaunchFromStation
ws.$TelescopeVPosHUD = ws.$VTarget_HUD_shift; // maintain for oxps
updateMenuVars();
// UI_and_docs
ConsoleMsgDurn = ws.$ConsoleMsgDurn;
GravScanMsgFreq = ws.$GravScanMsgFreq;
IdentMessages = ws.$IdentMessages;
// ShowSummary = ws.$ShowSummary; // not used in closure
debug = ws.$DebugMessages;
// experimental
TargetOnlyHostile = ws.$TargetOnlyHostile;
RemoveInFlight = ws.$RemoveInFlight;
MFDFiltering = ws.$MFDFiltering;
MFDPrimaryStatic = ws.$MFDPrimaryStatic;
MFDPrimaryDynamic = ws.$MFDPrimaryDynamic;
SeparateMFDs = ws.$SeparateMFDs;
MFDAuxStatic = ws.$MFDAuxStatic;
MFDAuxDynamic = ws.$MFDAuxDynamic;
// ws.$Thargoids // not used in closure; gets applied in shipWillLaunchFromStation
if( report ) _report_config();
} catch( err ) {
log( ws.name, ws._reportError( err, '_reload_config', report ) );
if( debug ) throw err;
}
}
function updateMenuVars() {
// update TelescopeMenu vars; values are 1-based index, as 0 used for description
var menu = 1; // off
if( MaxTargets < 20 )
MaxTargets = ws.$MaxTargets = 20;
else if( MaxTargets > 200 )
MaxTargets = ws.$MaxTargets = 200;
menu += MaxTargets > 100 ? 3 : MaxTargets > 50 ? 2 : MaxTargets > 20 ? 1 : 0;
ws.$TelescopeMenuTargets = menu;
ws.$TelescopeMenuSteering = Steering + 1;
menu = 1; // off
if( LightBalls ) menu = 2;
if( ShipLightBalls ) menu = 3;
if( LargeLightBalls ) menu = 4;
ws.$TelescopeMenuLightballs = menu;
menu = 1; // off
let state = ws._getShowState(), // on/off for current alert/weaps state
currFlags = ws._currMLFlags();
if( currFlags & state ) {
menu = 2;
if( BrightMassLockRings )
menu = 3;
}
ws.$TelescopeMenuMasslockRings = menu;
menu = 1; // off
if( SniperMinRange !== SniperRange ) {
let min = round(SniperMinRange / 5000); // round( SniperMinRange / 5000 )
menu = 1 + (min <= 1 ? 1 : min >= 3 ? 3 : min) + (SniperRange <= 25600 ? 0 : 3);
}
ws.$TelescopeMenuSniper = menu;
menu = 6; // all
if( !ShowVisualQuestionMark ) menu = 5;
if( !ShowVisualStation ) menu = 4;
if( !VisualTargetRing ) menu = 3;
if( ShowVisualTarget === 1 ) menu = 2;
else if( ShowVisualTarget === 0 ) menu = 1;
ws.$TelescopeMenuVisual = menu;
menu = VisualTargetCombatSize < VisualTargetNormalSize
? VisualTargetCombatSize : VisualTargetNormalSize;
ws.$TelescopeMenuVisualSize = menu > 0 ? menu : 1;
/*
if( debug ) {
log('updateMenuVars, TelescopeMenuTargets: ' + ws.$TelescopeMenuTargets
+ ', TelescopeMenuSteering: ' + ws.$TelescopeMenuSteering
+ ', TelescopeMenuLightballs: ' + ws.$TelescopeMenuLightballs
+ ', TelescopeMenuMasslockRings: ' + ws.$TelescopeMenuMasslockRings
+ ', \n\tTelescopeMenuSniper: ' + ws.$TelescopeMenuSniper
+ ', TelescopeMenuVisual: ' + ws.$TelescopeMenuVisual
+ ', TelescopeMenuVisualSize: ' + ws.$TelescopeMenuVisualSize
);
}
*/
}
function report_config( limit ) {
try {
_report_config( limit );
} catch( err ) {
log( ws.name, ws._reportError( err, 'report_config' ) );
if( debug ) throw err;
}
}
function fmtStaticFlags( stat ) {
var flag = '';
if( stat & MFD_SALVAGE ) flag += '| SALVAGE ';
if( stat & MFD_MINING ) flag += '| MINING ';
if( stat & MFD_WEAPONS ) flag += '| WEAPONS ';
if( stat & MFD_TRADERS ) flag += '| TRADERS ';
if( stat & MFD_POLICE ) flag += '| POLICE ';
if( stat & MFD_PIRATES ) flag += '| PIRATES ';
if( stat & MFD_MILITARY ) flag += '| MILITARY ';
if( stat & MFD_ALIENS ) flag += '| ALIENS ';
if( stat & MFD_NEUTRAL ) flag += '| NEUTRAL ';
if( stat & MFD_STATION ) flag += '| STATION ';
if( stat & MFD_NAVIGATION ) flag += '| NAVIGATION ';
if( stat & MFD_CELESTIAL ) flag += '| CELESTIAL ';
return flag.length ? flag + '|' : '';
}
function fmtDynFlags( dyn ) {
var flag = '';
if( dyn & MFD_FRIENDLY ) flag += '| FRIENDLY ';
if( dyn & MFD_UNSOCIABLE ) flag += '| UNSOCIABLE ';
if( dyn & MFD_ACTIVE ) flag += '| ACTIVE ';
if( dyn & MFD_HOSTILE ) flag += '| HOSTILE ';
if( dyn & MFD_NEARBY ) flag += '| NEARBY ';
if( dyn & MFD_PROTECTED ) flag += '| PROTECTED ';
if( dyn & MFD_FARAWAY ) flag += '| FARAWAY ';
return flag.length ? flag + '|' : '';
}
function fmtSteering() {
return Steering > 1 ? "'Each in list'" : Steering > 0 ? "'Nearest'" : "'Off'";
}
function fmtMassLockViewDirn() {
var flag = '';
for( let num = 0; num <= 4; num++ ) {
if( MassLockViewDirn & pow( 2, num ) )
flag += '| ' + VIEWS_LIST[ num ].slice( 5 );
}
return flag.length ? flag + ' |' : '';
}
function fmtAlertWeapsState( bitflag ) {
var flag = '';
if( bitflag & SHOW_GREEN_WEAPS_OFF ) flag += '| GREEN_OFF ';
if( bitflag & SHOW_GREEN_WEAPS_ON ) flag += '| GREEN_ON ';
if( bitflag & SHOW_YELLOW_WEAPS_OFF ) flag += '| YELLOW_OFF ';
if( bitflag & SHOW_YELLOW_WEAPS_ON ) flag += '| YELLOW_ON ';
if( bitflag & SHOW_RED_WEAPS_OFF ) flag += '| RED_OFF ';
if( bitflag & SHOW_RED_WEAPS_ON ) flag += '| RED_ON ';
if( flag.length )
return flag + '|';
return '';
}
function fmtGSMsgFreq( stat ) {
var flag = '';
if( stat & 1 ) flag += '| ++ endpoints ';
if( stat & 2 ) flag += '| ++ quarterly ';
if( stat & 4 ) flag += '| ++ tenths ';
if( stat & 8 ) flag += '| -- endpoints ';
if( stat & 16 ) flag += '| -- quarterly ';
if( stat & 32 ) flag += '| -- tenths ';
return flag.length ? flag + '|' : '';
}
function _report_config( limit ) { // also reports on experimental
var rpt, idt = ' ', pad = ',' + idt,
flags = ' -> ', nlIdt = '\n' + idt;
log( ws.name, '\n' );
if( !limit || limit === 'config' ) {
rpt = idt + 'AutoScan = ' + AutoScan
+ pad + 'AutoScanMaxRange = ' + AutoScanMaxRange
+ pad + 'AutoLock = ' + AutoLock + '°'
+ pad + 'GravLock = ' + GravLock + '°'
+ pad + 'IdentLock = ' + IdentLock + '°'
+ nlIdt + 'IdentDelay = ' + IdentDelay
+ pad + 'FarStatus = ' + FarStatus
+ pad + 'MaxTargets = ' + MaxTargets
+ pad + 'RedAlertDist = ' + RedAlertDist
+ pad + 'Steering = ' + fmtSteering()
+ nlIdt + 'LightBalls = ' + LightBalls
+ pad + 'ShipLightBalls = ' + ShipLightBalls
+ pad + 'LargeLightBalls = ' + LargeLightBalls
+ pad + 'LightBallMinDist = ' + LightBallMinDist
+ pad + 'LightBallShipMinDist = ' + LightBallShipMinDist
+ nlIdt + 'MassLockRings = ' + MassLockRings
+ flags + fmtAlertWeapsState( MassLockRings )
+ nlIdt + 'MassLockViewDirn = ' + MassLockViewDirn
+ flags + fmtMassLockViewDirn()
+ nlIdt + 'BrightMassLockRings = ' + BrightMassLockRings
+ nlIdt + 'SniperRingSize = ' + SniperRingSize
+ pad + 'SniperRingActive = ' + SniperRingActive
+ flags + fmtAlertWeapsState( SniperRingActive ) // vs binary: .toString(2)
+ nlIdt + 'SniperRange = ' + SniperRange
+ pad + 'SniperMinRange = ' + SniperMinRange
+ pad + 'SniperRingColor = ' + SniperRingColor
+ nlIdt + 'ShowVisualTarget = ' + ShowVisualTarget
+ pad + 'VisualTargetNormalSize = ' + VisualTargetNormalSize
+ pad + 'VisualTargetCombatSize = ' + VisualTargetCombatSize
+ pad + 'VisualTargetRing = ' + VisualTargetRing
+ nlIdt + 'ShowVisualStation = ' + ShowVisualStation
+ pad + 'ShowVisualQuestionMark = ' + ShowVisualQuestionMark
+ pad + 'ModelRingColor = ' + ModelRingColor
+ pad + 'ws.$VTarget_HUD_shift = ' + ws.$VTarget_HUD_shift
+ nlIdt;
log( ws.name, 'config:\n' + rpt );
}
if( !limit || limit === 'UI_and_docs' ) {
rpt = idt + 'ConsoleMsgDurn = ' + ConsoleMsgDurn
+ pad + 'GravScanMsgFreq = ' + GravScanMsgFreq
+ flags + fmtGSMsgFreq( GravScanMsgFreq )
+ nlIdt + 'IdentMessages = ' + IdentMessages
+ pad + 'ShowSummary = ' + ws.$ShowSummary // not used in closure
+ pad + 'DebugMessages = ' + debug
+ nlIdt;
log( ws.name, 'UI_and_docs:\n' + rpt );
}
if( !limit || limit === 'experimental' ) {
rpt = idt + 'MFDFiltering = ' + MFDFiltering
+ nlIdt + 'MFDPrimaryStatic = ' + MFDPrimaryStatic
+ flags + fmtStaticFlags( MFDPrimaryStatic )
+ nlIdt + 'MFDPrimaryDynamic = ' + MFDPrimaryDynamic
+ flags + fmtDynFlags( MFDPrimaryDynamic )
+ nlIdt + 'SeparateMFDs = ' + SeparateMFDs
+ nlIdt + 'MFDAuxStatic = ' + MFDAuxStatic
+ flags + fmtStaticFlags( MFDAuxStatic )
+ nlIdt + 'MFDAuxDynamic = ' + MFDAuxDynamic
+ flags + fmtDynFlags( MFDAuxDynamic )
+ nlIdt + 'Thargoids = ' + ws.$Thargoids // not used in closure; gets used in _AddShips
+ nlIdt + 'BetaLicenceTimestamp = ' + ws.$BetaLicenceTimestamp
+ pad + 'BetaLicenceSystem = ' + ws.$BetaLicenceSystem
+ '\n';
log( ws.name, 'experimental:\n' + rpt );
}
log( ws.name, '\n' );
}
var have_shutdown = false;
function shutdown_Sightings() {
try {
_shutdown_Sightings();
} catch( err ) {
log( ws.name, ws._reportError( err, '_shutdown_Sightings' ) );
if( debug ) throw err;
}
}
function _shutdown_Sightings() {
if( have_shutdown ) return;
if( debug ) log( ws.name, '_shutdown_Sightings, shutting down...');
ws.$Telescope_not_in_use = have_shutdown = true;
clear_all_pending();
if( ps ) { // in case called from dock
equip_ok = ps.equipmentStatus( 'EQ_TELESCOPE' ) === 'EQUIPMENT_OK';
if( MFD_is_visible( PrimaryMFD_name ) )
doClear_MFD( PrimaryMFD_name );
if( MFD_is_visible( AuxilaryMFD_name ) )
doClear_MFD( AuxilaryMFD_name )
}
_set_curr_Sighting( null, '_shutdown_Sightings' ); // no parms resets
ws.$IdentKeyPress = identKeyPress = IDENT_READY;
_newList();
if( ps ) // in case called from dock
_clear_HUD_Effects();
// purge pools -happens either in witchspace or when docked before garbage is collected
if( debug ) log(ws.name, '_shutdown_Sightings, used_Sightings = ' + used_Sightings.length
+ ', used_arrays = ' + used_arrays.length
+ ', used_pending = ' + used_pending.length );
used_Sightings.length = 0;
used_arrays.length = 0;
used_pending.length = 0;
if( turn_off_fps_monitor )
turn_off_fps_monitor();
}
var system_sun = null;
var system_name = null;
var isInterstellarSpace = false;
var mainPlanet = null;
var system_planets = null; // must be init'd after launch; see orbName
var system_stations = null;
function _restart_after_shutdown() { // called only once _init_player_vars() succeeds
// - see shipLaunchedFromStation & shipExitedWitchspace
try {
if( debug ) log( ws.name, '_restart_after_shutdown, starting up...');
ws.$Telescope_not_in_use = have_shutdown = false;
clearNameCaches();
system_sun = system.sun;
if( system_sun && system_sun.hasGoneNova ) // thanks Milo
system_sun = null;
system_name = system.name;
isInterstellarSpace = system.isInterstellarSpace;
mainPlanet = system.mainPlanet;
system_planets = system.planets;
system_stations = system.stations;
setDistanceUnits();
buildEclipsers();
doClear_MFD( PrimaryMFD_name );
doClear_MFD( AuxilaryMFD_name );
stationNearby = false; //to send gravscanner message after launch
gravScanProgress = 0; //begin new gravity detection process
ws.$IdentKeyPress = identKeyPress = IDENT_READY; // reset target lock
_resetIdentDelay();
if( turn_on_fps_monitor )
turn_on_fps_monitor();
} catch( err ) {
log( ws.name, ws._reportError( err, '_restart_after_shutdown' ) );
if( debug ) throw err;
}
}
function buildEclipsers() {
if( !systemEclipsers ) {
systemEclipsers = alloc_array();
} else {
systemEclipsers.length = 0;
}
// eclipsers checked from start of array, so put most likely ones first: stations, planets, ARHs, sun
for( let idx = 0, len = system_stations.length; idx < len; idx++ ) {
let ent = system_stations[ idx ];
if( ent && ent.isValid ) {
systemEclipsers.push( ent );
}
}
for( let idx = 0, len = system_planets.length; idx < len; idx++ ) {
let ent = system_planets[ idx ];
if( ent && ent.isValid ) {
systemEclipsers.push( ent );
}
}
if( SpicyHermits ) {
let abandonedRHs = entitiesWithScanClass( 'CLASS_ROCK' );
for( let idx = 0, len = abandonedRHs.length; idx < len; idx++ ) {
let ent = abandonedRHs[ idx ];
if( ent && ent.isValid
&& ent.isMinable && !ent.isFrangible ) { // normal Rock Hermits caught in system_stations
systemEclipsers.push( ent );
}
}
free_array( abandonedRHs );
}
var sun = fetchSun();
if( sun ) {
systemEclipsers.push( sun );
}
}
/* Stranger's OU is system specific
var mainOrbitVector = new Vector3D(system.sun.position.subtract(system.mainPlanet.position));
var ouScale = mainOrbitVector.magnitude(); // OU in custom system
*/
/* navi_mfd (NB: OU is static)
.$unitSetting is index into $distUnits = ["OU", "km", "m"] while $rounding = [6, 3, 0];
message += "Distance: " + dist.toFixed(this.$rounding[this.$unitSetting]) + " " + this.$distUnits[this.$unitSetting] +"\n";
from market inquirer:
unitBase = worldScripts.navi_mfd.$ostronomicalUnits[worldScripts.navi_mfd.$unitSetting];
unit = worldScripts.navi_mfd.$distUnits[worldScripts.navi_mfd.$unitSetting];
rnd = worldScripts.navi_mfd.$rounding[worldScripts.navi_mfd.$unitSetting]
...
function dist(entity, rounding) {
var distInKm = (player.ship.position.distanceTo(entity) - entity.collisionRadius)/unitBase;
return (distInKm.toFixed(rounding) + " " + unit);
};
*/
/* randomstationnames: extra units besides (static) OU (NB: strangers world overrides?)
15800 TS "Torans" == 1000 KM "Kilometres" == 905520 OU "Orthodox Units" == 1609.34 MI "Miles" == 2.08641 CZ "Cavezzi"
"Torans. One Unit is the distance travelled in one second under Torus Dilation.";
"Kilometres. One Unit is the distance travelled by light in three microseconds.";
"Orthodox Units. One Unit is the distance between the planet Lave and its Star.";
"Miles. One Unit is equal to the combined hight of nine hundred Anciant Earthians.";
"Cavezzi. One Unit is one twenty millionth the circumference of Ancient Earth.";
- all .toFixed(3) except Cavezzi which is .toFixed(0)
!must be checked upon each launch as configurable in station's F4
if (missionVariables.random_station_names_units == "Torans") var unitBase = 15800;
if (missionVariables.random_station_names_units == "Torans") var unit = "TS";
if (missionVariables.random_station_names_units == "Kilometres") var unitBase = 1000;
if (missionVariables.random_station_names_units == "Kilometres") var unit = "KM";
if (missionVariables.random_station_names_units == "Orthodox Units") var unitBase = 905520;
if (missionVariables.random_station_names_units == "Orthodox Units") var unit = "OU";
if (missionVariables.random_station_names_units == "Miles") var unitBase = 1609.34;
if (missionVariables.random_station_names_units == "Miles") var unit = "MI";
if (missionVariables.random_station_names_units == "Cavezzi") var unitBase = 2.08641;
if (missionVariables.random_station_names_units == "Cavezzi") var unit = "CZ";
var rounding = missionVariables.random_station_names_mfd_rounding; // is 3 except for CZ where it's 0
var distranceinUnits = (player.ship.position.distanceTo(entity) - entity.collisionRadius)/missionVariables.random_station_names_mfd_unitBase;
var almanacDisplayDistance = distranceinUnits.toFixed(rounding);
if (almanacDisplayDistance <0) var almanacDisplayDistance = 0;
*/
function setDistanceUnits() {
distanceUnits = 'm';
baseDistance = 1;
if( GalacticAlmanac ) {
let units = missionVariables.random_station_names_units;
// baseDistance = missionVariables.random_station_names_mfd_unitBase;
if( units === 'Torans' ) {
distanceUnits = 'TS';
baseDistance = 15800;
} else if( units === 'Kilometres' ) {
distanceUnits = 'KM';
baseDistance = 1000;
} else if( units === 'Orthodox Units' ) {
distanceUnits = 'OU';
baseDistance = 905520;
} else if( units === 'Miles' ) {
distanceUnits = 'MI';
baseDistance = 1609.34;
} else if( units === 'Cavezzi' ) {
distanceUnits = 'CZ';
baseDistance = 2.08641;
}
} else if( AstroLibrary ) {
distanceUnits = 'OU';
if( mainPlanet && system_sun ) {
let pos = system_sun.position;
if( pos )
baseDistance = pos.subtract( mainPlanet ).magnitude(); // OU in custom system
} // else continue using that from previous system
} else if( Navigation_MFD ) {
let unitSetting = Navigation_MFD.$unitSetting;
distanceUnits = Navigation_MFD.$distUnits[ unitSetting ];
baseDistance = Navigation_MFD.$ostronomicalUnits[ unitSetting ];
// cache & update every system -> _restart_after_shutdown
}
}
function index_in_list( item, list ) { // for arrays only; faster than indexOf
if( !list ) return -1;
var len = list.length;
while( len-- ) {
if( list[ len ] === item )
return len;
}
return -1;
}
function equal_value( a, b ) { return abs( a - b ) < PRECISION; }
// function abs_diff( a, b ) { return abs( abs(a) - abs(b) ); }
// vector functions & array pool //////////////////////////////////////////////////////////////
function popArrayItem( arr, idx ) { // garbage free alternative to array slice
var popped = arr[ idx ], len = arr.length;
for( var idx = idx; idx < len - 1; idx++ ) {
arr[ idx ] = arr[ idx + 1 ];
}
arr.length = --len;
return popped;
}
var used_arrays = [];
function free_array( array ) { // attempt to reduce garbage collection by managing used objects
if( !array ) return;
if( !isArray( array ) ) return;
array.length = 0; // scrub old data
used_arrays.push( array ); // toss into recycle bin
if( used_arrays.length >= 100 ) { // build up over time
used_arrays.length = 10;
if( debug ) log(ws.name, 'free_array, pool EXCEEDED 100, reduced to 10' );
}
}
function alloc_array() { // attempt to reduce garbage collection by managing used objects
if( used_arrays.length > 0 ) { // re-use old array
return used_arrays.pop();
}
return [];
}
/* From Wikipedia
Because the magnitude of the cross product goes by the sine of the angle between its arguments,
the cross product can be thought of as a measure of perpendicularity in the same way that the
dot product is a measure of parallelism. Given two unit vectors, their cross product has a
magnitude of 1 if the two are perpendicular and a magnitude of zero if the two are parallel.
The dot product of two unit vectors behaves just oppositely: it is zero when the unit vectors
are perpendicular and 1 if the unit vectors are parallel.
Unit vectors enable two convenient identities: the dot product of two unit vectors yields the
cosine (which may be positive or negative) of the angle between the two unit vectors. The
magnitude of the cross product of the two unit vectors yields the sine (which will always be positive).
*/
function chk_vparms( a, N, parm, testNaN ) { // a: vector, N: expected length, parm: callers parm #
if( !a ) ///throw( 'chk_vparms, !a' );
ws._reportError( '"a" is not defines', chk_vparms, [a, N, parm, testNaN] )
var errMsg = '';
if( isArray( a ) ) { /// insignificant difference in profiling
let len = a.length;
if( len > 0 && len < N ) { // can be 0 if re-used
errMsg = 'isArray but length too short';
// chk_vparms[ ('parm isArray but length too short') ].err = 0;
}
} else if( N === 3 && !(a instanceof Vector3D) ) {
errMsg = '!isArray and !Vector3D';
// chk_vparms[ 'parm !isArray and !Vector3D' ].err = 0;
} else if( N === 4 && !(a instanceof Quaternion) ) {
errMsg = '!isArray and !Quaternion';
// chk_vparms[ 'parm !isArray and !Quaternion' ].err = 0;
} else if( !isArray( a ) && !(a instanceof Vector3D) && !(a instanceof Quaternion) ) {
errMsg = 'is invalid for a vector';
// chk_vparms[ ('parm invalid for a vector: ' + a) ].err = 0;
}
if( errMsg ) {
errMsg = 'caller\'s #' + parm + ' parm ' + errMsg + ': ' + a;
ws._reportError( errMsg, chk_vparms, [a, N, parm, testNaN] )
/// throw( errMsg );
// chk_vparms[ errMsg ].err = 0
}
if( testNaN ) {
let len = isArray( a ) ? a.length : a instanceof Vector3D ? 3 : a instanceof Quaternion ? 4 : 0;
for( let idx = 0; len > 0 && idx < N; idx++ ) {
if( isNaN( a[ idx ] ) ) {
errMsg = 'caller\'s #' + parm + ' parm, ' + 'item ' + idx + ' isNaN: ' + a[ idx ] + ', parm: ' + a;
ws._reportError( errMsg, chk_vparms, [a, N, parm, testNaN] )
///throw( errMsg );
// log( 'chk_vparms, item ' + idx + ' isNaN: ' + a[ idx ] );
// chk_vparms[ 'an item isNaN' ].err = 0;
}
}
}
}
// NEVER use copy_vector/copy_quaternion when setting a property (eg. ps.position/ps.orientation), as this
// will generate 3/4 property gets/sets (& garbage) vs. just 1 by doing 'ps.position = my_var' (& no garbage)
// Also, ALWAYS use copy_vector/copy_quaternion when getting a property, so all subsequent calculations are
// local (ie. using arrays)
// Yes, this does generate garbage (array object) but can't be helped until core gives us a method where we
// provide the destination. Eg. ps.getPosition( my_var );
/*
function describeVector( vect ) {
var v = vect.direction();
// .dot -1..0..1 spans PI radians; abs spans PI/2
var dot2deg = RADIANS_TO_DEGREES * Math.PI / 2;
var msg = ' -> vector points ', decimals = 3;
var dotForward = v.dot( ps.vectorForward ), diffForward = Math.abs(dotForward) * dot2deg;
var dotRight = v.dot( ps.vectorRight ), diffRight = Math.abs(dotRight) * dot2deg;
var dotUp = v.dot( ps.vectorUp ), diffUp = Math.abs(dotUp) * dot2deg;
var fwdMsg = false;
if( equal_value( dotForward, 1 ) ) {
msg += 'directly on heading';
fwdMsg = true;
} else if( !equal_value( dotForward, 0 ) ) {
msg += diffForward.toFixed(decimals) + '° ';
msg += dotForward > 0 ? 'fore ' : 'aft ';
msg += dotUp > 0 ? 'of zenith' : 'of nadir';
fwdMsg = true;
}
if( !equal_value( dotRight, 0 ) ) {
if (fwdMsg) {
msg += ", ";
}
msg += diffRight.toFixed(decimals) + '° ';
msg += dotRight > 0 ? 'starboard' : 'port';
}
if( !equal_value( dotUp, 0 ) ) {
if (fwdMsg) {
msg += ", ";
}
msg += diffUp.toFixed(decimals) + '° ';
msg += dotUp > 0 ? 'up' : 'down';
}
return msg;
}
*/
function copy_vector( a, b, skipChk ) { // a -> b
if( debug && !skipChk ) { // skipChk for colors (arrays of length 4)
chk_vparms( a, 3, 1, true );
chk_vparms( b, 3, 2 );
}
b[0] = a[0];
b[1] = a[1];
b[2] = a[2];
}
function same_vectors( a, b ) { // w/i limits of PRECISION
if( debug ) {
chk_vparms( a, 3, 1, true );
chk_vparms( b, 3, 2, true );
}
if( !equal_value( b[0], a[0] ) ) return false;
if( !equal_value( b[1], a[1] ) ) return false;
if( !equal_value( b[2], a[2] ) ) return false;
return true;
}
function exact_same_vectors( a, b ) {
if( debug ) {
chk_vparms( a, 3, 1, true );
chk_vparms( b, 3, 2, true );
}
if( b[0] !== a[0] ) return false;
if( b[1] !== a[1] ) return false;
if( b[2] !== a[2] ) return false;
return true;
}
function add_vectors( a, b, c ) { // a + b -> c
if( debug ) {
chk_vparms( a, 3, 1, true );
chk_vparms( b, 3, 2, true );
chk_vparms( c, 3, 3 );
}
c[0] = a[0] + b[0];
c[1] = a[1] + b[1];
c[2] = a[2] + b[2];
}
function subtract_vectors( a, b, c ) { // a - b -> c
if( debug ) {
chk_vparms( a, 3, 1, true );
chk_vparms( b, 3, 2, true );
chk_vparms( c, 3, 3 );
}
c[0] = a[0] - b[0];
c[1] = a[1] - b[1];
c[2] = a[2] - b[2];
}
function scale_vector( a, s, b ) { // s * a -> b
if( debug ) {
chk_vparms( a, 3, 1, true );
if( typeof s !== 'number' ) log( 'scale_vector, s = ' + s );
if( typeof s !== 'number' ) scale_vector[ 'typeof s !== "number"' ].err = 0;
chk_vparms( b, 3, 2 );
}
b[0] = a[0] * s;
b[1] = a[1] * s;
b[2] = a[2] * s;
}
function vector_magnitude( a ) {
if( debug ) chk_vparms( a, 3, 1, true );
return sqrt( a[0]*a[0]
+ a[1]*a[1]
+ a[2]*a[2] );
}
function unit_vector( a, b ) { // |a| -> b
if( debug ) {
chk_vparms( a, 3, 1, true );
chk_vparms( b, 3, 2 );
}
var magnitude = vector_magnitude( a );
let abs_mag = abs( magnitude );
if( abs_mag === 0 || abs_mag === 1 ) {
copy_vector( a, b ); // return original vector
} else {
scale_vector( a, (1 / magnitude), b );
}
}
function dot_product( a, b ) {
if( debug ) {
chk_vparms( a, 3, 1, true );
chk_vparms( b, 3, 2, true );
}
return a[0]*b[0]
+ a[1]*b[1]
+ a[2]*b[2];
}
var vector = []; // working vector available to functions
var __vector = []; // internal working vectors
/* normal_dot_product
var __vector2 = []; // internal working vectors
function normal_dot_product( a, b ) {
unit_vector( a, __vector );
unit_vector( b, __vector2 );
var dot = dot_product( __vector, __vector2 );
if( dot > 1 ) dot = 1; // for identical vectors the dot_product sometimes returns a value > 1.0 because of
if( dot < -1 ) dot = -1; // rounding errors, resulting in an undefined result for the acos (see angle_between).
return dot;
}
function angle_between( a, b ) {
return acos( normal_dot_product( a, b ) );
}
*/
function angle_between_unitV( a, b ) { // faster version as 'a' is known to be a unit vector
unit_vector( b, __vector );
var dot = dot_product( a, __vector );
if( dot > 1 ) dot = 1; // for identical vectors the dot_product sometimes returns a value > 1.0 because of
if( dot < -1 ) dot = -1; // rounding errors, resulting in an undefined result for the acos (see angle_between).
return acos( dot );
}
function angle_between_two_unitV( a, b ) { // faster still as both are known to be a unit vector
var dot = dot_product( a, b );
if( dot > 1 ) dot = 1; // for identical vectors the dot_product sometimes returns a value > 1.0 because of
if( dot < -1 ) dot = -1; // rounding errors, resulting in an undefined result for the acos (see angle_between).
return acos( dot );
}
var cross = []; // working vector available to functions
function cross_product( a, b, c ) { // a X b -> c
if( debug ) {
if( a === c || b === c ) cross_product[ '' ].err = 0;
chk_vparms( a, 3, 1, true );
chk_vparms( b, 3, 2, true );
chk_vparms( c, 3, 3 );
}
c[0] = a[1]*b[2] - (a[2]*b[1]);
c[1] = a[2]*b[0] - (a[0]*b[2]);
c[2] = a[0]*b[1] - (a[1]*b[0]);
}
function copy_quaternion( a, b ) { // a -> b
if( debug ) {
if( a === b ) copy_quaternion[ 'a === b' ].err = 0;
chk_vparms( a, 4, 1, true );
chk_vparms( b, 4, 2 );
}
b[0] = a[0];
b[1] = a[1];
b[2] = a[2];
b[3] = a[3];
}
var quaternion = []; // working quaternion available to functions
/*
function quat_dot_product( a, b ) {
if( debug ) {
chk_vparms( a, 4, 1, true );
chk_vparms( b, 4, 2, true );
}
return a[0]*b[0]
+ a[1]*b[1]
+ a[2]*b[2]
+ a[3]*b[3];
}
function negate_quaternion( a, b ) { // -a -> b
if( debug ) {
if( a === b ) copy_quaternion[ 'a === b' ].err = 0;
chk_vparms( a, 4, 1, true );
chk_vparms( b, 4, 2 );
}
b[0] = -a[0];
b[1] = -a[1];
b[2] = -a[2];
b[3] = -a[3];
}
*/
function rotate_vector( vector, quat ) { // rotate vector by quat (ala rotateBy)
var that = rotate_vector;
var qw = (that.qw = that.qw || []); // working quaternion
qw.length = 0;
if( debug ) {
chk_vparms( vector, 3, 1, true );
chk_vparms( quat, 4, 2, true );
}
qw[0] = 0.0 - quat[1] * vector[0] - quat[2] * vector[1] - quat[3] * vector[2];
qw[1] = -quat[0] * vector[0] + quat[2] * vector[2] - quat[3] * vector[1];
qw[2] = -quat[0] * vector[1] + quat[3] * vector[0] - quat[1] * vector[2];
qw[3] = -quat[0] * vector[2] + quat[1] * vector[1] - quat[2] * vector[0];
vector[0] = qw[0] * -quat[1] + qw[1] * -quat[0] + qw[2] * -quat[3] - qw[3] * -quat[2];
vector[1] = qw[0] * -quat[2] + qw[2] * -quat[0] + qw[3] * -quat[1] - qw[1] * -quat[3];
vector[2] = qw[0] * -quat[3] + qw[3] * -quat[0] + qw[1] * -quat[2] - qw[2] * -quat[1];
}
function rotate_about_axis( quat, vector, angle, result ) {
var that = rotate_about_axis;
var rotn = (that.rotn = that.rotn || []);
rotn.length = 0;
if( debug ) {
if( quat === result ) rotate_about_axis[ 'quat === result' ].err = 0;
if( typeof angle !== 'number' ) log( ws.name, 'rotate_about_axis, angle = ' + angle );
if( typeof angle !== 'number' ) rotate_about_axis[ 'typeof angle !== "number"' ].err = 0;
chk_vparms( quat, 4, 1, true );
chk_vparms( vector, 3, 2, true );
chk_vparms( result, 4, 3 );
if( !equal_value( 1, vector_magnitude( vector ) ) ) {
if( debug ) {
log('rotate_about_axis, NOT a unit vector, vector: ' + vector
+ ' has magnitude: ' + vector_magnitude( vector ) );
// rotate_about_axis[ 'vector is not normalized' ].err = 0;
}
}
}
var a = angle / 2;
var c = cos(a);
var s = sin(a);
// rotation quaternion
rotn[0] = c;
rotn[1] = vector[0] * s;
rotn[2] = vector[1] * s;
rotn[3] = vector[2] * s;
// multiply quaternions
result[0] = quat[0]*rotn[0] - quat[1]*rotn[1] - quat[2]*rotn[2] - quat[3]*rotn[3];
result[1] = quat[0]*rotn[1] + quat[1]*rotn[0] + quat[2]*rotn[3] - quat[3]*rotn[2];
result[2] = quat[0]*rotn[2] + quat[2]*rotn[0] + quat[3]*rotn[1] - quat[1]*rotn[3];
result[3] = quat[0]*rotn[3] + quat[3]*rotn[0] + quat[1]*rotn[2] - quat[2]*rotn[1];
}
function vector_forward_from_quaternion( quat ) {
if( debug ) chk_vparms( quat, 4, 1, true );
var w, wy, wx;
var x, xz, xx;
var y, yz, yy;
var z, zz;
var qx, qy, qz;
w = quat[0];
x = quat[1];
y = quat[2];
z = quat[3];
xx = 2 * x; yy = 2 * y; zz = 2 * z;
wx = w * xx; wy = w * yy;
xx = x * xx; xz = x * zz;
yy = y * yy; yz = y * zz;
if( !ps_vectorForward ) ps_vectorForward = alloc_array();
if( isArray( ps_vectorForward ) ) {
qx = ps_vectorForward[0] = xz - wy;
qy = ps_vectorForward[1] = yz + wx;
qz = ps_vectorForward[2] = 1 - xx - yy;
if( qx || qy || qz ) {
unit_vector( ps_vectorForward, ps_vectorForward )
} else {
ps_vectorForward[0] = 0;
ps_vectorForward[1] = 0;
ps_vectorForward[2] = 1;
}
}
}
function basis_vectors_from_quaternion( quat ) {
if( debug ) chk_vparms( quat, 4, 1, true );
var w, wz, wy, wx;
var x, xz, xy, xx;
var y, yz, yy;
var z, zz;
var qx, qy, qz;
w = quat[0];
x = quat[1];
y = quat[2];
z = quat[3];
xx = 2 * x; yy = 2 * y; zz = 2 * z;
wx = w * xx; wy = w * yy; wz = w * zz;
xx = x * xx; xy = x * yy; xz = x * zz;
yy = y * yy; yz = y * zz;
zz = z * zz;
if( !ps_vectorRight ) ps_vectorRight = alloc_array();
if( isArray( ps_vectorRight ) ) {
qx = ps_vectorRight[0] = 1 - yy - zz;
qy = ps_vectorRight[1] = xy - wz;
qz = ps_vectorRight[2] = xz + wy;
if( qx || qy || qz ) {
unit_vector( ps_vectorRight, ps_vectorRight )
} else {
ps_vectorRight[0] = 1;
ps_vectorRight[1] = 0;
ps_vectorRight[2] = 0;
}
}
if( !ps_vectorUp ) ps_vectorUp = alloc_array();
if( isArray( ps_vectorUp ) ) {
qx = ps_vectorUp[0] = xy + wz;
qy = ps_vectorUp[1] = 1 - xx - zz;
qz = ps_vectorUp[2] = yz - wx;
if( qx || qy || qz ) {
unit_vector( ps_vectorUp, ps_vectorUp )
} else {
ps_vectorUp[0] = 0;
ps_vectorUp[1] = 1;
ps_vectorUp[2] = 0;
}
}
if( !ps_vectorForward ) ps_vectorForward = alloc_array();
if( isArray( ps_vectorForward ) ) {
qx = ps_vectorForward[0] = xz - wy;
qy = ps_vectorForward[1] = yz + wx;
qz = ps_vectorForward[2] = 1 - xx - yy;
if( qx || qy || qz ) {
unit_vector( ps_vectorForward, ps_vectorForward )
} else {
ps_vectorForward[0] = 0;
ps_vectorForward[1] = 0;
ps_vectorForward[2] = 1;
}
}
}
// event call stack ///////////////////////////////////////////////////////////////////////////
function Pending( fn, parm ) { this.fn = fn; this.parm = parm; }// constructor
var tasks_pending = [];
var tasks_deferred = []; // tasks awaiting current cycle to complete
var used_pending = [];
function fns_are_pending() { return tasks_pending.length > 0; }
function show_pending() {
if( !debug ) return;
if( tasks_pending.length > 0 ) {
let rpt = ''
for( let task in tasks_pending )
if( tasks_pending.hasOwnProperty( task ) )
rpt += '\n\t' + task + ': ' + tasks_pending[task].fn.name + '( ' + tasks_pending[task].parm + ' )';
log(ws.name, 'tasks_pending = ' + rpt );
} else {
log(ws.name, 'tasks_pending is empty ' );
}
if( tasks_deferred.length > 0 ) {
let rpt = ''
for( let task in tasks_deferred )
if( tasks_deferred.hasOwnProperty( task ) )
rpt += '\n\t' + task + ': ' + tasks_deferred[task].fn.name + '( ' + tasks_deferred[task].parm + ' )';
log(ws.name, 'tasks_deferred = ' + rpt );
} else {
log(ws.name, 'tasks_deferred is empty ' );
}
}
/* show_pending
function show_pending() { // debug
if( fns_are_pending() )
log(ws.name, 'show_pending, tasks_pending = \n' + cd._showProps( tasks_pending, 'tasks', false, 2 ) );
else
log(ws.name, 'show_pending, tasks_pending list is empty' );
if( tasks_deferred.length > 0 )
log(ws.name, 'show_pending, tasks_deferred = \n' + cd._showProps( tasks_deferred, 'tasks', false, 2 ) );
else
log(ws.name, 'show_pending, tasks_deferred list is empty' );
}
*/
function free_pending( event ) {
if( !event ) return;
event.fn = null;
event.parm = null;
used_pending.push( event );
if( used_pending.length >= 100 ) { // ?build up over time
used_pending.length = 20;
if( debug ) log(ws.name, 'free_pending, pool EXCEEDED 100, reduced to 20' );
}
}
function alloc_pending( fn, parm ) {
var event;
if( used_pending.length > 0 ) {
event = used_pending.pop();
event.fn = fn;
event.parm = parm;
} else {
event = new Pending( fn, parm );
}
return event;
}
function set_fn_pending( fn, parm, deferred ) {
var passing = parm === undefined ? null : parm; // parm could be zero
var event, list = deferred ? tasks_deferred : tasks_pending, idx = list.length;
while( idx-- ) { // no dups in stack
event = list[ idx ];
if( event.fn === fn && event.parm === passing ) {
//if( debug ) log(ws.name, 'set_fn_pending, duplicate call back function "' + fn.name
// +'", parm = '+passing+ ' ... DISCARDING.' );
return;
}
}
list.push( alloc_pending( fn, passing ) );
if( tasks_pending.length > 10 || tasks_deferred.length > 10 ) {
log(ws.name, 'set_fn_pending, stack has reached '+10+'! BAILING out by creating new Sightings ...' );
_create_Sightings();
return;
}
}
function tasks_queued( func ) {
var len = tasks_pending.length,
fname = func.name;
while( len-- > 0 ) {
if( tasks_pending[ len ].fn.name === fname ) {
return true;
}
}
len = tasks_deferred.length;
while( len-- > 0 ) {
if( tasks_deferred[ len ].fn.name === fname ) {
return true;
}
}
return false;
}
/* purge_pending
function purge_pending( func ) {
var len = tasks_pending.length,
fname = func.name;
while( len-- > 0 ) {
let fn = tasks_pending[ len ];
if( fn.name === fname )
free_pending( popArrayItem( tasks_pending, len ) );
}
len = tasks_deferred.length;
while( len-- > 0 ) {
let fn = tasks_deferred[ len ];
if( fn.name === fname)
free_pending( popArrayItem( tasks_deferred, len ) );
}
}
*/
function clear_all_pending( keep_deferred ) {
var len = tasks_pending.length;
if( len > 0 ) {
while( len-- )
free_pending( tasks_pending.pop() );
tasks_pending.length = 0;
}
if( keep_deferred ) return;
len = tasks_deferred.length;
if( len > 0 ) {
while( len-- )
free_pending( tasks_deferred.pop() );
tasks_deferred.length = 0;
}
}
function _call_pending( num ) {
try{
if( !equip_ok ) return;
var list = tasks_pending;
var len = list.length;
if( len === 0 ) {
list = tasks_deferred;
len = list.length;
if( len === 0 ) return;
}
var event, rtn;
var count = ( num === undefined ? 2 : num ); // Sighting tasks limited to num, scan tasks to 2 arbitrarily
while( len-- > 0 && count-- > 0 ) {
event = list.shift();
if( event === undefined ) throw 'list unexpectedly empty';
try {
// log('_call_pending, list: ' + (list === tasks_pending ? 'tasks_pending' : 'tasks_deferred') + ', ' + event.fn.name + '(' + event.parm + ')' );
rtn = event.fn( event.parm );
} catch( err ) {
log( ws.name, ws._reportError( err, event.fn.name, event.parm ) );
if( debug ) throw err;
} finally {
free_pending( event );
}
}
} catch( err ) {
log( ws.name, ws._reportError( err, '_call_pending', num ) );
if( debug ) throw err;
}
}
// Sighting classification ////////////////////////////////////////////////////////////////////
function is_hostile( ent, set_all ) { // returns boolean as to whether an entity is hostile
// called in different places in notable_ent
// set_all is true on update_one_Sighting for all ents
// so, using_common_vars is *assumed* true
var ent_defence, have_scanned = false;
if( isHostile === true && !set_all ) return true; // prevent repeat gets unless directed by set_all
if( distance < 0 ) distance = _detect_distanceTo( ent );
if( distance > scannerRange ) {
if( FarStatus ) { //reveal pirates over normal scanner if have already been in scannerRange
let index = _Sighting_index( ent );
if( index >= 0 ) {
have_scanned = mapping[ index ].have_scanned;
if( have_scanned !== true && have_scanned !== -1 ) {// have never been w/i scannerRange
isHostile = false;
if( set_all )
bounty = has_targets = targeting_ps = in_ents_Targets = in_ps_Targets = false;
return false;
}
}
} else { // w/o FarStatus, can only know status inside scannerRange
isHostile = false;
if( set_all )
bounty = has_targets = targeting_ps = in_ents_Targets = in_ps_Targets = false;
return false;
}
}
if( ent.hasHostileTarget ) {
isHostile = true;
if( !set_all ) return true;
}
if( bounty < 0 ) bounty = ent.bounty > 0 || ent.markedForFines;
if( bounty ) {
isHostile = true;
if( !set_all ) return true;
}
var ent_target = ent.target;
if( has_targets < 0 ) { // used to classify for MFD_ACTIVE
if( ent_target ) has_targets = true; // targeting itself is not hostile
ent_defence = ent.defenseTargets;
if( ent_defence && ent_defence.length > 0 ) // defending oneself is not hostile
has_targets = true;
}
if( in_ents_Targets < 0 )
in_ents_Targets = index_in_list( ps, ent_defence ) >= 0;
if( in_ents_Targets ) { // or in the defenseTargets of the other ship
isHostile = true;
if( !set_all ) return true;
}
if( in_ps_Targets < 0 )
in_ps_Targets = index_in_list( ent, ps.defenseTargets ) >= 0;
if( in_ps_Targets ) { // or in player's defenseTargets
isHostile = true;
if( !set_all ) return true;
}
if( targeting_ps < 0 ) targeting_ps = ent_target === ps;
if( alertCondition > YELLOW_ALERT && targeting_ps ) { // target is hostile if targeting back during a fight
isHostile = true; // otherwise, he's just checking you over
if( !set_all ) return true;
}
return isHostile < 0 ? false : isHostile;
}
function is_jamming( ent ) {
/*
http://oolite.aegidian.org/bb/viewtopic.php?f=4&t=3484#p35623
The military jammer is a complement to the cloak, not a countermeasure. It makes a ship
invisible to scanners, except to ships with a military scanner filter (who see it as a purple/orange flashing
thing). has_military_scanner_filter seems to have fallen out of that list, it’s also a “fuzzy boolean”. The
corresponding player equipment key is EQ_MILITARY_SCANNER_FILTER (and for has_military_jammer,
EQ_MILITARY_JAMMER).
*/
if( isJamming < 0 || !using_common_vars ) {
isJamming = ent.isJamming || false; // orbs lack a .isJamming prop
}
return !scanFilter_ok // player has working EQ_MILITARY_SCANNER_FILTER
&& isJamming;
}
function is_cloaked( ent ) {
/*
OoRef _Ship.htm:
isCloaked : Boolean (read/write)
true if the ship has a cloaking device which is currently active false otherwise. If the ship
has a cloaking device and sufficient energy to use it (energy > 0.75 * maxEnergy), you can
activate it by setting isCloaked to true.
isJamming : Boolean (read-only)
true if the ship has a military scanner jammer which is currently active false otherwise.
*/
if( isCloaked < 0 || !using_common_vars ) {
isCloaked = ent.isCloaked;
}
return isCloaked;
}
function is_beacon( ent ) {
if( isBeacon < 0 || !using_common_vars )
isBeacon = ent.beaconCode || ent.isBeacon;
return isBeacon;
}
function _has_good_status( ent, ent_status ) {
if( status < 0 || !using_common_vars ) // ent_status optional, save a property get if already known
status = ent_status || ent.status;
if( status === 'STATUS_IN_FLIGHT'
|| status === 'STATUS_ACTIVE'
|| status === 'STATUS_EXITING_WITCHSPACE'
|| status === 'STATUS_LAUNCHING' )
return true;
if( status === 'STATUS_EFFECT' ) {
if( isWormhole < 0 || !using_common_vars )
isWormhole = ent.isWormhole;
if( isWormhole ) {
if( collisionRadius < 0 || !using_common_vars )
collisionRadius = ent.collisionRadius;
if( collisionRadius > 0 )
return true;
}
}
return false;
}
function has_bad_status( ent, ent_status ) {
try {
return _has_bad_status( ent, ent_status );
} catch( err ) {
log( ws.name, ws._reportError( err, 'has_bad_status', [ ent, ent_status ] ) );
if( debug ) throw err;
}
}
function _has_bad_status( ent, ent_status ) {
if( !ent || !ent.isValid ) return true;
if( !using_common_vars || status < 0 )
status = ent_status || ent.status;
if( status === 'STATUS_ENTERING_WITCHSPACE' // ship jumped; needed, as ship still around after this status is achieved
|| status === 'STATUS_BEING_SCOOPED'
|| status === 'STATUS_IN_HOLD' // ditto, scooped entities stick around & will target you (slivers too!)
|| status === 'STATUS_DOCKED' // - also, if ps.target is set, it becomes null
|| status === 'STATUS_DEAD' )
return true;
if( status === 'STATUS_EFFECT' ) {
if( isWormhole < 0 || !using_common_vars )
isWormhole = ent.isWormhole;
if( isWormhole ) {
if( collisionRadius < 0 || !using_common_vars )
collisionRadius = ent.collisionRadius;
if( collisionRadius === 0 ) // evaporated
return true;
}
}
return false;
}
function is_ignored_ship( ent ) { // exclude from mapping marker, docked escorts & towed ship
if( !ent ) return true;
if( ent === curr_S.marker ) return true; // is the 'telescopemarker'
if( status < 0 || !using_common_vars ) status = ent.status;
if( _has_bad_status( ent, status ) ) return true;
if( Escortdeck ) { // skip escorts if docked
let index = index_in_list( ent, Escortdeck.$EscortDeckShip );
if( index >= 0 && Escortdeck.$EscortDeckShipPos[ index ] ) {
return true; // ent is on deck so exclude it
}
}
if( Towbar && ent === Towbar.$TowbarShip ) {
return true; //skip the towed ship
}
return false;
}
function read_scriptInfo( ent, map ) { //read detection from the scriptInfo telescope entry & * set mass *
if( !map || map.script_mass === undefined ) {
let info = ent.scriptInfo; // can give custom mass to the ship/station
if( info && info.telescope ) {
script_mass = parseInt( info.telescope, 10 );
} else {
script_mass = null;
if( map ) map.script_mass = null;
if( mass < 0 ) mass = ent.mass;
return null;
}
} else {
script_mass = map.script_mass;
}
// * Positive integer: give new mass to the ship in kg which can increase the gravity detection.
// * Negative integer: will be substracted from the ship.mass in kg to reduce gravity detection.
// * 0: detected within normal scanner only as without telescope.
// * 1: detected in visible range only (due to gravity scanner can see a ship with 1kg mass from 2km only).
if( script_mass > 0 ) {
mass = script_mass;
return script_mass;
}
if( mass < 0 ) mass = ent.mass;
if( script_mass < 0 ) { //substract the mass instead of overwrite it
mass += script_mass;
}
return script_mass;
}
function getDetected( ent, restoring ) { //read detection from the scriptInfo telescope entry; no need to
// check using_common_vars, as only called from notable_ent
// a check for beacons is NOT done here for oxp's with ents
// that are to remain hidden but insist on having beacons
// - radio signals are pervasive except when ... \_O_/
if( is_cloaked( ent ) ) return false; // is_jamming ents can be seen, just not targetted
if( distance < 0 ) distance = _detect_distanceTo( ent );
if( scanClass === 'CLASS_CARGO' // ent must 1st be detected w/i scannerRange
&& (!mk_maps || restoring) ) { // no grow_hidden_scanned array in update cycle
let index = _Sighting_index( ent ); // need to test RFID range
let limit = scannerRange; // newly discovered must be inside scannerRange
limit += !restoring && index < 0 ? 0 // updating & not in mapping
: randomInt( 0, scannerRange >> 1 ); // while known ones detectable by RFID
if( distance > limit ) // RFID not yet detected or lost to background noise
return false; // (telescope DB garbage collects data on lost RFID, so must re-aquire)
}
if( script_mass === undefined ) // has never been read
script_mass = read_scriptInfo( ent ); //can give custom detection distance to the target ship
if( script_mass === 0 ) // script_mass = 0: detected within normal scanner only as without telescope.
return distance < scannerRange; // => am assuming they're not to be detected beyond scannerRange, else mk work like cargo
// "once detected then tracked over scanner range while in visible range until next scan (small help and save performance)"
if( script_mass === 1 ) { // script_mass = 1: detected in visible range only
if( isVisible < 0 ) isVisible = ent.isVisible;
if( !isVisible || (distance > scannerRange && !ext_ok) )
return false;
}
if( script_mass === null ) { // old code has any script_mass precluding station test
if( isStation < 0 ) isStation = ent.isStation;
if( isStation ) { //hide custom station over 4x scanner range
if( primaryRole < 0 ) primaryRole = ent.primaryRole;// 9 compares profiles twice as fast as using index_in_list
if( primaryRole && primaryRole !== 'station' && primaryRole !== 'coriolis' && primaryRole !== 'dodo'
&& primaryRole !== 'dodec' && primaryRole !== 'dodecahedron' && primaryRole !== 'ico'
&& primaryRole !== 'icosa' && primaryRole !== 'icosahedron' && primaryRole.substring(0, 10) !== 'rockhermit' )
// rockhermit role can be "rockhermit", "rockhermit-chaotic", "rockhermit-pirate" and more in the future?
if( distance > scannerRange_X_4 ) return false;
}
}
//stealth ships and non-standard stations detected in normal scanner range only, requested by Svengali
if( dataKey < 0 ) dataKey = ent.dataKey;
if( dataKey ) {
if( dataKey === 'vector_arn' || dataKey.indexOf( 'stealth' ) >= 0 ) {
if( distance > scannerRange ) return false; //mission ship in Vector OXP
}
}
if( primaryRole < 0 ) primaryRole = ent.primaryRole;
if( primaryRole ) {
if( primaryRole.indexOf( 'stealth' ) >= 0 || primaryRole.indexOf( 'rescue_blackbox' ) >= 0 ) {
if( distance > scannerRange ) return false; //mission ships in Rescue Stations OXP
}
}
return true;
}
// Sighting distance calculations /////////////////////////////////////////////////////////////
function hullOffset( ent ) {
var offset = 0;
if( radius < 0 || !using_common_vars )
radius = ent.radius || false;
if( radius ) { // distance to near surface
offset = radius;
} else { // distance to near (hull) surface
if( collisionRadius < 0 || !using_common_vars )
collisionRadius = ent.collisionRadius;
offset = collisionRadius;
}
return offset;
}
// dist for near vs. far targets is when .distanceTo === scannerRange, regardless of any radius/collisionRadius
// - core crosshair shows .distanceTo less cr of target
// => marker should read _detect_distanceTo, ie. position.distanceTo - ent.collisionRadius
function _detect_distanceTo( ent ) { // to x; distanceTo gives distance to centers, not hulls
try {
/// some of this function is duplicated in _reposition_effects for speed
var that = _detect_distanceTo;
var distanceTo = (that.distanceTo = that.distanceTo || []);
distanceTo.length = 0;
if( (ps_position && ps_position.length === 0) || !using_common_vars ) { // set every frame
copy_vector( ps.position, ps_position );
}
subtract_vectors( ps_position, ent.position, distanceTo );
var distTo = vector_magnitude( distanceTo ); // dist from ship's hull to x's center; NB: core crosshairs give hull to hull
distTo -= hullOffset( ent ); // distance to near surface
return distTo < 0 ? 0 : distTo;
} catch( err ) {
log( ws.name, ', ent.position: ' + ent.position + ', distTo: ' + distTo
+ ', radius: ' + radius + ', collisionRadius: ' + collisionRadius );
ws._reportError( err, _detect_distanceTo, ent );
}
}
function grav_scan_dist( ent, rtn_curr, map ) { // return gravity scan distance for ent
if( radius < 0 ) radius = ent.radius || false;
if( radius ) return -1; // ignore planets, moons & sun
if( script_mass === undefined ) // will set 'mass' variable if required, ie. calling read_scriptInfo() sets both
script_mass = read_scriptInfo( ent, map ); // mass & script_mass, if had not already been called (if script_mass === undefined )
if( mass === 0 ) return -1; // ignore wormholes
var dist;
if( !rtn_curr || gravScanProgress === 1 ) { // return max. gravity scan detection distance
if( !map ) { // "mass of the target in kg must be larger than d2*d2*d2/100 where d2 = distance*2 in km"
dist = pow( 100 * mass, 1/3 ) // invert 'mass > d2*d2*d2/100' => 'd2 < cube root(mass * 100)'
* gs_mult * 500; // '* 500' to cnv to meters: 'd2 = distance*2 in km' => 'distance = (1000 * d2)/2'
return dist;
}
return map.gs_max_dist; // skip calc if poss. (when updating)
} else if( gravScanProgress > 0 && gravScanProgress < 1 ) { // grav. scan progress varies by mass, so distance varies as the cube root of mass,
dist = pow( 100 * gravScanProgress * mass, 1/3 ) // thus the 2nd call of grav_scan_dist (can't just scale, ie. use gravScanProgress * max)
* gs_mult * 500;
return dist;
}
return 0; // because gravScanProgress === 0
}
// Sighting creation & recycling pool /////////////////////////////////////////////////////////
var used_Sightings = [];
function free_Sighting( map ) { // attempt to reduce garbage collection by managing used objects
if( !map ) return;
// scrub old data
// these 3 set in init_Sighting
map.ent = null;
map.last_posn.length = 0;
map.entityPersonality = -1; // unique ID# for spreading updates across frames
// and generating random detection distance for cargos
// these 11 set in mk_Sighting
map.rank = -1; // category used for sorting
map.ent_dist = -1; // distance to entity measured by whatever equipment is installed
map.gs_curr_dist = -1; // distance grows as grav. scan progresses
map.gs_max_dist = -1; // max. grav. scan distance, calc'd on creation
map.script_mass = undefined; // save scriptInfo so read_scriptInfo() only called once/ent (don't
// init to null, as set null when we know there's no scriptInfo
// => needs to be init'd as undefined
map.dynamicMFD = 0; // MFD flags for dynamic properties
map.staticMFD = 0; // MFD flags for static properties
map.headingTo = 180; // in degrees, offset from player's vectorForward; init behind so not immediately found
map.ve_colour = ''; // visualEffect colour
map.hasJammer = false; // if true, name is not cached as different if on/off
map.ml_radius = 0; // for support of VariableMassLock
map.have_scanned = false; // for support of scriptInfo = { telescope = 0 }; => set to true
// also used for cargo RFID => set to a detection range > 0
// and FarStatus => set -1 when come w/i scannerRange
// these may be set in update_one_Sighting et al
map.lb_size = ''; // lightball size
map.ml_size = ''; // masslock ring size
let effect = map.lightball; // visualEffect ref. if any
if( effect ) {
effect.remove();
map.lightball = null;
}
effect = map.masslock; // visualEffect ref. if any
if( effect ) {
effect.remove();
map.masslock = null;
}
// toss into recycle bin
used_Sightings.push( map );
if( used_Sightings.length >= 300 ) { // ?build up over time
used_Sightings.length = 50;
if( debug ) log(ws.name, 'free_Sighting, pool EXCEEDED 300, reduced to 50' );
}
}
function alloc_Sighting() { // attempt to reduce garbage collection by managing used objects
if( used_Sightings.length > 0 ) { // re-use old map
return used_Sightings.pop();
}
return {};
}
function mkSighting( ent ) { // assumes 'rank' has been set before calling!
var map = alloc_Sighting();
map.ent = ent;
if( position.length === 0 ) // not already set
copy_vector( ent.position, position );
if( !map.last_posn ) map.last_posn = alloc_array();
copy_vector( position, map.last_posn ); // position @ time of last scan/update -> $ListPos
if( scanClass === 'CLASS_NO_DRAW' ) { // celestial objects have no personality; [32768, 49151]
map.entityPersonality = 32768 + floor((position[0] + position[1] + position[2]) % 16384);
} else if( scanClass === 'CLASS_WORMHOLE' ) { // don't use collisionRadius, as it varies; [49152, 65535]
let spawnTime = ent.spawnTime;
spawnTime -= floor(spawnTime); // fractional part only; [0, 1]
map.entityPersonality = 49152 + floor(spawnTime * 16384);
} else { // normal entities limited to [0, 32767]
map.entityPersonality = ent.entityPersonality;
}
map.rank = rank; // category used in sorting
if( distance < 0 ) distance = _detect_distanceTo( ent );
map.ent_dist = distance;
if( gs_curr < 0 ) gs_curr = grav_scan_dist( ent, true );
map.gs_curr_dist = gs_curr;
if( gs_max < 0 ) gs_max = grav_scan_dist( ent );
map.gs_max_dist = gs_max;
if( script_mass === undefined )
script_mass = read_scriptInfo( ent ); // also sets 'mass'
map.script_mass = script_mass;
map.dynamicMFD = dynamicMFD;
map.staticMFD = staticMFD;
map.headingTo = 180;
if( radius < 0 ) radius = ent.radius || false; // ents available to be swapped out for closer ones
map.swapable = !radius && !is_beacon( ent ); // are those that are not orbs and not beacons
is_jamming( ent ); // sets isJamming; return includes scanFilter_ok which we ignore here
map.hasJammer = isJamming; // never gets set false, need to know ent has one, not if it's turned on
// - used to bypass naming cache, as becomes unknown-ship if it's on
if( isWormhole < 0 ) isWormhole = ent.isWormhole;
if( isWormhole && !ent.name ) ent.name = 'wormhole';
if( ve_colour !== -1 ) map.ve_colour = ve_colour;
if( VariableMassLock ) {
let ml_radius = VariableMassLock.$Range( mass ); // mass * 0.02 + 17000 //small masslock radius of this ship
// VariableMassLock scales masslock radius between Adder (16 t) = 17 km -> Anaconda (438 t) = 26 km
// - doesn't enforce an upper bound, so ships heavier than Anaconda will have even larger radius!
// - he's using checkScanner, so never deals w/ ships beyond scannerRange
map.ml_radius = ml_radius > scannerRange ? scannerRange // upper limit on ring size
: ml_radius;
}
map.have_scanned = false;
if( script_mass === 0 ) { // ents w/ scriptInfo telescope = 0 use .have_scanned
map.have_scanned = true; // to be remembered beyond scannerRange once detected within
// for entities; with scriptInfo.telescope=0, entities are hidden until inside scanner
// range, "but once detected then tracked over scanner range while in visible range until next scan"
} else if( is_cargo === true ) {
map.have_scanned = scannerRange + (map.entityPersonality >> 1);// pod's RFID range
// .have_scanned used for cargo's extended range (RFID tracking) once it's entered scannerRange
}
return map;
}
// Sighting functions /////////////////////////////////////////////////////////////////////////
function Sighting_index( ent ) {
try {
return _Sighting_index( ent );
} catch( err ) {
log( ws.name, ws._reportError( err, 'Sighting_index', ent ) );
if( debug ) throw err;
}
}
function _Sighting_index( ent /*,caller*/ ) { // ent can be either a Sighting or entity
if( !equip_ok ) return -1;
if( !ent ) return -1;
if( mapping === null ) return -1; // mapping not yet initialized or empty list
if( ent.ent_dist ) // ent is a Sighting
return index_in_list( ent, mapping );
var target = ent === curr_S.marker ? curr_S.ent : ent;
if( !target || !target.isValid ) return -1; // target died
for( let idx = 0; idx < maplen; idx++ ) {
let map = mapping[ idx ];
if( target === map.ent ) return idx;
}
return -1;
}
function set_curr_Sighting( ent, caller ) {
try {
_set_curr_Sighting( ent, caller );
} catch( err ) {
log( ws.name, ws._reportError( err, 'set_curr_Sighting', ent ) );
if( debug ) throw err;
}
}
function _set_curr_Sighting( ent /*,caller */ ) { // ent can be an entity or map to be located
if( ent === undefined || ent === null ) { // no parm is signal to reset
curr_target = curr_S.ent = curr_S.map = null;
curr_S.index = -1;
if( curr_S.marker ) removeMarker(); // cannot hang onto it for next target, as still see lollipop
curr_S.name = '';
_clear_HUD_Effects(); // clear model, ring & sniper ring
ws.$IdentKeyPress = identKeyPress = IDENT_READY; // ensure it's reset
TelescopeList[ 0 ] = null;
ws.$TelescopeListi = 0; // $TelescopeListi = 0 => not in $TelescopeList
// if( debug ) log('_set_curr_Sighting, curr_S cleared by ' + caller);
return;
}
var index = _Sighting_index( ent, '_set_curr_Sighting' );
if( index < 0 || index >= maplen ) {
_set_curr_Sighting( null, '_set_curr_Sighting' ); // recurse to reset
return;
}
var map = mapping[ index ];
curr_S.map = map;
curr_target = curr_S.ent = map.ent;
curr_S.index = index;
// curr_S.marker, curr_S.marker_type remain unchanged unless explicity changed by marker code
if( curr_S.marker ) { // oxp's can retrieve telescope target via marker.target
curr_S.marker.$TelescopeTarget = curr_target;
}
TelescopeList[ 0 ] = curr_target; // for oxp support, $TelescopeList is always an array of 1 entity
ws.$TelescopeListi = 1; // and $TelescopeListi is 1 if have a target, 0 otherwise
// if( debug ) log('_set_curr_Sighting, (IdentKeyPress='+identKeyPress +') curr_S set to '
// + (map.name ? map.name : map.ent.displayName || map.ent.name) + ' by ' + caller);
}
function farthestToSwap( chkRank, chkDist ) { // return map for a ship to swap out of mapping
var maxDist = 0,
farthest = null,
secondBest = null,
psTarget = curr_S.map;
var saved_isBeacon = isBeacon, // preserve so don't repeat property get
was_using_common_vars = using_common_vars; // (beaconCode is not a common var)
isBeacon = -1;
using_common_vars = false;
for( let idx = 0; idx < maplen; idx++ ) {
let map = mapping[ idx ];
if( map === psTarget ) continue; // is player's target
if( map.rank < chkRank ) continue; // cannot swap more important entity
// - can use string compare as ranks are named to be alphabetically increasing
if( !map.swapable ) continue; // orbs and beacons are always maintained
// in mapping, ie. not available for swap
let map_dist = map.ent_dist;
if( map_dist < chkDist ) continue; // cannot swap closer entity
if( map_dist > maxDist ) {
maxDist = map_dist;
if( farthest )
secondBest = farthest; // also a candidate for deletion
farthest = idx;
}
}
isBeacon = saved_isBeacon;
using_common_vars = was_using_common_vars;
if( secondBest !== null ) {
if( secondBest < farthest ) {
farthest--;
}
_delete_Sighting( secondBest );
}
return farthest;
}
function numberSwapable() {
var swapable = 0;
for( let idx = 0, len = mapping.length; idx < len; idx++ ) {
if( mapping[idx].swapable ) swapable++;
}
return swapable;
}
function add_Sighting( ent, is_notable, forced ) {
try {
return _add_Sighting( ent, is_notable, forced );
} catch( err ) {
log( ws.name, ws._reportError( err, 'add_Sighting', [ ent, is_notable ] ) );
if( debug ) throw err;
}
}
function _add_Sighting( ent, is_notable, forced /*, caller */ ) {
if( !equip_ok ) return;
if( !mappingReady ) return -1; // launching/exiting witchspace & haven't made 1st _Scan()
if( !ent || !ent.isValid ) return -3; // ship died, docked or jumped
if( !ps || !ps.isValid || alertCondition === DOCKED ) //if player died or docked
return -4;
if( _Sighting_index( ent, '_add_Sighting' ) >= 0 ) { // already in mapping
return -5;
}
let now = clock.absoluteSeconds;
let spawned = ent.spawnTime;
if( spawned > 0 && now - spawned < SPAWN_DELAY ) // too new; will be picked up as a new target
return -10;
scanClass = ent.scanClass;
radius = ent.radius || false;
if( scanClass === 'CLASS_NO_DRAW' && !radius ) { // not an orb, probably wreckage
return -9;
}
status = ent.status;
if( !_has_good_status( ent, status ) ) {
return -6;
}
if( !is_notable ) { // else was done already in check_if_new_targets
let save_status = status,
save_scanClass = scanClass,
save_radius = radius; // preserve so don't repeat property get
reset_common_vars();
status = save_status;
scanClass = save_scanClass;
radius = save_radius;
if( !notable_ent( ent ) ) { // sets rank, ve_colour & (maybe) distance
using_common_vars = false;
return -7;
}
}
if( rank === 'ukn' ) { // must be detected before it can become lost
return -8; // - rank may be set by caller
}
if( distance < 0 ) distance = _detect_distanceTo( ent ); // needed if call farthestToSwap (used in mkSighting)
let swapable = numberSwapable(); // orbs & beacons excluded from MaxTargets
if( !forced
&& alertCondition !== RED_ALERT /// until someone complains, exclude RED_ALERT from MaxTargets restriction
&& swapable >= MaxTargets ) { // mapping is full, swap if ent is closer or a priority
let swapIdx = farthestToSwap( rank, distance );
if( swapIdx ) { // found one futher out from ent
free_Sighting( popArrayItem( mapping, swapIdx ) );
maplen = mapping.length;
} else {
return swapable > 0 ? -8 : -2;
// -2 used for 'memory full' message; -8 => something else stopped the insert
}
}
found_new = true;
var map = mkSighting( ent );
mapping.push( map );
maplen++;
var insert_i = maplen - 1;
update_one_Sighting( map, ent, insert_i );
if( curr_target === ent && !profiling ) {
_manage_marker( map, false, '_add_Sighting' );
}
if( !is_notable ) {
using_common_vars = false;
}
return insert_i;
}
function delete_Sighting( ent, caller ) {
try {
_delete_Sighting( ent, caller );
} catch( err ) {
log( ws.name, ws._reportError( err, 'delete_Sighting' ) );
if( debug ) throw err;
}
}
function _delete_Sighting( ent /*, caller */) { // ent can be an ent, Sighting or index (faster)
if( !equip_ok ) return;
if( !mappingReady || maplen === 0 ) return; // mapping not yet initialized OR it's empty
if( ent === null && ent === undefined ) return;
var index = typeof( ent ) === 'number' ? ent : null;
if( index !== null && (index < 0 || index >= maplen ) ) return;
var found = -1, map = null;
if( index !== null ) { // were passed an index, will ignore ent arg
found = index;
map = mapping[ found ];
} else if( ent.ent !== undefined ) { // were passed a map
map = ent;
found = index_in_list( map, mapping );
if( found < 0 ) {
if( map === curr_S.map ) {
_set_curr_Sighting( null, '_delete_Sighting' );
}
return;
}
} else { // were passed an ent
found = _Sighting_index( ent, '_delete_Sighting' );
if( found < 0 ) {
return;
}
map = mapping[ found ];
}
if( map === curr_S.map ) { // is player's target
_set_curr_Sighting( null, '_delete_Sighting2' ); // no parms resets $curr_Sighting
}
free_Sighting( popArrayItem( mapping, found ) );
maplen = mapping.length;
}
const RANK_STRS = [ 'bad', 'isr', 'loc', 'mng', 'nsr', 'orb', 'ukn' ];
// names for grouping hostiles(bad), ships in scannerRange(isr), stations & cargo(loc), mining stuff(mng),
// far targets(nsr), sun/planet/moon(orb), lost targets(ukn)
function select_Sightings( count, rank, compare ) { // 'count' (0 => all); 'rank' (0 => all) & 'compare' are optional but need at least 1
// 'rank' limits search to that group; 'compare' is a boolean function applied to each
// NB: return null or static array selected_Sightings
if( !equip_ok ) return null;
if( !mappingReady || maplen === 0 ) return null; // launching/exiting witchspace & haven't made 1st mapping OR it's empty
if( count === undefined ) return null; // no point selecting entire list! (can be zero)
do {
if( compare ) break;
if( rank === 0 ) break; // all ranks
if( index_in_list( rank, RANK_STRS ) >= 0 ) break; // a valid rank
return null; // have to specify at least compare fn or some rank
} while( false );
selected_Sightings.length = 0; // remove any previous results
var num = count ? count : maplen; // if no count, default to all
for( let idx = 0; idx < maplen; idx++ ) {
let map = mapping[ idx ];
if( rank && rank !== map.rank ) continue;
if( compare && !compare( map ) ) continue;
selected_Sightings.push( map );
num--;
if( num <= 0 ) break;
}
return selected_Sightings;
}
function check_Sightings( parm ) { // loop through Sightings & delete those no longer valid
var that = check_Sightings; // NB: neither update can delete ents that become !notable (expensive)
if( that.blocked === undefined ) that.blocked = 0; // so that must (eventually) happen here
if( that.adjusted === undefined ) that.adjusted = 0;
var adjusted = that.adjusted,
blocked = that.blocked;
if( !equip_ok ) return;
if( !mappingReady || maplen === 0 ) return; // mapping not yet initialized OR empty
if( fns_are_pending() && parm === false ) { // called in midst of creating a new mapping, abort!
blocked = that.blocked = blocked + 1; // count blocked full checks (can get blocked by update on slow PCs)
if( blocked < 3 ) return; // each called once/second, prevent blocks longer than 2 sec
} // wait for full, not quick check to unblock
if( parm === false ) that.blocked = 0; // checking, so reset counter
var starting, index, quickly, fps, del;
if( parm === true ) {
quickly = that.quickly = true;
fps = that.fps = current_fps ? current_fps() : -1; // quickly is fast, so check fps/frame
if( fps < 0 ) fps = that.fps = 30; // current_fps returns -1 until 1st min. has passed
starting = index = maplen;
} else if( parm === false || parm === undefined ) {
quickly = that.quickly = false;
fps = current_fps ? current_fps() : -1;
if( fps < 0 ) fps = 30; // current_fps returns -1 until 1st min. has passed
fps = that.fps = floor(fps / (5 - adjusted)); // store as fn prop for next frames' execution
starting = index = maplen; // - #/frame scales w/ framerate; override increases #/frame to reduce discarded calls
} else { // parm is an index # to resume
quickly = that.quickly || true;
fps = that.fps || 6;
starting = maplen;
index = parm;
}
using_common_vars = !quickly;
while( index-- ) { // work backwards thus list, so indices are simple
let map = mapping[ index ];
if( !map ) continue;
if( index > 0 && index % fps === 0 ) { // checking list can take more time than we'd like in a frame
set_fn_pending( check_Sightings, index ); // @ 43 fps, 0.33 ms/Sighting; fps/5 = 8 => 2.67 ms
return; // so do a chunk each frame, its size a fn of fps
}
let ent = map.ent;
if( quickly ) {
scanClass = collisionRadius = isVisible = -1;
} else {
reset_common_vars();
}
isWormhole = ent ? ent.isWormhole : false;
if( isWormhole ) _handle_wormhole( ent ); // keep an eye on clock to annouce destination
del = true;
do {
if( !ent || !ent.isValid ) break; // ship destroyed or wormhole expired
status = ent.status;
if( _has_bad_status( ent, status ) ) break;
distance = map.ent_dist; // needs to be set for notable_ent
if( distance < scannerRange && is_cloaked( ent ) ) // range check limits calls to is_cloaked
break;
if( scanClass < 0 ) scanClass = ent.scanClass;
if( scanClass === 'CLASS_CARGO' && is_ignored_ship( ent ) ) break;
if( collisionRadius < 0 ) collisionRadius = ent.collisionRadius;
if( isWormhole && collisionRadius === 0 ) break; // wormhole expired
let have_scanned = map.have_scanned;
if( have_scanned === true && !ent.isVisible ) break;// lose sight of hidden (scriptInfo{ telescope= 0;})
if( typeof have_scanned === 'number' && have_scanned !== -1
&& distance > have_scanned ) // lose cargo RFID in background noise
break;
if( quickly ) { // restrict check to the above for speed
del = false;
break;
}
if( notable_ent( ent, false, distance ) ) del = false;
} while( false );
if( del ) {
// if( debug ) log('check_Sightings, deleting (' + ent.entityPersonality + '): ' + ent.name );
_delete_Sighting( index, 'check_Sightings' + (quickly ? ' -quickly' :'') ); // not ok, remove
}
}
if( !quickly ) using_common_vars = false;
// if( debug && starting !== maplen ) {
// log('check_Sightings, started w/ ' + starting + ', ended w/ ' + maplen );
// }
}
// user Sighting functions ////////////////////////////////////////////////////////////////////
function chg_curr_Sighting( step ) {
try {
_chg_curr_Sighting( step );
} catch( err ) {
log( ws.name, ws._reportError( err, 'chg_curr_Sighting', step ) );
if( debug ) throw err;
}
}
///
function _chg_curr_Sighting( step ) { // incr or decr used when user steps through the Sightings
var that = _chg_curr_Sighting;
if( that.last_curr_S === undefined ) that.last_curr_S = null;// to remember across calls
var last_curr_S = that.last_curr_S;
if( !mappingReady || maplen === 0 ) return; // map not initialized OR empty
check_Sightings( true ); // clear out any that died, jumped or scooped
var map, ent, index, skip_scan = false;
mapping.sort( map_sort_dist ); // match sort of mfd
// mapping.sort( map_sort_rank_dist );
///??with filtering, must remember what's in mfd(s) and follow that
/// vs leave as in 1.15, stepping thru all
///?if !separate && !filtering, scroll primary MFD
///?elif !filtering, jump to aux & scroll it
if( last_curr_S ) { // resuming after we forced a rescan by wrapping end of list
index = _Sighting_index( last_curr_S, '_chg_curr_Sighting' );
last_curr_S = that.last_curr_S = null;
skip_scan = true;
} else {
index = _Sighting_index( curr_S.map, '_chg_curr_Sighting' );
}
if( index < 0 ) { // no target, start @ list end
index = step > 0 ? maplen - 1 : 0;
skip_scan = true;
}
map = index >= 0 && index < maplen ? mapping[ index ] : null;
if( !map ) { // no target, default to start of list (may have been removed by rescan)
_set_curr_Sighting( maplen > 0 ? mapping[ 0 ] : null, '_chg_curr_Sighting' );
last_curr_S = that.last_curr_S = null; // ensure it's cleared before premature exit
return;
}
do {
index += step;
if( index >= maplen || index < 0 ) { // past an end of the mapping
if( !skip_scan ) { // so create a new one
last_curr_S = that.last_curr_S = map.ent; // remember which ent to resume at
_auto_updates( true ); // true initiates create (which wipes deferred tasks), suppresses call to _manage_marker in _auto_updates
set_fn_pending( _chg_curr_Sighting, step, true );// true sets a 'deferred' task for after update
return;
} else { // back from creating a new one
index = index < 0 ? maplen - 1 : 0;
}
}
map = mapping[ index ];
ent = map.ent;
} while( ( ent && ent.isWormhole // after wormhole scanner started, they become normal Sightings but
&& ent.$TelescopeScanStart === undefined ) ); // not until manually targetted (ie. 'r' button), req'd by core
_manage_marker( map, map.ent_dist > scannerRange, // distance test prevents double msgs on near targets
'_chg_curr_Sighting' ); // ours & ident msg (still get double @ transition, on telescope marker)
if( Steering === 2 ) {
// ||( Steering === 1 && ent === findNearestEnt() ) ?is player expecting to steer to nearest when stepping through list
start_Steering(); //turn to the target
}
// suspend _mostCentered while user is stepping
// - there is no event to tell us player has stopped stepping, so a time limit is used
// - delay_counter is set to double that of IdentDelay, which gets reset when no longer
// stepping
// - mode() calls _resetIdentDelay if player switches to a different function
// (all activated() does is call this fn)
// NB: if weaponsOnline, the stepped ent remains current target (as _mostCentered not called unless have no target)
// else navigation mode reasserts itself using GravLock
delay_counter = IdentDelay * 2; // suspend mostCentered for twice IdentDelay
ws.$IdentKeyPress = identKeyPress = IDENT_STEP_DELAY; // this starts IdentDelay counter once steering complete
}
function targeting_player( map ) {
// called from find_most_central & _nearest_Sighting (via select_Sightings) only in RED_ALERT
// checks needed, as eg. scooped splinters/cargo targets player
var ent = map.ent;
var target = ent.target;
if( target !== ps ) return false;
if( !ent.isShip && !ent.isStation ) return false;
if( ent.isDerelict ) return false; // don't lock on derelicts when in combat (dybal)
if( weaponsOnline && TargetOnlyHostile) { // limit to hostile targetters (no cargo, pods, etc.)
/// mk an option so player can toggle this fn, default false (like prev. ver.s)
let weap = target.currentWeapon;
if( !weap || weap.equipmentKey === 'EQ_WEAPON_NONE' )
return false;
return true;
}
return true;
}
function findNearestEnt() {
var list = mapping,
len = maplen;
if( alertCondition > YELLOW_ALERT && weaponsOnline ) {
//in Red Alert lock the last attacker if any and weapons are online
list = select_Sightings( 0, 0, targeting_player ); // first, target those targeting player; 0 => all, 0 => any rank
len = list && list.length;
if( !len ) {
list = select_Sightings( 0, 'bad' ); // none, try any hostiles; 0 => all
len = list && list.length;
}
if( !len ) {
list = mapping; // none found, search entire mapping
len = maplen;
}
}
var map, ent;
var min_dist = MaxRange;
var closest = null;
while( len-- ) {
map = list[ len ];
ent = map.ent;
if( ent && ent.isValid ) {
let dist = map.ent_dist;
if( dist < min_dist ) {
closest = map;
min_dist = dist;
}
}
}
return closest;
}
function nearest_Sighting() {
try {
_nearest_Sighting();
} catch( err ) {
log( ws.name, ws._reportError( err, 'nearest_Sighting' ) );
if( debug ) throw err;
}
}
function _nearest_Sighting() { //lock the nearest target
if( !ps || !ps.isValid || alertCondition === DOCKED )
return;
if( !mappingReady || maplen === 0 ) return; // not yet built OR empty
check_Sightings( true ); // clear out any that died, jumped or scooped
var closest = findNearestEnt();
if( closest ) {
_manage_marker( closest, true, '_nearest_Sighting' );
if( Steering > 0 ) start_Steering(); //turn to the target
}
}
// Sighting updating - lightball //////////////////////////////////////////////////////////////
function add_lt_ball( map, index ) { // in order to appear on scanner, light ball effects are placed just inside
// scannerRange, not @ target, as with masslock rings
// also called via add_pending_lightballs so DO need to check !using_common_vars
var lightball = map.lightball;
if( lightball && !profiling ) {
lightball.remove();
lightball = map.lightball = null;
}
if( ve_colour < 0 ) ve_colour = map.ve_colour;
if( lb_size < 0 ) lb_size = map.lb_size;
if( !ve_colour || !lb_size ) return; // must have both
var ent = map.ent;
if( scanClass < 0 || !using_common_vars ) scanClass = ent.scanClass;
if( isFrangible < 0 || !using_common_vars ) isFrangible = ent.isFrangible;
if( scanClass === 'CLASS_ROCK' && isFrangible ) return; // Rock Hermits, even abandoned ones, but not asteroids, boulders
var effect_key = 'telescope-' + ve_colour + lb_size;
lb_position( map, index );
if( profiling ) return; // else profiler goes BOOM!
lightball = map.lightball = addVisualEffect( effect_key, lightball_posn );
if( debug && !lightball )
log(ws.name, 'add_lt_ball, add effect failed! effect_key = ' + effect_key + ', lightball_posn = ' + lightball_posn );
}
var target_direction = []; // unit vector to target
var lightball_posn = [];
function lb_position( map, index, haveTD ) { // calc lightball_posn; haveTD == true => target_direction already calc'd
// is also called via add_pending_lightballs, so DO need to check !using_common_vars
var lb_dist;
if( distance < 0 || !using_common_vars )
distance = map.ent_dist; //distance to the target (or to last known pos)
// light balls for far ships occupy ring inside scannerRange: -400..-600 (400+max_sightings)
// - target marker lies at scannerRange - 600
// while ships inside scannerRange have light balls outsice scannerRange: +300..+500 (300+max_sightings)
if( distance > scannerRange ) {
lb_dist = scannerRange - 400;
//do not set closer to scannerRange so won't leave behind aft markers during torus travel
} else { // near ent, core supplies lollipop
lb_dist = scannerRange + 400; //show the lightball only without shadow lollipop
}
lb_dist -= index;
// using index to prevent 2 light balls having same distance, so they don't interfere with each other when coincide in line of sight
if( rank < 0 || !using_common_vars ) rank = map.rank;
if( position.length === 0 || !using_common_vars )
copy_vector( map.last_posn, position ); // if 'ukn', ball shouldn't move
if( !haveTD ) {
if( ent_vector.length === 0 || !using_common_vars ) {
subtract_vectors( position, ps_position, ent_vector );
}
unit_vector( ent_vector, target_direction ); // unit vector to target (or to last known pos)
} // else calling fn has already calc'd target_direction
scale_vector( target_direction, lb_dist, vector );
add_vectors( ps_position, vector, lightball_posn );
if( moving_fast ) {
apply_speed_adj( lightball_posn );
}
}
function a_non_ship_colour( colour ) {
// "non-ships with Blue, Cyan, Gray, Green and White colours will remain to help find these."
return colour === 'green' || colour === 'white' || colour === 'cyan'
|| colour === 'blue' || colour === 'gray' || colour === 'lightgray';
}
function showing_lightball( ent ) { // separate function to eliminate blinking lollipop
let showing = true;
do {
if( !weaponsOnline ) break; // show all
if( curr_target === ent ) break; // black lollipops except the current target
if( collisionRadius < 0 || !using_common_vars )
collisionRadius = ent.collisionRadius;
let max = RedAlertDist > SniperRange + collisionRadius
? RedAlertDist : SniperRange + collisionRadius;
if( distance < max ) break; // flag color relies on alertCondition for gray, black
if( alertCondition < RED_ALERT ) { // in Green or Yellow alert, weaponsOnline
if( scanClass < 0 || !using_common_vars )
scanClass = ent.scanClass;
if( scanClass === 'CLASS_CARGO' ) break; // RFID tag (are deleted when go out of range)
if( is_beacon( ent ) ) break;
}
if( alertCondition < YELLOW_ALERT ) { // in Green alert, weaponsOnline
if( isVisible < 0 || !using_common_vars )
isVisible = ent.isVisible;
showing = isVisible; // gravity scanner off-line, so visible only
} else {
showing = false; // in Red alert, weaponsOnline & beyond RedAlertDist
}
} while( false );
return showing;
}
var black_color = [ 0, 0, 0 ],
darkgray_color = [ 0.1, 0.1, 0.1 ],
lightgray_color = [ 2/3, 2/3, 2/3 ];
function lb_showing_colour( ent, showing ) { // separate function to eliminate blinking lollipop
let newColor = null; //original colour from effectdata.plist
if( curr_target === ent ) { // avoid flicker w/ shadow by setting same color
newColor = lightgray_color;
} else if( !showing ) {
if( alertCondition > YELLOW_ALERT )
newColor = black_color; //black lollipops in red alert
else if( alertCondition > GREEN_ALERT )
newColor = darkgray_color; //and very dark gray in yellow alert
}
return newColor;
}
function lb_effect_size( map, showing ) { // only called via update_one_Sighting so no need to check !using_common_vars
// "_largeball", "_ball", "_marker", "_smallmarker", "_tinymarker", "_dotmarker", "_flag"; lightgray also has _moon, _moonflag
var ent = map.ent, collsn_rad = 0;
if( ve_colour < 0 ) ve_colour = map.ve_colour;
if( !ve_colour ) return '';
if( radius < 0 ) radius = ent.radius || false;
if( collisionRadius < 0 ) collisionRadius = ent.collisionRadius;
collsn_rad = radius ? 0 : collisionRadius; // 0 for planets, moons & sun
if( distance < 0 ) distance = map.ent_dist;
if( distance < LightBallMinDist + collsn_rad //too near
|| ( distance < LightBallShipMinDist + collsn_rad
&& ve_colour !== 'cyan' && ve_colour !== 'white' && ve_colour !== 'pink' ) // ship too near
|| redAlertOptimize() ) {
//in red alert show ball marked targets only to save CPU and clean scanner (masslock rings are also removed)
return '';
}
if( !collisionRadius ) { // planets, moons & sun
if( isVisible < 0 ) isVisible = ent.isVisible;
if( hasAtmosphere < 0 ) hasAtmosphere = ent.hasAtmosphere;
if( isVisible && distance < 1e7 )
return hasAtmosphere ? '_moonflag' : '_flag'; //no dot
else
return hasAtmosphere ? '_moon' : '_dotmarker';
}
if( !LightBalls || (!ShipLightBalls // user set to off
&& !a_non_ship_colour( ve_colour )) ) { //if ship lightballs are disabled then show others only
return '_flag'; //lollipop without lightball
}
if( !showing ) return '_flag'; //lollipop without lightball
if( LargeLightBalls ) {//large balls
if( distance < SniperMinRange + collsn_rad ) return '_largeball'; //xl size
else if( distance < SniperRange + collsn_rad ) return '_ball'; //large size
else if( distance > 1e6 ) return '_tinymarker'; //over 1000km show tiny ball
else if( distance > 1e5 ) return '_smallmarker'; //over 100km show smaller ball
return '_marker'; //average size
}
if( script_mass === undefined ) script_mass = read_scriptInfo( ent, map ); // also sets 'mass'
if( distance > 1e6 )
return '_dotmarker'; //over 1000km
else if( mass < 4e5 )
return '_tinymarker'; //escort ship -was hitting here w/ slivers
else
return '_smallmarker'; //large ship (Cobra3 and over)
}
function update_lt_ball( map, index ) {
var lightball, new_size, ent;
var non_ship_colour = a_non_ship_colour( ve_colour );
var disallowed = !LightBalls // turned off by user
|| ( !ShipLightBalls && !non_ship_colour ); //if ship lightballs are disabled, show others only
lightball = map.lightball;
if( lb_size < 0 ) lb_size = map.lb_size; // get current size
if( disallowed ) {
if( lightball && lb_size !== '_flag' && !profiling ) { // need to check for existing, as (Ship)LightBalls are in-game settings
lightball.remove();
map.lightball = lightball = null;
}
} // else this is not a ship ("navigation only" lightballs)
ent = map.ent;
if( distance < 0 ) distance = map.ent_dist;
let showing = showing_lightball( ent );
map.lb_size = new_size = lb_effect_size( map, showing ); // calc size to see if it's changed
if( rank < 0 ) rank = map.rank;
if( position.length === 0 ) {
copy_vector( map.last_posn, position ); // lightball doesn't move if 'ukn'
}
if( lightball && new_size === lb_size
&& ve_colour === map.ve_colour )
return; // need only to reposition existing effect - now done in an fcb
lb_size = new_size;
if( lb_size !== '' ) { // size chg => add new sized/coloured one
add_lt_ball( map, index );
if( map.lightball )
map.lightball.scannerDisplayColor1 = lb_showing_colour( ent, showing ); // prevent blinking when cycle weapons
} else if( lightball && !profiling ) { // no ball, remove current one
lightball.remove();
map.lightball = null;
}
}
// Sighting updating - masslock ring //////////////////////////////////////////////////////////
const SHOW_GREEN_WEAPS_OFF = 1, /// default from 1.15 is
SHOW_GREEN_WEAPS_ON = 2, /// $TelescopeMassLockBorders && ( pla === 1 || !ps.weaponsOnline )
SHOW_YELLOW_WEAPS_OFF = 4, /// so 1 | 2 | 4 | 16 = 23, aka $DEFAULT_ML_RINGS
SHOW_YELLOW_WEAPS_ON = 8,
SHOW_RED_WEAPS_OFF = 16, ///
SHOW_RED_WEAPS_ON = 32,
SHOW_WEAPS_OFF = SHOW_GREEN_WEAPS_OFF | SHOW_YELLOW_WEAPS_OFF | SHOW_RED_WEAPS_OFF,
SHOW_WEAPS_ON = SHOW_GREEN_WEAPS_ON | SHOW_YELLOW_WEAPS_ON | SHOW_RED_WEAPS_ON,
SHOW_ALERT_GREEN = SHOW_GREEN_WEAPS_OFF | SHOW_GREEN_WEAPS_ON,
SHOW_ALERT_YELLOW = SHOW_YELLOW_WEAPS_OFF | SHOW_YELLOW_WEAPS_ON,
SHOW_ALERT_RED = SHOW_RED_WEAPS_OFF | SHOW_RED_WEAPS_ON;
function setShowFlags() {
show_on_Alert = alertCondition === GREEN_ALERT ? SHOW_ALERT_GREEN :
alertCondition === YELLOW_ALERT ? SHOW_ALERT_YELLOW :
alertCondition === RED_ALERT ? SHOW_ALERT_RED : 0;
show_on_Weapons = weaponsOnline ? SHOW_WEAPS_ON
: SHOW_WEAPS_OFF;
}
// on/off for masslock rings in current alertCondition/weaponsOnline state
function _getShowState() { return show_on_Weapons & show_on_Alert; }
function _getShowStateText() { // text for dynamic activate messages
var alert = alertCondition === GREEN_ALERT ? 'green' :
alertCondition === YELLOW_ALERT ? 'yellow' :
alertCondition === RED_ALERT ? 'red' : 'docked';
var weapons = weaponsOnline ? 'online' : 'off-line';
return [ alert, weapons ];
}
function _currMLFlags() {
return ws.$MassLockRings === null
? SHOW_ALERT_GREEN | SHOW_WEAPS_OFF // default, as player never set flags
: ws.$MassLockRings;
}
function _currSniperRingFlags() {
return SniperRingActive === null
? SHOW_WEAPS_ON // default, as player never set flags
: SniperRingActive;
}
function _adjustMLFlags( turnOn ) {
// when traversing state of TelescopeMenuLightballs, player expects masslock rings
// even if current state missing from MassLockRings, so we fold it in
// - when turned off, we also remove state from MassLockRings
var state = _getShowState(),
currFlags = _currMLFlags();
if( turnOn ) { // ensure state is on
MassLockRings = ws.$MassLockRings = currFlags | state;
} else { // turn state off, masslock rings off
MassLockRings = ws.$MassLockRings = currFlags & ~state;
}
}
function show_ml_ring() {
if( !viewIsStandard ) return false; // only shown from inside ship
if( !viewHasMLRings ) return false; // looking out wrong porthole
return ( MassLockRings & show_on_Alert // test alert state
& show_on_Weapons ) > 0; // test weapons state
}
var mlVector = []; // working vector for add_ml_ring to call orientToFace
function add_ml_ring( map ) { // only called via update_one_Sighting so no need to check !using_common_vars
var masslock = map.masslock;
if( masslock && !profiling ) {
masslock.remove();
map.masslock = masslock = null;
}
if( ve_colour < 0 ) ve_colour = map.ve_colour;
if( ml_size < 0 ) ml_size = map.ml_size;
if( !ve_colour || !ml_size ) return; // must have both
var effect_key = 'telescope-' + ve_colour + ml_size;
var ent = map.ent;
if( script_mass === undefined ) script_mass = read_scriptInfo( ent, map ); // also sets 'mass'
var ring_radius = VariableMassLock ? map.ml_radius : scannerRange;
var ring_scale;
if( isPlanet < 0 ) isPlanet = ent.isPlanet;
if( isPlanet ) { // planets (but not suns)
if( radius < 0 ) radius = ent.radius || false;
ring_scale = ( radius + (radius > scannerRange ? radius : scannerRange) ) /* max(radius, scannerRange) */
/ MASSLOCK_RING_SCALE;
} else {
ring_scale = ring_radius / MASSLOCK_RING_SCALE;
}
if( profiling ) return; // else profiler goes BOOM!
if( position.length === 0 ) {
copy_vector( ent.position, position );
}
map.masslock = masslock = addVisualEffect( effect_key, position );
if( masslock ) {
masslock.scale( ring_scale ); //ring radius == scanner range
// not waiting to be orientated in reposition_effects as misaligned ring visible momentarily
subtract_vectors( position, ps_position, vector );
unit_vector( vector, mlVector );
orientToFace( masslock, mlVector ); // orientToFace uses both 'vector' & 'cross'
}
}
function ml_effect_size( map ) { // only called via update_one_Sighting so no need to check !using_common_vars
if( distance < 0 ) distance = map.ent_dist;
if( distance < scannerRange ) return '';
if( distance > 2.5e6 ) return ''; // current models are not visible beyond 2500 km
var ent = map.ent;
if( redAlertOptimize() ) return ''; // lightballs are also removed
if( scanClass < 0 ) scanClass = ent.scanClass;
if( scanClass === 'CLASS_CARGO' ) return '';
// if( isFrangible < 0 ) isFrangible = ent.isFrangible;
if( scanClass === 'CLASS_ROCK' ) return ''; // exclude all (abandoned) Rock Hermits, may have the mass but don't masslock
if( scanClass === 'CLASS_BUOY' ) return '';
if( isWormhole < 0 ) isWormhole = ent.isWormhole;
if( isWormhole ) return '';
if( is_cloaked( ent ) ) return '';
// if( is_jamming( ent ) ) return '';
// since beyond scannerRange, jammer not applicable
var bright = BrightMassLockRings ? '2' : '';
var size = '_ml';
var fardist = 3e5; //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)
if( isPlanet < 0 ) isPlanet = ent.isPlanet;
if( distance > fardist ){ //far masslock border over this distance (station or gravity scanner target)
if( isPlanet ) {
if( radius < 0 ) radius = ent.radius || false;
let save_radius = radius;
if( map.planetRadius ) { // it may be a moon
radius = map.planetRadius;
} else {
if( hasAtmosphere < 0 ) hasAtmosphere = ent.hasAtmosphere;
if( !hasAtmosphere ) { // it's a moon, find it's planet
let orb, oi, olen,
orbs = entitiesWithScanClass( 'CLASS_NO_DRAW', ent, AutoScanMaxRange );
for( oi = 0, olen = orbs.length; oi < olen; oi++ ) {
orb = orbs[ oi ];
if( orb.hasAtmosphere ) {
radius = orb.radius; // temporarily override, gets restored by save_radius
map.planetRadius = radius; // cache for later
break;
}
}
}
}
if( distance > farplanet * radius )
// (radius < 20000 ? 20000 : radius) ) // min. radius for small moons
// when based on radius, a moon gets mlf before its planet, looks wierd
size = '_mlf'; //set far masslock border
else if( distance < 3 * radius )
size = '_mlt'; //set thin masslock border
else
size = '_ml'; //set normal masslock border (default)
radius = save_radius;
} else { //is not planet but far
size = '_mlf'; //set far masslock border
}
} else if( isPlanet ) { // near a planet
if( radius < 0 ) radius = ent.radius || false;
if( distance < 3 * radius ) size = '_mlt'; //set thin masslock border
} else {
// size = distance > scannerRange_X_2 ? '_ml' : '_mlt';
size = '_ml';
do {
if( distance > scannerRange_X_2 ) break; //check for thin border within 2x scanner range
if( position.length === 0 ) {
copy_vector( ent.position, position );
}
if( ent_vector.length === 0 ) subtract_vectors( position, ps_position, ent_vector );
var angle = angle_between_unitV( ps_vectorForward, ent_vector );
if( angle < FORTYFIVE_DEGREES ) break; // under 45 degree mean ship is near and ring is out of sight?
if( angle > QUARTER_ARC ) break; // and less than 90 degree mean ship's ring is in front you (it's parallel to vectorRight)
let run = sin( angle ) * distance; // sin( angle ) = run / distance
let rise = cos( angle ) * distance; // cos( angle ) = rise / distance
let ring_radius = VariableMassLock ? map.ml_radius : scannerRange;
let center_dist = run - ring_radius;
center_dist = center_dist < 0 ? -center_dist : center_dist;
var fov_cutoff = center_dist / sin_fov2;
if( cos_fov2 < rise / fov_cutoff ) break;
size = '_mlt'; //so if ring is in screen then can be close so need thin border
} while( false );
}
return bright + size;
}
/*
if( Math.sin( angle ) < scannerRange/dist ) break; //and crosshairs points out of ring which is closer
\ ^<--fov/2--> / | - we have angleTo & hypotenuse, and same orientation (ring parallels vectorRight)
| \ | / | sin( angle ) = run / distance => run = sin( angle ) * distance
| \ |<-c_d->(--r_r---X | ^ cos( angle ) = rise / distance => rise = cos( angle ) * distance
| \ | / | | - masslock ring edge is at center_dist = run - ring_radius
| \ | / | | rise - for fov at rise, sin( fov/2 ) = center_dist / hypotenuse
| \ | / | | => hypotenuse = center_dist / sin( fov/2 ), called fov_cutoff
|---------|---------\_/---------|---------| | - for ring to be visible, cos( fov/2 ) > rise / fov_cutoff
2 scannerRange scannerRange 2
<----- run ---->
*/
function update_ml_ring( map ) { // only called from update_one_Sighting so no need to check !using_common_vars
var new_size, ent, masslock = map.masslock;
ent = map.ent;
var showingMLRings = show_ml_ring();
if( showingMLRings && !ext_ok ) { // "Without a Telescope Extender, these are shown around
do { // planets and stations only"
if( isStation < 0 ) isStation = ent.isStation;
if( isStation ) break;
if( radius < 0 ) radius = ent.radius || false;
if( radius ) break;
showingMLRings = false;
} while( false );
}
if( !showingMLRings ) {
if( masslock && !profiling ) { // need to check for existing, as MassLockRings is an in-game setting
if( debug ) log(ws.name, 'update_ml_ring, as !show_ml_ring, removing masslock ring ("'
+ map.ml_size + '") for' + map.ent );
masslock.remove();
map.masslock = null;
}
return;
}
if( isSun < 0 ) isSun = ent.isSun;
if( isSun ) return; // no longer has one
if( ml_size < 0 ) ml_size = map.ml_size; // get current size
map.ml_size = new_size = ml_effect_size( map ); // calc size to see if it's changed
if( rank < 0 ) rank = map.rank;
if( position.length === 0 ) {
copy_vector( map.last_posn, position ); // ring stays w/ lightball, even if 'ukn'
}
if( masslock && new_size === ml_size
&& ve_colour === map.ve_colour ) { //move masslock ring
return; // need only to _reposition_effects
}
ml_size = new_size;
if( ml_size !== '' && ve_colour !== 'gray' ) { // size chg => add new sized
add_ml_ring( map );
} else if( masslock && !profiling ) {
masslock.remove();
map.masslock = null;
}
}
// Sighting updating //////////////////////////////////////////////////////////////////////////
function proc_stealthy( map, ent, have_scanned ) {
if( have_scanned === true ) { // ships using scriptInfo {...telescope = 0...}
// if( map.rank === 'ukn' ) { // lost contact of stealthy ent, wipe all info
if( !ent.isVisible ) { // lost contact of stealthy ent, wipe all info
_delete_Sighting( map.ent, 'proc_stealthy' ); // - threshold is visibility, not gravity
return true;
}
} else if( distance > have_scanned ) { // cargo pod's RFID signal lost in noise
_delete_Sighting( map.ent, 'proc_stealthy cargo' ); // also deleted in check_Sightings
return true;
}
return false;
}
// bitflags for dynamic MFD filtering
const MFD_FRIENDLY = 1, // bounty === 0 && !markedForFines
MFD_UNSOCIABLE = 2, // bounty || markedForFines
MFD_ACTIVE = 4, // has .target || defenseTargets.length > 0
MFD_HOSTILE = 8, // in_ents_Targets || targeting_ps
MFD_ATTITUDE = 15, // those of 1st 4 flags used to choose targets
MFD_NEARBY = 16, // distance < scannerRange
MFD_PROTECTED = 32, // .withinStationAegis
MFD_FARAWAY = 64, // distance > scannerRange
MFD_RANGED = 112; // those of prev. 3 flags used to limit those chosen
// if add more flags, be sure to update line 90: this.$MFD_DYNAMIC_ALLSET = 127;
function update_one_Sighting( map, ent, index, check_notable ) {
if( check_notable || status < 0 ) status = ent.status;
if( _has_bad_status( ent, status ) ) // shipScoopedOther can fire & del before we get to it!
return;
if( check_notable || distance < 0 )
distance = map.ent_dist;
if( check_notable ) { // false when called from include_ent, _add_Sighting, as already checked
let save_status = status, // but true from refresh_Sightings
save_dist = distance;
reset_common_vars(); // prepare for call to notable_ent
status = save_status;
distance = save_dist;
if( !notable_ent( ent ) ) { // sets rank, ve_colour and (maybe) distance
if( index !== -1 ) // notable_ent checks if eclipsed
_delete_Sighting( index, 'update_one_Sighting' + ' CHECK_NOTABLE' );
return;
}
map.staticMFD = staticMFD;
} else {
isBeacon = is_beacon( ent ); // fn tests isBeacon < 0
radius = ent.radius || false;
let hidden = index < 0 ? false : eclipsed( ent, map ); // index = -1 => call from include_ent, eclipsed already checked
if( !isBeacon && !radius && hidden ) {
_delete_Sighting( (index >= 0 ? index : ent), 'update_one_Sighting' + ' !check_notable' );
return;
}
}
dynamicMFD = distance < scannerRange ? MFD_NEARBY // clear all as all are reset here
: MFD_FARAWAY;
if( scanClass < 0 ) scanClass = ent.scanClass;
if( scanClass === 'CLASS_CARGO' ) { // scannerRange exception for cargo
if( check_notable ) {
if( is_cargo === true )
dynamicMFD = MFD_NEARBY;
} else {
if( is_ignored_ship( ent ) ) {
_delete_Sighting( (index >= 0 ? index : ent), 'update_one_Sighting' + ' is_ignored_ship' );
return;
}
if( shipClassName < 0 ) shipClassName = ent.shipClassName;
if( shipClassName !== 'Splinter'
&& shipClassName !== 'Boulder'
&& shipClassName !== 'Metal fragment' )
dynamicMFD = MFD_NEARBY; // cargo, escape pods detectable until deleted
}
}
if( gravScanProgress > 0 ) { // once gs stops running, this degrades over time (ie. not checking gs_state)
if( gs_curr < 0 ) gs_curr = grav_scan_dist( ent, true, map );
map.gs_curr_dist = gs_curr;
}
let have_scanned = map.have_scanned;
if( rank < 0 ) rank = map.rank;
else map.rank = rank; // may have been altered in notable_ent
if( ve_colour < 0 ) ve_colour = map.ve_colour;
if( rank === 'ukn' ) {
ve_colour = 'gray'; // becomes 'gray' if ent departs our equipment's range
if( map.ve_colour !== 'gray' ) { // just became 'ukn', remove masslock ring so a gray one will be created
let masslock = map.masslock;
if( masslock && !profiling ) {
masslock.remove();
map.masslock = null;
}
}
isHostile = false;
dynamicMFD = MFD_FARAWAY; // lost contact, clear all dynamic MFD bitflags but Faraway
} else {
let moody = scanClass !== 'CLASS_BUOY'
&& scanClass !== 'CLASS_CARGO'
&& scanClass !== 'CLASS_ROCK'
&& scanClass !== 'CLASS_NO_DRAW'
&& scanClass !== 'CLASS_WORMHOLE';
isHostile = moody ? is_hostile( ent, true ) : false; // true to set all glocals
if( isHostile ) {
if( have_scanned !== true && have_scanned !== -1 ) {// must be explicit, as prop has multiple uses
if( distance < scannerRange ) {
map.have_scanned = -1; // once scanned, offender status can be remembered (FarStatus)
}
}
ve_colour = 'red'; // FarStatus & distance dealt with in is_hostile
dynamicMFD |= MFD_UNSOCIABLE;
} else if( moody ) {
if( have_scanned === -1 ) { // an offender has reformed?
map.have_scanned = false;
}
dynamicMFD |= MFD_FRIENDLY;
}
if( has_targets ) dynamicMFD |= MFD_ACTIVE;
if( targeting_ps || in_ents_Targets ) dynamicMFD |= MFD_HOSTILE;
if( ent.withinStationAegis ) dynamicMFD |= MFD_PROTECTED;
}
map.dynamicMFD = dynamicMFD;
if( have_scanned === true || have_scanned > 0 ) { // hidden ent or cargo
if( proc_stealthy( map, ent, have_scanned ) ) { // gets deleted if no longer detectable
if( check_notable ) using_common_vars = false; // premature exit, reset when necessary
return;
}
}
if( index >= 0 ) { // when called by grow_new_list, has not been added to
update_ml_ring( map ); // mapping, so wait for next update (index is req'd
update_lt_ball( map, index ); // for update_lt_ball)
}
map.ve_colour = ve_colour; // may have been altered
if( check_notable ) using_common_vars = false; // reset when necessary
}
var systemEclipsers = null; // cache of system's orbs & stations
ws._eclipsed = eclipsed; // for debug
function eclipsed( ent, map, dist ) { // determine if it's behind orb or station (notable_ent only caller)
var that = eclipsed;
var eclipsed_ent = (that.eclipsed_ent = that.eclipsed_ent || []),// vector to ent we're checking
orb_vector = (that.orb_vector = that.orb_vector || []);// vector to candidate eclipser
eclipsed_ent.length = 0; // reset array
orb_vector.length = 0; // reset array
if( !systemEclipsers || systemEclipsers.length === 0 ) // cache not initialized
return false;
if( grav_eq_ok )
return false; // gravity scanner overcomes line of sight
let eclipsing_dist = 0; // its distance
if( map ) // map optional, as is called by grow_list sequence
eclipsing_dist = distance = map.ent_dist;
else if( dist ) // dist optional, to save on call to _detect_distanceTo
eclipsing_dist = distance = dist;
else {
if( distance < 0 ) distance = _detect_distanceTo( ent );
eclipsing_dist = distance;
}
var threshold, tangentAngle, ecl_ent, ecl_map, dist,
edge, opp, adj, msize, isOrb,
len = systemEclipsers.length;
if( !len ) return false;
for( var idx = 0; idx < len; idx++ ) {
ecl_ent = systemEclipsers[ idx ];
if( ecl_ent === ent ) continue;
if( ecl_ent && !ecl_ent.isValid ) continue;
if( ecl_ent && !ecl_ent.position ) {
/// trap for dybal's bug where _detect_distanceTo call mks call to subtract_vectors w/ null 2nd parm
/// (may have fixed via fetchSun when building systemEclipsers)
if( debug ) {
log( ws.name, 'eclipsed, stale systemEclipser w/o a position, idx = ' + idx );
var tmp = _Sighting_index( ecl_ent );
if( tmp < 0 ) {
log( ws.name, 'eclipsed, NOT in _Sightings!!!' );
} else {
log( ws.name, 'eclipsed, _Sighting_index = ' + tmp );
if( cd )
cd._showProps( mapping[tmp], 'systemEclipser' );
}
}
continue;
}
let index = _Sighting_index( ecl_ent );
// if( index < 0 ) continue; // not in map, cannot eclipse (ok, 1st create in system will thrash, a bit)
if( index < 0 ) {
ecl_map = null;
let save_radius = radius,
save_collRad = collisionRadius; // preserve current ent's property gets
radius = collisionRadius = -1;
try {
dist = _detect_distanceTo( ecl_ent ); // sets radius, maybe collisionRadius
} catch( err ) {
log( ws.name, 'ATTN: Dybal' );
log( ws.name, 'eclipsed, failed to calculate distance to un-mapped ent: ' + ecl_ent );
log( ws._reportError( err, eclipsed, [ent, map, dist], 2, true ) );
if( !that.DybalMsg || that.DybalMsg < 3 ) {
consoleMessage( '!! check log for error !!', ConsoleMsgDurn );
that.DybalMsg = !that.DybalMsg ? 1 : that.DybalMsg + 1;
}
continue;
}
edge = radius;
radius = save_radius; // restore current ent's property gets
collisionRadius = save_collRad;
} else {
ecl_map = mapping[ index ];
dist = ecl_map.ent_dist;
edge = ecl_ent.radius;
}
if( eclipsing_dist < dist ) continue; // is in front of ecl_map
// calc dist from center to rim/outer hull
isOrb = edge > 0; // only they have a .radius property
if( isOrb ) {
opp = edge + (ecl_ent.hasAtmosphere === true ? 500 : 0);// 500 is default height of atmosphere
} else if( ecl_ent.isStation || (SpicyHermits && ecl_ent.isRock && !ecl_ent.isFrangible) ) {
if( dist > scannerRange_X_4 ) continue; // don't bother with distant stations
edge = ecl_ent.collisionRadius;
opp = edge;
} else continue; // should never happen but, well, you know, bugs
adj = dist + edge; // is subtracted in _detect_distanceTo
if( adj <= 0 ) continue; // should never happen
tangentAngle = asin( opp / adj ) * RADIANS_TO_DEGREES; // angle from center to limb/hull
copy_vector( (index < 0 ? ecl_ent.position
: ecl_map.last_posn), vector );
subtract_vectors( vector, ps_position, orb_vector );
if( eclipsed_ent.length === 0 ) { // 1st time here, calc ent's vector, etc., as now it's needed
copy_vector( (map ? map.last_posn
: ent.position), vector );
subtract_vectors( vector, ps_position, eclipsed_ent );
unit_vector( eclipsed_ent, eclipsed_ent );
if( isOrb ) {
if( isStation < 0 ) isStation = ent.isStation;
let marker = map ? map.lb_size : null;
msize = !marker ? (LargeLightBalls ? 500 : 200) :// no lightball/map, use median size
marker === '_largeball' ? 1000 : // sizes vary a bit across colors; these are the largest values
marker === '_ball' ? 800 :
marker === '_marker' ? 600 :
marker === '_smallmarker' ? 400 :
marker === '_tinymarker' ? 300 : 150; // '_dotmarker'
msize = msize / 533 * // in right ballpark, if _smallmarker is ~3/4 degree
(isStation ? (1e6 - eclipsing_dist) / 1e6 : 1); // adj for station apparent size
} else
msize = 0;
}
threshold = tangentAngle - msize; // lightball has constant size
let span = angle_between_unitV( eclipsed_ent, orb_vector ) * RADIANS_TO_DEGREES;
/*
if( debug && ent === curr_target )
log(ws.name, 'eclipsed, ' + (span<threshold ? 'HIDDEN by ' + ecl_ent.name : '')
+', span = ' + span.toFixed(2)
+ ', threshold = ' + threshold.toFixed(2) + ', tangentAngle = ' + tangentAngle.toFixed(2) + ', msize = ' + msize.toFixed(2)
);
player.ship.removeEquipment( 'EQ_GRAVSCANNER' )
player.ship.awardEquipment( 'EQ_GRAVSCANNER' )
*/
if( span < threshold ) return ecl_ent;
}
return false;
}
function refresh_Sightings() { // update existing ents a few at a time (1 update ~ 0.86 ms); use_map limits to known targets
try {
if( mk_maps ) return; // have started creating new mapping, abort
if( maplen <= 0 ) return;
var index = map_update_index;
var count = updates_per_frame;
var map, ent;
for( ; count > 0 && index < maplen; count--, index++ ) {
if( index >= mapping.length ) break; // scooped/died/jumped during update, which shortened mapping!
map = mapping[ index ];
if( !map ) continue;
ent = map.ent;
if( !ent || !ent.isValid ) continue;
update_one_Sighting( map, ent, index, true ); // true directs call to notable_ent
}
map_update_index = index;
if( index >= maplen ) { // finished w/ list
set_fn_pending( grow_new_list, 'refresh' );
} else { // more to process in next call
set_fn_pending( refresh_Sightings );
}
} catch( err ) {
if( debug ) log(ws.name, 'refresh_Sightings, map = '
+ (cd ? cd._showProps( map, 'map' ) :'')
+ '\nent = ' + ent + '\nindex = ' + index
+ ', count = ' + count + ', maplen = ' + maplen );
log( ws.name, ws._reportError( err, 'refresh_Sightings', updates_per_frame ) );
if( debug ) throw err;
}
}
var updates_per_frame = 2; // # of ships updated in a single frame; adjusted to frame rate
var map_update_index = 0; // saves index across calls
function update_Sightings( just_mapping ) {
try {
_update_Sightings( just_mapping );
} catch( err ) {
log( ws.name, ws._reportError( err, 'update_Sightings', just_mapping ) );
if( debug ) throw err;
}
}
function _update_Sightings( just_mapping ) { // initiate update cycle; just_mapping limits update to known targets
var that = _update_Sightings;
if( that.fps === undefined ) that.fps = 0;
if( !equip_ok ) return;
if( !mappingReady || maplen === 0 ) return; // mapping not yet initialized OR it's empty
if( fns_are_pending() ) return; // called in midst of creating a new mapping, abort!
if( !ps || !ps.isValid || alertCondition === DOCKED ) //if player died or docked
return;
mk_maps = false; // suppress creation of Sightings
map_update_index = 0;
if( just_mapping ) { // update just known entities (ie. in mapping)
set_fn_pending( refresh_Sightings );
/*
- a user setting (MaxTargets) deals with the max # of targets to consider. At 200, processing
5 per frame, this completes in 40 frames but at a cost of 4.3 ms/frame (@~30 fps on my PC)
- there is available, @ 60 fps 16.7 ms/frame,
@ 50 20 ms
@ 40 25 ms
@ 30 33 ms
@ 20 50 ms
- so on my PC, I reduce my frame rate from 30 to 26.8 ( 1/33 -> 1/37.3 )
- if I limit to 2 per frame, 200 targets will take 10 sec @20 fps, but only 3.33 sec @ 60 fps
I'm sure the pilot @20 won't like the 10 seconds but all I can do is suggest (s)he reduce the max # of
targets. And the one @60 isn't thrilled either, but there I can do something:
- start w/ a low rate until fps stats mature
- then starting w/ an fps based target, incr # Sightings/frame
- monitor the % diff in ms/Sighting and keep it below a threshold based on fps
(see init_growing; uses similar scheme & will decrease MaxTargets)
*/
var fps, base, target;
if( !current_fps || !long_term_fps )
return;
fps = current_fps();
base = long_term_fps(); // mean of last 5 min
if( fps > 0 && fps !== that.fps ) { // more than a minute has passed & got new value
if( base > 0 ) { // in flight for at least 5 min
target = floor( 2 + fps / 30 ); // aim for twice min of 2 @ 60 Hz
if( fps < base && // reduction in fps beyond threshold
percentDiffOver( fps, base, (fps / 8) ) )// 7.5% @ 60 (> 55.5 fps), 3.75% @ 30 (> 27.6 fps)
target--;
else
target++;
} else {
target = floor( fps / 20 ); // start w/ a low rate
}
if( target > 2 && target !== updates_per_frame ) { // adjust target every update based on fps stats from the last minute
if( debug ) log(ws.name, '_update_Sightings, updates_per_frame changed from '
+ updates_per_frame + ' to ' + target );
updates_per_frame = target;
}
that.fps = fps;
}
} else { // new entities detected
init_growing( false );
set_fn_pending( grow_new_list, 'add_orbs' );
}
}
function percentDiffOver( a, b, p ) { // for comparing fps readings; reciprocals compare time
if( a === 0 || b === 0 || p === 0 ) return false;
var diff = a > b ? (a - b) / a // (1/b - 1/a) => a-b/ab; * b => a-b/a
: (b - a) / b; // (1/a - 1/b) * a => (b - a)/ab * a => (b - a) / b
return diff > p / 100;
}
// Sighting effect positioning ////////////////////////////////////////////////////////////////
var speed_adj = [];
function apply_speed_adj( dst_posn ) { // used in position calc for lightballs, marker
// prevDist is distance from last frame
// travelling at high speed can distort positon calculations,
// lightballs off-center or lose lock on far target marker
// eg. @reg. Torus of 12000 and fps < 30, travel > 400 m/frame
// so we contract the range for positioning lightballs & the marker
// at rest, marker: scannerRange - 600, placed just in front of band of far lightballs
// far lightballs: scannerRange - 400 - index, a ring MaxTargets deep, ending @ 25200
// near lightballs: scannerRange + 400 - index, ie. a buffer of +- 200 around scannerRange
/*
var that = apply_speed_adj;
if( that.absMaxSpeed === undefined ) that.absMaxSpeed = 0;
var absMaxSpeed = that.absMaxSpeed;
var absMSShip = (that.absMSShip = that.absMSShip || {});
let chkValue = TorusToSun ? TorusToSun.$TorusToSunBonus :
FarPlanets ? FarPlanets.$FarPlanetsBonus :
WarpDrive ? WarpDrive.$scanScale : ps_maxSpeed;
if( !absMaxSpeed || !absMSShip[ ps ] || absMSShip[ ps ] !== chkValue ) {
if( TorusToSun ) { // using 31, not 32, as its chkValue is 1 based, ie. 1 => no bonus
absMaxSpeed = ps_maxSpeed * 31 * chkValue; // his code uses (b - 1) which doesn't work here
} else if( FarPlanets ) {
absMaxSpeed = ps_maxSpeed * 31 * chkValue;
} else if( WarpDrive ) {
absMaxSpeed = WarpDrive.$basicMaxSpeed * (WarpDrive.$warpFlag ? chkValue : 1);
} else {
absMaxSpeed = chkValue * 32;
}
that.absMaxSpeed = absMaxSpeed;
that.absMSShip[ ps ] = chkValue;
}
*/
var travel = ps_speed * frame_delta; // distance expect to travel this frame
// - frame_delta is set by call to _hud_effects() which preceeds _reposition_effects()
var dotP = dot_product( ps_vectorForward, target_direction );
// - more sensitive to change in frame rate for ents parallel to heading, less so when perpendicular
var contract = 250 + (250 * ps_speed/(ps_maxSpeed * 32)); // base amt for all directions
if( dotP >= 0 ) { // moving towards light ball
contract += 0.5 * travel * dotP;
} else { // moving away from light ball
contract += -1.5 * travel * dotP;
}
let adjust = -100 * ( floor(contract/100) ); // to hundreds to reduce jitter
scale_vector( target_direction, adjust, speed_adj );
add_vectors( dst_posn, speed_adj, dst_posn );
}
function redAlertOptimize() {
// RedAlertDist: show lollipops in red alert within this distance only
return RedAlertDist > 0 && distance > RedAlertDist && alertCondition > YELLOW_ALERT && weaponsOnline;
}
var view_vector = [],
rotated_orient = []; // working quaternion
// "The Gravity scanner works only when you turn off your weapons with underscore ("_") button,
// otherwise only visible targets are displayed." (from readme)
function orientToFace( ent, direction ) {
// 'direction' is a vector pointing at 'ent'
// orient entity so it faces 'direction', ie. its vectorForward is the negative of 'direction'
copy_vector( ent.vectorForward, vector ); // 'vector' is a common working array
cross_product( vector, direction, cross ); // axis of rotation ('cross' is the other working vector)
unit_vector( cross, cross ); // must be normalized for rotation
let angle = -angle_between_two_unitV( vector, direction ); // angle is negated to close the gap
if( equal_value( angle, 0 ) ) // don't bother if w/i PRECISION, ie. close enough
return;
copy_quaternion( ent.orientation, quaternion ); // 'quaternion' is a common working array
rotate_about_axis( quaternion, cross, angle, rotated_orient );
ent.orientation = rotated_orient; // 'rotated_orient' is another common working array
}
function _reposition_effects() { // quick fcb that just updates effects
try {
if( !equip_ok ) return;
if( !mappingReady || maplen === 0 ) return; // mapping not yet initialized OR it's empty
var index = maplen;
var map, ent, distTo, lightball, masslock, redAlertOpt,
showingMLRings = show_ml_ring();
while( index-- ) {
map = mapping[ index ];
ent = map.ent;
if( !ent || !ent.isValid ) continue;
reset_common_vars(); // ensure no data carries over from last frame
ve_colour = map.ve_colour;
rank = map.rank;
if( rank !== 'ukn' ) { // if lost detection, position data goes stale (not updated)
copy_vector( ent.position, map.last_posn );
}
copy_vector( map.last_posn, position );
subtract_vectors( position, ps_position, ent_vector );
unit_vector( ent_vector, target_direction );
/// brought in from _detect_distanceTo for efficiency (share vector calc's)
distTo = vector_magnitude( ent_vector );
distTo -= hullOffset( ent ); // distance to near surface
map.ent_dist = distance = distTo;
/// end of _detect_distanceTo dup'd code
redAlertOpt = redAlertOptimize(); // a fn of distance
if( !redAlertOpt && viewIsStandard ) {
map.headingTo = angle_between_two_unitV( view_vector, target_direction ) * RADIANS_TO_DEGREES;
}
lightball = map.lightball;
lb_size = map.lb_size;
let showingEffect = true;
if( lightball )
showingEffect = showing_lightball( ent );
if( lightball && lb_size !== '_flag'
&& ( redAlertOpt || !showingEffect ) ) {
lightball.remove();
map.lightball = null;
} else if( lightball ) {
lightball.scannerDisplayColor1 = lb_showing_colour( ent, showingEffect );
lb_position( map, index, true ); // reposition existing effect, sets lightball_posn
// uses distance, rank, position, ps_position, ent_vector, speed_adj
lightball.position = lightball_posn;
}
masslock = map.masslock;
if( masslock
&& ( redAlertOpt || !showingMLRings )) {
masslock.remove();
map.masslock = null;
} else if( masslock ) {
masslock.position = position; //move masslock ring
if( distance < scannerRange_X_2 || (radius // set orientation of near ones for easier navigation
&& distance < 2 * (scannerRange + radius)) ) {// including planets/moons (calc so masslock spheres don't touch)
// setting to ps_orientation allows player to approach ring and steer
// just outside masslock range
masslock.orientation = ps_orientation;
} else { // orient ring to be perpendicular, face player.ship
orientToFace( masslock, target_direction );
}
}
}
using_common_vars = false;
} catch( err ) {
log( ws.name, ws._reportError( err, '_reposition_effects' ) );
if( debug ) throw err;
}
}
// new Sightings //////////////////////////////////////////////////////////////////////////////
var new_targets = []; // list of new target entities for Combat_MFD
function process_new_targets() {
var index;
if( new_targets && new_targets.length > 0 ) {
var ent, idx, len;
init_headingView(); // prep for showTargetName
len = new_targets.length;
for( idx = 0; idx < len; idx++ ) { //search new target
ent = new_targets[ idx ];
if( ent.radius )
continue; // on launch, all targets are 'new' and system.planets may not be ready!
index = _Sighting_index( ent, 'process_new_targets' );
if( index < 0 )
continue; // should always be there but ...
showTargetName( mapping[ index ] ); //but do not jump out of the cycle, keep to print all new name
}
new_targets.length = 0; // mk sure array ready for re-use
} else {
checkCombatMFD(); // update existing data
}
}
function checkCombatMFD() { // check health of target listed in Combat_MFD
var index, map, ent;
if( Combat_MFD ) { //Combat_MFD support
index = index_in_list( prevMFDTarget, mapping );
if( index >= 0 ) {
map = mapping[ index ];
ent = map.ent;
if( ent && ent.isValid ) { // prev target is still ok
showTargetName( map, true ); //update the direction (true suppresses console msg as this call 1/sec)
return; // init_headingView called in _auto_updates
}
}
prevMFDTarget = null;
Combat_MFD.$TelescopeLine = ''; //clear the line in MFD
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// mapping functions //////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
function newList( keep_deferred ) {
try {
_newList( keep_deferred );
} catch( err ) {
log( ws.name, ws._reportError( err, 'newList', keep_deferred ) );
if( debug ) throw err;
}
}
function _newList( keep_deferred ) { // clears all Sightings & removes effects prior to docking/witchspace
if( selected_Sightings )
selected_Sightings.length = 0; // remove any previous results
clear_all_pending( keep_deferred ); // true will preserve deferred tasks queue
if( !keep_deferred ) { // are shutting down
halt_steering();
}
while( maplen-- )
free_Sighting( mapping[ maplen ] );
mapping.length = maplen = 0; // used to test when mapping not ready (launching, witchspace) - system var.s need re-init'g
mappingReady = false;
ws.$TelescopeList.length = 0;
ws.$TelescopeListi = 0; // $TelescopeListi = 0 => not in $TelescopeList
}
function map_sort_heading( a, b ) { // sort by headingTo
var a_heading = a.headingTo; // - to conform w/ find_most_central's logic, if 2 ships
var b_heading = b.headingTo; // are within half a degree, choose the closer
var diff = a_heading < b_heading ? b_heading - a_heading
: a_heading - b_heading;
if( diff < 0.5 ) { // w/i HALF_a_DEGREE of crosshairs,
return a.ent_dist - b.ent_dist; // sort by distance
} else {
return a_heading - b_heading; // sort by heading
}
}
function create_Sightings() {
try {
_create_Sightings();
} catch( err ) {
log( ws.name, ws._reportError( err, 'create_Sightings' ) );
if( debug ) throw err;
}
}
function _create_Sightings() {
clear_all_pending(); // started in midst of _update_Sightings() or deferred tasks; shut that down!
if( maplen === 0 ) // only needed when launching or exiting witchspace
_init_player_vars();
if( !equip_ok )
return;
grow_hidden_scanned.length = 0;
var idx = maplen;
while( idx-- ) { // preserve list of 'hidden' entities that have been w/i scannerRange
let map = mapping[ idx ];
if( map.have_scanned !== false ) // for specials (hidden, cargo, FarStatus), should not be present otherwise
grow_hidden_scanned.push( map.ent ); // preserve scan of specials (scriptInfo: telescope = 0), ie. once detected,
}
init_growing( true );
set_fn_pending( grow_new_list, 'add_orbs' );
// adj_update_MFDs.count = 0; // "
}
var grow_maps = []; // list of Sightings built to replace current mapping
var grow_hidden_scanned = []; // info carried over to new mapping; support for scriptInfo: telescope=0
var max_sightings; // counter during grow so don't exceed MaxTargets
var mk_maps = true; // flag as to whether (true) or not (false) we're creating a mapping
var grow_list_index = 0; // save index across calls
var grow_start_count = 0; // # of current iteration of 'start' step
var grow_step_num = 12; // arbitrary @start; scale it w/ fps_monitor
var grow_target = 0; // target step_num based on fps
function init_growing( creating ) {
var that = init_growing;
if( that.fps === undefined ) that.fps = 0;
if( that.base === undefined ) that.base = 0;
mk_maps = creating;
if( creating && current_fps && long_term_fps ) { // adjust MaxTargets to prevent create cycle using too many frames
var fps, base, limit;
fps = current_fps();
base = long_term_fps(); // mean of last 5 min
if( fps > 0 && fps !== that.fps ) { // more than a minute has passed/got new value
that.fps = fps;
if( base > 0 && base !== that.base ) { // in flight for at least 5 min/got new 5 min baseline
that.base = base;
grow_target = floor( 6 + base / 5 ); // aim for 3 x's the minimum of 6 @ 60 Hz
limit = grow_step_num;
if( fps < base && // reduction in fps beyond threshold
percentDiffOver( fps, base, (fps / 8) ) )// 7.5% @ 60 (> 55.5 fps), 3.75% @ 30 (> 27.6 fps)
limit--;
else
limit++;
if( abs( grow_target - limit ) > 2 ) // it's a trend (occurs in 3/5)
grow_target = limit;
/*
let max_frames = floor( base / 5 ); // cycle must finish in 1/5 sec to allow time for others
let total = grow_target * max_frames;
if( debug && total !== MaxTargets ) {
log(ws.name, 'init_growing, MaxTargets changed to ' + total
+ ' due to frame rate of ' + base + ' fps' );
MaxTargets = total; // max # to finish in (arbitrary) 1/5 sec
// Milo reports miss important new sightings; w/ various hardware/oxp config's, can creep too low
// ? grow only, shrink to min of MaxTargets vs KISS
}
*/
} else {
limit = floor( fps / 5 ); // initial rate, 12 per frame @ 60 fps, 6 @ 30
}
if( limit > 6 && limit !== grow_step_num ) { // adjust limit every update based on fps stats from the last minute
// min of 6 is triple min in update because not all entities are kept,
// efficiency gains from immed. update (re-uses property get values)
// and 1st 4 frames limited to 1/2 limit (more are kept)
if( debug ) log(ws.name, 'init_growing, grow_step_num changed from '
+ grow_step_num + ' to ' + limit );
grow_step_num = limit;
}
}
}
max_sightings = MaxTargets; // orbs no longer counted in MaxTargets
grow_list_index = grow_start_count = 0;
allShips = system.allShips; // array of all ships in system, sorted by distance
past_range = 0;
found_new = beacons_only = false; // reset flag if full update of mapping started
}
function include_ent( ent, using_past_range, restoring ) { // dual use: restoring is for adding from grow_hidden_scanned after create
if( notable_ent( ent, using_past_range, null, restoring ) ) {
var index = mk_maps && !restoring ? -1 : _Sighting_index( ent, 'include_ent' );
var map = index < 0 ? mkSighting( ent ) : mapping[ index ];
if( !map ) return false;
if( mk_maps && !restoring ) {
grow_maps.push( map );
} else if( index < 0 ) { // found new target or restoring
mapping.push( map );
maplen++;
index = maplen - 1;
if( !restoring ) {
new_targets.push( ent );
}
}
if( !restoring && map.swapable ) { // orbs & beacons no longer counted in MaxTargets
max_sightings--;
}
update_one_Sighting( map, ent, index ); // index can be -1, for those yet to be added into mapping
return map;
}
return false;
}
var allShips, beacons_only;
function fetchSun() {
var ent = system_sun;
if( isInterstellarSpace || !ent || !ent.isValid
|| ent.hasGoneNova || ent.isGoingNova ) { // sun is present and not nova
ent = system_sun = null;
}
return ent;
}
function grow_new_list( step, testing ) { // grow list over several frames
if( testing !== undefined ) mk_maps = testing;
var list, len, ent, map, idx;
if( step === 'add_orbs' ) { // add sun, planets & moons
ent = fetchSun();
len = system_planets.length;
idx = 0;
do { // 1st loop adds sun, then ith loop add planet idx-1
if( ent ) {
reset_common_vars(); // sets using_common_vars true
include_ent( ent );
}
if( idx >= len ) break;
ent = system_planets[ idx++ ];
} while( true );
using_common_vars = false;
set_fn_pending( grow_new_list, 'start' );
} else if( step === 'start' ) { // create or update cycle
var stop, alllen = allShips.length;
stop = grow_list_index + grow_step_num > alllen
? alllen - grow_list_index : grow_step_num;
if( grow_start_count <= 3 ) // to counter clusters near player, where high % are
stop = stop >> 1; // notable, 1st 4 loops are 1/2 grow_step_num
grow_start_count++;
// let psUnderAttack = ps.AIPrimaryAggressor;
// psUnderAttack = psUnderAttack ? psUnderAttack.length !== 0 : false;
let now = clock.absoluteSeconds;
while( stop-- ) {
ent = allShips[ grow_list_index++ ];
if( !ent ) break; // a ship died since start of loop, shortening list
if( ent === ps ) continue;
// newly spawned ships have .isVisible == true regardless of their distance
let spawned = ent.spawnTime;
if( spawned > 0 && now - spawned < SPAWN_DELAY ) // too new; will be picked up as a new target
continue;
reset_common_vars(); // clears distance
if( beacons_only ) { // once past AutoScanMaxRange, can only detect beacons
isBeacon = is_beacon( ent );
isStation = ent.isStation;
if( !isBeacon ) continue;
}
include_ent( ent, true ); // sets distance
if( !beacons_only && distance > scannerRange_X_2
&& alertCondition > YELLOW_ALERT && weaponsOnline ) {
beacons_only = true;
}
if( !beacons_only && distance > AutoScanMaxRange ) {
beacons_only = true;
}
if( max_sightings === 0 ) { // decr'd by include_ent
beacons_only = true;
}
}
using_common_vars = false;
if( stop <= 0 // exit loop before finished this iteration
&& grow_list_index < alllen - 1 ) // not at end of allShips
set_fn_pending( grow_new_list, 'start' ); // still have work to do
else if( mk_maps ) // new mapping created
set_fn_pending( grow_new_list, 'create' );
else // list finished update cycle
set_fn_pending( grow_new_list, 'update' );
} else if( step === 'create' ) { // creating new mapping finished
if( debug ) log(ws.name, 'grow_new_list, create, grow_maps.length = ' + grow_maps.length );
for( idx = 0, len = grow_maps.length; idx < len; idx++ ) { // transfer effects
let new_map = grow_maps[ idx ];
let index = _Sighting_index( new_map.ent );
if( index < 0 ) continue;
let curr_map = mapping[ index ];
if( curr_map.ve_colour !== new_map.ve_colour ) // colour change => new effects needed
continue;
if( curr_map.lightball ) {
new_map.lb_size = curr_map.lb_size;
new_map.lightball = curr_map.lightball;
curr_map.lightball = null; // prevent removal in free_Sighting via _newList
}
if( curr_map.masslock ) {
new_map.ml_size = curr_map.ml_size;
new_map.masslock = curr_map.masslock;
curr_map.masslock = null; // prevent removal in free_Sighting via _newList
}
}
_newList( true ); // true => don't clear deferred tasks
for( idx = 0, len = grow_maps.length; idx < len; idx++ ) { // to maintain external references, copy array
mapping[ idx ] = grow_maps[ idx ];
}
maplen = mapping.length;
mappingReady = true;
grow_maps.length = 0;
if( curr_S.ent ) // refresh current target
_set_curr_Sighting( curr_S.ent, 'grow_new_list create' );
list = grow_hidden_scanned;
idx = list.length;
while( idx-- ) { // preserve list of 'hidden' entities that have been w/idx scannerRange
ent = list[ idx ];
reset_common_vars();
map = include_ent( ent, false, true ); // true => checks it's still notable, update_one_Sighting (sets )
if( !map ) continue;
// when mkSighting, read_scriptInfo called, .script_mass is set set
if( map.script_mass === 0 ) { // ent hidden using scriptInfo {telescope = 0;}
map.have_scanned = true;
} else if( ent.scanClass === 'CLASS_CARGO' ) { // cargo pod (restore its RFID range)
map.have_scanned = scannerRange + (map.entityPersonality >> 1);
} else { // an offender, preserve detection for FarStatus
map.have_scanned = -1; // not using true, as offender status knowledge can
} // survive a scan whereas hidden does not
}
using_common_vars = false;
set_fn_pending( grow_new_list, 'finish' );
} else if( step === 'update' ) { // update cycle finished
// if( debug ) log(ws.name, 'grow_new_list, update, mapping.length = ' + mapping.length );
if( curr_target === null ) {
ent = curr_S.ent || null;
if( !ent || !ent.isValid || ent.radius )
if( !testing && !profiling ) // sometimes doesn't return when profiling
_clear_HUD_Effects(); //cleanup needed in some cases
}
set_fn_pending( grow_new_list, 'finish' );
} else if( step === 'refresh' ) { // finished updating existing mapping's ents
mapping.sort( map_sort_heading );
} else if( step === 'finish' ) { // follow-up common to both create & update cycle
process_new_targets();
mapping.sort( map_sort_heading );
if( debug && mk_maps ) log( ws.name, 'grow_new_list, finished w/ ' + maplen + ' Sightings' );
if( mk_maps ) { // once mapping creation is done, update its ents
grow_maps.length = 0; // is now held in $SightingsMap (aka mapping)
grow_hidden_scanned.length = 0; // free up for garbage collection
} // - really only apparent on create cycle
}
}
var past_range = 0; // # of scannerRange's, to save calc's, as lists sorted by distance
function set_range( ent, divide, using_past_range, dist ) { // eliminate distance calc's where class is limited to
if( using_past_range ) { // a multiple of scannerRange
if( past_range < divide && distance < 0 ) { // have not reached divide, so must calc distance
distance = _detect_distanceTo( ent );
}
} else { // calc distance if not passed
if( distance < 0 ) {
distance = dist ? dist
: _detect_distanceTo( ent );
}
}
if( distance >= 0 ) { // have calc'd distance; never decrease past_range!
if( past_range < divide && distance > scannerRange * divide ) {
past_range = divide;
}
}
return past_range >= divide;
}
// bitflags for static MFD filtering
const MFD_SALVAGE = 1, // cargo, escape pods, derelicts
MFD_MINING = 2, // asteroids, boulders, splinters & metal fragments
MFD_WEAPONS = 4, // mines & missiles
// MFD_INANIMATE = 7, // those of 1st 3 flags excluded from dynamic filtering
MFD_TRADERS = 8, // ships .isTrader & escorts
MFD_POLICE = 16, // scanClass === 'CLASS_POLICE'
MFD_PIRATES = 32, // .isPirate & .isPirateVictim
MFD_MILITARY = 64, // scanClass === 'CLASS_MILITARY'
MFD_ALIENS = 128, // scanClass === 'CLASS_THARGOID'
MFD_NEUTRAL = 256, // scanClass === 'CLASS_NEUTRAL' and not in any above category (e.g., miners, hunters, etc.)
// MFD_ALLSHIPS = 504, // all of the previous 6
MFD_STATION = 512, // .isStation
MFD_NAVIGATION = 1024,// some stations & beacons (may include a ship if emitting a beacon)
MFD_CELESTIAL = 2048; // sun, planets, moons
// MFD_ORIENT = 3584; // all of the previous 3
// if add more flags, be sure to update line 89: this.$MFD_STATIC_ALLSET = 4095;
function classify_ship( ent, dist ) { // police, military & alien flags set in notable_ent
var markOffender = dist <= scannerRange;
if( dist > scannerRange && FarStatus ) {
// with FarStatus, once of an offender is seen w/i scannerRange, it's status is remembered when it leaves
let idx, have_scanned = false;
if( grow_hidden_scanned.length > 0 ) {
idx = index_in_list( ent, grow_hidden_scanned );
if( idx >= 0 )
have_scanned = grow_hidden_scanned[ idx ].have_scanned;
} else {
idx = _Sighting_index( ent, 'classify_ship' );
if( idx >= 0 )
have_scanned = mapping[ idx ].have_scanned;
}
if( have_scanned === true || have_scanned === -1 ) { // has been seen inside scannerRange
markOffender = true;
}
}
if( markOffender && ent.isPirate ) {
staticMFD |= MFD_PIRATES;
}
if( markOffender && ent.isPirateVictim ) { // include in pirate filter if under attack by pirates
let defenseTargets = ent.defenseTargets;
for( let idx = 0, len = defenseTargets.length; idx < len; idx ++ ) {
if( defenseTargets[ idx ].isPirate ) {
staticMFD |= MFD_PIRATES;
break;
}
}
}
if( ent.isTrader ) {
staticMFD |= MFD_TRADERS;
}
var group = ent.group,
areTraders = false;
if( group ) {
let leader = group.leader;
if( leader && leader.isTrader ) {
staticMFD |= MFD_TRADERS;
areTraders = true; // having found group are traders, don't need to check escorts
}
}
group = ent.escortGroup;
if( group && !areTraders ) {
let leader = group.leader;
if( leader && leader.isTrader ) {
staticMFD |= MFD_TRADERS;
}
}
if( (staticMFD & MFD_TRADERS) || (staticMFD & MFD_POLICE)
|| (staticMFD & MFD_PIRATES)
|| (staticMFD & MFD_MILITARY)
|| (staticMFD & MFD_ALIENS) ) { // not NEUTRAL
return;
}
staticMFD |= MFD_NEUTRAL; // all other ships that are not pirates or traders (e.g., miners, hunters, etc.)
}
function isGalactic_Navy( ent ) {
var roles = ent.roles;
for( let idx = 0, len = roles.length; idx < len; idx++ ) {
let role = roles[ idx ];
if( role === 'SeccomLocator' || role === 'seccom-medship'
|| role === 'GN_sortie_target' || role === 'navyradar'
|| role === 'kurtz-pod' || role === 'nelly_crew' ) {
return true;
}
let dash = role.indexOf( '-' );
if( dash !== -1 ) {
let pref = role.slice( 0, dash );
if( pref === 'intercept' || pref === 'reserve'
|| pref === 'picket' || pref === 'patrol'
|| pref === 'hofd' || pref === 'galNavy' ) {
return true;
} else if( pref === 'navy' ) {
if( dataKey < 0 ) dataKey = ent.dataKey;
if( dataKey === 'FA_Titan'
|| dataKey === 'FA_Sunracer_N' ) { // Montana's Far_Arm_Ships.OXZ
return false;
}
return true;
}
} else {
let pref = role.slice( 0, 5 );
if( pref === 'navyS' || pref === 'navys' ) {
return true;
}
}
}
}
///
function notable_ent( ent, using_past_range, dist, restoring ) {// ALL calls must be preceeded by reset_common_vars
// - not done here as there may be some vars set before call
// 'using_past_range' only true in call from grow_new_list, to save distance
// calculations when batch processing ents sorted by distance
// 'restoring' is only used for ents in grow_hidden_scanned
var is_past_range;
if( !using_past_range ) past_range = 0; // also being called from _add_Sighting, check_Sightings, update_one_Sighting
if( scanClass < 0 ) scanClass = ent.scanClass;
if( scanClass === 'CLASS_VISUAL_EFFECT'
|| scanClass === 'CLASS_PLAYER' ) {
return false;
}
if( status < 0 ) status = ent.status;
if( _has_bad_status( ent, status ) )
return false;
switch( scanClass ) {
case 'CLASS_BUOY':
ve_colour = 'green';
isBuoy = true;
is_past_range = set_range( ent, 1, using_past_range, dist );
isBeacon = is_beacon( ent ); // can't assume all buoys are beacons // fn tests isBeacon < 0
if( isBeacon ) staticMFD |= MFD_NAVIGATION;
if( !isBeacon && is_past_range ) return false;
if( !isBeacon && eclipsed( ent, null, (distance < 0 ? dist : distance) ) )
return false;
rank = is_past_range ? 'nsr' : 'loc';
break;
case 'CLASS_CARGO': // cargo, pods & scoopable minables; all have distance < scannerRange_X_2
ve_colour = 'white';
is_past_range = set_range( ent, 1, using_past_range, dist );
if( !ext_ok && is_past_range ) return false;
shipClassName = ent.shipClassName;
if( shipClassName === 'Splinter' || shipClassName === 'Boulder'
|| shipClassName === 'Metal fragment' ) {
if( is_past_range ) return false; // discard rocks past scannerRange
if( eclipsed( ent, null, (distance < 0 ? dist : distance) ) )
return false;
rank = 'mng';
is_minable = true;
staticMFD |= MFD_MINING;
return true;
} else if( shipClassName === 'Thargoid Robot Fighter' ) {
if( is_past_range ) return false;
if( eclipsed( ent, null, (distance < 0 ? dist : distance) ) )
return false;
rank = 'loc'; //will be like cargo when inactive, ve_colour stays white
staticMFD |= MFD_ALIENS;
return true;
}
isBeacon = is_beacon( ent ); // some (escape?) pods may have a beacon // fn tests isBeacon < 0
if( isBeacon ) staticMFD |= MFD_NAVIGATION;
if( mk_maps && !restoring // unkown cargo must enter scannerRange in order to obtain its
&& is_past_range && !isBeacon ) // RFID frequency; existing preserved in grow_hidden_scanned
return false;
is_past_range = set_range( ent, 2, using_past_range, dist );// can 'see' cargo beyond scannerRange (RFID tags?)
let isCargo = ent.isCargo;
if( is_past_range ) { // exclude all beyond 2 * scannerRange
if( !isBeacon ) return false;
if( isCargo ) return false; // escortdeck ships are 'CLASS_CARGO' but !isCargo, have beacons
if( isVisible < 1 ) isVisible = ent.isVisible;
if( !isVisible ) return false;
}
if( !isBeacon && eclipsed( ent, null, (distance < 0 ? dist : distance) ) )
return false;
if( !getDetected( ent, restoring ) ) return false;
if( !is_past_range && isBeacon && is_ignored_ship( ent ) )
// - cargo w/ beacons, mainly EscortDeckShip's
// - for escorts: beaconCode = "E"+pad+" "+ship.name;
// - for towed ships: beaconCode = "D"; //Derelict
return false; // limit is_ignored_ship calls (expensive) by doing them last
is_cargo = isCargo; // escortdeck ships are 'CLASS_CARGO' but !isCargo
rank = 'loc'; // 'loc' rank, as distance < scannerRange_X_2
staticMFD |= MFD_SALVAGE;
break;
case 'CLASS_MINE':
case 'CLASS_MISSILE':
is_past_range = set_range( ent, 1, using_past_range, dist );
if( is_past_range ) return false;
if( eclipsed( ent, null, (distance < 0 ? dist : distance) ) )
return false;
ve_colour = 'cyan';
rank = 'isr'; // as distance < scannerRange
staticMFD |= MFD_WEAPONS;
break;
case 'CLASS_THARGOID':
case 'CLASS_POLICE':
case 'CLASS_MILITARY':
case 'CLASS_NEUTRAL':
if( scanClass === 'CLASS_THARGOID' ) {
dataKey = ent.dataKey;
if( dataKey !== 'tharglet' )
ve_colour = 'red'; //warship is red but tharglet is pink or white
else
ve_colour = 'pink';
isHostile = true;
staticMFD |= MFD_ALIENS;
} else if( scanClass === 'CLASS_POLICE' ) {
if( isGalactic_Navy( ent ) ) {
staticMFD |= MFD_MILITARY;
} else {
staticMFD |= MFD_POLICE;
}
ve_colour = 'purple';
} else if( scanClass === 'CLASS_MILITARY' ) {
staticMFD |= MFD_MILITARY;
}
// pre-filtered by equipment: !ext_ok: < scannerRange, !grav_eq_ok: .isVisible
if( is_cloaked( ent ) )
return false;
// jamming ents are still notable (can be seen, not locked); map.hasJammer gets set using isJamming in mkSighting
// is_jamming( ent ); // sets isJamming, returns true if effective (ie. scanFilter_ok)
if( isVisible < 0 ) isVisible = ent.isVisible;
isBeacon = is_beacon( ent ); // fn tests isBeacon < 0
if( isBeacon ) staticMFD |= MFD_NAVIGATION;
if( !isBeacon && eclipsed( ent, null, (distance < 0 ? dist : distance) ) )
return false;
if( !getDetected( ent, restoring ) ) return false;
if( dataKey < 0 ) dataKey = ent.dataKey;
if( is_drone < 0 && dataKey )
is_drone = dataKey.indexOf( 'drone' ) >= 0; // allow drones from HardShips OXP
if( is_drone ) { // core 'sees' then over scannerRange, even when !isVisible
if( distance < 0 ) distance = _detect_distanceTo( ent );
if( distance > scannerRange ) { // so are treated as special case (incompatable w/ code below)
if( debug ) log(ws.name, 'notable_ent, is_drone && distance > scannerRange, discarding ' + ent );
return false;
}
if( isHostile < 0 ) isHostile = is_hostile( ent );// is_hostile accounts for distance & FarStatus
ve_colour = isHostile ? 'red' : ve_colour < 0 ? 'yellow' : ve_colour;
if( debug && ve_colour === 'red' ) log(ws.name, 'notable_ent, ve_colour = ' + ve_colour + ', scanClass = ' + scanClass );
rank = isHostile ? 'bad' : 'isr'; // undetectible beyond scannerRange
if( scanClass === 'CLASS_NEUTRAL' ) {
classify_ship( ent, distance ); // set MFD_'s for traders, pirates
}
return true;
}
is_past_range = set_range( ent, 1, using_past_range, dist );
if( is_past_range && !ext_ok && !isBeacon ) return false;
if( distance < 0 ) distance = _detect_distanceTo( ent );
let in_mapping = -1, dist_visible = scannerRange, lost_target = false;
if( !isBeacon && is_past_range && ext_ok ) { // visible detection
is_past_range = !isVisible;
if( is_past_range && !grav_eq_ok ) { // w/o gs, 'Lost target's hang around until beyond 10% of
in_mapping = mk_maps ? false // visible distance (or next scan)
: _Sighting_index( ent, 'notable_ent' ) >= 0;
if( in_mapping ) { // only ents in mapping get is_past_range extended, so only they can
is_past_range = distance > dist_visible * 1.10;// get assigned a rank of 'ukn' below, ie. become lost target
lost_target = true;
}
}
}
if( !isBeacon && !isVisible && is_past_range && !grav_eq_ok ) // discard unseen ships
return false;
if( !isBeacon && is_past_range && gravScanProgress > 0 ) {// gravity scanner detection
gs_curr = grav_scan_dist( ent, true ); // true gets current range else max detection range
isVisible = distance < gs_curr;
is_past_range = !isVisible;
if( is_past_range ) {
if( in_mapping < 0 )
in_mapping = mk_maps ? false
: _Sighting_index( ent, 'notable_ent #2' ) >= 0;
if( in_mapping ) { // only ents in mapping get is_past_range extended, so only they can
gs_max = !stationNearby ? gs_curr * 1.10// get assigned a rank of 'ukn' below, ie. become lost target
: grav_scan_dist( ent );
is_past_range = distance > gs_max; // w/ gs, 'Lost target's hang around until beyond detectable range,
lost_target = distance < gs_max;
}
}
}
if( !isBeacon && !isVisible && is_past_range ) // discard undetectible ships
return false;
if( isHostile < 0 ) isHostile = is_hostile( ent ); // is_hostile accounts for distance & FarStatus
if( isHostile ) rank = 'bad';
else if( distance < scannerRange ) rank = 'isr';
else if( isVisible || isBeacon ) rank = 'nsr';
else if( lost_target ) rank = 'ukn';
else return false;
ve_colour = isHostile ? 'red' : // colour police too if hostile
ve_colour < 0 ? 'yellow' : ve_colour; // preserve previously assigned colour
if( ent.isDerelict ) {
ve_colour = 'blue';
staticMFD |= MFD_SALVAGE;
} else if( scanClass === 'CLASS_NEUTRAL' ) {
classify_ship( ent, distance ); // set MFD_'s for traders, pirates
}
if( !isVisible ) {
if( rank === 'ukn' ) {
ve_colour = 'gray'; // overrides all other colours
} else {
if( mass < 0 ) // mass set this way to catch any scriptInfo
script_mass = read_scriptInfo( ent ); // sets mass
ve_colour = mass < 130000 ? 'brown' : 'orange';
}
}
break;
case 'CLASS_ROCK': // rocks are either minables or Rock Hermits
if( dataKey < 0 ) dataKey = ent.dataKey;
if( dataKey === 'telescopemarker' ) return false;
is_past_range = set_range( ent, 1, using_past_range, dist );
if( isFrangible < 0 ) isFrangible = ent.isFrangible;
if( is_past_range && isFrangible ) return false; // discard rocks beyond scannerRange; isFrangible works for asteroids
// and boulders, the smaller rocks (eg. splinters) are CLASS_CARGO
if( isStation < 0 ) isStation = ent.isStation; // abandoned rock hermits are !isStation
isBeacon = is_beacon( ent ); // fn tests isBeacon < 0
if( isBeacon ) staticMFD |= MFD_NAVIGATION;
let hidden = -1;
if( is_past_range ) { // rock hermits
if( !ext_ok ) return false;
if( !isStation && // discard abandoned Rock Hermits beyond scannerRange
!(grav_eq_ok && (small_ok || large_ok)))// unless has a working dish
return false;
if( isVisible < 0 ) isVisible = ent.isVisible
if( !isBeacon && !isVisible ) return false;
hidden = !isBeacon && eclipsed( ent, null, (distance < 0 ? dist : distance) );
if( hidden ) return false;
if( isStation && !getDetected( ent, restoring ) ) {// rock hermit; abandoned ones are !isStation
return false;
}
} // else isFrangible
if( !isStation && !getDetected( ent, restoring ) ) // stealth mines are rocks
return false;
is_past_range = set_range( ent, 4, using_past_range, dist );
if( hidden === -1 )
hidden = !isBeacon && eclipsed( ent, null, (distance < 0 ? dist : distance) );
if( hidden ) return false;
isPiloted = ent.isPiloted;
if( isFrangible && !isPiloted ) { // only those < scannerRange or !isFrangible get here
ve_colour = 'white'; // so distance calc not required
rank = 'mng';
is_minable = true;
staticMFD |= MFD_MINING;
} else if( isStation ) { // abandoned Rock Hermits are !isStation but are isMinable
staticMFD |= MFD_STATION;
ve_colour = 'green';
rank = is_past_range ? 'nsr' : 'loc';
} else { // isFrangible
if( isPiloted ) { // lave.oxp has piloted rocks!
ve_colour = 'white'; // since isFrangible, must be in scannerRange
rank = 'loc'; // grouped w/ cargo
} else if( grav_eq_ok && (small_ok || large_ok) &&
ent.isMinable ) { // allows 'Abandoned Rock Hermit' for working dishes
staticMFD |= MFD_STATION;
staticMFD |= MFD_MINING;
rank = distance < scannerRange_X_4 ? 'loc' : 'nsr';
ve_colour = 'pink';
} else
return false; // fallback -> discard
}
break;
case 'CLASS_STATION':
ve_colour = 'green';
isStation = true;
staticMFD |= MFD_STATION;
isBeacon = is_beacon( ent ); // fn tests isBeacon < 0
if( isBeacon ) staticMFD |= MFD_NAVIGATION;
if( isVisible < 0 ) isVisible = ent.isVisible;
is_past_range = ext_ok ? !isVisible
: set_range( ent, 1, using_past_range, dist );
// jamming ents are still notable (can be seen, not locked); map.hasJammer gets set using isJamming in mkSighting
// is_jamming( ent ); // sets isJamming, returns true if effective (ie. scanFilter_ok)
if( !isBeacon ) {
if( is_past_range )
return false;
if( eclipsed( ent, null, (distance < 0 ? dist : distance) ) )
return false;
if( !getDetected( ent, restoring ) )
return false;
}
if( distance <= scannerRange ) {
rank = 'isr';
} else {
rank = 'nsr';
if( is_past_range && !isBeacon
&& _Sighting_index( ent, 'notable_ent' ) >= 0 ) {// only known (ie. existing) ents can become lost
rank = 'ukn';
}
}
break;
case 'CLASS_NO_DRAW':
if( radius < 0 ) radius = ent.radius || false;
if( !radius ) return false; // not an orb, probably wreckage
if( ext_ok ) {
if( isVisible < 0 ) isVisible = ent.isVisible;
is_past_range = isVisible; // sun, planets & moons always visible?
} else
is_past_range = set_range( ent, 1, using_past_range, dist );
ve_colour = 'lightgray';
rank = is_past_range ? 'orb' : 'loc';
isSun = ent.isSun;
isPlanet = !isSun;
staticMFD |= MFD_CELESTIAL;
break;
case 'CLASS_WORMHOLE':
is_past_range = set_range( ent, ext_ok ? 4 : 1, using_past_range, dist ); // scannerRange_X_4 = 102400, an arbitrary choice
if( is_past_range ) return false;
if( collisionRadius < 0 ) collisionRadius = ent.collisionRadius;
if( collisionRadius === 0 ) return false; // wormhole has evaporated
if( eclipsed( ent, null, (distance < 0 ? dist : distance) ) )
return false;
ve_colour = 'blue';
rank = distance < scannerRange ? 'loc' : 'nsr';
isWormhole = true;
staticMFD |= MFD_NAVIGATION;
break;
default: // what slips thru
if( debug && scanClass !== undefined && scanClass !== 'CLASS_VISUAL_EFFECT' && scanClass !== 'CLASS_PLAYER' )
log(ws.name, 'notable_ent, MISSING case for scanClass ' + scanClass );
return false;
}
// no code here unless repl. 'return true' cases above
return true;
}
///
// profiling functions ////////////////////////////////////////////////////////////////////////////
var profiling = false;
/* //!cagiife
function profile_create() { _profile_growing( true, false, false ); }
function profile_update() { _profile_growing( false, true, false ); }
function profile_refresh() { _profile_growing( false, false, true ); }
function _profile_growing( create, update, refresh ) {
function profiled_code() { _call_pending( 1 ); }
function profile_run( fn, result_array ) {
var profile, fname, title, total, jstime, start, end, saved;
set_fn_pending( fn );
while( tasks_pending.length ) {
title = fname = tasks_pending[0].fn.name;
if( fname.length > 12 )
fname = fname.substr( 0, 13 );
else
fname += ' '.slice( fname.length - 13 );
console.writeJSMemoryStats();
profile = console.profile( profiled_code, worldScripts.telescope._Sightings_closure );
start = profile.indexOf( ':' ) + 2; // ': '
end = saved = profile.indexOf( ' ms');
total = parseFloat( profile.slice( start, end ) );
start = profile.indexOf( ':', end ) + 2; // ': '
end = profile.indexOf( ' ms', start );
jstime = parseFloat( profile.slice( start, end ) );
result_array.push([ fname, total, jstime ]);
log(ws.name, '\n\nprofiling ' + title +'(): Total time: ' + total
+ ' ms\n ==' + '========================'.substr( 0, title.length )
+ ' =====' + (total < 10 ? '\n': '=\n' ) + profile.substr( saved + 4 ) );
}
}
clear_all_pending();
let saved_debug = debug;
debug = false;
profiling = true;
console.clearConsole();
var creating = null, updating = null, refreshing = null;
console.garbageCollect();
if( create ) {
creating = [];
profile_run( _create_Sightings, creating );
console.writeJSMemoryStats();
}
if( update ) {
updating = [];
profile_run( _update_Sightings, updating );
console.writeJSMemoryStats();
}
if( refresh ) {
refreshing = [];
profile_run( refresh_Sightings, refreshing );
console.writeJSMemoryStats();
}
report_timings( creating, updating, refreshing );
profiling = false;
debug = saved_debug;
}
*/ //!cagiife
/*
ws.time_create()
ws.profile_create()
JavaScript heap: 5.11 MiB (limit 32.00 MiB, 106 collections to date)
JavaScript heap: 5.11 MiB (limit 32.00 MiB, 106 collections to date)
JavaScript heap: 5.11 MiB (limit 32.00 MiB, 106 collections to date) => no garbage!
_create_Sightings (12) _update_Sightings (12) updating is:
====================== ====================== ============
_create_Sight = 0.9050 _update_Sight = 0.3110 65.6% faster (diff: 0.5940, js: 0.5830)
grow_new_list = 1.8510 grow_new_list = 1.3360 27.8% faster (diff: 0.5150, js: 0.4940)
grow_new_list = 2.0140 grow_new_list = 1.9240 4.5% faster (diff: 0.0900, js: 0.0740)
grow_new_list = 1.5700 grow_new_list = 1.7830 -13.6% slower (diff: -0.2130, js: -0.1470)
grow_new_list = 1.3030 grow_new_list = 1.2590 3.4% faster (diff: 0.0440, js: 0.0530)
grow_new_list = 0.9010 grow_new_list = 0.8300 7.9% faster (diff: 0.0710, js: 0.0370)
grow_new_list = 2.2960 grow_new_list = 1.8980 17.3% faster (diff: 0.3980, js: 0.3700)
grow_new_list = 2.7040 grow_new_list = 2.5930 4.1% faster (diff: 0.1110, js: 0.1280)
grow_new_list = 1.3190 grow_new_list = 1.0830 17.9% faster (diff: 0.2360, js: 0.1510)
grow_new_list = 0.6150 grow_new_list = 0.4360 29.1% faster (diff: 0.1790, js: 0.1510)
grow_new_list = 1.2130 grow_new_list = 0.1310 89.2% faster (diff: 1.0820, js: 1.0860)
grow_new_list = 0.3260 grow_new_list = 0.2070 36.5% faster (diff: 0.1190, js: 0.1190)
======= ======= ======
creating: 17.0170 updating: 13.7910 updating is 18.96% faster
*/
/* //!cagiife
function set_profiling() { profiling = true; } // debug access to glocal
function clear_profiling() { profiling = false; } // debug access to glocal
function time_create( solo ) { _time_growing( true, solo ? false : true, false ); }
function time_update( solo ) { _time_growing( false, true, solo ? false : true ); }
function time_refresh() { _time_growing( false, false, true ); }
function _time_growing( create, update, refresh ) {
function profiled_code() { _call_pending( 1 ); }
function profile_run( fn, result_array ) {
var profile, fname, total, jstime, parm;
set_fn_pending( fn );
while( tasks_pending.length ) {
fname = tasks_pending[0].fn.name;
parm = tasks_pending[0].parm;
profile = console.getProfile( profiled_code, worldScripts.telescope._Sightings_closure );
total = ( profile.totalTime * 1000 );
jstime = ( profile.javaScriptTime * 1000 );
if( fname.length > 12 )
fname = fname.substr( 0, 13 );
else
fname += ' '.slice( fname.length - 13 );
result_array.push([ fname, total, jstime, parm ]);
}
}
try {
clear_all_pending();
let saved_debug = debug;
debug = false;
profiling = true;
console.garbageCollect();
console.writeJSMemoryStats();
var creating = null, updating = null, refreshing = null;
if( create && update && refresh ) {
log(ws.name, '_time_growing, support for all 3 simultaneously NOT supported' );
return;
}
if( !create && !update && !refresh ) create = update = true;
if( create ) {
creating = [];
profile_run( _create_Sightings, creating );
console.writeJSMemoryStats();
}
if( update ) {
updating = [];
profile_run( _update_Sightings, updating );
console.writeJSMemoryStats();
}
if( refresh ) {
refreshing = [];
profile_run( refresh_Sightings, refreshing );
console.writeJSMemoryStats();
}
report_timings( creating, updating, refreshing );
profiling = false;
debug = saved_debug;
} catch( err ) {
log( ws.name, ws._reportError( err, 'time_create' ) );
if( debug ) throw err;
}
}
function report_timings( creating, updating, refreshing ) {
var first_col = null, second_col = null;
if( creating ) {
first_col = creating;
if( updating ) second_col = updating;
else if( refreshing ) second_col = refreshing;
} else if( updating ) {
first_col = updating;
if( refreshing ) second_col = refreshing;
} else if( refreshing ) {
first_col = refreshing;
}
var ctotal, utotal, cstep, ustep, cjs, ujs, diff, last_c = false, last_u = false;
var csum = 0, usum = 0, cjsum = 0, ujsum = 0, out_c = true, out_u = true;
var out, ci, ui, jspercent, last_cr, last_up, parm, spacer = ' ';
var cr_len = first_col ? first_col.length : 0;
var up_len = second_col ? second_col.length : 0;
var both = first_col && second_col;
// out = '_create_Sightings ('+cr_len+') _update_Sightings ('+up_len+') updating is:'
out = '\n' + (creating ? '_create' : updating ? '_update' : 'refresh');
//if( first_col )
out += '_Sightings ('+cr_len+')';
if( both ) out += spacer;
if( second_col ) out += (creating ? (updating ? '_update' : 'refresh') : 'refresh') + '_Sightings ('+up_len+')';
if( both ) out += spacer + (creating ? (updating ? 'updat' : 'refresh') : 'refresh') + 'ing is:';
// +'\n====================== ====================== ============';
out += '\n';
if( first_col ) out += '======================';
if( both ) out += spacer;
if( second_col ) out += '======================';
if( both ) out += spacer + '============';
var count = 0;
for( ci = 0, ui = 0; ci < cr_len || ui < up_len; ) { // cr_len !== up_len
out += '\n';
if( out_c && ci < cr_len ) [ cstep, ctotal, cjs, parm ] = first_col[ ci ];
if( out_u && ui < up_len ) [ ustep, utotal, ujs, parm ] = second_col[ ui ];
if( both ) {
out_c = last_c || ci < cr_len
&& ( ci === 0 || cstep === ustep // 1st row is always diff
|| cstep === last_cr ); // complete run of same steps
out_u = last_u || ui < up_len
&& ( ui === 0 || ustep === cstep // 1st row is always diff
|| ustep === last_up ); // complete run of same steps
} else {
out_c = true;
out_u = false;
}
if( !out_c && !out_u )
if( ci < cr_len && ci < ui ) out_c = true; // allow shorter to catch up
else if( ui < up_len && ui < ci ) out_u = true;
else if( ci === ui ) {
if( cr_len < up_len ) out_u = true;
else out_c = true;
} else if( ci < cr_len ) out_c = true; // go w/ unfinished one
else if( ui < up_len ) out_u = true;
if( !out_c && !out_u ) { log(ws.name, 'time_create, stalled w/ cstep = ' + first_col[ ci ][0] + ', ustep = ' + second_col[ ui ][0] ); break; }
if( !out_c && !out_u ) out_c = true; // create has extra grow_new_list entries
if( out_c && !last_c ) {
ci++;
csum += ctotal;
cjsum += cjs;
last_cr = cstep;
let txt = cstep + ' = ' + ctotal.toFixed( 4 );
if( both && ci === cr_len ) {
last_c = txt;
if( ui < up_len - 1 ) out += ' ';
} else out += txt;
} else if( both && !last_c && !last_u ) {
out += ' ';
}
if( out_u && !last_u ) {
ui++;
usum += utotal;
ujsum += ujs;
last_up = ustep;
let txt = (first_col ? spacer : '') + ustep + ' = ' + utotal.toFixed( 4 );
if( both && ui === up_len ) {
last_u = txt;
if( ci < cr_len ) out += ' ';
} else out += txt;
} else if( both && !last_c ) {
out += ' ';
}
if( out_c && out_u && !last_c && !last_u || (last_c && last_u)) {
if( last_c && last_u ) out += last_c + last_u;
diff = ((ctotal - utotal) / ctotal * 100).toFixed( 1 );
diff = ' '.slice( diff.length-8 ) + diff;
let totdiff = (ctotal - utotal).toFixed(4);
let jsdiff = (cjs - ujs).toFixed(4);
out += ' ' + diff + '% '+( diff < 0 ? 'slower' :'faster' )
+' (diff: '+(totdiff > 0 ? ' '+totdiff : totdiff)
+', js: '+(jsdiff > 0 ? ' '+jsdiff : jsdiff)+')';
} else if( out_u && !last_c ) {
jspercent = (ujs / utotal * 100).toFixed( 1 );
out += spacer + 'native: '+ (utotal - ujs).toFixed(4) +', js: '+ ujs.toFixed(4)+' ('+jspercent+'%)';
} else if( out_c && !last_u ) {
jspercent = (cjs / ctotal * 100).toFixed( 1 );
out += spacer + 'native: '+ (ctotal - cjs).toFixed(4) +', js: '+ cjs.toFixed(4)+' ('+jspercent+'%)';
}
if( parm ) out += ' parm: ' + parm;
if( last_c && (!both || last_u )) break;
if( ++count > 25 ) break;
}
if( both ) diff = ((csum - usum) / csum * 100).toFixed( 2 );
// out += '\n ======= ======= ======';
out += '\n';
if( first_col ) out += ' =======';
if( both ) out += spacer;
if( second_col ) out += ' =======';
if( both ) out += spacer + '======';
// out += '\n creating: ' +csum.toFixed( 4 )+ ' updating: ' +usum.toFixed( 4 )+ ' updating is '
// + diff + '% '+( diff < 0 ? 'slower' :'faster' );
out += '\n';
if( first_col ) out += (creating ? ' creating: ' : updating ? ' updating: ' : ' refreshing: ') +csum.toFixed( 4 );
if( both ) out += spacer;
if( second_col ) out += (updating ? ' updating: ' : ' refreshing: ') +usum.toFixed( 4 );
if( both ) out += spacer + (updating ? 'updating' : 'refreshing') + ' is '+ diff + '% '+( diff < 0 ? 'slower' :'faster' );
log(ws.name, out );
profiling = false;
}
*/ //!cagiife
///////////////////////////////////////////////////////////////////////////////////////////////////
// _target_marker_closure /////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
function _update_target_marker() { // FCB to make and update shadow and vmarkship
try {
if( have_shutdown )
return;
var map = curr_S.map;
if( !equip_ok ) { // 1st FCB fn called, so responsible for orderly shutdown if equipment damaged
if( map )
_set_curr_Sighting( null, '_update_target_marker' );// no parms resets
_newList();
have_shutdown = true;
return;
}
if( !map )
return;
var marker = curr_S.marker;
if( marker && !marker.isValid ) {
marker = removeMarker(); // convenience return of null
}
var ent = curr_S.ent;
if( !ent || _has_bad_status( ent ) ) {
_set_curr_Sighting( null, '_update_target_marker' );// no parms resets
return;
}
reset_common_vars();
// ensure target's distance is up to date so setting ps.target will always succeed
// - when crossing boundary, must be exact if switching from far to near as cannot
// set ps.target to an ent that is scannerRange + 0.000000001 distant!
distance = map.ent_dist = _detect_distanceTo( ent );
radius = ent.radius || false;
if( !marker
|| crossing_boundary( map, distance + ent.collisionRadius,
curr_S.marker_type, radius, '_update_target_marker' ) ) {
_manage_marker( map, false, '_update_target_marker (IdentKeyPress = '
+ identKeyPress + ')' );
if( !marker ) {
marker = curr_S.marker;
if( !marker ) {
using_common_vars = false;
return;
}
}
} else {
calc_marker_posn( map, distance, radius );
marker.position = marker_posn;
marker.velocity = ps_velocity; //keep target over Torus speeds in FarPlanets OXP
}
if( curr_S.marker_type === 'marker' ) { // update km
let displayName = set_displayName( map );
let distAndUnits = distWithUnits( distance );
marker.displayName = distAndUnits + ' ' + displayName;
}
using_common_vars = false;
} catch( err ) {
log( ws.name, ws._reportError( err, '_update_target_marker' ) );
if( debug ) throw err;
}
}
var marker_posn = [];
var target_posn = [];
function calc_marker_posn( map, map_ent_dist, is_an_orb ) {
copy_vector( map.last_posn, position );
copy_vector( position, target_posn );
subtract_vectors( position, ps_position, target_vector );
unit_vector( target_vector, target_direction ); // unit vector to target (or to last known pos)
let marker_dist = scannerRange - 600;
// - the shadow lollipop lies in front of the lightballs (sR - 400 in lb_position) to reduce flickering
//do not set closer to scannerRange so won't leave behind aft markers during torus travel
if( map_ent_dist < scannerRange && is_an_orb ) { // close to a planet, moon or suns
if( (map_ent_dist - 300) < marker_dist ) {
marker_dist = map_ent_dist - 300; // min( marker_dist, map_ent_dist - 299.6 ) from old code
}
if( marker_dist < ps_collisionRadius ) // so we don't collide with marker
marker_dist = ps_collisionRadius;
}
scale_vector( target_direction, marker_dist, vector )
add_vectors( ps_position, vector, marker_posn );
if( moving_fast )
apply_speed_adj( marker_posn );
}
function removeMarker() {
if( curr_S.marker ) {
/*
if( curr_S.marker_type === 'marker' ) {
curr_S.marker.removeCollisionException(ps);
} // not really necessary
*/
curr_S.marker.$TelescopeTarget = null;
curr_S.marker.remove();
}
curr_S.marker = null;
curr_S.marker_type = '';
return null; // convenience return
}
function manage_marker( new_map, showName, caller ) {
try {
_manage_marker( new_map, showName, caller );
} catch( err ) {
log( ws.name, ws._reportError( err, 'manage_marker', [showName, caller] ) );
if( debug ) throw err;
}
}
function _manage_marker( new_map, showName, caller ) {
if( !mappingReady || have_shutdown ) return; // not yet built OR empty
/*
if( debug ) {
log(ws.name, '\n_manage_marker, new_map'
+ (new_map && new_map.ent ? ' (' + new_map.ent.entityPersonality + '): ' + new_map.ent.displayName : ': ' + new_map)
+ ', showName: ' + showName + ', caller: ' + caller );
}
*/
var ent, new_marker, map = null;
var index = new_map === null ? -1 : _Sighting_index( new_map, '_manage_marker' );
var actual = curr_S.map || false; // ? may have died, docked, jumped, out of range, etc., ?must remove telescopemarker
// check we have a valid target (current or new)
var still_alive = actual && actual.ent && actual.ent.isValid;// if he's ok, may have to just update marker position
if( index < 0 && still_alive ) { // no new target, fetch existing one
map = actual;
} else { // get ready to switch targets
map = index >= 0 && index < maplen ? mapping[ index ] : null;
}
/*
if( debug ) {
log(ws.name, ' index: ' + index + ', actual'
+ (actual && actual.ent ? ' (' + actual.ent.entityPersonality + '): ' + actual.ent.displayName : ': ' + actual)
+ ', still_alive: ' + still_alive + ', map'
+ (map && map.ent ? ' (' + map.ent.entityPersonality + '): ' + map.ent.displayName : ': ' + map) );
}
*/
if( !map ) { // nothing to do
return;
}
ent = map.ent;
if( _has_bad_status( ent ) ) { // target died, jumped or docked, clean up
if( actual && new_map === actual ) { // ent is not a new target, reset curr_S
_set_curr_Sighting( null, '_manage_marker, _has_bad_status, via ' + caller );
} // else we just don't switch to new ent (ie. stay w/ curr_S.map)
return;
}
if( is_jamming( ent ) ) { // ent just started jamming
if( actual && new_map === actual ) { // jammer is current target, reset curr_S
_set_curr_Sighting( null, '_manage_marker, is_jamming, via ' + caller );
} // else we just don't switch to new ent (ie. stay w/ curr_S.map)
return;
}
if( is_cloaked( ent ) ) { // ent cloaked before we got here!
if( actual && new_map === actual ) { // current target just cloaked, reset curr_S
_set_curr_Sighting( null, '_manage_marker, is_cloaked, via ' + caller );
} // else we just don't switch to new ent (ie. stay w/ curr_S.map)
_delete_Sighting( map, '_manage_marker, via ' + caller );// telescope cannot 'see' cloaked ships
return;
}
if( radius < 0 || !using_common_vars )
radius = ent.radius || false; // only orbs have .radius
if( map !== actual ) { // update curr_S.name
set_displayName( map );
}
// determine if we keep current marker (reposition) or must create new one
var marker = curr_S.marker || null;
if( marker && !marker.isValid ) {
marker = removeMarker(); // convenience return of null
}
if( ent.isWormhole ) {
// core requires user to manually target wormhole (player hits 'u', 'r') for the wormhole scanner to work
// we cannot target a wormhole, only the user can; setting ps.target = wormhole generates exception
// "Exception: Error: Cannot set property target of instance of PlayerShip to invalid value"
_handle_wormhole( ent ); // mimic core wormhole scanner
if( (!curr_S || curr_S.ent !== ent) && ent.$TelescopeScanStart !== undefined ) {
_set_curr_Sighting( ent, '_manage_marker wormhole, via ' + caller );
}
return;
}
var mark_type = curr_S.marker_type || '';
// edge for near/far targets is .distanceTo === scannerRange, regardless of any radius (core tries for 25k)
// => marker should read _detect_distanceTo, ie. position.distanceTo - ent.collisionRadius
let map_ent_dist = map.ent_dist; // now updated every frame in reposition_effects
new_marker = true;
do { // determine if we may need a new marker
if( !marker || mark_type === '' ) break; // - launching or leaving witchspace
if( curr_target === null ) break; // - had no target
if( crossing_boundary( map, map_ent_dist, mark_type, // - ent has crossed scannerRange
radius, '_manage_marker, via ' + caller ) ) // will switch marker type
break;
new_marker = false; // marker checks out ok, we can just move it
} while( false );
let ent_dist = map_ent_dist + hullOffset( ent ); // reverse adj from _detect_distanceTo, so it's .distanceTo
// collisionRadius << scannerRange
/*
if( debug ) {
log(ws.name, ' for new_marker: ' + new_marker + ', mark_type: ' + mark_type
+ ', ent_dist: ' + ent_dist.toFixed(2)
+ ' (map_ent_dist: ' + map_ent_dist.toFixed(2) + ' + hullOffset( ent ): ' + hullOffset( ent ).toFixed(2)
+ ' vs distanceTo: ' + ps.position.distanceTo( ent ).toFixed(2)
+ '\n marker: ' + marker + '\n curr_target: ' + curr_target );
}
*/
mark_type = ent_dist < scannerRange && !radius // sun/planet/moon must remain far targets as core does not
? '-shadow' : 'marker'; // allow ship to target them; esp a prob w/ small moons with
// collisionRadius << scannerRange
// do the actual updating of target & marker
calc_marker_posn( map, map_ent_dist, radius );
if( markerInsideOrb( map ) ) {
_set_curr_Sighting( null, '_manage_marker, markerInsideOrb, via ' + caller );
return;
}
/*
if( debug ) {
log(ws.name, ' new_marker: ' + new_marker + ', mark_type: ' + mark_type
+ ', identKeyPress: ' + identKeyPress + ' (ws$: ' + ws.$IdentKeyPress
+'), \n curr_target' + (curr_target ? ' (' + curr_target.entityPersonality + '): ' + curr_target.displayName : ': ' + curr_target) );
}
*/
if( !new_marker ) { // just move the existing telescopemarker, so marker & marker_type stay the same
marker.position = marker_posn;
marker.velocity = ps_velocity; //keep target over Torus speeds in FarPlanets OXP
if( mark_type === '-shadow' ) {
if( curr_target !== ent ) { // switch from one near target to another near one
switch_PS_target( ent, map, showName, '1_manage_marker, !new_marker, via ' + caller );
} else if( identKeyPress === IDENT_LOCKED && !ps.target ) {
switch_PS_target( ent, map, showName, '2_manage_marker, !ps.target but known marker, via ' + caller );
}
} else if( mark_type === 'marker' ) { // have switched from a far target to a different far one, where
if( curr_S.map !== map ) { // _switch_PS_target is not called for far targets (it's always the target marker)
if( !curr_S.ent.radius ) { // not an orb
marker.removeCollisionException( curr_S.ent ); // remove previous target
}
if( !radius ) { // not an orb
marker.addCollisionException( ent ); // avoid impacting target
}
marker.displayName = set_displayName( map );
_set_curr_Sighting( map, '_manage_marker, !new_marker, via ' + caller );
if( showName ) {
init_headingView();
showTargetName( map );
}
_showVShip( ent.dataKey );
}
if( !ps.target ) { // added to support ident sequence (restore after target lost)
// } else if( identKeyPress === IDENT_LOCKED && !ps.target ) { // added to support ident sequence (restore after target lost)
switch_PS_target( marker, map, showName, '3_manage_marker, !ps.target but known marker, via ' + caller );
}
}
// since re-using marker (& not chg'g ps.target), get no new 'ID locked' verbal msg
// - no msg if browsing (!weaponsOnline) otherwise must hit ident key to chg far target, so do get verbal msg
} else { // create a new one (it gets remove()'d there)
if( marker ) removeMarker(); // remove any existing marker/shadow
curr_S.marker_type = mark_type;
if( mark_type === 'marker' ) { // addShips(role, count, position, radius);
marker = addShips( 'telescopemarker', 1, marker_posn, 1 )[ 0 ];
marker.addCollisionException( ps ); // avoid limiting Torus speed (Milo & dybal)
if( !radius ) { // not an orb
marker.addCollisionException( ent ); // avoid impacting target
}
} else { //replace the markership with visuall effect shadow lollipop
marker = addVisualEffect( 'telescope-shadow', marker_posn );
}
if( marker ) {
marker.velocity = ps_velocity; //keep target over Torus speeds in FarPlanets OXP
curr_S.marker = marker;
if( mark_type === 'marker' ) {
switch_PS_target( marker, map, showName, '4_manage_marker, new marker, via ' + caller );
} else if( mark_type === '-shadow' ) {
switch_PS_target( ent, map, showName, '5_manage_marker, new -shadow, via ' + caller );
}
} else {
log(ws.name, '_manage_marker, unable to create marker; shutting down telescope operations!' );
_shutdown_Sightings();
}
}
}
/* when a user manually targets a wormhole, the wormhole scanner, if present, begins & after about 7 sec.s reports
its findings. when user targets it, we add property $TelescopeScanStart to emulate the scanner and update its displayName
*/
function _handle_wormhole( ent ) { // mimic behavior of core game wormhole scanner
if( !ent || !ent.isValid || ent.collisionRadius === 0 ) { // expired wormholes eventually (!) become invalid
_delete_Sighting( ent, '_handle_wormhole' );
return;
}
if( ps.equipmentStatus( 'EQ_WORMHOLE_SCANNER' ) !== 'EQUIPMENT_OK' )
return;
if( !curr_target || curr_target !== ent ) // wait for it to be targetted before starting countdown
return;
if( ent.$TelescopeScanStart === undefined ) { // save time acquired, to display destination after wormhole scanner (using 7 sec)
ent.$TelescopeScanStart = clock.seconds;
if( !ent.displayName ) { // if not named by someone else
ent.displayName = ent.$TelescopeName = 'Wormhole'; // name is 'wormhole' when Sighting created
}
} else if( clock.seconds - ent.$TelescopeScanStart >= 7 ) { // once 7 seconds have expired, we'll update the displayName w/ the destination
if( ent.$TelescopeName ) // if we named it
ent.displayName = 'Wormhole to ' + System.systemNameForID( ent.destination );
}
}
// : ' + + '
function switch_PS_target( ent, map, showName, caller ) {
// if( debug ) log(ws.name, 'switch_PS_target, ent: ' + (ent ? ent.displayName : ent) + ', map: ' + (map && map.ent ? map.ent.displayName : map)
// + ', showName: ' + showName + ', caller: ' + caller);
if( ent === null ) { // explicitly null target
ws.$TelescopeTargetSet = true; // prevent shipTargetAcquired and shipTargetLost treating
ps.target = null; // this assignment as a new target
ws.$TelescopeTargetSet = false;
_set_curr_Sighting( null, '_switch_PS_target, ent is null, via ' + caller );
return;
}
if( ps.target === ent ) { // prevent double ident system verbal message on near targets
_set_curr_Sighting( map, '_switch_PS_target #3-aborted, via ' + caller );
_showVShip( map.ent.dataKey );
return;
}
if( curr_S.marker_type === 'marker' ) { //remove km from ident message
ent.displayName = set_displayName( map ); // set displayName of telescopemarker
}
ws.$TelescopeTargetSet = true; // prevent shipTargetAcquired and shipTargetLost treating
ps.target = ent; // this assignment as a new target
ws.$TelescopeTargetSet = false;
if( ps.target !== ent ) { // unlockable!
let cloaked = is_cloaked( ent ),
bad_status = _has_bad_status( ent );
log(ws.name, 'switch_PS_target, target lock FAILED, ' + caller + ', _has_bad_status: ' + bad_status
+ ', is_cloaked: ' + cloaked + ', ps.target = ' + ps.target
+ '\n ship ent_dist = ' + map.ent_dist + ', .distanceTo( ent ): ' + ps.position.distanceTo( ent ) +': ' + ent );
if( !bad_status && !cloaked ) {// can take time to register
log(ws.name, ' * UNABLE to lock * ' + cd._showProps( map, 'map', 1,1,1 ) );
}
} else {
_set_curr_Sighting( map, '_switch_PS_target #4, via ' + caller );// make sure in sync w/ ps.target, in case fn called from other than _manage_marker
if( showName ) {
init_headingView();
showTargetName( map );
}
_showVShip( map.ent.dataKey );
}
}
function crossing_boundary( map, dist, mark_type, targetRadius/*, caller*/ ) {// crossing the scannerRange threshold will generate a
// shipTargetLost event, we need to change marker type
if( !map ) return false;
let ent = map.ent;
if( !ent || !ent.isValid ) return false;
var marker_type = mark_type || curr_S.marker_type;
let isanOrb = targetRadius === null ? ent.radius : targetRadius;// radius can be false
if( isanOrb ) return marker_type === '-shadow'; // sun/planet/moon can never use shadow marker
var targetDist = dist;
if( dist === null ) { // null dist forces distance update calc
targetDist = map.ent_dist = _detect_distanceTo( ent ); // distance to hull/surface
}
targetDist += hullOffset( ent ); // reverse adj from _detect_distanceTo, so it's .distanceTo
if( (marker_type === 'marker' && targetDist <= scannerRange) // just came into scannerRange
|| (marker_type === '-shadow' && targetDist > scannerRange) ) { // just left scannerRange
return true;
}
return false;
}
function mostCentered( mode, chg_by_ident ) {
try {
_mostCentered( mode, chg_by_ident );
} catch( err ) {
log( ws.name, ws._reportError( err, 'mostCentered', [mode, chg_by_ident] ) );
if( debug ) throw err;
}
}
// : ' + + '
function _mostCentered( mode, chg_by_ident ) { // chg_by_ident is false (explicit in 2 calls), may be true in call from shipTargetLost
// where target lock lost but ship not destroyed, then ident key press is assumed
// called from _auto_updates only when not Steering and IdentKeyPress is IDENT_READY
if( have_shutdown )
return;
var orig = curr_S.map,
map = orig;
reset_common_vars(); // ensure no data carries over to new ent
using_common_vars = false; // not using glocals as code path complex & not in fcb
// when called w/ null for distance, crossing_boundary will update map.ent_dist
let crossed = crossing_boundary( map, null, null, null, '_mostCentered' );
if( map && crossed ) { // keep same target
_manage_marker( map, false, '_mostCentered (scannerRange)' );
return;
}
var ident_was_pressed = mode === 'ident' && chg_by_ident; // called from shipTargetLost and target still alive
/*
if( debug && mode === 'ident' ) {
log('\n_mostCentered, ident_was_pressed: ' + ident_was_pressed
+ ' ==> mode (' + mode + ') === ident: ' + (mode === 'ident')
+ ' && chg_by_ident: ' + (chg_by_ident || false) );
log('\t\t identKeyPress: ' + identKeyPress + ', orig'
+ (orig && orig.ent ? ' (' + orig.ent.entityPersonality + '): ' + orig.ent.displayName : ': ' + orig) );
}
*/
find_most_central( mode ); // sets found_map to a Sighting or null if it fails
if( !ident_was_pressed || !map ) {
if( !found_map ) {
// if( debug && mode === 'ident' ) log(' !found_map, bailing');
return;
}
map = found_map;
}
if( !map ) { // may have jumped/died
_set_curr_Sighting( null, '_mostCentered (!map)' ); // sets identKeyPress to IDENT_READY
// if( debug && mode === 'ident' ) log(' !map, reset curr_S, bailing');
return;
}
isWormhole = map.ent.isWormhole;
if( orig !== map ) { // changing to a new target
ws.$IdentKeyPress = identKeyPress = IDENT_READY; // reset lock
if( !isWormhole ) {
_manage_marker( map, false, '_mostCentered (new target)' );
// if( debug && mode === 'ident' ) log('\n_mostCentered, back from _manage_marker');
}
/*
if( debug && mode === 'ident' ) {
log('_mostCentered, orig !== map, ident found new target, identKeyPress := IDENT_READY');
log(' bailing, new map'
+ (map && map.ent ? ' (' + map.ent.entityPersonality + '): ' + map.ent.displayName : ': ' + map) );
}
*/
return;
}
if( !ident_was_pressed ) { // the rest of function is for ident only
return;
}
if( identKeyPress !== IDENT_READY && found_map != orig ) { // new most centered ship
/*
if( debug && mode === 'ident' ) {
log('_mostCentered, identKeyPress := IDENT_READY because found_map: '
+ (found_map && found_map.ent ? ' (' + found_map.ent.entityPersonality + '): ' + found_map.ent.displayName : ': ' + found_map)
+ ' is != orig: '
+ (orig && orig.ent ? ' (' + orig.ent.entityPersonality + '): ' + orig.ent.displayName : ': ' + orig) );
}
*/
ws.$IdentKeyPress = identKeyPress = IDENT_READY;
map = found_map;
}
if( identKeyPress === IDENT_READY ) {
if( !isWormhole && orig && orig === found_map ) { // wormhole need extra ident from player
// if( !isWormhole || (orig && orig !== map) ) { // wormhole need extra ident from player
ws.$IdentKeyPress = identKeyPress = IDENT_LOCKED; // really lock the target
/*
if( debug && mode === 'ident' ) {
log('_mostCentered, lock the target, identKeyPress := IDENT_LOCKED, curr_target: '
+ (curr_target ? ' (' + curr_target.entityPersonality + '): ' + curr_target.displayName : curr_target));
}
*/
if( IdentMessages )
consoleMessage( 'Telescope locked: ' + curr_S.name, ConsoleMsgDurn );
}
_manage_marker( map, false, '_mostCentered (IdentKeyPress = ' + (identKeyPress === 1 ? 'IDENT_LOCKED' : 'IDENT_READY') + ')' );
// if( debug && mode === 'ident' ) log('\n_mostCentered, back from _manage_marker2');
} else {
if( identKeyPress === IDENT_LOCKED ) {
ws.$IdentKeyPress = identKeyPress = IDENT_STEERING; //next time will do unlock
_manage_marker( map, false, '_mostCentered2 (IdentKeyPress = IDENT_STEERING)' );
// if( debug && mode === 'ident' ) log('\n_mostCentered, back from _manage_marker3');
if( Steering > 0 ) {
if( ps_speed < ps_maxSpeed * 1.1 ) { //prevent unwanted steer when lost marker at high speeds
if( IdentMessages )
consoleMessage( 'Telescope lock, auto-steering', ConsoleMsgDurn );
start_Steering(); //turn to the target
/*
if( debug && mode === 'ident' ) {
log('_mostCentered, steering, next time will do unlock, identKeyPress := IDENT_STEERING');
}
*/
return; // exit to allow steering to complete
}
/*
} else {
if( debug && mode === 'ident' ) {
log('_mostCentered, not steering, fall thru to unlock, identKeyPress := IDENT_STEERING');
}
*/
} // steering is turned off, so proceed to unlock now
}
// since not steering, proceed to unlock stage
if( identKeyPress === IDENT_STEERING ) { // manual unlock, as steering is off or failed to reach target
ws.$IdentKeyPress = identKeyPress = IDENT_UNLOCK; // need a delay, else relock immediately; see _auto_updates()
/*
if( debug && mode === 'ident' ) {
log('_mostCentered, ' + (Steering > 0 ? 'finished' : 'not') + ' steering, proceed to unlock stage, identKeyPress := IDENT_UNLOCK');
}
*/
if( IdentMessages )
consoleMessage( 'Telescope lock released', ConsoleMsgDurn );
}
}
}
var entHeading = []; // working vector
var vectorToOrb = []; // working vector
var vectorToPerp = []; // working vector
function markerInsideOrb( map ) { // PlanetFall support: cannot mark distant target if marker
// will be inside planet!
// at rest, marker: scannerRange - 600, placed just in front of band of far lightballs
var neededDist = scannerRange - 600; // for head-on approach
entHeading.length = 0;
for( let mi = 0; mi < maplen; mi++ ) {
let orb = mapping[ mi ];
if( orb.rank !== 'orb' ) continue;
let orbDist = orb.ent_dist; // distance to surface
if( orbDist > neededDist ) continue;
// the rest only executes if an orb is within scannerRange
if( entHeading.length === 0 ) { // delay as may not be needed
subtract_vectors( map.last_posn, ps_position, entHeading );
unit_vector( entHeading, entHeading );
}
let orbRadius = orb.ent.radius;
subtract_vectors( orb.last_posn, ps_position, vectorToOrb );
let distToOrb = vector_magnitude( vectorToOrb ) - orbRadius; // distance to surface
unit_vector( vectorToOrb, vector );
let angleCos = dot_product( entHeading, vector ); // only if both are unit vectors is dot_product the cosine
if( angleCos < 0 ) continue; // orb is > 90° from heading
// projecting orb 'vector' onto entHeading is a quick way to catch most cases (unless quite near an orb)
if( angleCos * distToOrb > neededDist )
continue;
// now check if entHeading intersects orb surface
let distToPerp = dot_product( entHeading, vectorToOrb );// projecting entire vector to center onto unit vector gives
scale_vector( entHeading, distToPerp, vectorToPerp ); // portion of entHeading up to perpendicular from orb's center
// find point along vector to target that meets the perpendicular
add_vectors( ps_position, vectorToPerp, vector );
subtract_vectors( orb.ent.position, vector, vector );
if( vector_magnitude( vector ) < orbRadius ) { // perpendicular clears the limb
return true;
}
}
return false;
}
// : ' + + '
function closest_to() {
var len = find_list.length;
var min_a = find_radius;
var best = null; // ie. not init'd
var best_d = MaxRange;
var angle, diff, distance, map, ent, idx;
var was_using_common_vars = using_common_vars;
using_common_vars = false;
find_list.sort( map_sort_heading );
for( idx = 0; idx < len; idx++ ) { //search target near the center
map = find_list[ idx ];
ent = map.ent;
if( !ent || !ent.isValid ) continue;
if( weaponsOnline
&& !ent.isVisible // cannot target !isVisible w/ grav. scanner off-line (see reposition_effects)
&& ent.scanClass !== 'CLASS_CARGO' // are deleted when go beyond RFID range
&& !is_beacon( ent ) ) // radio always detectable
continue;
let ent_rank = ent.rank;
if( ent_rank === 'ukn' ) continue; // is lost (but recoverable) target
if( find_rank >= 0 && find_rank !== ent_rank ) // wrong rank in limited rank search
continue;
if( is_cloaked( ent ) ) continue;
if( weaponsOnline && alertCondition > YELLOW_ALERT
&& ent.isDerelict ) // ignore when fighting
continue;
if( ent.isWormhole ) {
if( find_mode !== 'ident' ) continue; // wormholes must be targeted by player ('r')
if( weaponsOnline && alertCondition > YELLOW_ALERT )// ignore when fighting
continue;
if( ent.collisionRadius === 0 ) continue; // has closed
if( ent.$TelescopeScanStart === undefined )
continue; // hasn't been manually ('ident' mode) targetted yet
}
if( redAlertOptimize() ) continue; // headingTo is NOT being updated (see _reposition_effects)
if( markerInsideOrb( map ) ) continue;
angle = map.headingTo;
if( find_mode !== 'ident' && angle > find_radius ) // halt search as remainings ents are outside specified cone
break;
diff = abs( angle - min_a ); // angle always >= 0, so abs(abs(angle) - abs(min_a)) not necessary
if( best && diff > 0.5 ) // HALF_a_DEGREE // found at least one and are beyond pt for distance chk
break; // now sorted by headingTo, so only need check 1st few ents
if( angle > min_a )
break; // now sorted by headingTo, so only need check 1st few ents
// if( angle > min_a ) continue;
distance = map.ent_dist;
if( distance <= scannerRange && is_jamming( ent ) ) // cannot target inside scannerRange
continue;
if( !best ) { // 1st target found
best_d = distance;
min_a = angle;
best = map;
continue;
}
if( diff < 0.5 ) {// HALF_a_DEGREE // for ships within a half degree, pick the closer one
if( distance > best_d ) continue;
}
best_d = distance;
min_a = angle;
best = map;
}
using_common_vars = was_using_common_vars;
// if(find_mode === 'ident')
// log('closest_to, returning best: ' + (best ? best.ent : best) + '\n');
return best;
}
var find_mode, find_list, find_radius, find_rank, found_map;
function find_most_central( mode ) {
if( !mappingReady || maplen === 0 ) return; // wait for mapping to be created OR it's empty
if( !viewIsStandard )
return;
find_mode = mode;
find_list = mapping;
found_map = null;
if( ILS && ILS.$L === ps ) { // suspend all autoscans while in ILS
return; // found_map being set to null should signal upstream
}
find_rank = -1;
find_radius = IdentLock;
var result = -1;
// just in case you target something before it gets entered into the mapping
if( curr_target && _Sighting_index( curr_target, '_find_most_central' ) === -1 ) {
if( !_has_bad_status( curr_target ) // need check for when scooping target!
&& !is_cloaked( curr_target )) {
let index = _add_Sighting( curr_target, false, false, '_find_most_central' );
if( index >= 0 ) {
found_map = mapping[ index ];
return;
}
}
}
if( alertCondition > YELLOW_ALERT && weaponsOnline ) { //in red alert find most centered hostile if not supressed with off-line weapons
// in ident, "In Red Alert it will not narrow the locking to the attackers; it can lock any target."
find_radius = 180; //in Red Alert lock from the whole sphere who target you
find_list = select_Sightings( 0, 0, // 0 => all, 0 => any rank
targeting_player ); // first target those targeting you
if( !find_list || find_list.length === 0 ) { // no attackers identified
find_list = mapping;
find_rank = 'bad'; // limit to bad guys; if none in list yet, search it all
} //do not target asteroids before ships in red alert
result = closest_to(); // try targeting bad guys 1st
if( result && (mode !== 'ident' // ident mode switches from existing target
|| result !== curr_S.map) ) {
found_map = result; //found, target it
return; //priority to targets in crosshairs for fighting/asteroid hunting
}
find_rank = -1; // open up search to all targets, as none are targeting & none ranked 'bad'
find_list = mapping;
find_radius = AutoLock > 0 ? AutoLock : IdentLock;
} //if no bad guy in crosshairs then do normal ident to a ship in telescope list
if( mode === "ident" ) { //button "r" pressed or target lost
find_radius = IdentLock;
} else if( mode === "auto" ) { //lock in the crosshairs only; not called if AutoLock <= 0
find_radius = AutoLock;
} else if( mode === "grav" ) { //panorama targeting or lock in the crosshairs
find_radius = GravLock;
}
result = closest_to();
if( result ) { //found, target it
found_map = result;
return;
}
return;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// naming functions ///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
sunName.sun_names = {}; //cache of names
function sunName( ent ) { // allow for multiple suns
if( !system || !ent || !ent.isValid )
return null;
var name, key = ent.position.toString();
if( sunName.sun_names.hasOwnProperty( key ) ) // use cached
return sunName.sun_names[ key ];
do {
name = system.info.sun_name;
if( name && name.length > 0 )
break;
name = system_sun.displayName;
if( name && name.length > 0 )
break;
name = system.info.name;
if( name && name.length > 0 ) {
if( name === system_name )
name += ' (Star)';
break;
}
name = system_name + ' (Star)';
} while( false );
sunName.sun_names[ key ] = name; // add to cache
return name;
}
function entityIsNamed( ent ) { // return name if someone else has named it
var name;
// copied property priority from GalacticAlmanac
name = ent.displayName;
if( name && name.length > 0 ) {
return name;
}
name = ent.beaconLabel;
if( name && name.length > 0 ) {
return name;
}
name = ent.beacon;
if( name && name.length > 0 ) {
return name;
}
name = ent.beaconCode;
if( name && name.length > 0 ) {
return name;
}
}
function planetIsNamed( ent ) { // return planet name if named by another oxp_name
var name, ent_name = entityIsNamed( ent );
if( ent_name && ent_name.length > 0 ) {
return( ent_name );
}
// farplanets oxz
if( PlanetNames ) { // worldScripts.planetnames.$PlanetNames_GetPlanetName( cs.ent )
name = PlanetNames.$PlanetNames_GetPlanetName( ent );
if( name && name.length > 0 ) {
return name;
}
}
if( PlanetaryCompass ) { // worldScripts[ 'planetaryCompass_worldScript.js' ]
// set beaconCode & beaconLabel (latter has type in parentheses)
// - for mainPlanet, instead sets name & displayName (latter has type in parentheses)
// which is caught by entityIsNamed
let pce = entitiesWithScanClass( "CLASS_VISUAL_EFFECT", ent, 10 );
for( let idx = 0, len = pce.length; idx < len; idx++ ) {
let first = pce[ idx ];
if( !first || !first.isValid )
continue;
if( first.dataKey.indexOf( 'planetaryCompass' ) < 0 )
continue;
if( first.beaconLabel ) {
name = first.beaconLabel; // others
break;
}
}
free_array( pce );
return name;
}
}
orbName.planet_names = []; //cache of names
function orbName( ent ) {
if( !ent ) return null;
if( isSun < 0 ) isSun = ent.isSun;
if( isPlanet < 0 ) isPlanet = ent.isPlanet;
if( !isPlanet && !isSun ) return null;
if( !system_planets || system_planets.length === 0 ) {
log(ws.name, 'orbName, SYSTEM_PLANETS INVALID: ' + system_planets );
return null;
}
var name = '';
if( isSun ) {
return sunName( ent );
} else {
var idx = index_in_list( ent, system_planets );
if( idx < 0 )
return null;
name = orbName.planet_names[ idx ];
if( !name || name.length === 0 ) {
name = planetNameString( ent );
orbName.planet_names[ idx ] = name;
}
}
return name;
}
planetNameString.orbList = null; // inventory of system's planets & moons generated when none exist
planetNameString.ROMAN = [ 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X',
'XI', 'XII', 'XIII', 'XIV', 'XV', 'XVI', 'XVII', 'XVIII', 'XIX', 'XX' ];
planetNameString.GREEK = [ 'Alpha', 'Beta', 'Gamma', 'Delta', 'Epsilon', 'Zeta', 'Eta',
'Theta', 'Iota', 'Kappa', 'Lambda', 'Mu', 'Nu', 'Xi', 'Omicron', 'Pi',
'Rho', 'Sigma', 'Tau', 'Upsilon', 'Phi', 'Chi', 'Psi', 'Omega' ];
function planetNameString( ent ) {
if( !system || !ent || !ent.isValid )
return 'nada';
if( !isPlanet )
return( 'Non-Planet' );
var name = planetIsNamed( ent );
if( name && name.length > 0 ) { // someone else has named it
return name;
}
name = ent.name;
if( name !== system_name ) { // someone else has named it
return name;
}
// create dictionary of default names, done once/system, taking 2.37 ms @ 32 fps
let orbList = planetNameString.orbList;
if( orbList === null ) { // generate planet names
planetNameString.orbList = orbList = {};
let orbs = entitiesWithScanClass( "CLASS_NO_DRAW", system_sun );
// all planets, moons ordered by distance from sun
let pnum = 0, pstr, mnum = 0, mstr,
ROMAN = planetNameString.ROMAN, romans = ROMAN.length,
GREEK = planetNameString.GREEK, greeks = GREEK.length;
for( let pl = 0, plen = orbs.length; pl < plen; pl++ ) {
let orb = orbs[ pl ];
if( !orb.hasOwnProperty( 'radius' ) ) continue;
if( orb.hasAtmosphere ) { // it's a planet
pstr = pnum < romans ? ROMAN[ pnum ] : pnum;
name = system_name + (orb.isMainPlanet ? ' Prime (Planet)'
: ' ' + pstr + ' (Planet)');
orbList[ orb.position.toString() ] = name;
pnum++;
let moons = entitiesWithScanClass( "CLASS_NO_DRAW", orb, orb.collisionRadius * 10 );
// all moons ordered by distance from orb
for( let mn = 0, mlen = moons.length; mn < mlen; mn++ ) {
let moon = moons[ mn ];
if( moon.hasAtmosphere ) break; // it's a planet, we're done (and have double counted!)
mstr = mnum < greeks ? GREEK[ mnum ] : mnum;
name = mnum < greeks ? mstr + ' Moon' : 'Moon ' + mstr;
name += ' (' + pstr + ')';
orbList[ moon.position.toString() ] = name;
mnum++;
}
free_array( moons );
}
}
free_array( orbs );
}
name = orbList[ ent.position.toString() ];
return name;
}
function entityName( map ) {
var ent = map.ent, name = '', ent_script = -1,
ent_name = ent.name;
do {
if( map.rank === 'ukn' ) {
name = lost_target_name;
break;
}
if( ent_name && ( ent_name === 'Railgun Projectile' //do not show launched bullets
|| ent_name === 'Debris'
|| ent_name.indexOf( 'customshields' ) >= 0 ) ) {
return null; //nor customshields parts
}
if( ent_name && ent_name.indexOf('Exhibition]' ) >= 0 ) {
return null; //do not show ships in exhibition of Gallery OXP
}
ent_script = ent.script;
if( ent_script && ent_script.$Detectors_Origname ) { // shipversion oxp
name = ent_script.$Detectors_Origname;
break;
}
name = entityIsNamed( ent );
if( name && name.length > 0 ) {
break;
}
if( ent_name && ent_name.length > 0 ) {
name = ent_name;
let unique = ent.shipUniqueName;
if( unique && unique.length > 0 )
name =+ ': ' + unique;
break;
}
name = unknown_ship_name;
} while( false );
return name;
}
// had to remove cache for ship names for other oxp name changes
// eg. cargoscanner sets .shipUniqueName, someone else applies it to .displayName
// and thus "Splinter" -> "Splinter: Minerals"
function clearNameCaches() { // reset name caches for each system
var cache = sunName.sun_names;
for( let prop in cache ) {
if( cache.hasOwnProperty( prop ) ) {
delete cache[ prop ];
}
}
orbName.planet_names.length = 0;
planetNameString.orbList = null; // deliberate object -> garbage heap (once/system)
}
// const isoPrefix = ['', 'K', 'M', 'G', 'T', 'P'];
function distWithUnits( distance ) {
/* telescope 1.15
var range = floor( ent_dist / 1000 );
if( range < 0 ) range = 0;
if( range >= 1e6 ) range = floor( range / 1e6 ) + 'M';
// ...
let km = distance / 1000;
if( km < 100 )
name_with_dist = km.toFixed( 3 ) + ' km ' + displayName;
else if( km < 1e6 )
name_with_dist = floor( km ) + ' km ' + displayName;
else
name_with_dist = floor( km / 1e6 ) + ' Mkm ' + displayName;
*/
/*
var displayDist = distance / baseDistance;
var milles = floor( log10( displayDist ) / 3 ); // # of 1000's
if( milles >= isoPrefix.length ) {
return 'really far';
}
var prefix = 0;
do {
displayDist /= 1000;
prefix++;
} while( --milles >= 0 );
var units = isoPrefix[ prefix ] + distanceUnits;
if( displayDist >= 1000 ) {
displayDist = displayDist.toFixed( 0 );
} else if( displayDist >= 100 ) {
displayDist = displayDist.toFixed( 1 );
} else if( displayDist >= 10 ) {
displayDist = displayDist.toFixed( 2 );
} else {
displayDist = displayDist.toFixed( 3 );
}
// oxp's needing update:
// Combat_MFD: has unit Mkm for 1000000 km, chg to Gm
// - he does use Mm/s for speed
// VimanaHUD: searches for 'km ' in telescopemarker's .displayName, ch to re /^(?:\d+[.]?\d*|[.]\d+)[kKMGTP]?m(.*)$/
// - also fix for telescope v2 support; optimize distanceTo, repl filteredEntities w/ entitiesWithScanClass
*/
// canon has Mkm as 1e9, not Gm; nothing defined for 1e6 (and Kkm is wierd)
let units = ['m ', 'km ', 'Kkm ', 'Mkm ', 'Gkm ', 'Tkm ', 'Pkm '];
let withUnits,
fixed = floor( log10( distance ) / 3 ); // # of 1000's
if( fixed >= units.length ) {
withUnits = 'really far';
} else {
let signif = (distance / pow(1000, fixed) );
if( fixed === 0 ) {
// withUnits = signif.toFixed( 3 - floor(log10( distance )) );
// VimanaHUD searches for 'km ' to remove distance from displayName
withUnits = (distance / 1000).toFixed( 3 );
withUnits += ' ' + units[ 1 ];
} else if( fixed === 2 ) { // skip Kkm (can't use Mm due to VimanaHUD)
withUnits = floor(distance / 1000);//.toFixed( 0 );
withUnits += ' ' + units[ 1 ];
} else {
withUnits = signif.toPrecision( 4 );
withUnits += ' ' + units[ fixed ];
}
}
return withUnits;
}
// : ' + + '
function set_displayName( map ) { // update curr_S.name and return new value (for marker.displayName)
var that = set_displayName;
var lastDisplayed = (that.lastDisplayed = that.lastDisplayed || null);
// called by _update_target_marker, _manage_marker & switch_PS_target ie. target only
var displayName = curr_S.name, //remove km from ident message
ent = map.ent;
if( lastDisplayed !== ent ) { // avoid repeating name construction as we cannot cache
that.lastDisplayed = ent; // because some oxp's alter displayName dynamically (eg. cargoscanner)
displayName = null;
}
if( map.rank === 'ukn' ) {
displayName = lost_target_name;
} else if( !displayName // save unaltered name for ident message
|| displayName === lost_target_name ) {
if( radius < 0 ) radius = ent.radius;
if( radius ) { // only orbs have .radius
displayName = orbName( ent );
} else if( is_jamming( ent ) ) { // sets isJamming, returns true if effective (ie. scanFilter_ok)
displayName = unknown_ship_name; // can see jamming ships but not identify (thanks Milo)
} else {
displayName = entityName( map );
}
}
if( !displayName ) {
// Error: Cannot set property displayName of instance of Ship to invalid value null
displayName = '';
}
curr_S.name = displayName;
return displayName;
}
function showTargetName( map, combatMFDonly ) {
if( !equip_ok ) return;
if( _Sighting_index( map, 'showTargetName' ) < 0 ) return;
reset_common_vars(); // required as done in groups of 3
var msg = showShipReport( map );
if( !msg || msg.length <= 0 ) return;
if( Combat_MFD && index_in_list( 'combat_MFD', ps.multiFunctionDisplayList ) !== -1) { //show in Combat MFD instead of console
prevMFDTarget = map; //store for Combat MFD
Combat_MFD.$TelescopeLine = msg;
} else if( !combatMFDonly ) {
consoleMessage( msg, ConsoleMsgDurn ); //fallback to console
}
using_common_vars = false;
}
function showShipReport( map ) { // format ship name/dist for format_line (MFD) & showTargetName
if( !map ) return;
var ent = map.ent;
var name = '', cached = false,
ent_script = -1,
jamming = is_jamming( ent ); // sets isJamming, returns true if effective (ie. scanFilter_ok)
copy_vector( map.last_posn, position ); // if 'ukn', can only report what was known
while( name.length === 0 ) {
if( radius < 0 ) radius = ent.radius;
if( ent.radius ) {
name = orbName( ent );
if( name === null ) {
log(ws.name, 'showShipReport, orbName FAILED for ent: ' + ent );
return;
}
break;
}
if( jamming ) { // can see jamming ships but not identify (thanks Milo)
name = unknown_ship_name;
break;
}
name = entityName( map );
if( name === null ) {
log(ws.name, 'showShipReport, entityName FAILED for ent: ' + ent );
return;
}
break;
}
var ent_dist = map.ent_dist;
var range = distWithUnits( ent_dist ) + ' ';
var prefix = '';
if( !ent.isWormhole ) {
if( ent.isDerelict ) {
if( ent_script < 0 ) ent_script = ent.script;
if( Towbar && ent_script) { //Towbar status
if( ent_script.$TowbarUsableShip ) prefix = 'Usable ';
else if( ent_script.$TowbarMinedShip ) prefix = 'Mined ';
else if( ent_script.$TowbarEmptyShip ) prefix = 'Empty ';
else prefix = 'Derelict ';
} else {
prefix = 'Derelict ';
}
} else if( (FarStatus || ent_dist < scannerRange) && ent.target === ps ) {
range = '! ' + range; //hostile
} else if( map && map.rank === 'bad' ) {
range = '* ' + range; //pirate
}
}
_relative_direction( position, map );
// log(ws.name, 'showShipReport, ' + name+', d:'+relative_dirn+' p:'+position[0].toFixed(2)+', '
// +position[1].toFixed(2)+', '+position[2].toFixed(2)); //debug
var colon = name.indexOf( ': ' );
if( !cached ) { // it has a name
// if( !cached && (colon = name.indexOf( ': ' )) >= 0 ) { // it has a name
let staticLen = strFontLen( range + prefix + ' ' + relative_dirn ),
openLen = 18 - staticLen,
nameLen = strFontLen( name );
if( nameLen > openLen ) { // replace 'Navigation Buoy' w/ 'Nav. Buoy'
[ name, nameLen ] = subLongest( name, openLen,
'Navigation Buoy', ['Nav. Buoy', 'Buoy']);
}
if( nameLen > openLen ) { // replace 'Station Buoy' w/ 'Stn. Buoy'
[ name, nameLen ] = subLongest( name, openLen,
'Station Buoy', ['Stn. Buoy', 'Buoy']);
}
if( nameLen > openLen ) { // replace 'Station' w/ 'Stn'
[ name, nameLen ] = subLongest( name, openLen,
'Station', ['Stn.']);
}
if( nameLen > openLen ) {
name = shortenShipName( name, colon, nameLen, openLen );
}
}
return range + prefix + name + ' ' + relative_dirn;
}
function subLongest( string, maxLen, target, candidates ) {
// pass candidates in descending length
var startLen = strFontLen( string ),
newStr = string,
len = candidates.length;
if( len === 0 || !maxLen | startLen <= maxLen
|| string.indexOf( target ) < 0 ) {
return [ string, startLen ];
}
for( let idx = 0; idx < len; idx++ ) {
newStr = string.replace( target, candidates[ idx ] );
let fontLen = strFontLen( newStr );
if( fontLen <= maxLen )
return [ newStr, fontLen ];
}
return [ string, startLen ];
}
function shortenShipName( name, colon, startLen, targetLen ) {
var that = shortenShipName;
var name_breaks = that.name_breaks; // const. props defined at end of function
var name_suffix = that.name_suffix; // "
var name_shorten = (that.name_shorten = that.name_shorten || []);
name_shorten.length = 0; // prep to re-use array
var idx, len, space, nameLen = startLen,
diff, start, brk = -1;
for( idx = 0, len = name.length; idx < len; idx++ )
name_shorten[ idx ] = name[ idx ];
space = name_shorten.lastIndexOf( ' ', colon );
if( space >= 0 ) { // try replacing superfluous tag
let ship_tags = that.ship_tags,
tags = ship_tags.length;
for( idx = 0; idx < tags; idx++ ) {
brk = findListInArray( ship_tags[ idx ], name_shorten, 0, colon );
if( brk >= 0 ) break;
}
if( brk >= 0 ) {
let shrink = colon - space;
for( idx = colon, len = name_shorten.length; idx < len; idx++ )
name_shorten[ idx - shrink ] = name_shorten[ idx ];
name_shorten.length -= shrink;
nameLen = strFontLen( name_shorten ) // strFontLen assumes spaces between elements
- (len - shrink - 1) * SpaceLen; // so we subtract # commas * SpaceLen
}
}
if( nameLen > targetLen ) { // still too long, break on logical words
diff = startLen - targetLen + colon * SpaceLen; // 'colon * SpaceLen' => start earlier for long ship types
start = floor(colon + (name.length - colon) / (1 + diff / 2));
brk = -1; idx = 0; len = name_breaks.length;
for( ; idx < len; idx++ ) {
brk = findListInArray( name_breaks[ idx ], name_shorten, start );
if( brk >= 0 ) break;
}
if( brk >= 0 ) {
idx = brk;
len = name_suffix.length;
for( let nsi = 0; nsi < len; nsi++ )
name_shorten[ idx++ ] = name_suffix[ nsi ];
name_shorten.length = brk + len;
nameLen = strFontLen( name_shorten ) - ( name_shorten.length - 1 ) * SpaceLen;
}
}
if( nameLen > targetLen ){ // still too long, break on a space
brk = name_shorten.indexOf( ' ', start );
if( brk >= 0 ) {
idx = brk;
len = name_suffix.length;
for( let nsi = 0; nsi < len; nsi++ )
name_shorten[ idx++ ] = name_suffix[ nsi ];
name_shorten.length = brk + len;
nameLen = strFontLen( name_shorten ) - ( name_shorten.length - 1 ) * SpaceLen;
}
}
if( debug ) {
let msg = 'name: "' + name + '", name_shorten: "' + name_shorten.join('') + '"';
if( sShipNameRpt.indexOf < 0 ) {
log(ws.name, 'shortenShipName, ' + msg );
sShipNameRpt.push( msg );
}
}
return name_shorten.join('');
}
shortenShipName.ship_tags = [ ['S','h','i','p'], ['B','o','a','t'], ['S','h','u','t','t','l','e'] ];
shortenShipName.name_suffix = [' ','.','.','.'];
shortenShipName.name_breaks = [ [' ','a','n','d',' '], [' ','o','f',' '], // must be lower case, will test upper
[' ','t','h','e',' '], [' ','o','r',' '],
[' ','o','n',' '], [' ','i','n',' '] ];
var sShipNameRpt = []; /// tmp4debug
function findListInArray( list, array, start, end ) { // return index of list in array
var ar = start || 0, // list is assumed lower case
arLen = end || array.length,
li, liLen = list.length;
if( arLen < liLen ) return -1;
for( ; ar < arLen; ar++ ) {
for( li = 0; li < liLen; li++ ) {
let achr = array[ ar + li ],
lchar = list[ li ];
if( achr === lchar ) continue;
if( lchar === ' ' ) break;
if( achr === lchar.toUpperCase() ) continue;
break;
}
if( li >= liLen ) return ar;
if( ar + liLen >= arLen ) return -1;
}
}
function relativeDirection( position, map ) { // stub for external call from telescope_debug._dump_map
try {
init_headingView();
_relative_direction( position, map );
return relative_dirn;
} catch( err ) {
log( ws.name, ws._reportError( err, 'relativeDirection', [position, map], 1 ) );
if( debug ) throw err;
}
}
var relative_dirn; // external store for relative_direction's return
function _relative_direction( position, map ) { // init_headingView is called by calling fn(s) to reduce #
if( !position || !map ) return;
if( !viewIsStandard ) return;
relative_dirn = '';
if( !ps_position || ps_position.length === 0 ) { // this fn can get called before FCB starts
if( !ps_position ) ps_position = alloc_array();
copy_vector( ps.position, ps_position );
}
subtract_vectors( position, ps_position, ent_vector );
unit_vector( ent_vector, ent_vector );
let right_v = angle_between_two_unitV( headingView, ent_vector );
let delta_right = abs( QUARTER_ARC - right_v );
let up_v = angle_between_two_unitV( ps_vectorUp, ent_vector );
let delta_up = abs( QUARTER_ARC - up_v );
let dirn_marks = '';
if( right_v > REL_DIR_HALF_PLUS )
dirn_marks += delta_right > (REL_DIR_STRESS * delta_up)
? '<<' : '<'; // extra chr when heading is mostly in that direction
if( right_v < REL_DIR_HALF_MINUS )
dirn_marks += (delta_right > REL_DIR_STRESS * delta_up)
? '>>' : '>';
if( up_v < REL_DIR_HALF_MINUS )
dirn_marks += (delta_up > REL_DIR_STRESS * delta_right)
? '^^' : '^';
if( up_v > REL_DIR_HALF_PLUS )
dirn_marks += (delta_up > REL_DIR_STRESS * delta_right)
? 'vv' : 'v';
if( dirn_marks.length === 2 && dirn_marks[ 0 ] === dirn_marks[ 1 ] )
dirn_marks = dirn_marks[ 0 ]; // remove doubled when near axis
relative_dirn = round( map.headingTo ) + "° " + dirn_marks;
}
function init_headingView() { // relativeDirection needs view vector
if( viewDirection === "VIEW_FORWARD" ) {
copy_vector( ps_vectorRight, headingView ); // right is right of fwd
} else if( viewDirection === "VIEW_AFT" ) {
scale_vector( ps_vectorRight, -1, headingView ); // -right is right of aft
} else if( viewDirection === "VIEW_STARBOARD" ) {
scale_vector( ps_vectorForward, -1, headingView ); // -fwd is right of starboard
} else if( viewDirection === "VIEW_PORT" ) {
copy_vector( ps_vectorForward, headingView ); // fwd is right of port
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// _auto_update_closure ///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// 'constant' variables unique to _auto_update_closure
var PlanetNames, PlanetaryCompass,
lost_target_name = '(lost target)',
unknown_ship_name = '(unknown ship)';
// local variables unique to _auto_update_closure
var quarter_sec_counter = 0, //counter to make colour of the visual marks, used to do once in a second within a 0.25s timer
report_status = false, // flag to restrict over-reporting of msgs
gravScanMsg = false, // flag to show messgage less frequently
found_new = false, // flag for triggering action required when a new target is detected
delay_counter = -1; // countdown for IdentDelay (see also _chg_curr_Sighting)
function _report_autovars() {
let tmp = '_report_autovars ,stationNearby = ' + stationNearby + ', gravScanProgress = ' + gravScanProgress
+ ', gs_state = ' + gs_state + ', gs_progress_report = ' + gs_progress_report
+ '\nfound_new = ' + found_new + ', delay_counter = ' + delay_counter
+ ', identKeyPress = ' + identKeyPress + '\n';
var idx, len = selected_Sightings.length;
tmp += 'selected_Sightings has ' + len + ' items\n'
if( len > 0 ) {
for( idx = 0, len = selected_Sightings.length; idx < len; idx++ ) {
if( idx > 0 ) tmp += '\n'
tmp += 'selected_Sightings['+idx+'] = \n';
if( cd ) tmp += cd._showProps( selected_Sightings[idx], 'selected_S['+idx+']' );
}
}
log( ws.name, tmp );
debug = ws.$DebugMessages;
}
function _set_GS_state() { //in the range of the Gravity Scanner if installed
var available = stationNearby && !weaponsOnline //near a station or baseship & weaps off-line
&& ext_ok && grav_eq_ok && gravScanProgress <= 1; // scan underway or done( == 1 )
//grav scan without weapons only to need force it
if( gravScanProgress === 1 ) {
gs_state = GS_COMPLETE;
} else if( gravScanProgress === 0 ) {
gs_state = available ? GS_STOPPED : GS_NONE;
} else {
gs_state = available ? GS_RUNNING : GS_DEGRADING;
}
}
function check_equip_ok() {
if( eq_status === 'EQUIPMENT_DAMAGED' ) {
if( ws.$DamageMsg ) {
consoleMessage( 'Telescope damaged', ConsoleMsgDurn );
ws.$DamageMsg = false;
}
return false;
} else if( eq_status !== 'EQUIPMENT_OK' ) {
if( BuyMsg ) {
consoleMessage( 'Buy Telescope! (x)', ConsoleMsgDurn );
BuyMsg = false;
}
ws.$DamageMsg = false;
return false;
}
return true;
}
function chk_energy_gs_status( on_demand ) { // on_demand = true for user directed scans (toggle weapons, Rescan, step thru end of list)
if( ps.energy < 64 ) {
if( on_demand )
consoleMessage( 'Not enough energy for Telescope', ConsoleMsgDurn );
return false;
}
if( on_demand ) {
_create_Sightings(); // create list from scratch
report_status = true;
}
var scanning = fns_are_pending(); // true => mapping creation/update is running
if( !AutoScan && !on_demand && !scanning
&& gs_state <= GS_STOPPED )
return false; // AutoScan turned off by user
if( found_new || on_demand || scanning ) {
ps.energy -= 2; //use a little energy to scan the whole sky
}
if( gs_state === GS_RUNNING // no energy drain during degradation
&& alertCondition < RED_ALERT ) { // suspended during Red Alert
ps.energy -= 6; //need 4x energy with Gravity Scanner
}
return true;
}
function is_station_near( map ) {
var ent = map.ent;
if( !ent.isStation ) return false; // not abandoned rock hermit, as has no power for its part of grav. scanner
if( ent.mass <= 1e7 ) return false; //skip ships with docking port (except baseships), rock hermit with 53508t must fit in
if( ent.target === ps ) return false; //target is hostile if targeting back
var d = map.ent_dist;
if( d > 5000 ) return false; // not within 5 km
if( index_in_list( ps, ent.defenseTargets ) >= 0 )
return false; // hostile
return true;
}
function report_scan_progress( forced, set_gs_progress ) {
if( forced ) report_status = true; // record now in case fns_are_pending
if( set_gs_progress === undefined && fns_are_pending() )
return; // report status when update is complete
if( !forced && !report_status && !gravScanMsg ) return; // already reported
var msg, progress;
if( ext_ok && grav_eq_ok ) {
if( gravScanProgress === 1 ) { // scan complete
msg = 'Gravity scan found ';
gravScanMsg = false;
} else if( gravScanProgress <= 0 ) { // scan totally degraded
msg = 'Gravity scan off-line';
gravScanMsg = false;
consoleMessage( msg, ConsoleMsgDurn );
report_status = false;
return;
} else { // scan still active
if( set_gs_progress !== undefined ) // ensure msg always an even amount
gs_progress_report = progress = set_gs_progress;
else
progress = floor( gravScanProgress * 100 );
if( !weaponsOnline ) {
msg = 'Gravity scan ' + (stationNearby ? 'up to ' : 'down to ') + progress + '%, ' + (stationNearby ? 'found ' : 'has ');
gravScanMsg = false;
} else
return; // no progress msg when weaponsOnline
}
} else { //send message about telescope scan
msg = 'Telescope found ';
}
if( maplen === 0 ) msg += 'no targets';
else if( maplen === 1 ) msg += '1 target';
else msg += maplen + ' targets';
consoleMessage( msg, ConsoleMsgDurn );
report_status = false;
}
const GSR_PROGRESS_ENDPTS = 1, // gravity scan reporting frequency
GSR_PROGRESS_QUARTERS = 2,
GSR_PROGRESS_TENTHS = 4,
GSR_DEGRADE_ENDPTS = 8,
GSR_DEGRADE_QUARTERS = 16,
GSR_DEGRADE_TENTHS = 32;
var gs_progress_report = 0; // remember progress of last report
function update_grav_scan() {
// reporting is triggered in _hud_effects(): sets report_status = true when you turn weapons off-line
if( !equip_ok || !ext_ok || !grav_eq_ok ) { // check equipment ok
gravScanMsg = false; // suppress any reporting
gravScanProgress = 0;
return;
}
if( ps_mass >= 1e8 ) {
stationNearby = true; //baseships can perform gravity scan anywhere
} else if( quarter_sec_counter <= 0 ) { //check a station is nearby for gravity scanner every 4. call, ie. 1/sec
var list = select_Sightings( 1, 'isr', is_station_near );
if( list && list.length > 0 ) {
if( !stationNearby && weaponsOnline ) //show when arrived near a station
consoleMessage( 'Gravity scan needs weapons off-line', ConsoleMsgDurn );
stationNearby = true;
} else { //too far or become hostile
if( stationNearby ) // show when leave 5km radius or you pissed them off
consoleMessage( 'Gravity scan needs a friendly station in 5km', ConsoleMsgDurn );
stationNearby = false;
}
}
if( stationNearby ) { // incr gravity scan progress counter
if( gravScanProgress === 0 )
ws.$SoundScan.play(); //GS scan sound
if( gravScanProgress < 1 ) {
let halted = false;
if( gravScanProgress > 0 && numberSwapable() >= MaxTargets ) {// orbs & beacons excluded from MaxTargets
gravScanProgress = 1.1; // terminate gravity scan, nothing to gains
halted = true; // suppress further reports
consoleMessage( 'Gravity scan halted, memory full; '
+ maplen + ' targets', ConsoleMsgDurn );
} else {
let gsm = 1; //gravity scanner multiplyer
if( ps_speed === 0 ) gsm = 4; //4 times faster if stopped
if( grav_eq2_ok )
gsm *= 2; //half time with 2 working grav.scanner
gravScanProgress += gsm * QUARTER_SECS_OF_4MIN; //normal gravity scan need 4 minutes
}
if( gravScanProgress > 1 ) {
gravScanProgress = 1;
gs_progress_report = 100;
++ws.$GravScanCount; //Gravity Scan counter to bring aliens
if( GravScanMsgFreq & GSR_PROGRESS_ENDPTS ) { // not turn off by user
gravScanMsg = true; // enable reporting
if( weaponsOnline ) {
consoleMessage( 'Gravity scan done, turn off weapons to see results', ConsoleMsgDurn );
} else if( !halted ) {
report_scan_progress();
}
}
let p = ws.$FixedGS === 1 ? 100 : 200; // every 100 scans if cheap fix, else every 200
if( ws.$GravScanCount >= p ) {
let num = ceil(pow( player.score, 0.5 ) / 10 ); //with 0 score do not get any
if( num > 0 ) {
consoleMessage( 'Aliens detected your Gravity Scan!', 10 );
addShips( 'thargoid', num, ps_position, 50000 );
}
ws.$GravScanCount = 0;
}
} else if( GravScanMsgFreq > 0 ) { // issue progress report
let progress = floor(gravScanProgress * 100);
let frequency = GravScanMsgFreq & GSR_PROGRESS_TENTHS ? 10 :
GravScanMsgFreq & GSR_PROGRESS_QUARTERS ? 25 : 0;
if( frequency > 0 ) {
let div = floor(progress / frequency);
let mark = progress % frequency;
if( mark < 2 && div * frequency > gs_progress_report ) {
report_scan_progress( true, div * frequency );
}
}
}
}
} else if( gravScanProgress > 0 ) { //degrading from 100% to 0% in 2 minute if away from stations
gravScanProgress -= QUARTER_SECS_OF_2MIN *
( 1 + ps_speed / ps_maxSpeed ); // faster if moving
if( gravScanProgress < 0
&& GravScanMsgFreq & GSR_DEGRADE_ENDPTS ) {
report_scan_progress( true, 0 );
gravScanProgress = 0;
} else if( GravScanMsgFreq >= GSR_DEGRADE_ENDPTS ) { // issue progress report
let frequency = GravScanMsgFreq & GSR_DEGRADE_TENTHS ? 10 :
GravScanMsgFreq & GSR_DEGRADE_QUARTERS ? 25 : 0;
if( frequency > 0 ) {
let progress = floor(gravScanProgress * 100);
let rpt = floor(progress / frequency) * frequency;
let mark = progress % frequency;
if( mark < 2 && rpt < gs_progress_report ) { // mark < 2 to ensure reported on slow machines
report_scan_progress( true, rpt );
if( rpt === frequency ) // last report, suppress report @ 0
gs_progress_report = 0;
}
}
}
}
}
// : ' + + '
function randomInt( min, max ) { return floor( random() * (max - min) ) + min; }
doClear_MFD.orig_msg = [ 'T','e','l','e','s','c','o','p','e',':',' ','N','o',' ','T','a','r','g','e','t','s' ];
doClear_MFD.aux_msg = [ 'T','e','l','e','s','c','o','p','e',' ','A','u','x','.',':',' ','N','o',' ','T','a','r','g','e','t','s' ];
doClear_MFD.aux_not = [ 'T','e','l','e','s','c','o','p','e',' ','A','u','x','.',':',' ','D','i','s','a','b','l','e','d' ];
function doClear_MFD( MFDname, fully ) { // ?completely empty MFD's vanish
var that = doClear_MFD;
var clear_msg = (that.clear_msg = that.clear_msg || []);
var msg = MFDname === PrimaryMFD_name ? that.orig_msg :
SeparateMFDs ? that.aux_msg : that.aux_not;
if( fully ) {
ps.setMultiFunctionText( MFDname, '', false );
} else if( equip_ok ) {
ps.setMultiFunctionText( MFDname, msg.join(''), false );
} else {
let idx, msg_len;
idx = msg_len = msg.length;
clear_msg.length = 0; // clear array
while( idx-- ) clear_msg[ idx ] = msg[ idx ]; // set up working array
let min, max, rand = randomInt( 4, 6 ); // distort msg by swapping a few, chg'g case
while( rand ) {
min = randomInt( 0, msg_len );
max = randomInt( 0, msg_len );
if( min === max ) continue; // need diff #'s
if( clear_msg[ min ] === clear_msg[ max ] )
continue; // want diff char's
rand--;
if( min > max ) {
[ max, min ] = [ min, max ];
}
if( min % 2 === 0 || max - min > msg_len >> 2 ) { // swap chars
// let tmp = clear_msg[ min ];
// clear_msg[ min ] = clear_msg[ max ];
// clear_msg[ max ] = tmp;
[ clear_msg[ min ], clear_msg[ max ] ] = [ clear_msg[ max ], clear_msg[ min ] ];
} else {
for( idx = min; idx < max; idx++ )
clear_msg[ idx ] = clear_msg[ idx ].toUpperCase();
}
}
ps.setMultiFunctionText( MFDname, clear_msg.join(''), false ); // '\n\n\n\n' +
}
}
function format_line( map, list ) {
let ent = map.ent;
if( !ent || !ent.isValid ) return false;
if( MFD_ents[ ent ] ) {
return false;
}
reset_common_vars(); // required as done in groups of 3
let rpt = showShipReport( map );
using_common_vars = false;
if( !rpt || rpt.length === 0 || rpt.indexOf( '(Lost ' ) === 0 )
return false;
if( curr_target ) {
if( curr_target === ent || ent === curr_S.ent )
rpt = '[ ' + rpt + ' ]'; //mark the current target
}
list.push( rpt );
MFD_ents[ ent ] = true;
return true;
}
/* "The Target list contains hostiles first, if any, then all ships in normal scanner (25.6km),
followed by Cargo and Escape Pods, then ending with ships which are not in the normal scanner."
function map_sort_rank_dist( a, b ) { // same as used in _chg_curr_Sighting
let a_rank = a.rank, b_rank = b.rank;
if( a_rank === b_rank )
return a.ent_dist - b.ent_dist;
else
return a_rank > b_rank;
}
*/
function map_sort_dist( a, b ) {
return a.ent_dist - b.ent_dist;
}
function MFD_is_visible( name ) {
var mfds = ps.multiFunctionDisplayList;
if( !mfds || mfds.length ===0 )
return false; // ship damaged
return index_in_list( name, mfds ) !== -1;
}
function qualifyMFD( map, staticFilter, dynamicFilter ) {
var passStatic = staticFilter === 0 // none specified OR matches static filter
|| (map.staticMFD & staticFilter) > 0;
var passAttitude = (dynamicFilter & MFD_ATTITUDE) === 0 // none specified OR matches attitude
|| (map.dynamicMFD & dynamicFilter & MFD_ATTITUDE) > 0;
var passRange = (dynamicFilter & MFD_RANGED) === 0 // none specified OR matches range limit
|| (map.dynamicMFD & dynamicFilter & MFD_RANGED) > 0;
return passStatic && passAttitude && passRange;
}
// : ' + + '
function bitsSet( map, stat, dyn ) { // return # bits set for map in both stat & dyn
var count = 0, // - used as a crude determination of which list is better fit
bits = map.staticMFD & stat;
while( bits > 0 ) {
if( bits & 1 )
count++;
bits >>>= 1;
}
bits = map.dynamicMFD & dyn;
while( bits > 0 ) {
if( bits & 1 )
count++;
bits >>>= 1;
}
return count;
}
var MFD_lines = []; // formated lines for MFD
var Aux_lines = [];
var depthMFD, depthAUX;
var MFD_ents = {}; // ents in MFD_lines, so don't repeat
function update_MFDs( started, testing ) {
var that = update_MFDs;
var maps2rpt = (that.maps2rpt = that.maps2rpt || []);
if( that.adjusted === undefined ) that.adjusted = 0;
var adjusted = that.adjusted;
var map, ent, rptlen, newLines, idx, primaryUp, auxiliaryUp;
if( !mappingReady || maplen === 0 ) // not yet built OR empty
return;
primaryUp = MFD_is_visible( PrimaryMFD_name );
auxiliaryUp = MFD_is_visible( AuxilaryMFD_name );
if( !primaryUp && !auxiliaryUp ) { // only update if being used (it's expensive)
doClear_MFD( PrimaryMFD_name, true );
doClear_MFD( AuxilaryMFD_name, true )
return;
}
if( !started || testing ) {
if( tasks_queued( update_MFDs ) ) // on a slow PC, _auto_updates may call before update cycle complete
return;
MFD_lines.length = 0; // re-use arrays
Aux_lines.length = 0;
for( let ent in MFD_ents ) // re-use dictionaries
if( MFD_ents.hasOwnProperty( ent ) )
delete MFD_ents[ ent ];
maps2rpt.length = 0;
depthMFD = depthAUX = newLines = 0;
mapping.sort( map_sort_dist );
for( idx = 0; idx < maplen; idx++ ) { // store maps to report so don't sort next frame
maps2rpt[ idx ] = mapping[ idx ];
}
idx = 0;
} else { // continue from last frame
depthMFD = MFD_lines.length;
depthAUX = Aux_lines.length;
newLines = 0;
idx = started;
}
var continueMFD = SeparateMFDs // aux is a continuation of primary when
&& ( !MFDFiltering // aux is active w/ no filter or same filter
|| (MFDPrimaryDynamic === MFDAuxDynamic
&& MFDPrimaryStatic === MFDAuxStatic) );
rptlen = maps2rpt.length;
for( ; idx < rptlen && ( (SeparateMFDs && (depthMFD < 10 || depthAUX < 10))
|| (!SeparateMFDs && depthMFD < 10) ); idx++ ) {
map = maps2rpt[ idx ];
ent = map && map.ent;
if( !map || !ent ) // was just deleted?
continue;
if( (newLines > 3 + adjusted) && !testing ) { // split across frames as formatting takes time
set_fn_pending( update_MFDs, idx, true ); // true is for tasks_deferred list
return;
}
let qualifies, mfdBits = 0, auxBits = 0;
if( !continueMFD && SeparateMFDs ) { // decide which one gets it
mfdBits = bitsSet( map, MFDPrimaryStatic, MFDPrimaryDynamic );
auxBits = bitsSet( map, MFDAuxStatic, MFDAuxDynamic );
}
if( depthMFD < 10 ) { // not full
if( !MFDFiltering ) { // just list the first 10
qualifies = true;
} else if( continueMFD // just list the first qualified 10
|| !SeparateMFDs // no fit to worry about
|| mfdBits >= auxBits ) { // mfd list is a better fit (>= favours primary)
qualifies = qualifyMFD( map, MFDPrimaryStatic,
MFDPrimaryDynamic );
} else { // - check that it passes filters
qualifies = false;
}
if( qualifies ) {
if( !primaryUp ) { // incr counter to keep aux aligned
depthMFD++;
if( !MFD_ents[ ent ] ) // add to reported ents dictionary
MFD_ents[ ent ] = true; // so it won't appear in aux (should filters overlap)
} else if( format_line( map, MFD_lines ) ) { // only do expensive call when necessary
newLines++; // format_line adds to dictionary if map used
depthMFD++;
}
continue; // an ent cannot be in both MFDs, so move on to next
}
}
if( depthAUX < 10 ) { // not full
if( !MFDFiltering ) { // just list the first 10
qualifies = true;
} else if( continueMFD // just list the first qualified 10
|| !SeparateMFDs // no fit to worry about
|| auxBits > mfdBits // aux list is a better fit (> excludes primary)
|| (auxBits === mfdBits // qualifies for both but didn't fit in primary
&& !MFD_ents[ ent ]) ) {
// || auxBits > mfdBits ) { // aux list is a better fit (> excludes primary)
qualifies = qualifyMFD( map, MFDAuxStatic,
MFDAuxDynamic );
} else { // - check that it passes filters
qualifies = false;
}
if( qualifies ) {
if( !auxiliaryUp ) { // incr counter to keep aux aligned
depthAUX++;
if( !MFD_ents[ ent ] ) // add to reported ents dictionary
MFD_ents[ ent ] = true; // so it won't appear in main (should filters overlap)
} else if( format_line( map, Aux_lines ) ) { // only do expensive call when necessary
newLines++; // format_line adds to dictionary if map used
depthAUX++;
}
}
}
}
if( ps.setMultiFunctionText ) {
if( depthMFD > 0 && primaryUp ) {
ps.setMultiFunctionText( PrimaryMFD_name, MFD_lines.join( '\n' ), false );
} else {
doClear_MFD( PrimaryMFD_name, !primaryUp );
}
if( depthAUX > 0 && auxiliaryUp ) {
ps.setMultiFunctionText( AuxilaryMFD_name, Aux_lines.join( '\n' ), false );
} else {
doClear_MFD( AuxilaryMFD_name, !auxiliaryUp );
}
}
}
/* "Telescope checks for new targets every second and performs an autoscan if it finds one: simple light sensors can see new dots
in the whole sky without using energy but must zoom with the main scope to determine the ship type which needs 2 energy points."
*/
function check_if_new_targets() { // detect when there are new target to consider
if( found_new ) return; // no need to check
// "There are no passive gravity sensors so AutoScan will happen only if a new target arrives into the visible range."
var ent, map, dist = -1, pastScannerRange = 0,
isStn, stnsOnly = false, index = 0,
allShips = system.allShips, // array of all ships in system, sorted by distance
alllen = allShips.length; // - use this to suppress calc of distance, as exact value irrelevant
var numEnts = alllen - system_planets.length - 1; // - 1 for sun
var breakEarly = MaxTargets <= 50 && numEnts > MaxTargets;// player has slow PC, so limit _create_Sightings() calls
while( index < alllen ) {
ent = allShips[ index++ ];
if( ent === ps ) continue;
let mapi = _Sighting_index( ent, 'check_if_new_targets' );
if( mapi >= 0 ) { // a known entity
map = mapping[ mapi ];
if( map.rank !== 'ukn' ) // ?is it an existing one that re-entered detection range
continue;
} else {
map = null;
}
if( breakEarly && weaponsOnline && alertCondition === RED_ALERT ) {
isBeacon = -1; // force entity property get
if( is_beacon( ent ) ) break; // limit scanning while in battle
}
if( pastScannerRange === 0 ) {
dist = _detect_distanceTo( ent );
if( dist > scannerRange ) {
pastScannerRange = index - 1; // last time in this if block
if( !ext_ok ) break;
if( !ent.isVisible ) continue; // above all, it must be visible
}
} else { // we use distance to know when to quit
if( !stnsOnly && index > pastScannerRange
&& (index - pastScannerRange) % 10 === 0 ) {// calc distance on every 10th ent
dist = _detect_distanceTo( ent );
if( dist > AutoScanMaxRange ) break;
if( dist > scannerRange_X_10 ) // hard limit enough for largest ship?
stnsOnly = true;
} else { // don't need to calc distance at all
dist = -1;
}
if( !ent.isVisible ) continue; // above all, it must be visible
isStn = !stnsOnly ? false : ent.isStation; // only check property once we're beyond limit
}
if( breakEarly && weaponsOnline ) {
if( alertCondition === YELLOW_ALERT && dist > scannerRange_X_2 ) {
isBeacon = -1; // force entity property get
if( is_beacon( ent ) ) break; // limit scanning of distant ents
}
if( alertCondition === GREEN_ALERT && dist > scannerRange_X_4 ) {
isBeacon = -1; // force entity property get
if( is_beacon( ent ) ) break; // limit scanning of distant ents
}
}
reset_common_vars();
distance = dist > 0 ? dist : -1; // restore known values if known
isStation = stnsOnly ? isStn : -1;
if( notable_ent( ent, false, (dist > 0 ? dist : null) ) ) {// found one missing or may need updating
if( mapi < 0 ) { // not in mapping
let enti = _add_Sighting( ent, true, false, 'check_if_new_targets' );
if( debug && (enti < -2 && enti > -8) ) {
let reason = add_Sighting_errors[ enti ];
log(ws.name, 'check_if_new_targets, _add_Sighting returned "' + reason
+ '", distance: ' + distance + ': ' + ent );
}
new_targets.push( ent );
///moved from below 2Cif--# repeat msgs
} else {
update_one_Sighting( map, ent, mapi, false ); // false suppresses call to notable_ent
}
///new_targets.push( ent );
break;
}
}
using_common_vars = false;
}
function auto_updates( forced_scan ) {
try {
_auto_updates( forced_scan );
} catch( err ) {
log( ws.name, ws._reportError( err, 'auto_updates', forced_scan ) );
if( debug ) throw err;
}
}
function _auto_updates( forced_scan ) { //check for most centered 4 times/second; new targets, update MFD once/second
// forced_scan is true for user directed actions: weapons off-line, a Rescan or stepping past list boundary
ps = player && player.ship;
if( !ps || !ps.isValid || alertCondition === DOCKED ) //player died or docked
return;
if( !check_equip_ok() ) return;
found_new = found_new
|| ( new_targets && new_targets.length > 0 ); // check if any found in last scan
if( forced_scan ) {
if( report_status && chk_energy_gs_status( true ) ) // true will cause chk_energy_gs_status() to create new mapping
consoleMessage( 'Starting new scan ...', ConsoleMsgDurn );
return; // must not incr. counter or select target
}
if( alertCondition < RED_ALERT ) // gravity scanner suspended during Red Alert
update_grav_scan(); // invoked on ea. 1/4 sec; handles gravScanProgress
if( AutoScan && quarter_sec_counter === 0
&& !fns_are_pending() ) { // create/update takes 20+ frames
if( chk_energy_gs_status( false ) ) { // false to suppress msgs; they only occur on user demand scans
if( found_new ) { // only when there are newly found
_update_Sightings(); // (routine update calc's are in reposition_effects())
} else {
_update_Sightings( gravScanProgress === 0 // _update_Sightings( just_mapping )
|| gravScanProgress === 1 ); // update only those ents in mapping, unless grav scan in flux
}
}
} else if( quarter_sec_counter === 1 ) {
check_Sightings( true ); // true -> 'quickly'; this just checks the health of ents in mapping
if( AutoScan ) {
check_if_new_targets();
}
} else if( quarter_sec_counter === 2 ) {
init_headingView();
checkCombatMFD();
update_MFDs();
} else { // quarter_sec_counter === 3
check_Sightings( false ); // update should be finished, so do proper check
}
if( !ws.$are_Steering ) { //no retarget during autosteering
if( identKeyPress >= IDENT_UNLOCK ) { // just did an Ident unlock, manage delay counter, if any
if( IdentDelay > 0 && delay_counter < 0 ) { // initiate delay counter
delay_counter = IdentDelay; // it's specified in quarter seconds, so ...
} else if( delay_counter > 0 ) { // counter is running
delay_counter--; // ...decr on each call here
}
if( delay_counter === 0 ) { // counter is finished
_resetIdentDelay();
if( identKeyPress === IDENT_STEER_DELAY ) // successful auto steer
_manage_marker( curr_S.map, false, '_mostCentered (IdentKeyPress = IDENT_STEER_DELAY)' );
ws.$IdentKeyPress = identKeyPress = IDENT_READY;// ...resume targeting
// if( debug ) log('_auto_updates, delay_counter === 0, identKeyPress := IDENT_READY');
}
} else if( identKeyPress === IDENT_READY ) {
if( weaponsOnline ) { //in auto mode
if( curr_target === null && AutoLock !== 0 ) { //no target and autolock not disabled
_mostCentered( 'auto' );
}
} else if( GravLock !== 0 ) { //in grav mode and not disabled
_mostCentered( 'grav' );
}
}
}
quarter_sec_counter--;
if( quarter_sec_counter < 0 ) // update counter for per sec tasks
quarter_sec_counter = 3; //do once in a second when reach 0
}
function _resetIdentDelay() { // called from mode()
delay_counter = -1;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// steering ///////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// local variable unique to steering
var steer_map, steer_ent, ps_maxPitch, towbarShip, towShipmass;
function halt_steering( angle_to_target ) { // stop steering
ws.$are_Steering = false;
ws.$TelescopeSteerFCB = null; // used by Towbar oxp
if( SniperLock ) { // improve compatibity (thanks Milo)
SniperLock.deactivate = "FALSE";
}
if( SniperLockPlus ) { // improve compatibity
SniperLockPlus.$enabled = true;
}
steer_ent = steer_map = null;
if( angle_to_target === undefined ) {
// if( debug ) log('halt_steering, angle_to_target === undefined' );
return;
}
if( angle_to_target <= ONE_DEGREE * 1.01
&& identKeyPress === IDENT_STEERING ) { // auto steer reached target
ws.$IdentKeyPress = identKeyPress = IDENT_STEER_DELAY; // get ident delay but no clearing of target
// if( debug ) log('halt_steering, identKeyPress := IDENT_STEER_DELAY');
consoleMessage( 'Telescope steering ended, lock released', ConsoleMsgDurn );
}
}
function start_Steering() { //turn to the target
steer_map = curr_S.map;
if( !steer_map ){
// if( debug ) log('start_Steering, !steer_map, bailing');
return;
}
steer_ent = steer_map.ent;
if( !steer_ent || !steer_ent.isValid ) {
// if( debug ) log('start_Steering, ' + (!steer_ent ? '!steer_ent' : '!steer_ent.isValid') + ', bailing');
return;
}
if( viewDirection !== 'VIEW_FORWARD' ) { //working in forward view only
// if( debug ) log('start_Steering, viewDirection !== VIEW_FORWARD, bailing');
return;
}
ps_maxPitch = ps.maxPitch; // not set in _init_player_vars as only used for steering
towbarShip = null;
/*
if( debug && (ws.$FixedTel === 1 || ws.$are_Steering) ) {
log('start_Steering, cannot start as ' + (ws.$FixedTel === 1 ? 'ws.$FixedTel === ' + ws.$FixedTel : 'ws.$are_Steering is ' + ws.$are_Steering));
}
*/
if( ws.$FixedTel !== 1 && !ws.$are_Steering ) { // fully repaired & not already steering
copy_vector( ps_vectorForward, prevHeading );
if( Towbar ) {
towbarShip = Towbar.$TowbarShip;
towShipmass = towbarShip ? towbarShip.mass : 0;
}
if( SniperLock ) { // improve compatibity (thanks Milo)
SniperLock.deactivate = "TRUE";
}
if( SniperLockPlus ) { // improve compatibity
SniperLockPlus.$enabled = false;
}
ws.$are_Steering = true;
ws.$TelescopeSteerFCB = ws.$Sighting_events_FCB; // Towbar oxp checks isValidFrameCallback
}
}
function _steerFCB( delta ) {
try {
var angle_to_target, angle_traversed;
if( !steer_ent || _has_bad_status( steer_ent ) || !steer_map // _has_bad_status checks isValid
|| !steer_map.last_posn || steer_map.last_posn.length === 0 ) {
/*
if( debug ) {
if( !steer_ent )
log('_steerFCB, !steer_ent, bailing');
else if( _has_bad_status( steer_ent ) )
log('_steerFCB, _has_bad_status( steer_ent ), bailing');
else if( !steer_map )
log('_steerFCB, !steer_map, bailing');
else if( !steer_map.last_posn )
log('_steerFCB, !steer_map.last_posn, bailing');
else if( steer_map.last_posn.length === 0 )
log('_steerFCB, steer_map.last_posn.length === 0, bailing');
else
log('_steerFCB, Yikes, do not know why, bailing');
}
*/
if( steer_ent ) {
halt_steering(); //end of steering (aborted)
}
return;
} // target still alive!
copy_vector( steer_map.last_posn, position ); // if 'ukn', steer to last known position
subtract_vectors( position, ps_position, vector );
unit_vector( vector, vector );
angle_to_target = angle_between_two_unitV( ps_vectorForward, vector );
angle_traversed = angle_between_two_unitV( ps_vectorForward, prevHeading );
if( angle_traversed < 0.005 && angle_to_target > ONE_DEGREE ) { //steer if no manual steering and not in 1 degree
//if the above angle_traversed value lower then can not start steering in my intel atom netbook
let opt1 = ps_maxPitch * delta;
let opt2 = angle_to_target / 12;
let angle = opt1 < opt2 ? opt1 : opt2; //half max turn/step and not too accutate
if( towbarShip && towbarShip.isValid ) {
let tow_opt = ps_mass / towShipmass;
let ma = tow_opt < 2 ? tow_opt : 2; ///small ship max. 2x
angle = angle * 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 '+angle_traversed);//debug
}
//log(ws.name, '_steerFCB, angle_to_target=' +angle_to_target*180 / Math.PI
// +', angle_traversed=' +angle_traversed*180 / Math.PI +', a='+a*180 / Math.PI);
cross_product( ps_vectorForward, vector, cross ); //set the plane where we should rotate in
unit_vector( cross, cross );
rotate_about_axis( ps_orientation, cross, -angle, quaternion );
ps.orientation = quaternion;
vector_forward_from_quaternion( quaternion );
copy_vector( ps_vectorForward, prevHeading );
} else { //end of steering (normal)
halt_steering( angle_to_target );
}
} catch( err ) {
log( ws.name, ws._reportError( err, '_steerFCB', delta ) );
if( debug ) throw err;
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// _hud_effects_closure ///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// local variables unique to _HUD_effects_closure
var prevHeading = [], //heading vector of the player ship in the previous frame to detect manual steering
viewPosition = [0,0,0],//base position of the visual effect
haveViewPosn = false, // flag for view positioning calculations
vShipShift = [], // HUD specific shift of visual target
haveHUDShift = false, // flag for HUD shift calculations
effect_start = [], //starting point for all effects (50m in front of view position)
effect_viewposn = [], //initially calc'd posn common to both virtual ship & sniper ring
vShip_posn = [], //position of the virtual ship model (adjusted from viewPosition for scale, ring)
weaps = false, //the previous state of the player weapons
vRing = null, //a ring around the visual effect target
vShip = null, //visual effect to show the selected target
vDataKey = null, //key of the visual effect
vDKsubst = null, //key of the visual effect substituted for missing effectdata
weaponPosition = [], //sniper ring adjusts for offset to enhance accuracy
weaponZOffset = null, // - used to calculate sniper ring offsets
sRingCorrection = [], // - used to calculate sniper ring offsets
haveWeaponPosn = false, // flag for view positioning calculations
sniper = null, //if this ring guided around the crosshair then the far target is lined up correctly
sRing_posn = [], //position of the sniper ring (adjusted from viewPosition for scale, ring)
vShipScale = 0, // scaling factor to fit vShip effect into vRing
vsize = !weaponsOnline ? VisualTargetNormalSize / 10 // these options can range from 0-8
: VisualTargetCombatSize / 10,
vsizechanged, wide;
function _clear_HUD_Effects() { //Clear Visual Effect Ship Model and Visual Marker also
clear_SnipeRing();
clearVShip();
}
function clear_SnipeRing() { //Clear small sniper ring
if( sniper ) {
sniper.remove();
sniper = null;
ws.$sniper = null; // debug
}
}
function clearVShip() { //Clear Visual Effect Ship Model and large visual ring
if( vShip ) {
vShip.remove();
vShip = null;
vDataKey = null; //need to show again when reident
vDKsubst = null;
}
if( vRing ) {
vRing.remove();
vRing = null;
}
}
/*
var fmt_position = function fmt_position(posn) {
if( !posn || !Array.isArray(posn))
return 'not a vector (' + posn + ')';
var out = '(';
for( let idx=0, len=posn.length; idx <len; idx++) {
out += idx > 0 ? ', ' : '';
out += posn[idx].toFixed(5);
}
return out + ')';
}
*/
function _set_vShip_posn( viewposFwd, vShift ) { // called from shipWillLaunchFromStation
// ws._set_vShip_posn( ps.viewPositionForward, ws.$VTarget_HUD_shift );
ps = player && player.ship;
if( !ps ) return;
haveViewPosn = haveWeaponPosn = haveHUDShift = false;
var weaponPosnFwd = ps.weaponPositionForward; // array of Vector3D
var wPosnX = 0, wPosnY = 0, wPosnZ = 0;
var idx = 0, len = weaponPosnFwd.length;
for( ; idx < len; idx++ ) {
wPosnX += weaponPosnFwd[idx][0];
wPosnY += weaponPosnFwd[idx][1];
wPosnZ += weaponPosnFwd[idx][2];
} // average the positions of weapons (in some oxps, can be multiple)
weaponPosnFwd[0] = wPosnX / len;
weaponPosnFwd[1] = wPosnY / len;
weaponPosnFwd[2] = wPosnZ / len;
copy_vector( weaponPosnFwd, weaponPosition );
haveWeaponPosn = !same_vectors( weaponPosition, VECTOR_ALL_ZEROS );
if( weaponZOffset === null ) {
weaponZOffset = weaponPosnFwd; // reuse array
weaponZOffset[0] = 0;
weaponZOffset[1] = 0;
} else {
free_array(weaponPosnFwd);
}
if( !viewposFwd ) return;
copy_vector( viewposFwd, viewPosition ); // save supplied position
haveViewPosn = !same_vectors( viewPosition, VECTOR_ALL_ZEROS );
if( !vShift ) return;
copy_vector( vShift, vShipShift ); // save supplied HUD shift for 3D model
haveHUDShift = !same_vectors( vShipShift, VECTOR_ALL_ZEROS );
}
var HUD_vars_init = false; // flag to prevent duplicate calculations
var grayLevels = [ [ 0.05, 0.05, 0.05, 1],
[ 0.1, 0.1, 0.1, 1],
[ 0.15, 0.15, 0.15, 1],
[ 0.2, 0.2, 0.2, 1],
[ 0.25, 0.25, 0.25, 1],
[ 0.3, 0.3, 0.3, 1],
[ 0.35, 0.35, 0.35, 1], // ~darkGrayColor
[ 0.4, 0.4, 0.4, 1],
//[ 0.5, 0.5, 0.5, 1] // grayColor
];
var brightnessLevel = 0; // level increased using clock.absoluteSeconds
var lastGrayLevelTime = 0;
function makeItBrighter( obj ) {
// log(ws.name, 'makeItBrighter, entry, parm obj: ' + obj);
// if(debug && obj.constructor !== VisualEffect) {
// log(ws.name, 'makeItBrighter, *** !VisualEffect ***, parm obj: ' + obj);
// if( obj && cd && !obj.position )
// log(ws.name, cd._showProps( obj, 'obj' ));
// }
var mats = obj.getMaterials();
for( var prop in mats ) {
if( mats.hasOwnProperty( prop ) ) {
if( mats[ prop ].hasOwnProperty( 'illumination_map' )
&& mats[ prop ].illumination_map !== 'telescope-illumination.png' )
continue; // it has its own, we didn't modify in collect_effectdata
if( mats[ prop ].hasOwnProperty( 'illumination_modulate_color' ) ) {
mats[ prop ].illumination_modulate_color = grayLevels[ brightnessLevel ];
obj.setMaterials( mats );
}
}
}
// log(ws.name, 'makeItBrighter, exit');
}
function illuminate() {
var start = illuminate.start || 0;
// log(ws.name, 'illuminate, entry, .start = ' + start );
if( !vShip ) { /// thanks to Dybal
log(ws.name, 'illuminate, invalid vShip: ' + vShip + ', start: ' + start
+ ', brightnessLevel: ' + brightnessLevel + ', lastGrayLevelTime: ' + lastGrayLevelTime );
// log(ws.name, 'illuminate, exit (false), .start = ' + illuminate.start );
return false;
}
makeItBrighter( vShip );
var idx, list = vShip.subEntities,
len = list && list.length || 0;
for( idx = start; idx < len; idx++ ) {
if( idx > start && idx % 3 === 0 ) { // setMaterials calls in makeItBrighter are expensive
illuminate.start = idx; // so spread over several frames
// log(ws.name, 'illuminate, exit (false), .start = ' + illuminate.start );
return false;
}
makeItBrighter( list[ idx ] );
}
illuminate.start = 0;
// log(ws.name, 'illuminate, exit (true), .start = ' + illuminate.start );
return true;
}
function mk_vship( key, noset ) { // by not setting vDataKey, prevent trashing
if( dataKey || key ) {
let add_key = key ? key : dataKey;
vShip = addVisualEffect( add_key, ps_position );
if( vShip ) {
// cannot use effect's collisionRadius as it may lack subEntities! eg. ddtmanta's wings, fighter swarm group
let vsCR = curr_target.collisionRadius;
if( vsCR === 0 || !vShip.isValid ) { // dataKey not in effecdata.plist
vShip.remove();
vShip = null;
log(ws.name, 'mk_vship, removed ' +add_key+ ' as '
+ (!vShip.isValid ? '!vShip.isValid' : 'vShip.collisionRadius === 0') );
return false;
}
ws.$vship = vShip; // debug
lastGrayLevelTime = clock.absoluteSeconds;
brightnessLevel = 0;
if( noset ) { // using a substitute model
vDKsubst = add_key;
vDataKey = dataKey;
} else {
vDataKey = vDKsubst = add_key; // only set if using target's dataKey
}
if( vDKsubst === 'oolite-unknown-ship' ) { // question mark size independent of target
vsizechanged = true; // signal for update of scaling, position
return true;
}
vShip.scannerDisplayColor1 = null; //hide from the scanner
vShip.scannerDisplayColor2 = null; // "
let bbox = curr_target.boundingBox; // effects have no boundingBox <sigh>
let bbRadius = sqrt( bbox[0]*bbox[0] + bbox[1]*bbox[1] + bbox[2]*bbox[2] ) / 2;
// let z = bbRadius / 6; // all models scaled as a 6th for constant apparent size
// - this assumes that the target & vship are close in size, not always true! thanks Dybal & Milo
// from the 'telescope-sniper.dat' file, model has an outer radius of 42, inner is 40
// historically, it's placed @ 50 meters and scaled by 1/6
// multiply by vsize in [0.1 .. 0.8] (a tenth of VisualTargetNormalSize or VisualTargetCombatSize)
// yields its collisionRadius in [0.6667 .. 5.333]
// => ensure vship is as large as possible while not exceeding ring's inner radius (whether shown or not)
let ringRadius = (40/6) * vsize;
let vShipBBRad;
if( curr_target.isStation || (SpicyHermits && curr_target.isRock && !curr_target.isFrangible)) {
// station effects have different collisionRadius than actual station
vShipBBRad = bbRadius;
} else {
vShipBBRad = bbRadius < vsCR ? vsCR : bbRadius; // use larger so subEntities will be enclosed by model's ring
}
let scaling = ringRadius / vShipBBRad;
if( scaling > 0 ) { // factor out vsize, as user can change on the fly
vShipScale = scaling / vsize; // - in position_and_orient, vShip.scale( vShipScale * vsize );
vsizechanged = true; // signal for update of scaling, position
// return true;
return false;
// - delay illumination sequence until next frame
} else {
log(ws.name, 'mk_vship, unable to scale effect ... removing: ' + add_key );
}
}
}
return false;
}
var basic_models = [ "adder", "anaconda", "asp", "boa", "boa-mk2", 'buoy', "cobra", "cobra3", "cobramk1",
"ferdelance", "moray", "morayMED", "python", //original player ships
"base", "corvette", "cruiser", "drone", "freighter", "frigate",
"fighter", "gunship", "miner", "runner" ]; //custom ships
function _showVShip( dkey ) { //Show Visual Effect
if( _showVShip.maxGrayLevel === undefined ) // store length so not checked every frame
_showVShip.maxGrayLevel = grayLevels.length; // (it's static)
var maxGrayLevel = _showVShip.maxGrayLevel;
HUD_vars_init = false;
let clear_it = true;
do {
if( ShowVisualTarget === 0 ) break; // turned off by user
if( !weaponsOnline
&& ( ShowVisualTarget < 1 || VisualTargetNormalSize === 0 ) )
break; // turned off by user
if( weaponsOnline
&& ( ShowVisualTarget < 2 || VisualTargetCombatSize === 0 ) )
break; // turned off by user
if( moving_fast ) {
vShipSuspended = ps_torusEngaged; // turn off model until torus ends (too jittery)
if( vShipSuspended )
break;
} else {
vShipSuspended = false;
}
if( viewDirection !== 'VIEW_FORWARD' ) break; // not facing forward
if( ws.$FixedTel !== 0 ) break; // cheap repair
if( !curr_target ) break;
if( !ShowVisualStation && curr_target.isStation ) break; // don't show stations
if( _has_bad_status( curr_target ) ) break;
if( curr_S && curr_S.map && curr_S.map.rank === 'ukn' )
break; // lost target
clear_it = false; // thru gauntlet, ok to show!
} while( false );
if( clear_it ) {
clearVShip();
return;
}
dataKey = dkey ? dkey : curr_target && curr_target.dataKey;
if( !dataKey || curr_target.radius // planet, moon or sun
|| !curr_target.isValid ) { // nothing to show
clearVShip();
return;
}
if( !vShip || !vShip.isValid // no model or gone bad
|| (vDataKey !== dataKey && vDKsubst !== dataKey )) {// OR different target
if( vShip ) vShip.remove();
vShip = null;
}
var made_ship = false;
//if( debug && !vShip ) log(ws.name, "_showVShip, dataKey = "+dataKey+", vShip "+vShip+", isStation "+curr_target.isStation+", ShowVisualStation "+ShowVisualStation);//debug
if( !vShip ) {
made_ship = mk_vship(); // 1st try ship's dataKey
}
let find_dk = '';
if( !vShip ) { // next, try basic model
let idx = basic_models.length;
while( idx-- ) { // in reverse so longer names match 1st
let model = basic_models[ idx ];
if( dataKey.indexOf( model ) >= 0 ) {
find_dk = model;
break;
}
}
if( find_dk !== '' ) {
made_ship = mk_vship( find_dk, true );
}
}
if( !vShip && ShowVisualQuestionMark ) { //fallback
made_ship = mk_vship( 'oolite-unknown-ship', true );
}
if( !vShip ) {
clearVShip(); //silent fallback
return;
}
if( vDKsubst !== 'oolite-unknown-ship' && !curr_target.isRock && // do not illuminate the question mark/rocks
!made_ship && brightnessLevel < maxGrayLevel ) { // do not illuminate on same call as made_ship (both are expensive)
// as illuminate doubles _showVShip execution time
let call_it = illuminate.start !== 0; // is current level done?
if( !call_it ) { // if it is, check if time for next
let absSeconds = clock.absoluteSeconds;
if( absSeconds - lastGrayLevelTime > 0.1 ) { // time to do next level
call_it = true;
lastGrayLevelTime = absSeconds;
}
}
if( call_it ) {
if( illuminate() && realtime_fps ) { // only incr when current level complete
if( realtime_fps() > 50 ) { // relatively fast PC
brightnessLevel++; // gradually increase brightness as telescope gathers light
} else { // PC is slow
brightnessLevel = maxGrayLevel; // display at maxGrayLevel immediately
}
}
}
}
position_and_orient();
}
function calc_effects_vars( forSniper ) { // 1st apply shift for viewPosition, then shift for vShip
// NB: sRing_posn is used as an intermediary; when we need to
// show sniper ring, calculations are ready (ie. cost nothing
var shift; // as were needed by _showVShip anyway)
if( haveViewPosn ) {
copy_vector( ps_position, effect_start );
shift = viewPosition[0];
if( shift ) {
scale_vector( ps_vectorRight, shift, vector );
add_vectors( vector, effect_start, effect_start );
}
shift = viewPosition[1];
if( shift ) {
scale_vector( ps_vectorUp, shift, vector );
add_vectors( vector, effect_start, effect_start );
}
shift = viewPosition[2];
shift = shift ? shift + 50 : 50; // all effects are forward +50 from viewPosition
scale_vector( ps_vectorForward, shift, vector );
add_vectors( vector, effect_start, effect_start );
} else {
scale_vector( ps_vectorForward, 50, vector ); // all effects are forward +50 from viewPosition
add_vectors( vector, ps_position, effect_start );
}
copy_vector( effect_start, sRing_posn );
if( !forSniper ) { // called for vShip (so calc's for sniper are free)
copy_vector( effect_start, effect_viewposn ); // apply any vShipShift
if( haveHUDShift ) {
shift = vShipShift[0];
if( shift ) {
scale_vector( ps_vectorRight, shift, vector );
add_vectors( vector, effect_viewposn, effect_viewposn );
}
shift = vShipShift[1];
if( shift ) {
scale_vector( ps_vectorUp, shift, vector );
add_vectors( vector, effect_viewposn, effect_viewposn );
}
shift = vShipShift[2];
if( shift ) {
scale_vector( ps_vectorForward, shift, vector );
add_vectors( vector, effect_viewposn, effect_viewposn );
}
}
}
if( target_vector.length === 0 ) { // not initialized by _update_target_marker
copy_vector( target_posn, vector );
subtract_vectors( vector, ps_position, target_vector );
unit_vector( target_vector, target_direction );
}
HUD_vars_init = true; // prevents unnecessary call from sniper_ring()
}
var ring_color = []; // working vector for position_and_orient, sniper_ring
var vShipSuspended = false; // stop showing vShip when speed gets too high, ie. Torus drive
function position_and_orient() { //orient vship model
var that = position_and_orient;
var model_orientation = (that.model_orientation = that.model_orientation || []);
if( !vShip || !vShip.isValid || vShip.radius ) return;
calc_effects_vars();
// update model's position
if( vsizechanged ) {
if( vShip.dataKey === 'oolite-unknown-ship' ) { // custom sized
vShip.scale( 10*vsize - 2 );
} else {
let z = vShipScale * vsize;
if( z > 0 ) {
vShip.scale( z ); //shrink
// log('position_and_orient, scaling vShip by ' + (vShipScale * vsize).toFixed(4)+ ', not z: ' + z.toFixed(4));
}
}
}
var up = 18; // arbitrarily chosen height; user can alter w/ viewPosition
// calc alignment so top is constant: vsize*6 is radius of model/ring; 24*( 0.75 - wide ) carry over - needed for HUDs???
copy_vector( effect_viewposn, vShip_posn );
// wide < 1, === gameWindow.height / gameWindow.width; ///widescreen correction
// is 0.75 for std screen, 0.625, 0.546875, etc. for wide screens
scale_vector( ps_vectorUp, ( up - vsize*6 - 24*( 0.75 - wide ) ), vector );
add_vectors( vector, vShip_posn, vShip_posn );
vShip.position = vShip_posn;
/*
// - frame_delta is set by call to _hud_effects() which preceeds _reposition_effects()
var dotP = dot_product( ps_vectorForward, target_direction );
// - more sensitive to change in frame rate for ents parallel to heading, less so when perpendicular
var contract = 250 + (250 * ps_speed/(ps_maxSpeed * 32)); // base amt for all directions
if( dotP >= 0 ) { // moving towards light ball
contract += 0.5 * travel * dotP;
} else { // moving away from light ball
contract += -1.5 * travel * dotP;
}
let adjust = -100 * ( floor(contract/100) ); // to hundreds to reduce jitter
scale_vector( target_direction, adjust, speed_adj );
add_vectors( dst_posn, speed_adj, dst_posn );
*/
///
/*
if( debug && vsizechanged ) log(ws.name, 'position_and_orient, vShip.scaleX = ' + vShip.scaleX.toFixed(2)
+ ', vShip.position = [ ' + vShip_posn[0].toFixed() + ', ' + vShip_posn[1].toFixed() + ', ' + vShip_posn[2].toFixed() + ' ]'
+ ', dist from ps = ' + ps.position.distanceTo(vShip).toFixed(2)
+ ', angled from Fwd, Up & Right: ' + (ps.vectorForward.angleTo(vShip) * 180/Math.PI).toFixed(1) + ', '
+ (ps.vectorUp.angleTo(vShip) * 180/Math.PI).toFixed(1) + ', '
+ (ps.vectorRight.angleTo(vShip) * 180/Math.PI).toFixed(1) );
*/
// orientate 3D model
if( curr_target.isVisible //orientation is known only if visible (Grav.Scanner can give position only)
|| curr_S.map.ent_dist <= scannerRange ) { // or in scannerRange (spec.case for small: missiles, drones, cargo, etc.)
// which may be !isVisible well inside scannerRange
let angle = angle_between_two_unitV( ps_vectorForward, target_direction );
cross_product( ps_vectorForward, target_direction, cross );
unit_vector( cross, cross ); // cross product of unit vectors not guaranteed to yield a unit vector
copy_quaternion( curr_target.orientation, model_orientation );
rotate_about_axis( model_orientation, cross, -angle, quaternion );
} else { //nonvisible, fixed view only
let fixed = vShip.isStation ? PI + 0.22 //stations facing
: QUARTER_ARC + 0.22; ///ships viewed from top (90 degree plus a bit)
if( vShip.isMainStation ) { //rotate to horizontal dock position
rotate_about_axis( ps_orientation, ps_vectorRight, fixed, model_orientation );
copy_vector( vShip.vectorForward, vector );
rotate_about_axis( model_orientation, vector, QUARTER_ARC, quaternion );
} else {
rotate_about_axis( ps_orientation, ps_vectorRight, fixed, quaternion );
}
}
vShip.orientation = quaternion;
// model's ring
let ring_scale = (vsize * 1.05)/ 6; ///shrink
if( VisualTargetRing && ring_scale > 0 ) { //large visual target ring
if( !vRing ) {
vRing = addVisualEffect( "telescope-sniper", vShip_posn );
if( exact_same_vectors( ModelRingColor, VECTOR_ALL_ZEROS ) ) { // special directives to match reticle color
copy_vector( ps.reticleColorTarget, ring_color, true ); // cannot cache reticleColors, as hud could change
} else if( exact_same_vectors( ModelRingColor, VECTOR_ALL_ONES ) ) {// special directives to match locking reticle color
copy_vector( ps.reticleColorTargetSensitive, ring_color, true );
} else {
copy_vector( ModelRingColor, ring_color, true );
} // 3rd parm to copy_vector prevents parm validation
vRing.setMaterials( { "telescope-ring.png": { emission_color: ring_color } } );
// from effecdata: materials = { "telescope-ring.png" = { emission_color = darkGrayColor;};}
vRing.scale( ring_scale );
} else {
vRing.position = vShip_posn;
if( vsizechanged ) { //if weapons switched on/off set new size (small/large)
vRing.scale( ring_scale ); ///shrink
}
}
vRing.orientation = ps_orientation;
ws.$vring = vRing;//debug
} else if( vRing ) {
vRing.remove();
vRing = null;
}
vsizechanged = false;
}
function sniper_ring() { //Visual FrameCallBack for the small sniper ring
if( !curr_target // lost target
// || SniperRange <= SniperMinRange // turned off by user
// simplified options by having single toggle
|| SniperRingSize === 0 // turned off by user
|| (_getShowState() & _currSniperRingFlags()) === 0 // turned off by user in this state
|| ws.$FixedTel !== 0 ) { // no target or cheap repairs
if( sniper )
clear_SnipeRing();
return;
}
/*
if( sniper && sniper.$TelescopeSniperTarget !== curr_target ) {
clear_SnipeRing();
}
*/
var snipertarget = false;
if( !HUD_vars_init ) calc_effects_vars( true ); // true limits calcs to those for sniper ring only
distance = vector_magnitude( target_vector );
let ctCR = curr_target.collisionRadius;
if( distance - ctCR < SniperRange && distance - ctCR > SniperMinRange ) {
/*
if( angle_between_two_unitV( ps_vectorForward, target_vector ) < QUARTER_ARC ) { //to exclude aft line-up
let dratio = -2 * distance / ctCR; //distant and smaller target tracked with larger ring movement
let ax = dratio * ( angle_between_two_unitV( ps_vectorRight, target_vector ) - QUARTER_ARC );
let ay = dratio * ( angle_between_two_unitV( ps_vectorUp, target_vector ) - QUARTER_ARC );
if( abs_v( ax ) < 5 && abs_v( ay ) < 6 * wide ) { //ay<4 if 4:3, <3.4 if 16:9
*/ // new method yields same magnitude for ax, ay
if( dot_product( target_direction, ps_vectorForward ) > 0 ) { //to exclude aft line-up
/* core code shoots from weapon position, sets reticle using view position
w/o adjustment, separation of view & weapon will mk sniper ring innaccurate
eg. DTT PE has viewPosition.y of 4.9 & weaponPosition.y of 13.5 (laser is almost 9m above view)
which places sniper ring too low */
let dratio = 0;
if( SniperLock && SniperLock.deactivate === "FALSE" && SniperLock.sniperlockchangeflag === "ON" ) {
let slsTargetPosn = SniperLock.sniperlocktargetlastposition3;
copy_vector( slsTargetPosn, vector );
subtract_vectors( vector, ps_position, vector );// make new target_vector
distance = vector_magnitude( vector );
dratio = -distance / ctCR;
unit_vector( vector, vector ); // make new target_direction
} else if( SniperLockPlus && SniperLockPlus.$enabled && SniperLockPlus.$slpState === "ON" ) {
let slpsTargetPosn = SniperLockPlus.$slpTargetPosition_3;
copy_vector( slpsTargetPosn, vector );
subtract_vectors( vector, ps_position, vector );// make new target_vector
distance = vector_magnitude( vector );
dratio = -distance / ctCR;
unit_vector( vector, vector ); // make new target_direction
} else if( haveWeaponPosn ) { // correct for weapon position offset wrt viewPositionForward
// dybal's solution from sniperlock_plus
subtract_vectors( weaponZOffset, weaponPosition, sRingCorrection );
rotate_vector( sRingCorrection, ps_orientation )
add_vectors( target_posn, sRingCorrection, vector )// shift target position by sRingCorrection
subtract_vectors( vector, ps_position, vector );// make new target_vector
distance = vector_magnitude( vector ); // ok to clobber distance here as not used again
dratio = -distance / ctCR;
unit_vector( vector, vector ); // make new target_direction
} else {
dratio = -distance / ctCR; //distant and smaller target tracked with larger ring movement
copy_vector( target_direction, vector );
}
let ax = dratio * dot_product( vector, ps_vectorRight ),
ay = dratio * dot_product( vector, ps_vectorUp );
if( abs( ax ) < 5 && abs( ay ) < 6 * wide ) { //ay<4 if 4:3, <3.4 if 16:9
// - restored original's criteria
// if( abs( ax ) < 4 && abs( ay ) < 4 ) { //ay<4 if 4:3, <3.4 if 16:9
scale_vector( ps_vectorRight, ax, vector ); //show misalignment
add_vectors( vector, sRing_posn, sRing_posn );
scale_vector( ps_vectorUp, ay, vector );
add_vectors( vector, sRing_posn, sRing_posn );
if( sniper ) {
sniper.position = sRing_posn;
} else {
sniper = addVisualEffect( "telescope-ring", sRing_posn);
// sniper.$TelescopeSniperTarget = curr_target;
ws.$sniper = sniper; // debug
sniper.scale( 0.01 * SniperRingSize ); //shrink
if( exact_same_vectors( SniperRingColor, VECTOR_ALL_ZEROS ) ) { // special directives to match reticle color
copy_vector( ps.reticleColorTarget, ring_color, true ); // cannot cache reticleColors, as hud could change
} else if( exact_same_vectors( SniperRingColor, VECTOR_ALL_ONES ) ) {// special directives to match locking reticle color
copy_vector( ps.reticleColorTargetSensitive, ring_color, true );
} else {
copy_vector( SniperRingColor, ring_color, true );
} // 3rd parm to copy_vector prevents parm validation
sniper.setMaterials( { "telescope-ring.png": { emission_color: ring_color, diffuse_color: ring_color } } );
}
sniper.orientation = ps_orientation;
snipertarget = true;
}
}
}
if( sniper && !snipertarget ) {
clear_SnipeRing();
}
}
var frame_delta = 0; // store this frame's delta; see apply_speed_adj
var scanner_cooldown = 0; // cool down period between consecutive scans
function _hud_effects( delta ) { //Visual FrameCallBack
try {
if( !ps_vectorRight || !curr_S.ent ) return; // in case not set (eg. console load)
// "If you turn off the weapons with the underscore button ("_") then a scan happens and you enter
// into "Navigation Mode", where autolock helps you see through targets (called Panorama targeting):
// continually relocks to the most centered target." (from readme)
frame_delta = delta;
scanner_cooldown = scanner_cooldown > delta ? scanner_cooldown - delta : 0;
if( weaponsOnline ) { //gravity scan if weapons turned off
if( !weaps ) { //state changed to on
if( curr_S.marker_type === 'marker' ) { // clear far target
switch_PS_target( null );
}
weaps = true; //save state
let new_size = VisualTargetCombatSize / 10;
vsizechanged = vsize !== new_size;
vsize = new_size;
_resetIdentDelay(); // reset counter for IdentDelay
if( gs_state === GS_NONE ) {
scanner_cooldown = 5; // (sec.) delay to start next scan
} else if( gs_state === GS_COMPLETE ) {
scanner_cooldown = 10; // (sec.) delay to start next gravity scan
}
}
} else if( weaps ) { //state changed to off
weaps = false; //save state
let new_size = VisualTargetNormalSize / 10;
vsizechanged = vsize !== new_size;
vsize = new_size;
_resetIdentDelay(); // reset counter for IdentDelay
var scanning = fns_are_pending(); // true => mapping creation/update is running
if( !scanning && scanner_cooldown === 0 )
_auto_updates( gs_state <= GS_STOPPED ); // user starts a 'rescan' unless scan in progress
report_status = true;
}
if( !mappingReady || maplen === 0 ) { // wait for mapping to be built OR it's empty
return;
}
if( viewDirection !== 'VIEW_FORWARD' ) { //working in forward view only
_clear_HUD_Effects();
return;
}
dataKey = curr_target && curr_target.dataKey;
var valid_target = dataKey && curr_target.isValid; // check dataKey to exclude wormholes & orbs (have no dataKey)
if( !valid_target || (vShip && !vShip.isValid) ) { // no ship to update
_clear_HUD_Effects();
return;
}
if( curr_target.isStation ) { // turn off hud effect when docking
distance = _detect_distanceTo( curr_target );
if( distance < 500 ) {
copy_vector( curr_target.position, vector );
subtract_vectors( vector, ps_position, vector );
unit_vector( vector, vector );
let dockDot = dot_product( vector, curr_target.vectorForward );
let headingDot = dot_product( ps_vectorForward, curr_target.vectorForward );
if( dockDot < -0.95 && headingDot < -0.95 ) { // in front of dock, pointing in its direction
_clear_HUD_Effects();
return;
}
}
}
_showVShip( dataKey );
sniper_ring();
} catch( err ) {
log( ws.name, 'vsize = ' + vsize + ', vShipScale = ' + vShipScale
+ ', dataKey: ' + dataKey + ', valid_target: ' + valid_target + ', curr_target: ' + curr_target
+ '\n\t vShip.isValid is ' + (vShip === null ? 'not available' : vShip.isValid)
+ ', vRing.isValid is ' + (vRing === null ? 'not available' : vRing.isValid)
+ ', sniper.isValid is ' + (sniper === null ? 'not available' : sniper.isValid) );
log( ws.name, ws._reportError( err, 'hud_effects', delta ) );
if( debug ) throw err;
}
}
// All functions called from outside closure are wrapped in try..catch blocks.
// In the key: value pairs below, values that do not start with '_' are
// entry stubs containing try..catch blocks that call the underscored value, (so they're not nested)
return {
_initOxpVars: _initOxpVars,
_init_player_vars: init_player_vars, // entry stub
_reload_config: _reload_config,
_adjustMLFlags: _adjustMLFlags,
_getShowState: _getShowState,
_getShowStateText: _getShowStateText,
_currMLFlags: _currMLFlags,
_shutdown_Sightings: shutdown_Sightings, // entry stub
_restart_after_shutdown: _restart_after_shutdown,
_has_bad_status: has_bad_status, // entry stub
_Sighting_index: Sighting_index, // entry stub
_set_curr_Sighting: set_curr_Sighting, // entry stub
_add_Sighting: add_Sighting, // entry stub
_delete_Sighting: delete_Sighting, // entry stub
_chg_curr_Sighting: chg_curr_Sighting, // entry stub
_nearest_Sighting: nearest_Sighting, // entry stub
_reposition_effects: _reposition_effects,
_update_Sightings: update_Sightings, // entry stub
_newList: newList, // entry stub
_call_pending: _call_pending,
_create_Sightings: create_Sightings, // entry stub
_update_target_marker: _update_target_marker,
_manage_marker: manage_marker, // entry stub
_mostCentered: mostCentered, // entry stub
_auto_updates: auto_updates, // entry stub
_resetIdentDelay: _resetIdentDelay,
_steerFCB: _steerFCB,
_clear_HUD_Effects: _clear_HUD_Effects,
_showVShip: _showVShip,
_set_vShip_posn: _set_vShip_posn,
_hud_effects: _hud_effects,
_relativeDirection: relativeDirection, // entry stub
_report_config: report_config, // entry stub
_report_autovars: _report_autovars,
// for debugging
reset_common_vars: reset_common_vars,
index_in_list: index_in_list,
getDetected: getDetected,
is_hostile: is_hostile,
grav_scan_dist: grav_scan_dist,
check_Sightings: check_Sightings,
select_Sightings: select_Sightings,
add_lt_ball: add_lt_ball,
lb_effect_size: lb_effect_size,
update_lt_ball: update_lt_ball,
add_ml_ring: add_ml_ring,
ml_effect_size: ml_effect_size,
update_ml_ring: update_ml_ring,
proc_stealthy: proc_stealthy,
update_one_Sighting: update_one_Sighting,
update_some: refresh_Sightings,
classify_ship: classify_ship,
is_ignored_ship: is_ignored_ship,
process_new_targets: process_new_targets,
fns_are_pending: fns_are_pending,
set_fn_pending: set_fn_pending,
clear_all_pending: clear_all_pending,
show_pending: show_pending,
grow_new_list: grow_new_list,
notable_ent: notable_ent,
check_if_new_targets: check_if_new_targets,
update_MFDs: update_MFDs,
qualifyMFD: qualifyMFD,
set_displayName: set_displayName,
showTargetName: showTargetName,
showShipReport: showShipReport,
entityIsNamed: entityIsNamed,
planetIsNamed: planetIsNamed,
sunName: sunName,
orbName: orbName,
planetNameString: planetNameString,
report_scan_progress: report_scan_progress,
/* for profiling (see also debug loading iife
time_create: time_create, //cagiife
time_update: time_update, //cagiife
time_refresh: time_refresh, //cagiife
profile_create: profile_create, //cagiife
profile_update: profile_update, //cagiife
profile_refresh: profile_refresh, //cagiife
set_profiling: set_profiling, //cagiife
clear_profiling: clear_profiling, //cagiife
*/
}
// }; // end of closure
}.bind(this); // get [native code] in debugger rather than entire function
}).call(this); // end of strict function call
// }).call(worldScripts.telescope);
// run ws._shutdown_Sightings() BEFORE reloading entire script!
// run ws.startUp() afterwards
// run ws.startUpComplete() afterwards
|