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

Expansion Target System Plugins

Content

Warnings

  1. Information URL mismatch between OXP Manifest and Expansion Manager string length at character position 0

Manifest

from Expansion Manager's OXP list from Expansion Manifest
Description A collection of equipment items that expand the functionality of the player ship's target system: Short Range Snapshot, Target Memory Interface and Target Banking Module. A collection of equipment items that expand the functionality of the player ship's target system: Short Range Snapshot, Target Memory Interface and Target Banking Module.
Identifier oolite.oxp.nicholasmenchise.target-system-plugins oolite.oxp.nicholasmenchise.target-system-plugins
Title Target System Plugins Target System Plugins
Category Equipment Equipment
Author Nicholas Menchise Nicholas Menchise
Version 0.81 0.81
Tags
Required Oolite Version
Maximum Oolite Version
Required Expansions
Optional Expansions
Conflict Expansions
Information URL n/a
Download URL https://wiki.alioth.net/img_auth.php/9/96/Target-System-Plugins_0.81.oxz n/a
License CC BY-NC-SA 4.0 CC BY-NC-SA 4.0
File Size n/a
Upload date 1610873378

Documentation

Also read http://wiki.alioth.net/index.php/Target%20System%20Plugins

readme.txt

Oolite Expansion Pack
Target System Plugins
Copyright 2019 Nicholas Menchise

License: Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)
available at https://creativecommons.org/licenses/by-nc-sa/4.0/


Introduction

    This expansion introduces three equipment items that add more features to the
player ship's targeting system. These items are intended to be compatible with any
target selector the player prefers.


Dependencies

    All of these items require some core game equipment before they will work. This
equipment must be functioning at all times. In other words, if the dependencies are
damaged or missing, then the plugins will fail and they will let you know about it.


The Equipment

    The Short Range Snapshot (SRS) counts the targets within the player ship's scanner
range, divides them into seven categories and displays the tallies in its MFD. Additional
tallies are appended when applicable.
Requires the Scanner Targeting Enhancement. Tech Level 12. Cost: 300 Cr.

    The Target Memory Interface (TMI) displays a list of up to eight of the most recently
targeted objects sorted by distance from the player ship in its MFD. It can also indicate
the category of each target if the SRS is installed.
Requires the Target System Memory Expansion. Tech Level 9. Cost: 200 Cr.

    The Target Banking Module (TBM) is primeable equipment that utilises the SRS target
categories as memory banking modes. The player can select a category on the SRS MFD
with the mode button, then press the activate button to assign up to 8 targets at once
to the memory bank through the TMI.
Requires the SRS and the TMI. Tech Level 12. Cost: 500 Cr.


Cascade Weapon Alert

    All MFDs of functioning plugins will alert the player if a cascade weapon like the
Quirium Cascade Mine is within the player ship's scanner range.


SRS Categories

    Hostile: all targets that are hostile toward the player ship
    Outlaw: offenders and fugitives, including Thargoids
    Neutral: traders, hunters, etc.
    State: police and military
    Cargo: cargo containers, escape capsules, metal fragments, derelicts, etc.
    Rock: asteroids, boulders, splinters, rock hermits
    Other: almost everything else (excludes wreckage and wormholes)


SRS Additional Tallies

    The SRS will append some tallies for more specific targets on the same line as
the relevant category, if applicable. For example, if there are 10 rocks and 4 of them
are splinters, then the SRS will display the following:

Rock: 10 ... 4 splinters.

    The categories with additional tallies are as follows:
        Outlaw: Thargoid
        Cargo: Escape Capsule
        Rock: Splinter


TMI Category Symbols

    The TMI MFD can indicate the category of each target if the SRS is functioning. This
information is displayed symbolically to save space for target names.

    There are eight symbols:
        !H!    Hostile
        !O.    Outlaw
        Nt-    Neutral
        St/    State
        -C-    Cargo
        -R-    Rock
        .o?    Other
        ???    Unknown


SRS and TMI Tips

    The plugins will display some helpful information about themselves on their MFDs
when no targets are listed.


Development History

    It all started when I discovered the Target System Upgrade expansion by Andrey
Belov (also known as "timer"). I noticed that the equipment's hostile/outlaw mode
was not selecting Thargoid ships that were attacking other ships. I had some prior
experience with Javascript, so I decided to modify one of the functions a bit. It started
selecting the Thargoids and I was satisfied for a little while, but then I wanted to add
more target modes and information on the equipment's MFD. One thing led to
another and I ended up creating what is now known as the Short Range Snapshot.

    The feature formerly known as the TSU Snapshot was almost ready to be part of
a new release, which could have been the first update to the TSU in five years.
However, the more I used and enjoyed the enhanced TSU, the more features I wanted.
I also wanted to release an expansion that did not favour one piece of targeting
equipment over another. I wanted compatibility with the Fast Target Selector at the
very least.

    I also became aware of some performance issues I had created by integrating
an MFD that tallied five target modes at once with filtering code that was designed
for one mode at a time. I had already replaced Andrey's MFD code and rewrote
the primeable equipment event handlers, but now I was facing the challenge of
modifying the code that actually made the targeting work, which I barely
understood at the time. I still can't figure out how the sort by distance function
works. Fortunately, I learned from the Oolite Javascript Reference for the System
class that sorting by distance was built into some of its methods.

    New ideas were occuring to me as I learned more about how to script Oolite
expansions. The idea for the Target Memory Interface was the turning point. I
decided to make a new expansion from scratch that I would call Target System
Plugins. The goal was to add features to the player ship's targeting system that
could be used with the player's preferred target selector in addition to the
memory banking feature of the TMI.

    I knew I had made the right decision after I discovered the wonders of the
descriptions property list, which wasn't used in the TSU. It was the first part of
the new expansion that I worked on, even before porting my Snapshot code. The
TSP equipment was going to use a lot of strings, to put it mildly, which made
descriptions.plist the best place for me to figure out the design of the frontend. For
example, it was during my work on that property list that I realised I needed to
have abbreviation symbols for the target modes in the TMI MFD, so that there
would be enough room in the MFD to make it a useful frontend for the target
memory bank. All of this happened before I wrote a single line of code for the
TMI.

    The next step was writing my own code that actually made the targeting work.
My experience with modifying and testing the TSU's filter parameters (i.e. what
to include and exclude in each target mode) would prove useful, but I wasn't
confident until I did some more research into the filteredEntities method of the
System class, the same method that Andrey used to get the targets from scanner
range. I finally understood how that method was filtering the entities, which
enabled me to write my own functions with similar functionality (pun not intended).
This was the key to avoiding the performance issues from my previous work, as
my entire expansion, even with three different pieces of equipment working
simultaneously, would only need to call the filteredEntities method once per
second. My own filtering code would do the rest.

    I spent the next couple of weeks developing the expansion when another idea
occurred to me. The first TMI design had two overall features: an MFD that
served as a front end for the Target System Memory Expansion (TSME); and
primeable equipment that would assign an entire category of targets from the
SRS to target memory. I wondered if I was over-engineering the TMI, which was
dependent on the SRS as well as the TSME (if either of them broke, then the TMI
wouldn't work). The SRS also had its own dependency (the Scanner Targeting
Enhancement), which meant that the TMI would fail if any of three other equipment
items were broken or missing and the player would lose access to both features.

    I decided to spin off the second feature into its own equipment item: the
Target Banking Module. The TMI would only be dependent on the TSME as it was
the front end for that equipment. The TBM would be dependent on the TMI and
SRS, so only the most advanced feature would have the highest dependency
stack (four equipment items in total). I was fortunate that the redesign only
required a few changes to the code that I already wrote.

    I finished the scripts on the 1st of February, 2019. The development history
up to this point spanned approximately one month. Testing and debugging was
about to begin.

    I knew that writing everything before testing any of the functions was not a
good idea, but my hubris got the better of me at the time. My lack of experience
with the descriptions property list and string expansion did not help matters. I
had not written the string expansion calls properly, because I had not read the
reference material thoroughly. A lot of corrections were needed before my scripts
would load at startup.

    I had started working on another expansion pack for Oolite by this time, the
Primeable Equipment MFD, because I wanted to make something less ambitious
and time-consuming while testing this one. That was when the idea for the Hostile
Awareness MFD occurred to me, but I would need to research the Vector3D class
and learn about vectors in general before I could even begin working on that
feature. My first project had become even more ambitious and time-consuming. I
knew that my second project would be my first public release.

    I was mistaken when I thought that the existing scripts were finished at the
beginning of February, to put it mildly. The bugs in the TMI made it useless as
a frontend for the TSME. One of the bugs took about a week to fix and I had to
study the relevant parts of the core game's source code several times to figure
out a solution. I was lucky that the syntax of Objective C was similar enough to
Javascript that I could read it.

    Unfortunately, it became apparent that the solution was not going to be
enough. The Javascript engine does not provide enough access to event handlers
like key presses to maintain the accuracy of the TMI list. It would not work as a
complete TSME frontend. I decided to redesign the TMI to have less scope in
a way that would be even more useful to the player.

    The third incarnation of the TMI is an MFD that displays up to 8 targets at once
sorted by distance from the player ship. This new design made my previous idea
for the Hostile Awareness MFD redundant, since the TMI combined with the SRS
can provide similar information, albeit without vector indicators. I decided to
remove the Hostile Awareness MFD from my planned feature set.

    This just leaves more testing and one more feature that would apply to all of
my MFDs: a cascade weapon alert. This idea occurred to me after discovering the
Q-Bomb Detector expansion by Diziet Sma. I liked the general idea behind it, but
I disagreed with charging 5000 credits for an alarm.

Equipment

Name Visible Cost [deci-credits] Tech-Level
Short Range Snapshot yes 3000 12+
Sell Short Range Snapshot yes 150 12+
Target Banking Module yes 5000 12+
Sell Target Banking Module yes 250 12+
Target Memory Interface yes 2000 9+
Sell Target Memory Interface yes 100 12+

Ships

This expansion declares no ships. This may be related to warnings.

Models

This expansion declares no models. This may be related to warnings.

Scripts

Path
Scripts/target-system-plugins_conditions.js
"use strict";

this.name           = "TargetSystemPlugins_Conditions";
this.author         = "Nicholas Menchise";
this.copyright      = "(C) 2019 Nicholas Menchise";
this.licence        = "CC BY-NC-SA 4.0";

//
// This condition script includes the instructions for disallowing the
// removal option for TSP equipment if the Sell Equipment expansion
// by Commander McLane is not installed.
//


this.allowAwardEquipment = function(eqKey, ship, context) {
    if (!worldScripts["sell_equipment"]) return false;
    return true;
}
Scripts/target-system-plugins_fs.js
"use strict";

this.name           = "TargetSystemPlugins_FilterSort";
this.author         = "Nicholas Menchise";
this.copyright      = "(C) 2019 Nicholas Menchise";
this.licence        = "CC BY-NC-SA 4.0";

//
// This world script includes the instructions for filtering entities within
// scanner range and sorting them into groups for the equipment.
//


// Total targets within scanner range
this.$targetsTotal = [];

// Hostile targets within scanner range
this.$targetsHostile = [];

// Outlaw targets within scanner range
this.$targetsOutlaw = [];

// Neutral targets within scanner range
this.$targetsNeutral = [];

// State targets within scanner range
this.$targetsState = [];

// Cargo targets within scanner range
this.$targetsCargo = [];

// Rock targets within scanner range
this.$targetsRock = [];

// Other targets within scanner range
this.$targetsOther = [];

// Memory bank of Entities for the TMI MFD.
this.$targetsMemoryData = [];

// Target category symbol strings. There should be a symbol for each Entity.
this.$targetsMemorySigns = [];

// Memory bank index positions of valid Entities within player ship scanner range, sorted by distance.
this.$targetsMemoryInRange = [];


//
// Sets the array of total targets within scanner range.
//
// This function is called by _updateTallies function in world script TargetSystemPlugins_ShortRangeSnapshot.
//
// The predicate for the filteredEntities method of System class excludes
// RRS autopilot screens for better compatibility with the Rescue Stations expansion.
//
this._getTargetsTotal = function() {
    this.$targetsTotal = system.filteredEntities(
        this,
        function(e) {
            if (
                e.isShip && !e.isCloaked && e.displayName !== "Wreckage" && e.dataKey !== "rrs_autopilot_screen"
            ) return e;
        },
        player.ship, player.ship.scannerRange
    );
}


//
// Sets the arrays of targets in seven categories.
//
// This function is called by _updateTallies function in world script TargetSystemPlugins_ShortRangeSnapshot.
//
this._getTargetsSorted = function() {

    // Hostile mode
    this.$targetsHostile = this._getTargetsFiltered(
        function(e) {
            if (e.hasHostileTarget && e.target === player.ship) {
                return true;
            }
                else {
                    return false;
                }
        }
    );

    // Outlaw mode
    this.$targetsOutlaw = this._getTargetsFiltered(
        function(e) {
            if (e.scanClass === "CLASS_THARGOID" || e.scanClass === "CLASS_NEUTRAL" && e.bounty !== 0) {
                return true;
            }
                else {
                    return false;
                }
        }
    );

    // Neutral mode, includes additional parameters for better compatibility with the Escort Contracts expansion
    this.$targetsNeutral = this._getTargetsFiltered(
        function(e) {
            if (e.beaconLabel === "Escort Mother" || e.scanClass === "CLASS_NEUTRAL" && e.bounty === 0) {
                return true;
            }
                else {
                    return false;
                }
        }
    );

    // State mode
    this.$targetsState = this._getTargetsFiltered(
        function(e) {
            if (e.scanClass === "CLASS_POLICE" || e.scanClass === "CLASS_MILITARY") {
                return true;
            }
                else {
                    return false;
                }
        }
    );

    // Cargo mode, includes additional parameters for better compatibility with the Ore Processor expansion
    this.$targetsCargo = this._getTargetsFiltered(
        function(e) {
            if (e.scanClass === "CLASS_CARGO" && e.displayName !== "Splinter") {
                return true;
            }
                else {
                    return false;
                }
        }
    );

    // Rock mode, includes additional parameters for better compatibility with Ore Processor and Escort Contracts expansions
    this.$targetsRock = this._getTargetsFiltered(
        function(e) {
            if (e.displayName === "Splinter" || e.scanClass === "CLASS_ROCK" && e.beaconLabel !== "Escort Mother") {
                return true;
            }
                else {
                    return false;
                }
        }
    );

    // Other mode
    this.$targetsOther = this._getTargetsFiltered(
        function(e) {
            if (
                e.scanClass !== "CLASS_THARGOID" && e.scanClass !== "CLASS_NEUTRAL" && e.scanClass !== "CLASS_POLICE" &&
                e.scanClass !== "CLASS_MILITARY" && e.scanClass !== "CLASS_CARGO" && e.scanClass !== "CLASS_ROCK"
            ) {
                return true;
            }
                else {
                    return false;
                }
        }
    );

}


//
// Returns an array of Entities that matches a target category.
//
// This function is called by _getTargetsSorted function.
//
// filterThisWay argument is a function with one argument variable and one condition that returns a boolean.
// The condition includes the filter parameters. It should return true within the if statement and false within the else statement.
//
this._getTargetsFiltered = function(filterThisWay) {
    var giveTheseTargets = [];

    for (var n = 0; n < this.$targetsTotal.length; n++) {
        var tryThisOne = filterThisWay(this.$targetsTotal[n]);
        if (tryThisOne) var x = giveTheseTargets.push(this.$targetsTotal[n]);
    }

    return giveTheseTargets;
}


//
// Returns the number of Entities in a target category that matches a parameter.
//
// This function is called by _updateTallies function in world script TargetSystemPlugins_ShortRangeSnapshot.
//
// This should only be called on the condition that there are any targets in the relevant category.
//
// whichTally argument is a string that specifies the targets to be counted.
// There are three valid strings for this argument:
// "thargoid" for Thargoid ships
// "escape" for escape capsules
// "splinter" for rock splinters.
//
// Returns a negative integer if the string is invalid or the array to be searched is empty.
//
this._getTally = function(whichTally) {
    var tallyResult = -1;
    var checkList = [];

    // Determines the target array to be searched.
    if (whichTally === "thargoid") checkList = this.$targetsOutlaw;
    if (whichTally === "escape") checkList = this.$targetsCargo;
    if (whichTally === "splinter") checkList = this.$targetsRock;

    if (checkList.length < 1) return tallyResult;
    tallyResult = 0;

    // Determines the filter function to be applied to the target array.
    var countThese = function(){};
    if (whichTally === "thargoid")
        countThese = function(e) {
            if (e.scanClass === "CLASS_THARGOID") {
                return true;
            }
                else {
                    return false;
                }
        };
    if (whichTally === "escape")
        countThese = function(e) {
            if (e.hasRole("escape-capsule")) {
                return true;
            }
                else {
                    return false;
                }
        };
    if (whichTally === "splinter")
        countThese = function(e) {
            if (e.displayName === "Splinter") {
                return true;
            }
                else {
                    return false;
                }
        };

    // Searches the target array and increments the tally when the correct entries are found.
    for (var n = 0; n < checkList.length; n++) {
        var tryThisOne = countThese(checkList[n]);
        if (tryThisOne) tallyResult++;
    }

    return tallyResult;
}


//
// Tests an Entity to determine if it goes into the target memory bank and does so if it passes.
//
// This function is called by shipTargetAcquired event handler in world script TargetSystemPlugins_SharedHandlers.
//
// testThis argument is the Entity to be tested.
//
this._tmiBankThisOrNot = function(testThis) {

    // Checks if the Entity is already in the memory bank to avoid duplication.
    var matchFound = this._tmiCheckForMatch(testThis, this.$targetsMemoryData);

    // Clears invalid entries and keeps the memory bank array length to 8 or less.
    this._tmiCleanUp();
    if (!matchFound && this.$targetsMemoryData.length == 8) var removeD = this.$targetsMemoryData.shift();
    if (!matchFound && this.$targetsMemorySigns.length == 8) var removeS = this.$targetsMemorySigns.shift();

    if (!matchFound) {
        // Adds target information to the end of the arrays.
        var x = this.$targetsMemoryData.push(testThis);
        var srsWorking = worldScripts[expandDescription("[tsplugins_script_srs]")]._statusCheck();
        var signOfThis = "";
        if (srsWorking) signOfThis = this._tmiSignThis(testThis);
        if (!srsWorking) signOfThis = expandDescription("[tsplugins_sign_missed]");    // symbol for unknown
        x = this.$targetsMemorySigns.push(signOfThis);
    }
        else {
            // Transfers the target information from its current position in the arrays to the end of the arrays.
            var whereIsThis = this.$targetsMemoryData.indexOf(testThis);
            var transferD = this.$targetsMemoryData[whereIsThis];
            var transferS = this.$targetsMemorySigns[whereIsThis];
            removeD = this.$targetsMemoryData.splice(whereIsThis, 1);
            removeS = this.$targetsMemorySigns.splice(whereIsThis, 1);
            var y = this.$targetsMemoryData.push(transferD);
            y = this.$targetsMemorySigns.push(transferS);
        }

}


//
// Tests the target memory bank for invalid entries and removes them, if so.
//
// This function is called by _tmiBankThisOrNot function.
//
this._tmiCleanUp = function() {
    for (var n = 0; n < this.$targetsMemoryData.length; n++) {
        if (!this.$targetsMemoryData[n].isInSpace) {
            var removeD = this.$targetsMemoryData.splice(n, 1);
            var removeS = this.$targetsMemorySigns.splice(n, 1);
            n--;
        } else {}
    }
}


//
// Sets an array of index positions of $targetsMemoryData entries within the player ship's scanner range, sorted
// by distance from the player ship. The array is assigned to $targetsMemoryInRange.
// Also moves targets in memory to the front of their arrays if they're out of scanner range, which puts them first
// in line for removal by _tmiBankThisOrNot function when trimming the length.
//
// This function is called by _timedUpdate function in world script TargetSystemPlugins_TargetMemoryInterface.
// It is also called by shipTargetAcquired event handler in world script TargetSystemPlugins_SharedHandlers.
// It is also called by activated event handler in equipment script TargetSystemPlugins_TargetBankingModule.
//
this._tmiGroupInRange = function() {

    // Gets the index positions of targets within scanner range and their distances from player ship.
    var youScanThem = [];
    var youMeasureThem = [];
    var youAreHere = player.ship.position;
    var outThere = player.ship.scannerRange;
    for (var n = 0; n < this.$targetsMemoryData.length; n++) {
        var checkThisOne = this.$targetsMemoryData[n];
        if (!checkThisOne.isInSpace || checkThisOne.isCloaked) {
        }
            else if (youAreHere.distanceTo(checkThisOne) < outThere) {
                var foundThisOne = youAreHere.distanceTo(checkThisOne);
                var x = youScanThem.push(n);
                x = youMeasureThem.push(foundThisOne);
            }
                else {
                    // Transfers the target information from its current position in the arrays to the start of the arrays.
                    var transferD = this.$targetsMemoryData[n];
                    var transferS = this.$targetsMemorySigns[n];
                    var removeD = this.$targetsMemoryData.splice(n, 1);
                    var removeS = this.$targetsMemorySigns.splice(n, 1);
                    x = this.$targetsMemoryData.unshift(transferD);
                    x = this.$targetsMemorySigns.unshift(transferS);
                }
    }

    // Sorts the entries by distance from player ship.
    var youSortThem = [];
    var howMany = youScanThem.length;
    for (var n = 0; n < howMany; n++) {
        var closestRange = outThere;
        var closestOne = -1;
        for (var p = 0; p < youMeasureThem.length; p++) {
            if (youMeasureThem[p] < closestRange) closestRange = youMeasureThem[p];
            if (youMeasureThem[p] == closestRange) closestOne = p;
        }
        var y = youSortThem.push(youScanThem[closestOne]);
        var removeN = youScanThem.splice(closestOne, 1);
        var removeM = youMeasureThem.splice(closestOne, 1);
    }

    this.$targetsMemoryInRange = youSortThem;
}


//
// Initialises the TMI arrays.
//
// This function is called by startUp, shipWillDockWithStation, shipWillEnterWitchspace, equipmentDamaged and
// equipmentRemoved event handlers in world script TargetSystemPlugins_SharedHandlers.
//
this._tmiReset = function() {
    this.$targetsMemoryData = [];
    this.$targetsMemorySigns = [];
    this.$targetsMemoryInRange = [];
}


//
// Tests the Entities in the target memory bank to determine if their TMI category symbols are current and changes
// them, if necessary.
//
// This function is called by _timedUpdate function in world script TargetSystemPlugins_TargetMemoryInterface.
//
// This should only be called if the SRS equipment is functional.
//
this._tmiSignUpdate = function() {
    var signRecorded = "";    // stores the TMI category symbol that's in the memory bank
    var signScanned = "";    // stores the TMI category symbol that's in the latest snapshot, if target is found

    for (var n = 0; n < this.$targetsMemorySigns.length; n++) {
        signRecorded = this.$targetsMemorySigns[n];
        if (!this.$targetsMemoryData[n].isInSpace || this.$targetsMemoryData[n].isCloaked) {
            signScanned = expandDescription("[tsplugins_sign_missed]");
        }
            else {
                signScanned = this._tmiSignThis(this.$targetsMemoryData[n]);
            }
        if (signRecorded !== signScanned) this.$targetsMemorySigns[n] = signScanned;
    }
}


//
// Tests an Entity to determine its target category and returns its TMI symbol string.
//
// This function is called by _tmiBankThisOrNot and _tmiSignUpdate functions.
//
// This should only be called if the SRS equipment is functional.
//
// whatsThat argument is the Entity to be tested.
//
this._tmiSignThis = function(whatsThat) {
    var signNotFound = expandDescription("[tsplugins_sign_missed]");    // symbol for unknown

    // Compares the Entity to the Entities in seven arrays until match found or searched all
    var matchFound = false;
    var checkThese = [
        this.$targetsHostile, this.$targetsOutlaw,
        this.$targetsNeutral, this.$targetsState,
        this.$targetsCargo, this.$targetsRock,
        this.$targetsOther
    ];
    var signWithThis = [
        expandDescription("[tsplugins_sign_mode0]"), expandDescription("[tsplugins_sign_mode1]"),
        expandDescription("[tsplugins_sign_mode2]"), expandDescription("[tsplugins_sign_mode3]"),
        expandDescription("[tsplugins_sign_mode4]"), expandDescription("[tsplugins_sign_mode5]"),
        expandDescription("[tsplugins_sign_mode6]")
    ];
    for (var n = 0; n < checkThese.length; n++) {
        matchFound = this._tmiCheckForMatch(whatsThat, checkThese[n]);
        if (matchFound) return signWithThis[n];
    }

    return signNotFound;
}


//
// Returns a boolean that indicates if an Entity matches an entry in an array of Entities.
//
// This function is called by _tmiBankThisOrNot and _tmiSignThis functions.
//
// checkThis argument is the Entity to be tested.
// againstThis argument is the array of Entities.
//
this._tmiCheckForMatch = function(checkThis, againstThis) {

    for (var n = 0; n < againstThis.length; n++) {
        if (checkThis === againstThis[n]) return true;
    }

    return false;
}

Scripts/target-system-plugins_sh.js
"use strict";

this.name           = "TargetSystemPlugins_SharedHandlers";
this.author         = "Nicholas Menchise";
this.copyright      = "(C) 2019 Nicholas Menchise";
this.licence        = "CC BY-NC-SA 4.0";

//
// This world script includes the miscellaneous instructions that are shared
// by the equipment. For example, the code that updates the MFDs. It also
// includes the event handlers.
//



// Whether or not the TBM primeable equipment is processing an activate event. Prevents TMI MFD overload when true.
this.$tbmActive = false;

// Stores the ready status of the TBM primeable equipment.
this.$tbmReady = false;

// Stores the current target mode of the TBM primeable equipment.
this.$tbmTargetMode = 7;    // set to Total mode by default, because it's always displayed on SRS MFD when equipment is activated.

// Calls the function that updates the cascade alert every quarter of a second.
this.$cascadeUpdate = new Timer(this, function() { this._cascadeWeaponAlert(); }, 0, 0.25);

// Stores the SoundSource for the cascade weapon alert. Property set by cascadeWeaponDetected event handler.
this.$cascadeUpdateSound = new SoundSource;

// Whether or not the cascade alert danger text is on the MFD during an alert. It's intended to flash with each timer interval.
this.$cascadeDangerText = true;

// Stores an array of cascade weapon Entities detected by player ship.
this.$cascadeDangerObject = [];


//
// Sets MFD text, if MFDs are available.
//
// This function is called by _statusUpdate and _setMfdString functions in world script TargetSystemPlugins_ShortRangeSnapshot.
// It is also called by the same functions in world script TargetSystemPlugins_TargetMemoryInterface. It is also called by some
// event handlers below. It is also called by _cascadeWeaponMfd function below.
//
// mfdKey argument stores the string that determines where the text will be displayed.
// mfdString argument stores the text to be displayed. It can also set the MFD to blank if the value is null.
//
this._setMfd = function(mfdKey, mfdString) {
    if (player.ship.multiFunctionDisplays > 0) player.ship.setMultiFunctionText(mfdKey, mfdString);
}


//
// Takes over all functioning TSP MFDs to display an alert when a cascade weapon is within the player ship scanner range.
//
// This function is called by $cascadeUpdate timer.
//
this._cascadeWeaponAlert = function() {
    var tspSRS = worldScripts[expandDescription("[tsplugins_script_srs]")];
    var tspTMI = worldScripts[expandDescription("[tsplugins_script_tmi]")];

    // Stops the SRS and TMI timers, if they are running.
    if (tspSRS.$readyUpdate.isRunning) tspSRS.$readyUpdate.stop();
    if (tspTMI.$readyUpdate.isRunning) tspTMI.$readyUpdate.stop();

    // Checks if cascade weapon is still within scanner range and acts accordingly.
    var srsWorking = tspSRS._statusCheck();
    var tmiWorking = tspTMI._statusCheck();
    var youAreHere = player.ship.position;
    var outThere = player.ship.scannerRange;
    if (this.$cascadeDangerObject.length < 1) {
        // Stops the cascade timer and restarts the MFDs, if the equipment is functional.
        this.$cascadeUpdate.stop();
        this.$cascadeDangerText = true;
        if (srsWorking) tspSRS._statusUpdate(true, 3);
        if (tmiWorking) tspTMI._statusUpdate(true, 3);
    }
        else if (!this.$cascadeDangerObject[0].isValid || youAreHere.distanceTo(this.$cascadeDangerObject[0]) > outThere) {
            // Removes the cascade weapon entry from the array.
            var removeCascade = this.$cascadeDangerObject.shift();
        }
            else {
                // Calls the function that sets the string for the MFDs and updates them, if TSP MFDs are available.
                if (srsWorking || tmiWorking) this._cascadeWeaponMfd(srsWorking, tmiWorking, youAreHere);
            }

}


//
// Sets the string for cascade weapon alert, passes the update to the MFDs of functional TSP equipment and plays warning sound.
//
// This function is called by _cascadeWeaponAlert function.
//
// sayThisSRS argument is a boolean that indicates whether or not the SRS is functioning.
// sayThisTMI argument is a boolean that indicates whether or not the TMI is functioning.
// youAreHere argument is the vector position of the player ship.
//
this._cascadeWeaponMfd = function(sayThisSRS, sayThisTMI, youAreHere) {

    // Sets the string for the danger text lines and switches the boolean for the next update.
    var dangerLines = "";
    if (this.$cascadeDangerText) {
        dangerLines = expandDescription("[tsplugins_cascade_danger_text]");
        this.$cascadeDangerText = false;
    }
        else {
            this.$cascadeDangerText = true;
        }

    // Gets the distance between the player ship and the weapon in kilometres.
    var rawDistance = youAreHere.distanceTo(this.$cascadeDangerObject[0]);
    rawDistance /= 1000;
    var fineDistance = "" + rawDistance;
    fineDistance = fineDistance.substring(0, 4);

    // Sets the string for the MFD update.
    var sayThis1 = dangerLines + "\n" + "\n" + "\n";
    var sayThis2 = expandDescription("[tsplugins_cascade_word1]") + "\n" + "\n";
    var sayThis3 = expandDescription("[tsplugins_cascade_word2]") + "\n" + "\n";
    var sayThis4 = "                        " + fineDistance + "km" + "\n" + "\n";
    var sayThis5 = dangerLines;
    var sayThisMfd = sayThis1 + sayThis2 + sayThis3 + sayThis4 + sayThis5;

    // Assigns the string to the functional TSP MFDs and plays the warning sound.
    if (sayThisSRS) this._setMfd(worldScripts[expandDescription("[tsplugins_script_srs]")].$mfdKey, sayThisMfd);
    if (sayThisTMI) this._setMfd(worldScripts[expandDescription("[tsplugins_script_tmi]")].$mfdKey, sayThisMfd);
    this.$cascadeUpdateSound.play();

}


//
//
// Event handlers. See Oolite Javascript Reference pages on Elite Wiki for more details.
//
//


this.startUp = function() {

    // Puts equipment keys in the global array of the sell_equipment world script, if available.
    // The sell_equipment world script is found in the Sell Equipment expansion pack by Commander McLane.
    if (worldScripts["sell_equipment"])
        var x = worldScripts["sell_equipment"].equipment_key.push(
            expandDescription("[tsplugins_eqkey_srs]"), expandDescription("[tsplugins_eqkey_tmi]"),
            expandDescription("[tsplugins_eqkey_tbm]")
        );

    var tspSRS = worldScripts[expandDescription("[tsplugins_script_srs]")];
    var tspTMI = worldScripts[expandDescription("[tsplugins_script_tmi]")];

    // Checks if the equipment is on the player ship and sets the availability status
    if (
        player.ship.equipmentStatus(expandDescription("[tsplugins_eqkey_srs]")) === "EQUIPMENT_OK" ||
        player.ship.equipmentStatus(expandDescription("[tsplugins_eqkey_srs]")) === "EQUIPMENT_DAMAGED"
    ) tspSRS.$onShip = true;
    if (
        player.ship.equipmentStatus(expandDescription("[tsplugins_eqkey_tmi]")) === "EQUIPMENT_OK" ||
        player.ship.equipmentStatus(expandDescription("[tsplugins_eqkey_tmi]")) === "EQUIPMENT_DAMAGED"
    ) tspTMI.$onShip = true;

    // Stops the timers that update the MFDs.
    if (tspSRS.$readyUpdate.isRunning) tspSRS.$readyUpdate.stop();
    if (tspTMI.$readyUpdate.isRunning) tspTMI.$readyUpdate.stop();
    if (this.$cascadeUpdate.isRunning) this.$cascadeUpdate.stop();

    // Initialises the TMI memory bank arrays.
    worldScripts[expandDescription("[tsplugins_script_fs]")]._tmiReset();

}


this.shipWillDockWithStation = function(station) {
    var tspSRS = worldScripts[expandDescription("[tsplugins_script_srs]")];
    var tspTMI = worldScripts[expandDescription("[tsplugins_script_tmi]")];

    // Deactivates the equipment and informs the player on the equipment MFDs, if functional.
    var srsWorking = tspSRS._statusCheck();
    var tmiWorking = tspTMI._statusCheck();
    if (srsWorking) {
        tspSRS.$ready = false;
        tspSRS.$readyUpdate.stop();
        this._setMfd(tspSRS.$mfdKey, expandDescription("[tsplugins_title_srs] [tsplugins_mfd_status_off]"));
    }
        else {
        }
    if (tmiWorking) {
        tspTMI.$ready = false;
        tspTMI.$readyUpdate.stop();
        worldScripts[expandDescription("[tsplugins_script_fs]")]._tmiReset();
        this._setMfd(tspTMI.$mfdKey, expandDescription("[tsplugins_title_tmi] [tsplugins_mfd_status_off]"));
    }
        else {
        }
    this.$tbmReady = false;
    this.$tbmTargetMode = 7;

}


this.shipLaunchedFromStation = function(station) {
    var tspSRS = worldScripts[expandDescription("[tsplugins_script_srs]")];
    var tspTMI = worldScripts[expandDescription("[tsplugins_script_tmi]")];

    // Activates the equipment and informs the player on the equipment MFDs, if functional.
    var srsWorking = tspSRS._statusCheck();
    var tmiWorking = tspTMI._statusCheck();
    if (srsWorking) tspSRS._statusUpdate(true, 3);
    if (tmiWorking) tspTMI._statusUpdate(true, 3);
    if (player.ship.equipmentStatus(expandDescription("[tsplugins_eqkey_tbm]")) === "EQUIPMENT_OK") this.$tbmReady = true;

}


this.shipWillEnterWitchspace = function(cause, destination) {
    var tspSRS = worldScripts[expandDescription("[tsplugins_script_srs]")];
    var tspTMI = worldScripts[expandDescription("[tsplugins_script_tmi]")];

    // Deactivates the equipment temporarily and informs the player on the equipment MFDs, if functional.
    var srsWorking = tspSRS._statusCheck();
    var tmiWorking = tspTMI._statusCheck();
    if (srsWorking) tspSRS._statusUpdate(false, 3);
    if (tmiWorking) {
        tspTMI._statusUpdate(false, 3);
        worldScripts[expandDescription("[tsplugins_script_fs]")]._tmiReset();
    }
        else {
        }
    this.$tbmReady = false;

}


this.shipExitedWitchspace = function() {
    var tspSRS = worldScripts[expandDescription("[tsplugins_script_srs]")];
    var tspTMI = worldScripts[expandDescription("[tsplugins_script_tmi]")];

    // Restores the equipment and informs the player on the equipment MFDs, if functional.
    var srsWorking = tspSRS._statusCheck();
    var tmiWorking = tspTMI._statusCheck();
    if (srsWorking) tspSRS._statusUpdate(true, 3);
    if (tmiWorking) tspTMI._statusUpdate(true, 3);
    if (player.ship.equipmentStatus(expandDescription("[tsplugins_eqkey_tbm]")) === "EQUIPMENT_OK") this.$tbmReady = true;

}


this.equipmentAdded = function(equipmentKey) {

    // Sets the availability status to true for added equipment.
    if (equipmentKey === expandDescription("[tsplugins_eqkey_srs]")) worldScripts[expandDescription("[tsplugins_script_srs]")].$onShip = true;
    if (equipmentKey === expandDescription("[tsplugins_eqkey_tmi]")) worldScripts[expandDescription("[tsplugins_script_tmi]")].$onShip = true;

    if (equipmentKey === expandDescription("[tsplugins_eqkey_tbm]")) this.$tbmReady = true;    // just in case it's added in-flight

}


this.equipmentDamaged = function(equipment) {

    // Informs the player that the equipment is damaged on the equipment MFDs.
    if (equipment === expandDescription("[tsplugins_eqkey_srs]") || equipment === "EQ_SCANNER_SHOW_MISSILE_TARGET")
        var srsWillFail = worldScripts[expandDescription("[tsplugins_script_srs]")]._statusCheck();
    if (equipment === expandDescription("[tsplugins_eqkey_tmi]") || equipment === "EQ_TARGET_MEMORY") {
        var tmiWillFail = worldScripts[expandDescription("[tsplugins_script_tmi]")]._statusCheck();
        worldScripts[expandDescription("[tsplugins_script_fs]")]._tmiReset();
    }
        else {
        }
    if (equipment === expandDescription("[tsplugins_eqkey_tbm]")) this.$tbmReady = false;

}


this.equipmentRemoved = function(equipmentKey) {
    var tspSRS = worldScripts[expandDescription("[tsplugins_script_srs]")];
    var tspTMI = worldScripts[expandDescription("[tsplugins_script_tmi]")];

    // Sets the availability status to false and switches off MFDs for removed equipment.
    if (equipmentKey === expandDescription("[tsplugins_eqkey_srs]")) {
        tspSRS.$onShip = false;
        this._setMfd(tspSRS.$mfdKey, null);
    }
        else {
        }
    if (equipmentKey === expandDescription("[tsplugins_eqkey_tmi]")) {
        tspTMI.$onShip = false;
        this._setMfd(tspTMI.$mfdKey, null);
        worldScripts[expandDescription("[tsplugins_script_fs]")]._tmiReset();
    }
        else {
        }

    if (equipmentKey === expandDescription("[tsplugins_eqkey_tbm]")) this.$tbmReady = false;    // just in case it's removed in-flight

}


this.equipmentRepaired = function(equipment) {
    // This code is for in-flight repairs only, which is possible with some expansion packs.

    // Informs the player if the equipment is restored on the equipment MFDs.
    if (equipment === expandDescription("[tsplugins_eqkey_srs]") || equipment === "EQ_SCANNER_SHOW_MISSILE_TARGET") {
        var srsWillWork = false;
        var tspSRS = worldScripts[expandDescription("[tsplugins_script_srs]")];
        if (!player.ship.docked) srsWillWork = tspSRS._statusCheck();
        if (srsWillWork) tspSRS._statusUpdate(true, 3);
    }
        else {
        }
    if (equipment === expandDescription("[tsplugins_eqkey_tmi]") || equipment === "EQ_TARGET_MEMORY") {
        var tmiWillWork = false;
        var tspTMI = worldScripts[expandDescription("[tsplugins_script_tmi]")];
        if (!player.ship.docked) tmiWillWork = tspTMI._statusCheck();
        if (tmiWillWork) tspTMI._statusUpdate(true, 3);
    }
        else {
        }

    if (equipment === expandDescription("[tsplugins_eqkey_tbm]") && !player.ship.docked) this.$tbmReady = true;

}


this.playerChangedPrimedEquipment = function(equipmentKey) {
    // Sets TBM to Total mode, because it's a reliable way to indicate that the TBM can access the SRS MFD.
    if (equipmentKey === expandDescription("[tsplugins_eqkey_tbm]")) {
        this.$tbmTargetMode = 7;
        worldScripts[expandDescription("[tsplugins_script_srs]")]._setMfdString();
    }
}


this.cascadeWeaponDetected = function(weapon) {
    // Initiates cascade weapon alert.
    var putThatHere = this.$cascadeDangerObject.push(weapon);
    this.$cascadeUpdateSound.sound = "warning.ogg";
    if (!this.$cascadeUpdate.isRunning) this.$cascadeUpdate.start();
}


this.shipTargetAcquired = function(target) {
    // Passes new target to the TMI, if functional.
    // Wormholes are not passed to the TMI because the Target System Memory Expansion ignores them.
    // The functions to update the TMI MFD are called by the TBM when it's active, hence the condition at the end,
    // which prevents excessive updates to the MFD.
    //
    var tspTMI = worldScripts[expandDescription("[tsplugins_script_tmi]")];
    var tspFS = worldScripts[expandDescription("[tsplugins_script_fs]")];
    var tmiWorking = tspTMI._statusCheck();
    if (tmiWorking && !target.isWormhole) tspFS._tmiBankThisOrNot(target);
    if (tmiWorking && !this.$tbmActive) {
        tspFS._tmiGroupInRange();
        tspTMI._setMfdString();
    }
        else {
        }
}

Scripts/target-system-plugins_srs.js
"use strict";

this.name           = "TargetSystemPlugins_ShortRangeSnapshot";
this.author         = "Nicholas Menchise";
this.copyright      = "(C) 2019 Nicholas Menchise";
this.licence        = "CC BY-NC-SA 4.0";

//
// This world script includes the instructions for the SRS MFD strings and checking the equipment status.
//


// Whether or not the equipment is on the player ship
this.$onShip = false;

// Whether or not the equipment is functioning
this.$ready = false;

// Whether or not MFD updates should be suspended to prevent tip strings from changing every second
this.$mfdSuspend = false;

// Calls the function that updates the equipment MFD every second, if the equipment is functioning
this.$readyUpdate = new Timer(this, function() { this._timedUpdate(); }, 0, 1);

// Stores the MFD key for the equipment
this.$mfdKey = "tsplugins_srs";

// Stores the equipment's target tallies from the last timed update
//
// This is stored as a global variable so that the target mode selection can be updated independently on the MFD.
// The tallies should be stored in the following order:
// Hostile, Outlaw, Neutral, State, Cargo, Rock, Other, Total, Thargoids, Escape Capsules, Splinters.
//
this.$tallies = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];


//
// Checks the status of the equipment and its dependencies. Returns a boolean.
//
// This function is called by _timedUpdate function. It is also called by _statusCheck function in equipment
// script TargetSystemPlugins_TargetBankingModule. It is also called by event handlers in world script
// TargetSystemPlugins_SharedHandlers. It is also called by _cascadeWeaponAlert function in world script
// TargetSystemPlugins_SharedHandlers.
//
// A negative status (false) will trigger a function call that results in a status update.
// An integer is calculated and passed in the same function call to provide more specific
// information in the status update.
//
this._statusCheck = function() {
    var dependsValue = 0;
    var dependsResult = false;

    if (player.ship.equipmentStatus(expandDescription("[tsplugins_eqkey_srs]")) === "EQUIPMENT_OK") dependsValue = 2;
    if (player.ship.equipmentStatus("EQ_SCANNER_SHOW_MISSILE_TARGET") === "EQUIPMENT_OK") dependsValue++;

    if (dependsValue == 3) dependsResult = true;
    if (this.$ready && dependsValue < 3) this._statusUpdate(false, dependsValue);

    return dependsResult;
}


//
// Updates the status of the equipment and informs the player when it's activated, disabled or damaged.
//
// This function is called by _statusCheck function. It is also called by event handlers in the world
// script TargetSystemPlugins_SharedHandlers.
//
// isWorking argument stores a boolean that indicates if the equipment is enabled.
//
// whatHappened argument stores an integer that indicates the cause of the status change.
// A value of 3 means that the equipment is fine, but something else may be stopping it, e.g. witchspace tunnel.
// A value of 2 means that the dependency check has failed.
// A value less than 2 means that the equipment is damaged.
//
this._statusUpdate = function(isWorking, whatHappened) {

    // Changes the ready status and stops the timer, if necessary. Stops the function if equipment is off the ship.
    if (this.$ready !== isWorking) this.$ready = isWorking;
    if (!isWorking && this.$readyUpdate.isRunning) this.$readyUpdate.stop();
    if (!this.$onShip) return;

    // Sets the string for the status update to the MFD.
    var theseLines = "";    // stores the MFD update string.
    if (isWorking) theseLines = expandDescription("[tsplugins_ready_srs]");
    if (!isWorking && whatHappened == 3) theseLines = expandDescription("[tsplugins_title_srs][tsplugins_mfd_status_witchspace]");
    if (!isWorking && whatHappened == 2) theseLines = expandDescription("[tsplugins_disabled1_srs]");
    if (!isWorking && whatHappened < 2) theseLines = expandDescription("[tsplugins_broken_srs]");

    // Calls the MFD setting function.
    worldScripts[expandDescription("[tsplugins_script_sh]")]._setMfd(this.$mfdKey, theseLines);

    // Resumes the timer that calls regular updates to the MFD, if the equipment has become functional.
    // The two second delay ensures that the player can see the status update when the equipment is activated.
    //
    if (isWorking && !this.$readyUpdate.isRunning) {
        this.$readyUpdate.nextTime = clock.absoluteSeconds + 2;
        this.$readyUpdate.start();
        this.$mfdSuspend = false;    // fixed a bug where the MFD could get stuck on startup instead of displaying tip
    }
        else {
        }
}


//
// Updates the equipment MFD at regular intervals, if the equipment is functional.
//
// This function is called by $readyUpdate timer.
//
this._timedUpdate = function() {
    var isWorking = this._statusCheck();
    if (!isWorking) return;
    this._updateTallies();
    if (this.$tallies[7] > 0) this.$mfdSuspend = false;
    if (!this.$mfdSuspend) this._setMfdString();
}


//
// Calculates and sets the integers in the tallies array.
//
// This function is called by _timedUpdate function.
//
this._updateTallies = function() {

    // Sets the entries for additional tallies to zero to prevent outdated values from showing up.
    for (var n = 8; n < this.$tallies.length; n++) this.$tallies[n] = 0;

    // Updates the target data within the player ship's scanner range.
    var tspFS = worldScripts[expandDescription("[tsplugins_script_fs]")];
    tspFS._getTargetsTotal();
    tspFS._getTargetsSorted();

    // Sets the tallies for the target modes.
    this.$tallies[0] = tspFS.$targetsHostile.length;
    this.$tallies[1] = tspFS.$targetsOutlaw.length;
    this.$tallies[2] = tspFS.$targetsNeutral.length;
    this.$tallies[3] = tspFS.$targetsState.length;
    this.$tallies[4] = tspFS.$targetsCargo.length;
    this.$tallies[5] = tspFS.$targetsRock.length;
    this.$tallies[6] = tspFS.$targetsOther.length;
    this.$tallies[7] = tspFS.$targetsTotal.length;

    // Sets additional tallies, if applicable.
    if (this.$tallies[1] > 0) this.$tallies[8] = tspFS._getTally("thargoid");
    if (this.$tallies[4] > 0) this.$tallies[9] = tspFS._getTally("escape");
    if (this.$tallies[5] > 0) this.$tallies[10] = tspFS._getTally("splinter");

}


//
// Sets the string that will be sent to the equipment MFD and calls the MFD update.
//
// This function is called by _timedUpdate function. It is also called by event handlers in equipment
// script TargetSystemPlugins_TargetBankingModule. It is also called by playerChangedPrimedEquipment
// event handler in world script TargetSystemPlugins_SharedHandlers.
//
this._setMfdString = function() {
    var sayThisMfd = "";
    var tspSH = worldScripts[expandDescription("[tsplugins_script_sh]")];

    // Sets the snapshot string when targets are found.
    if (this.$tallies[7] > 0) {

        // Sets the first line, which includes the title and timestamp. The second line is blank.
        var snapshotTime = "";
        if (clock.isAdjusting) snapshotTime = "Ad:ju:st";
        if (!clock.isAdjusting) snapshotTime = clock.clockStringForTime(clock.adjustedSeconds).substring(8);
        var lineOne = expandDescription("[tsplugins_title_srs]");
        lineOne += "        " + snapshotTime + "\n" + "\n";

        // Sets the target mode selection column.
        var modeColumn = [];
        for (var n = 0; n < 8; n++) modeColumn[n] = expandDescription("[tsplugins_mfd_unselected]");
        var tmiWorking = worldScripts[expandDescription("[tsplugins_script_tmi]")]._statusCheck();
        if (
            tmiWorking && tspSH.$tbmReady && this.$tallies[tspSH.$tbmTargetMode] > 0 &&
            player.ship.primedEquipment === expandDescription("[tsplugins_eqkey_tbm]")
        ) modeColumn[tspSH.$tbmTargetMode] = expandDescription("[tsplugins_mfd_selected]");

        // Sets the target tally lines and puts them next to the target mode selection column.
        var tallyText = this._getTallyList();
        var linesEight = "";
        for (var n = 0; n < modeColumn.length; n++) {
            linesEight = linesEight + modeColumn[n] + tallyText[n];
            if (n < 7) linesEight += "\n";
        }

        sayThisMfd = lineOne + linesEight;
    }
        else {
            // Sets the SRS Tip string when no targets are found.
            sayThisMfd = expandDescription("[tsplugins_mfd_tip_srs]");
            this.$mfdSuspend = true;
        }

    // Calls the MFD setting function.
    tspSH._setMfd(this.$mfdKey, sayThisMfd);
}


//
// Returns an array of strings for the target tally text.
//
// This function is called by _setMfdString function.
//
this._getTallyList = function() {
    var tallyList = ["", "", "", "", "", "", "", ""];
    var tallyLabels = [
        expandDescription("[tsplugins_title_mode0]"), expandDescription("[tsplugins_title_mode1]"),
        expandDescription("[tsplugins_title_mode2]"), expandDescription("[tsplugins_title_mode3]"),
        expandDescription("[tsplugins_title_mode4]"), expandDescription("[tsplugins_title_mode5]"),
        expandDescription("[tsplugins_title_mode6]"), expandDescription("[tsplugins_title_mode7]")
    ];

    // Sets the left column of the tally text. Tallies with no targets are not displayed.
    for (var n = 0; n < tallyList.length; n++) {
        if (this.$tallies[n] > 0) tallyList[n] = tallyLabels[n] + ": " + this.$tallies[n];
    }

    // Appends additional tallies, if applicable.
    if (this.$tallies[8] > 0) tallyList[1] += expandDescription("[tsplugins_mfd_gap]") + this.$tallies[8] + " Thargoid ship";
    if (this.$tallies[8] > 1) tallyList[1] += "s";
    if (this.$tallies[9] > 0) tallyList[4] += expandDescription("[tsplugins_mfd_gap]") + this.$tallies[9] + " escape pod";
    if (this.$tallies[9] > 1) tallyList[4] += "s";
    if (this.$tallies[10] > 0) tallyList[5] += expandDescription("[tsplugins_mfd_gap]") + this.$tallies[10] + " splinter";
    if (this.$tallies[10] > 1) tallyList[5] += "s";

    return tallyList;
}

Scripts/target-system-plugins_tbm.js
"use strict";

this.name           = "TargetSystemPlugins_TargetBankingModule";
this.author         = "Nicholas Menchise";
this.copyright      = "(C) 2019 Nicholas Menchise";
this.licence        = "CC BY-NC-SA 4.0";

//
// This equipment script includes the instructions for the TBM's "mode" and
// "activated" event handlers and their support functions, e.g. the status check.
//



// Stores an integer that symbolises the state of the equipment, which enables the HUD
// console messages to provide more specific explanations when the controls are disabled.
//
// There are four valid integer values. The values are set by _statusCheck function.
// Zero means that a dependency was unavailable on the last two status checks.
// One means that dependencies are available, but the equipment is temporarily disabled by a witchspace tunnel.
// Two means that a dependency was unavailable on the last status check.
// Three means that all equipment is fine, but the controls could still be disabled if there are no targets in SRS.
//
this.$statusInfo = 0;


//
// Checks the status of the equipment and its dependencies. Returns a boolean.
//
// This function is called by the .mode and .activated event handlers.
//
// An integer is calculated and set to the status info variable. See the variable comments for details.
//
// The equipment's ready status variable in world script TargetSystemPlugins_SharedHandlers is set to false if a
// dependency is unavailable.
//
this._statusCheck = function() {
    var dependsValue = 0;
    var dependsResult = false;
    var tspSRS = worldScripts[expandDescription("[tsplugins_script_srs]")];
    var tspSH = worldScripts[expandDescription("[tsplugins_script_sh]")];
    var srsWorking = tspSRS._statusCheck();
    var tmiWorking = worldScripts[expandDescription("[tsplugins_script_tmi]")]._statusCheck();
    var tbmWorking = tspSH.$tbmReady;

    // The condition below refers to the tbmReady variable instead of the equipmentStatus method of PlayerShip object,
    // because it applies to situations where the equipment is temporarily disabled. The equipment script does nothing
    // if the equipment is damaged, because it's not primeable.
    //
    if (tbmWorking) dependsValue = 2;
    if (srsWorking && tmiWorking) dependsValue++;

    if (dependsValue == 3) dependsResult = true;
    if (tbmWorking && dependsValue < 3) tspSH.$tbmReady = false;

    // Disables the equipment controls if no targets are in SRS arrays.
    // The tbmReady variable is not changed, because it's unnecessary.
    //
    if (tspSRS.$tallies[7] < 1) dependsResult = false;

    this.$statusInfo = dependsValue;
    return dependsResult;
}


//
// Informs the player that the equipment is disabled and why. The message is sent to the HUD console.
//
// This function is called by the .mode and .activated event handlers.
//
// This should only be called after a status check returns a negative value.
//
this._statusMessage = function() {
    var sendThis = expandDescription("[tsplugins_console_tbm_disabled_alert] ");
    if (this.$statusInfo == 1) sendThis += expandDescription("[tsplugins_console_tbm_disabled_why1]");
    if (this.$statusInfo == 2 || this.$statusInfo == 0) sendThis += expandDescription("[tsplugins_console_tbm_disabled_why2]");
    if (this.$statusInfo == 3) sendThis += expandDescription("[tsplugins_console_tbm_disabled_why3]");
    player.consoleMessage(sendThis);
}


//
// Returns the correct array of Entities for target banking.
//
// This function is called by .activated event handler.
//
// targetMode argument is the equipment's current target mode.
//
this._bankTheseTargets = function(targetMode) {
    var theseTargets = [];
    var tspFS = worldScripts[expandDescription("[tsplugins_script_fs]")];
    if (targetMode == 0) theseTargets = tspFS.$targetsHostile;
    if (targetMode == 1) theseTargets = tspFS.$targetsOutlaw;
    if (targetMode == 2) theseTargets = tspFS.$targetsNeutral;
    if (targetMode == 3) theseTargets = tspFS.$targetsState;
    if (targetMode == 4) theseTargets = tspFS.$targetsCargo;
    if (targetMode == 5) theseTargets = tspFS.$targetsRock;
    if (targetMode == 6) theseTargets = tspFS.$targetsOther;
    if (targetMode == 7) theseTargets = tspFS.$targetsTotal;
    return theseTargets;
}


//
// Event handlers. Specified on Elite wiki page Oolite Javascript Reference: Equipment scripts.
//


this.mode = function() {

    // Checks if the equipment is working.
    var tbmWorking = this._statusCheck();

    if (tbmWorking) {

        // Finds and sets the next valid target mode.
        var tspSH = worldScripts[expandDescription("[tsplugins_script_sh]")];
        var tspSRS = worldScripts[expandDescription("[tsplugins_script_srs]")];
        tspSH.$tbmTargetMode++;
        if (tspSH.$tbmTargetMode > 7) tspSH.$tbmTargetMode = 0;
        for (var n = tspSH.$tbmTargetMode; n < 8; n++) {
            if (tspSRS.$tallies[n] > 0) break;
            tspSH.$tbmTargetMode++;
        }

        // Updates the SRS MFD with the next valid target mode.
        tspSRS._setMfdString();

    }
        else {
            // Informs the player that the equipment is not working.
            this._statusMessage();
        }

}


this.activated = function() {

    var tspSH = worldScripts[expandDescription("[tsplugins_script_sh]")];
    var tspSRS = worldScripts[expandDescription("[tsplugins_script_srs]")];

    // Checks if the equipment is working.
    var tbmWorking = this._statusCheck();

    if (!tbmWorking) {
        // Informs the player that the equipment is not working.
        this._statusMessage();
    }
        else if (tspSRS.$tallies[tspSH.$tbmTargetMode] < 1) {
            // Informs the player that there are no targets in the selected mode and switches to Total mode.
            player.consoleMessage(expandDescription("[tsplugins_console_tbm_none_mode]"));
            tspSH.$tbmTargetMode = 7;
            tspSRS._setMfdString();
        }
            else {

                // Assigns targets in a valid mode to player ship.
                // The maximum number of targets is 8, because that's the limit of the TMI MFD.
                // The loop works backwards so that the last new target is the nearest one to the player ship.
                // The loop checks if the target is still around in case something happened to it after the last SRS update.
                // The loop avoids passing targets already in TMI memory, because doing so would be a waste of cycles.
                // The tbmActive state prevents the shipTargetAcquired event handler from updating the TMI MFD.
                //
                tspSH.$tbmActive = true;
                var tspFS = worldScripts[expandDescription("[tsplugins_script_fs]")];
                var howMany = tspSRS.$tallies[tspSH.$tbmTargetMode];
                if (howMany > 8) howMany = 8;
                var whichTargets = this._bankTheseTargets(tspSH.$tbmTargetMode);
                for (var n = 1; n <= howMany; n++) {
                    var thisTarget = whichTargets[howMany - n];
                    var matchFound = tspFS._tmiCheckForMatch(thisTarget, tspFS.$targetsMemoryData);
                    if (thisTarget.isInSpace && !matchFound) player.ship.target = thisTarget;
                }

                // Updates the TMI MFD.
                tspFS._tmiGroupInRange();
                worldScripts[expandDescription("[tsplugins_script_tmi]")]._setMfdString();
                tspSH.$tbmActive = false;

            }

}

Scripts/target-system-plugins_tmi.js
"use strict";

this.name           = "TargetSystemPlugins_TargetMemoryInterface";
this.author         = "Nicholas Menchise";
this.copyright      = "(C) 2019 Nicholas Menchise";
this.licence        = "CC BY-NC-SA 4.0";

//
// This world script includes the instructions for the TMI MFD strings and checking the equipment status.
//


// Whether or not the equipment is on the player ship
this.$onShip = false;

// Whether or not the equipment is functioning
this.$ready = false;

// Whether or not MFD updates should be suspended to prevent tip strings from changing every second
this.$mfdSuspend = false;

// Calls the function that updates the equipment MFD at regular intervals, if the equipment is functioning
this.$readyUpdate = new Timer(this, function() { this._timedUpdate(); }, 0, 1);

// Stores the MFD key for the equipment
this.$mfdKey = "tsplugins_tmi";


//
// Checks the status of the equipment and its dependencies. Returns a boolean.
//
// This function is called by _timedUpdate function. It is also called by _setMfdString function in
// world script TargetSystemPlugins_ShortRangeSnapshot. It is also called by _statusCheck function
// in equipment script Target SystemPlugins_TargetBankingModule. It is also called by event handlers
// in world script TargetSystemPlugins_SharedHandlers. It is also called by _cascadeWeaponAlert function
// in world script TargetSystemPlugins_SharedHandlers.
//
// A negative status (false) will trigger a function call that results in a status update.
// An integer is calculated and passed in the same function call to provide more specific
// information in the status update.
//
this._statusCheck = function() {
    var dependsValue = 0;
    var dependsResult = false;

    if (player.ship.equipmentStatus(expandDescription("[tsplugins_eqkey_tmi]")) === "EQUIPMENT_OK") dependsValue = 2;
    if (player.ship.equipmentStatus("EQ_TARGET_MEMORY") === "EQUIPMENT_OK") dependsValue++;

    if (dependsValue == 3) dependsResult = true;
    if (this.$ready && dependsValue < 3) this._statusUpdate(false, dependsValue);

    return dependsResult;
}


//
// Updates the status of the equipment and informs the player when it's activated, disabled or damaged.
//
// This function is called by _statusCheck function. It is also called by event handlers in the world
// script TargetSystemPlugins_SharedHandlers.
//
// isWorking argument stores a boolean that indicates if the equipment is enabled.
//
// whatHappened argument stores an integer that indicates the cause of the status change.
// A value of 3 means that the equipment is fine, but something else may be stopping it, e.g. witchspace tunnel.
// A value of 2 means that the dependency check has failed.
// A value less than 2 means that the equipment is damaged.
//
this._statusUpdate = function(isWorking, whatHappened) {

    // Changes the ready status and stops the timer, if necessary. Stops the function if equipment is off the ship.
    if (this.$ready !== isWorking) this.$ready = isWorking;
    if (!isWorking && this.$readyUpdate.isRunning) this.$readyUpdate.stop();
    if (!this.$onShip) return;

    // Sets the string for the status update to the MFD.
    var theseLines = "";    // stores the MFD update string.
    if (isWorking) theseLines = expandDescription("[tsplugins_ready_tmi]");
    if (!isWorking && whatHappened == 3) theseLines = expandDescription("[tsplugins_title_tmi][tsplugins_mfd_status_witchspace]");
    if (!isWorking && whatHappened == 2) theseLines = expandDescription("[tsplugins_disabled1_tmi]");
    if (!isWorking && whatHappened < 2) theseLines = expandDescription("[tsplugins_broken_tmi]");

    // Calls the MFD setting function.
    worldScripts[expandDescription("[tsplugins_script_sh]")]._setMfd(this.$mfdKey, theseLines);

    // Resumes the timer that calls regular updates to the MFD, if the equipment has become functional.
    // The two second delay ensures that the player can see the status update when the equipment is activated.
    //
    if (isWorking && !this.$readyUpdate.isRunning) {
        this.$readyUpdate.nextTime = clock.absoluteSeconds + 2;
        this.$readyUpdate.start();
        this.$mfdSuspend = false;    // fixed a bug where the MFD was stuck on startup instead of displaying tip
    }
        else {
        }
}


//
// Updates the equipment MFD at regular intervals, if the equipment is functional and targets are within scanner range.
//
// This function is called by $readyUpdate timer.
//
this._timedUpdate = function() {
    var isWorking = this._statusCheck();
    if (!isWorking) return;
    var tspFS = worldScripts[expandDescription("[tsplugins_script_fs]")];
    tspFS._tmiGroupInRange();
    if (tspFS.$targetsMemoryInRange.length > 0) this.$mfdSuspend = false;
    var srsWorking = worldScripts[expandDescription("[tsplugins_script_srs]")]._statusCheck();
    if (srsWorking) tspFS._tmiSignUpdate();
    if (!this.$mfdSuspend) this._setMfdString();
}


//
// Sets the string that will be sent to the equipment MFD and calls the MFD update.
//
// This function is called by _timedUpdate function.
// It is also called by shipTargetAcquired event handler in world script TargetSystemPlugins_SharedHandlers.
// It is also called by .activated event handler in equipment script TargetSystemPlugins_TargetBankingModule.
//
// This should only be called shortly after _tmiGroupInRange function in world script
// TargetSystemPlugins_FilterSort is called, if not immediately afterwards.
//
this._setMfdString = function() {
    var sayThisMfd = "";
    var srsWorking = worldScripts[expandDescription("[tsplugins_script_srs]")]._statusCheck();
    var tspFS = worldScripts[expandDescription("[tsplugins_script_fs]")];
    var targetsIndex = tspFS.$targetsMemoryInRange;

    if (targetsIndex.length > 0) {
    // Sets the target list string when targets are found.

        // Sets the first line, which includes the title and number of targets in memory that are within scanner range.
        // The second line is blank.
        //
        var lineOne = expandDescription("[tsplugins_title_tmi]") + "                " + targetsIndex.length + "\n" + "\n";

        // Gets the columns for the remaining lines.
        var selectColumn = this._getSelectionColumn(tspFS.$targetsMemoryData, targetsIndex);
        var symbolColumn = this._getCategoryColumn(tspFS.$targetsMemorySigns, targetsIndex, srsWorking);
        var namesColumn = this._getTargetNamesColumn(tspFS.$targetsMemoryData, targetsIndex);

        // Sets up the columns.
        var linesEight = "";
        for (var n = 0; n < 8; n++) {
            linesEight = linesEight + selectColumn[n] + symbolColumn[n] + "  " + namesColumn[n];
            if (n < 7) linesEight += "\n";
        }

        sayThisMfd = lineOne + linesEight;
    }
        else if (srsWorking) {
            // Sets the TMI Guide string when no targets are found. The type of string depends on the SRS status.
            sayThisMfd = expandDescription("[tsplugins_mfd_tip1_tmi]");
            this.$mfdSuspend = true;
        }
            else {
                sayThisMfd = expandDescription("[tsplugins_mfd_tip2_tmi]");
                this.$mfdSuspend = true;
            }

    // Calls the MFD setting function.
    worldScripts[expandDescription("[tsplugins_script_sh]")]._setMfd(this.$mfdKey, sayThisMfd);
}


//
// Returns an array of strings that define the target selection column of the TMI MFD.
//
// This function is called by _setMfdString function.
//
// targetsInHistory argument is an array of objects selected with the player ship's targeting system recently.
// targetsInRange argument is an array of index positions of objects in targetsInHistory that are within the
// player ship's scanner range.
//
this._getSelectionColumn = function(targetsInHistory, targetsInRange) {
    var thisColumn = [];
    for (var n = 0; n < 8; n++) thisColumn[n] = expandDescription("[tsplugins_mfd_unselected]");
    for (var n = 0; n < targetsInRange.length; n++) {
        var thisOne = targetsInRange[n];
        if (targetsInHistory[thisOne] === player.ship.target)
            thisColumn[n] = expandDescription("[tsplugins_mfd_selected]");
    }
    return thisColumn;
}


//
// Returns an array of strings that define the target category column of the TMI MFD.
//
// This function is called by _setMfdString function.
//
// symbolsInHistory argument is an array of target category symbols corresponding to objects selected
// with the player ship's targeting system recently.
// targetsInRange argument is an array of index positions of symbolsInHistory corresponding to objects
// that are within the player ship's scanner range.
// snapshotAvailable argument is a boolean that indicates whether or not the SRS is functioning.
//
this._getCategoryColumn = function(symbolsInHistory, targetsInRange, snapshotAvailable) {
    var thisColumn = [];
    for (var n = 0; n < 8; n++) thisColumn[n] = expandDescription("[tsplugins_sign_blank]");
    for (var n = 0; n < targetsInRange.length; n++) {
        if (!snapshotAvailable) break;
        var thisOne = targetsInRange[n];
        thisColumn[n] = symbolsInHistory[thisOne];
    }
    return thisColumn;
}


//
// Returns an array of strings that define the target names column of the TMI MFD.
//
// This function is called by _setMfdString function.
//
// targetsInHistory argument is an array of objects selected with the player ship's targeting system recently.
// targetsInRange argument is an array of index positions of objects in targetsInHistory that are within the
// player ship's scanner range.
//
this._getTargetNamesColumn = function(targetsInHistory, targetsInRange) {
    var thisColumn = [];
    for (var n = 0; n < 8; n++) thisColumn[n] = "";
    for (var n = 0; n < targetsInRange.length; n++) {
        var thisOne = targetsInRange[n];
        var nameThisOne = targetsInHistory[thisOne].displayName;
        if (nameThisOne.length > 31) nameThisOne = nameThisOne.substring(0, 29) + "...";
        thisColumn[n] = nameThisOne;
    }
    return thisColumn;
}