Back to Index Page generated: Sep 15, 2026, 9:03:40 PM

Expansion Interstellar Tweaks

Content

Warnings

  1. Extreanous whitespace on key ' IST_thargoidWarship02'
  2. Unresolved dependency reference to oolite.oxp.Draco_Caeles.GenerationShips.oxz:1.6
  3. Unresolved dependency reference to oolite.oxp.Murgh.MilitaryFiasco.oxz:2.5.3
  4. Unresolved dependency reference to oolite.oxp.spara.behemoth.oxz:3.1.1
  5. Unresolved dependency reference to oolite.oxp.Draco_Caeles.GenerationShips.oxz:1.6
  6. Unresolved dependency reference to oolite.oxp.Murgh.MilitaryFiasco.oxz:2.5.3
  7. Unresolved dependency reference to oolite.oxp.spara.behemoth.oxz:3.1.1
  8. Wiki check failed: org.apache.http.conn.ConnectTimeoutException: Connect to wiki.alioth.net:443 [wiki.alioth.net/109.70.41.29] failed: Connection timed out
  9. Wiki check failed: org.apache.http.conn.ConnectTimeoutException: Connect to wiki.alioth.net:443 [wiki.alioth.net/109.70.41.29] failed: Connection timed out
  10. Wiki check failed: org.apache.http.conn.ConnectTimeoutException: Connect to wiki.alioth.net:443 [wiki.alioth.net/109.70.41.29] failed: Connection timed out
  11. Wiki check failed: org.apache.http.conn.ConnectTimeoutException: Connect to wiki.alioth.net:443 [wiki.alioth.net/109.70.41.29] failed: Connection timed out
  12. Wiki check failed: org.apache.http.conn.ConnectTimeoutException: Connect to wiki.alioth.net:443 [wiki.alioth.net/109.70.41.29] failed: Connection timed out
  13. Wiki check failed: org.apache.http.conn.ConnectTimeoutException: Connect to wiki.alioth.net:443 [wiki.alioth.net/109.70.41.29] failed: Connection timed out
  14. Wiki check failed: org.apache.http.conn.ConnectTimeoutException: Connect to wiki.alioth.net:443 [wiki.alioth.net/109.70.41.29] failed: Connection timed out
  15. Wiki check failed: org.apache.http.conn.ConnectTimeoutException: Connect to wiki.alioth.net:443 [wiki.alioth.net/109.70.41.29] failed: Connection timed out
  16. Wiki check failed: org.apache.http.conn.ConnectTimeoutException: Connect to wiki.alioth.net:443 [wiki.alioth.net/109.70.41.29] failed: Connection timed out
  17. Wiki check failed: org.apache.http.conn.ConnectTimeoutException: Connect to wiki.alioth.net:443 [wiki.alioth.net/109.70.41.29] failed: Connection timed out
  18. Wiki check failed: org.apache.http.conn.ConnectTimeoutException: Connect to wiki.alioth.net:443 [wiki.alioth.net/109.70.41.29] failed: Connection timed out
  19. Wiki check failed: org.apache.http.conn.ConnectTimeoutException: Connect to wiki.alioth.net:443 [wiki.alioth.net/109.70.41.29] failed: Connection timed out
  20. Wiki check failed: org.apache.http.conn.ConnectTimeoutException: Connect to wiki.alioth.net:443 [wiki.alioth.net/109.70.41.29] failed: Connection timed out
  21. Wiki check failed: org.apache.http.conn.ConnectTimeoutException: Connect to wiki.alioth.net:443 [wiki.alioth.net/109.70.41.29] failed: Connection timed out
  22. Wiki check failed: org.apache.http.conn.ConnectTimeoutException: Connect to wiki.alioth.net:443 [wiki.alioth.net/109.70.41.29] failed: Connection timed out
  23. Wiki check failed: org.apache.http.conn.ConnectTimeoutException: Connect to wiki.alioth.net:443 [wiki.alioth.net/109.70.41.29] failed: Connection timed out

Manifest

from Expansion Manager's OXP list from Expansion Manifest
Description Adds variety to what happens in interstellar space (and makes Frame's FuelCollector OXP more compatible with Oolite >= 1.8). Adds variety to what happens in interstellar space (and makes Frame's FuelCollector OXP more compatible with Oolite >= 1.8).
Identifier oolite.oxp.UK_Eliter.InterstellarTweaks oolite.oxp.UK_Eliter.InterstellarTweaks
Title Interstellar Tweaks Interstellar Tweaks
Category Mechanics Mechanics
Author UK_Eliter UK_Eliter
Version 8 8
Tags interstellar, ambience, systems, thargoids interstellar, ambience, systems, thargoids
Required Oolite Version
Maximum Oolite Version
Required Expansions
Optional Expansions
  • oolite.oxp.Draco_Caeles.GenerationShips.oxz:1.6
  • oolite.oxp.Murgh.MilitaryFiasco.oxz:2.5.3
  • oolite.oxp.spara.behemoth.oxz:3.1.1
  • oolite.oxp.UK_Eliter.Ferdelance_3G:7.0
  • oolite.oxp.UK_Eliter.ExtraThargoids:7.0
  • oolite.oxp.Draco_Caeles.GenerationShips.oxz:1.6
  • oolite.oxp.Murgh.MilitaryFiasco.oxz:2.5.3
  • oolite.oxp.spara.behemoth.oxz:3.1.1
  • oolite.oxp.UK_Eliter.Ferdelance_3G:7.0
  • oolite.oxp.UK_Eliter.ExtraThargoids:7.0
  • Conflict Expansions
    Information URL https://wiki.alioth.net/index.php/Interstellar_Tweaks n/a
    Download URL https://wiki.alioth.net/img_auth.php/7/7c/Oolite.oxp.UK_Eliter.InterstellarTweaks.oxz http://wiki.alioth.net/img_auth.php/7/7c/Oolite.oxp.UK_Eliter.InterstellarTweaks.oxz
    License Creative Commons Attribution - Non-Commercial - Share Alike 4.0 license Creative Commons Attribution - Non-Commercial - Share Alike 4.0 license
    File Size n/a
    Upload date 1789431384

    Relationships Diagram

    Documentation

    Also read http://wiki.alioth.net/index.php/Interstellar%20Tweaks

    Readme & License.txt

     _        _                   _        _  _             ___                  _
    | |._ _ _| |_ ___  _ _  ___ _| |_ ___ | || | ___  _ _  |_ _|_ _ _  ___  ___ | |__ ___
    | || ' | | | / ._>| '_><_-<  | | / ._>| || |<_> || '_>  | || | | |/ ._><_> || / /<_-<
    |_||_|_| |_| \___.|_|  /__/  |_| \___.|_||_|<___||_|    |_||__/_/ \___.<___||_\_\/__/
    
    
    An expansion pack by the foolishly named 'UK_Eliter' for the (comically-named) Oolite
    
    
    =====================
    CONTENTS OF THIS FILE
    =====================
    
    - Requirements
    
    - General notes [though that section is a placeholder for the online 'wiki']
    
    - License
    
    - Acknowledgements
    
    - Note for expansion pack makers
    
    
    ------------
    REQUIREMENTS
    ------------
    
    � This expansion pack requires Oolite version *1.82* or above.
    � One installs the pack via the in-game expansion pack manager.
    � Various expansion packs are recommended (but not necessary) to complement this one - see the next section of this document.
    
    
    -------------
    GENERAL NOTES
    -------------
    
    See <https://wiki.alioth.net/index.php/Interstellar_Tweaks>.
    
    
    -------
    LICENSE
    -------
    
    This expansion pack is released under the Creative Commons Attribution - Non-Commercial - Share Alike 3.0 license. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/2.0/ or send a letter to Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA. 
    
    You are free:
    
    � to copy, distribute, display, and perform the work;
    � to make derivative works. 
    
    But only under the following conditions.
    
    � Attribution: you must give the original author credit. 
    � Non-commercial use: you may not use this work for commercial purposes. 
    � Share Alike: if you alter, transform, or build upon this work, you may distribute the resulting work only under a license materially identical to this one. 
    
    Your fair use right and other rights are in no way affected by the above.
    
    
    ----------------
    ACKNOWLEDGEMENTS
    ----------------
    
    I thank the following (from some of whom I have adapted code):
    
    	Another Commander;
    	Arexack Heretic;
    	Capt. Murphy;
    	Cholmondely;
    	cim;
    	Commander McLane;
    	Dr Nil and other contributors to the 'Your Ad Here' expansion pack;
    	Draco_Caeles;
    	Duggan;
    	Eric Walch; 
    	Gimi; 
    	Norby;
    	phkb;
    	Rustem;
    	SirAdrian;
    	Solonar;
    	Switeck;
    	Thargoid.
    
    Other OOliters have contributed in smaller ways. I am grateful for that, too.
    
    
    ------------------------------
    NOTE FOR EXPANSION PACK MAKERS
    ------------------------------
    
    This expansion pack takes over Oolite's populator and re-populator functions for interstellar space. Sometimes, though, it will run Oolite's inbuilt populator together with populators from other expansion packs.
    
    Also, and partly because of the old way in which Oolite used to do population, my expansion pack populates and depopulates interstellar space in other, more hack-ish ways. And, as of version 8 of this OXP, it should now be humanely possible to actually understand my code. Or at least most of it. It is true that I wrote a few small-ish pieces of the new code with the help of artificial intelligence. Those parts, though, are among the most readable bits of the code! For, where I sought such help was, principally, in making clarificatory abstractions.
    
    
    ------ END OF FILE ------
    

    Equipment

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

    Ships

    Name
    IST_thargoidWarship02
    IST_TCAT_droneAndControllerOVERRIDE
    IST_ThargletDead
    IST_alien_cargopod
    IST_alloy
    IST_asteroid
    IST_asteroid2
    IST_asteroid2a
    IST_boulder
    IST_cargopod
    IST_derelict_Adder
    IST_derelict_AdderRusty
    IST_derelict_AdderRusty_Tionisla
    IST_derelict_Anaconda
    IST_derelict_AnacondaRusty
    IST_derelict_AnacondaRusty_Tionisla
    IST_derelict_Asp
    IST_derelict_AspRusty
    IST_derelict_AspRusty_Tionisla
    IST_derelict_Boa
    IST_derelict_BoaRusty
    IST_derelict_BoaRusty2
    IST_derelict_BoaRusty2_Tionisla
    IST_derelict_BoaRusty_Tionisla
    IST_derelict_CobraI
    IST_derelict_CobraIII
    IST_derelict_CobraIIIRusty
    IST_derelict_CobraIIIRusty_Tionisla
    IST_derelict_CobraIRusty
    IST_derelict_CobraIRusty_Tionisla
    IST_derelict_Ferdelance
    IST_derelict_Krait
    IST_derelict_KraitRusty
    IST_derelict_KraitRusty_Tionisla
    IST_derelict_MambaRusty
    IST_derelict_MambaRusty_Tionisla
    IST_derelict_Moray
    IST_derelict_MorayMED
    IST_derelict_MorayRusty
    IST_derelict_Python
    IST_derelict_PythonRusty
    IST_derelict_PythonRusty_Tionisla
    IST_derelict_Sidewinder
    IST_derelict_SidewinderRusty
    IST_derelict_Thargoid
    IST_derelict_Viper
    IST_derelict_ViperInterceptor
    IST_derelict_ViperInterceptorRusty
    IST_derelict_ViperRusty
    IST_generation-ship-cylinder
    IST_generation-ship-dock
    IST_generation-ship-frame
    IST_generation-ship-thruster
    IST_hunter01
    IST_hunter02
    IST_hunter02a
    IST_hunter03
    IST_hunter03b
    IST_hunter04
    IST_hunter05
    IST_hunter05b
    IST_hunter06
    IST_hunter07
    IST_hunter08
    IST_hunter09
    IST_hunter10
    IST_hunter11
    IST_hunter12
    IST_hunter13
    IST_hunter14
    IST_hunter15
    IST_hunter16
    IST_hunter17
    IST_hunter18
    IST_hunter19
    IST_hunter20
    IST_hunter20b
    IST_hunter21
    IST_hunter22
    IST_hunter23
    IST_hunter24
    IST_hunter25
    IST_hunter26
    IST_hunter27
    IST_hunter28
    IST_hunter29
    IST_hunter30
    IST_hunter31
    IST_hunter32
    IST_hunter33
    IST_hunter34
    IST_hunter35
    IST_lobster
    Priest of the Witchspace Lobster
    Acolyte of the Witchspace Lobster
    Acolyte of the Witchspace Lobster
    GalCop Viper (naval)
    IST_mil02
    IST_mil03
    IST_mil04
    IST_mil05
    IST_mil06
    IST_mil07
    IST_mil08
    IST_mil09
    IST_mil10
    IST_mil11
    IST_mil12
    IST_mil13
    IST_mil14
    IST_mil15
    IST_mil16
    IST_mil17
    Super-Sidewinder (Military version)
    TIE Fighter (naval)
    Star Destroyer (naval)
    IST_miner01
    IST_miner01a
    IST_miner02
    IST_miner03
    IST_miner03a
    IST_miner04
    IST_miner05
    IST_miner06
    IST_miner06a
    IST_miner6b
    IST_miner7
    IST_miner8
    IST_pirate01
    IST_pirate02
    IST_pirate02a
    IST_pirate03
    IST_pirate03a
    IST_pirate04
    IST_pirate05
    IST_pirate05b
    IST_pirate06
    IST_pirate06a
    IST_pirate07
    IST_pirate08
    IST_pirate09
    IST_pirate10
    IST_pirate11
    IST_pirate11a
    IST_pirate12
    IST_pirate13
    IST_pirate14
    IST_pirate15
    IST_pirate16
    IST_pirate17
    IST_pirate18
    IST_pirate19
    IST_pirate20
    IST_pirate21
    IST_pirate22
    IST_pirate23
    IST_pirate23b
    IST_pirate24
    IST_pirate25
    IST_pirate26
    IST_pirate27
    IST_pirate28
    IST_pirate29
    IST_pirate30
    IST_pirate30b
    IST_pirate31
    IST_pirate32
    IST_pirate33
    IST_pirate33a
    IST_pirate34
    IST_pirate35
    IST_pod1
    IST_pod2
    IST_pod3
    IST_pod4
    IST_thargoidDefenceShip
    IST_thargoidLurker
    Thargoid Battleship (rebel)
    IST_thargoidRebelTougherWarship
    Thargoid Warship (rebel)
    IST_thargoidSweeper
    IST_thargoidWarship
    IST_thargoidWarship-buggly
    IST_thargoidWarship-buggly02
    IST_thargoidWarship-buggly02b
    IST_thargoidWarship-bugglyB
    IST_thargoidWarship-frigate
    IST_thargoidWarship_standard
    Generation Ship (Hijacked)

    Models

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

    Scripts

    Path
    Scripts/IST_derelict_dead.js
    /*
    /////////////////////////////////////////////////////////////////////////////
    
    This file:      IST_derelict_dead.js
    Part of:        Interstellar Tweaks OXP
    Associated AI:  None / nullAI.plist; see the shipdata
    Author:         UK_Eliter
    License:        2017 Creative Commons: attribution, non-commercial, sharealike
    
    //////////////////////////////////////////////////////////////////////////////
    */
    
    /*
    ==============
    LINTER OPTIONS
    ==============
    */
    
    /* jshint esversion: 6 */
    /* jshint -W030 */
    
    
    /*
    ======
    HEADER
    ======
    */
    
    this.name = 'IST_derelict_dead.js';
    this.author = 'UK_Eliter';
    this.copyright = '2014 Creative Commons: attribution, non-commercial, sharealike.';
    this.licence = 'CC-NC-SA 3';
    this.description = 'A script for InterstellarTweaks OXP';
    
    /*
    ---------------------------
    Start of wrapper for JSHINT
    ---------------------------
    */
    (
        function() {
    
            'use strict';
    
            /*
            ------------------
            DEBUGGING SWITCHES
            ------------------
            */
    
            this.logging = false;
            var consoleDebugMessages = false; // do NOT comment this out. Rather, to disable, set to false.
    
    
            /*
            --------------
            EVENT HANDLERS
            --------------
            */
    
            /*
                NB: allocating cargo is hard; see https://github.com/OoliteProject/oolite/discussions/715
                To it, we have to restort to a shipTakingDamage function.
            */
    
            this.shipSpawned = function() {
                const r = Math.random();
    
                if (r < 0.95) this.ship.lightsActive = false;
    
                // FUEL
                if (r < 0.2) {
                    var fuel = 2.5 * Math.random(); // this is in lightyears
                    if (this.ship.hasRole('IST_derelictThargoid')) {
                        fuel *= ( (r * 25) );
                    } else if (r < 0.12) {
                        fuel *= (r * 10);
                    }
                    this.ship.fuel = fuel;
                }
    
                // ENERGY
                const u = Math.random();
                const x = Math.pow(u, 3);
                this.ship.energy *= x; // Reduces ship by a factor-biased-towards-lower-energy.
    
                // delete this.shipSpawned; // Need this?
            };
    
            this.shipDied = function(whom, why) {
                // Can still refer to this.ship and can still spawn and (yes?) become energy blast.
    
                var c;
                if (this.ship.hasRole('IST_derelictThargoid')) {
                    c = 0.04;
                } else {
                    c = 0.01;
                }
                if (Math.random() <= c) {
                    this.ship.spawn('IST_alloy', 2);
                    this.ship.becomeCascadeExplosion();
                    return;
                }
                if (this.ship.cargoSpaceCapacity < 2) return;
                if (Math.random() > 0.15) return;
                var 
                    cargoCapacity = this.ship.cargoSpaceCapacity,
                    numPods_toEject;
                // Generates a random number with a lower-end bias.
                const u = Math.random();
                const x = Math.pow(u, 3);
                numPods_toEject = 1 + ~~(x * cargoCapacity); // 1 + Math.floor(x * n)
                this.ship.spawn('cargopod', numPods_toEject);
            };
    
        
    
            /*
            ------------------------------
            End of wrapper for JSHINT.
            See also Jshint options above.
            ------------------------------
            */
    
        }).call(this);
    
    // EOF
    
    Scripts/IST_derelict_live.js
    /*
    ///////////////////////////////////////////////////////////////////////////////////
    
    This file:          IST_derelict_live.js
    Associated AI:      IST_derelict_live.plist
    Part of:            Interstellar Tweaks OXP
    Author:             UK_Eliter
    License:            2017 Creative Commons: attribution, non-commercial, sharealike
    
    ///////////////////////////////////////////////////////////////////////////////////
    */
    
    
    /*
    ==============
    LINTER OPTIONS
    ==============
    */
    
    /* jshint esversion: 6 */
    /* jshint -W030 */
    
    
    /*
    ======
    HEADER
    ======
    */
    
    this.name = 'IST_derelict_live.js';
    this.author = 'UK_Eliter';
    this.copyright = '2014 Creative Commons: attribution, non-commercial, sharealike.';
    this.licence = 'CC-NC-SA 3';
    this.description = 'A ship script for the Interstellar Tweaks OXP';
    
    
    /*
    ==============
    DEBUG SWITCHES
    ==============
    */
    
    // this.logging = true;
    var consoleDebugMessages = false; // do NOT comment this out. Rather, to disable, set to false.
    
    
    /*
    ========================
    JSHINT: start of wrapper
    ========================
    */
    
    (function () {
        'use strict';
    
        /*
        --------------
        EVENT HANDLERS
        --------------
        */
    
        this.playerWillEnterWitchspace = function () {
            this.$timer_SOS_end();
        };
    
        this.shipAttackedWithMissile = function () {
            if (
                this.actuallyDead ||
                this.ship.podded
            ) {
                return;
            }
            (Math.random() < 0.9) && this.$_spawnEscapePod();
        };
    
        this.shipDied = function () {
            this.$timer_SOS_end();
        };
    
        this.shipSpawned = function () {
            if (Math.random() > 0.85) {
                this.actuallyDead = false;
            } else {
                this.actuallyDead = true; // broadcast SOS, but will not issue personal plea and will not jettison pod.
            }
            // this.actuallyDead = false; // TEST
            var t = 9 + ~~(Math.random() * 11);
            this.SOS_Timer = new Timer(
                this,
                this.$broadcast_SOS,
                t,
                t
            );
        };
    
        this.shipTakingDamage = function (amount, whom, type) {
            if (
                this.actuallyDead ||
                this.ship.podded
            ) {
                return;
            }
            if (this.ship.energy < 20) {
                if (Math.random() < 0.8) {
                    this.$_spawnEscapePod();
                }
                return;
            }
            // Else ..
            if (Math.random() < 0.25) {
                this.$_spawnEscapePod();
            }
            // When the pod jettisoned the new script takes care of that.
        };
    
    
        /*
        ---------------------
        ROUTINES CALLED BY AI
        ---------------------
        */
    
        this.$_spawnEscapePod = function () {
            if (
                !this.ship ||
                !this.ship.position ||
                this.ship.podded ||
                this.actuallyDead ||
                !player.ship
            ) return;
    
            if (this.SOS_Timer && (Math.random < 0.3))
                this.$timer_SOS_end();
    
            if (Math.random() > 0.04) {
                this.ship.podded = true;
                const pod = this.ship.spawnOne('IST_pod');
                this.ship.switchAI('nullAI.plist');
                this.ship.setScript('IST_derelict_dead.js');
                return;
            }
            if (Math.random() > 0.15) {
                this.ship.commsMessage('Oh dear . . ');
            }
            this.ship.switchAI('nullAI.plist');
            this.ship.explode();
        };
    
        this.$_pleaToPlayer = function () {
            if (
                !this.ship ||
                !this.ship.position ||
                this.actuallyDead ||
                this.ship.podded ||
                !player.ship
            ) return;
    
            this.$timer_SOS_end();
    
            var txt;
            switch (Math.ceil(Math.random() * 35)) {
            case 1:
                txt = 'Please pick up my capsule! I am completely stranded!';
                break;
            case 2:
                txt = 'Help me out, please!';
                break;
            case 3:
                txt = 'I am stuck out here!';
                break;
            case 4:
                txt = 'Take me back to normal space! I will pay you!';
                break;
            case 6:
                txt = "Pick me up! I'll pay!";
                break;
            case 7:
                txt = 'Pick me up, and I will make you rich!';
                break;
            case 8:
                txt = 'My ship is broken!';
                break;
            case 9:
                txt = 'My ship is completely broken!';
                break;
            case 10:
                txt = 'Help! Broken ship!';
                break;
            case 11:
                txt = 'Help! No engines!';
                break;
            case 12:
                txt = 'Pick up my escape pod! Please!';
                break;
            case 13:
                txt = 'Help at last! Amazing! Please scoop me (carefully!).';
                break;
            case 14:
                txt = 'Any chance of a lift?';
                break;
            case 15:
                txt = 'Help!';
                break;
            case 16:
                txt = 'Please help!';
                break;
            case 17:
                txt = 'Help!';
                break;
            case 18:
                txt = 'Help!';
                break;
            case 19:
                txt = 'Help!';
                break;
            case 20:
                txt = 'I need help!';
                break;
            case 21:
                txt = 'Please pick up my capsule!';
                break;
            case 22:
                txt = '<Static>';
                break;
            default:
                txt = 'S.O.S!';
                break;
            }
            this.ship.commsMessage(txt);
        };
    
    
        /*
        ---------------
        OTHER FUNCTIONS
        ---------------
        */
    
        this.$timer_SOS_end = function () {
            if (this.SOS_Timer) {
                this.SOS_Timer.isRunning && this.SOS_Timer.stop();
                delete this.SOS_Timer;
            }
        };
    
        this.$broadcast_SOS = function () {
            if (!this.ship) return;
            if (!this.ship.position) return;
            this.ship.commsMessage('S.O.S');
            // ^ this.ship.broadcastDistressMessage() is (1) only available in += 1.79, (2) perhaps applicable only to combat.
        };
    
    
        /*
        ----------------------
        JSHINT: end of wrapper
        ----------------------
        */
    
    }).call(this);
    
    
    // EOF
    Scripts/IST_hunter.js
    /*
    ///////////////////////////////////////////////////////////////////////////////////
    
    This file:          IST_hunter.js
    Associated AI:      IST_hunter.plist
    Part of:            Interstellar Tweaks OXP
    Author:             UK_Eliter
    License:            2017 Creative Commons: attribution, non-commercial, sharealike
    
    ///////////////////////////////////////////////////////////////////////////////////
    */
    
    
    /*
    ==============
    LINTER OPTIONS
    ==============
    */
    
    /* jshint esversion: 6 */
    /* jshint -W030 */
    
    
    /*
    ======
    HEADER
    ======
    */
    
    this.name = 'IST_hunter.js';
    this.author = 'UK_Eliter';
    this.copyright = '2014 Creative Commons: attribution, non-commercial, sharealike.';
    this.licence = 'CC-NC-SA 3';
    this.description = 'A ship script for the Interstellar Tweaks OXP';
    
    
    /*
    ------------------
    DEBUGGING SWITCHES
    ------------------
    */
    
    // this.logging = true;
    
    var consoleDebugMessages = false;
    // Do NOT comment this out. Rather, to disable, set to false.
    
    this.dbg = false; // NB
    
    
    /*
    ------------------------
    JSLINT: start of wrapper
    ------------------------
    */
    
    (function () {
        'use strict';
    
        /*
        --------------
        EVENT HANDLERS
        --------------
        */
    
        this.shipSpawned = function () {
    
            /*
            ----------------------------------
            Determine ordinance we might award
            ----------------------------------
            */
    
            const
                ordinance_shipSlots = this.ship.missileordinance_shipSlots,
                oxp = {
                    missilesAndBombs: worldScripts['Missiles & Bombs'],
                    nexus: !EquipmentInfo.infoForKey('EQ_NEXUS_BUG_MISSILE')
                };
    
            var
                l,
                i,
                ordinance_available = ['EQ_HARDENED_MISSILE', 'EQ_HARDENED_MISSILE'];
    
            if (oxp.nexus === true) {
                ordinance_available.push(
                    'EQ_NEXUS_BASIC_MISSILE',
                    'EQ_NEXUS_BUG_MISSILE',
                    'EQ_NEXUS_BUG_MISSILE',
                    'EQ_NEXUS_BUG_MISSILE',
                    'EQ_NEXUS_BUG_MISSILE',
                    'EQ_NEXUS_PREMIUM_MISSILE'
                );
            }
            if (oxp.missilesAndBombs) {
                ordinance_available.push(
                    'EQ_RMB_FRAG_MISSILE',
                    'EQ_RMB_THARGOID_MISSILE',
                    'EQ_RMB_THARGOID_MISSILE',
                    'EQ_RMB_THARGOID_MISSILE'
                );
            }
    
            /*
            ----------------------------
            Allocate available ordinance
            ----------------------------
            */
    
            l = ordinance_available.length,
            i = ordinance_shipSlots - 1;
            //  Can use a do-while loop because all the ships at issue will have at least one missile slot.
            do {
                this.ship.awardEquipment(ordinance_available[~~(Math.random() * l)]);
            }
            while (i--);
    
            /*
            ------
            Bounty
            ------
            */
    
            const r = Math.random();
            if (r > 0.65)
                s[0].bounty +=
                ~~(
                    1 + ~~(60 * r)
                );
        };
    
    
        /*
        ----------------------
        JSHINT: end of wrapper
        ----------------------
        */
    
    }).call(this);
    
    
    // EOF
    Scripts/IST_lobster.js
    /*
    ///////////////////////////////////////////////////////////////////////////////////
    
    This file:          IST_lobster.js
    Associated AI:      IST_lobster.plist
    Part of:            Interstellar Tweaks OXP
    Author:             UK_Eliter
    License:            2017 Creative Commons: attribution, non-commercial, sharealike
    
    ///////////////////////////////////////////////////////////////////////////////////
    */
    
    
    /*
    ==============
    LINTER OPTIONS
    ==============
    */
    
    /* jshint esversion: 6 */
    /* jshint -W030 */
    
    
    /*
    ======
    HEADER
    ======
    */
    
    this.name = 'IST_lobster.js';
    this.author = 'UK_Eliter';
    this.copyright = '2014 Creative Commons: attribution, non-commercial, sharealike.';
    this.licence = 'CC-NC-SA 3';
    this.description = 'A script for InterstellarTweaks OXP';
    
    
    /*
    ==================
    DEBUGGING SWITCHES
    ==================
    */
    
    // this.logging = true;
    var consoleDebugMessages = false; // do NOT comment this out. Rather, to disable, set to false.
    
    
    /*
    ========================
    JSHINT: start of wrapper
    ========================
    */
    
    (function() {
        'use strict';
    
    
        /*
        ================
        EVENT HANDLER(S)
        ================
        */
    
        this.shipAttackedWithMissile = function() {
            var shipWithECM_localVariable = this.$shipWithECM;
            // log('InterstellarTweaks - debugging', 'trying to ecm - 1');
            if (shipWithECM_localVariable && shipWithECM_localVariable.isValid) {
                // log('InterstellarTweaks - debugging', 'trying to ecm - 2");
                shipWithECM_localVariable.fireECM();
                return;
            }
            this.ship.reactToAIMessage('ECM_UNAVAILABLE');
            // log('InterstellarTweaks - debugging', 'no ecm');
        };
    
    
        /*
        ----------------------
        JSHINT: end of wrapper
        ----------------------
        */
    
    }).call(this);
    
    
    // EOF
    Scripts/IST_masterScript.js
    /*
    ===============================================================================================
    
    This file:          IST_masterScript.js
    Part of             Interstellar Tweaks OXP
    Author:             UK_Eliter
    License:            2017 Creative Commons: attribution, non-commercial, sharealike
    
        ____      __                 __       ____              ______                    __
       /  _/___  / /____  __________/ /____  / / /___ ______   /_  __/      _____  ____ _/ /_______
       / // __ \/ __/ _ \/ ___/ ___/ __/ _ \/ / / __ `/ ___/    / / | | /| / / _ \/ __ `/ //_/ ___/
     _/ // / / / /_/  __/ /  (__  ) /_/  __/ / / /_/ / /       / /  | |/ |/ /  __/ /_/ / ,< (__  )
    /___/_/ /_/\__/\___/_/  /____/\__/\___/_/_/\__,_/_/       /_/   |__/|__/\___/\__,_/_/|_/____/
    
    
    For Oolite >= 1.82.
    
    ===============================================================================================
    */
    
    
    /*
    
    =====================
    C O N T E N T S table
    for what follows
    =====================
    
    1   SET LINTER OPTIONS
    
    2   HEADER
    
    3   DEBUGGING: set switches
    
    4   SOUNDS
    
    5   CONSTANTS & VARIABLES
    
    6   DEBUGGING: apply switches
    
    7   WRAPPER FOR JSLINT, start
    
    8   ERROR & MESSAGE HANDLING
    
    9   FINDING & MOVING, ADDING & REMOVING
    
    10  TIMER HANDLING
    
    11  MISCELLANEOUS
    
    12  EVENT HANDLERS
    
    13  MAIN TWEAKING ROUTINES
    
    14  End of WRAPPER FOR JSLINT
    
    */
    
    
    /*
    ======================
    1   SET LINTER OPTIONS
    ======================
    */
    
    /* jshint esversion: 6 */
    /* jshint -W030 */
    
    
    /*
    ==========
    2   HEADER
    ==========
    */
    
    this.name = 'IST_masterScript';
    this.author = 'UK_Eliter';
    this.copyright = '2017-2022 Creative Commons: attribution, non-commercial, sharealike.';
    this.licence = 'CC-NC-SA 3';
    this.description = 'Interstellar Tweaks';
    
    
    /*
    ===========================
    3   DEBUGGING: set switches
    ===========================
    */
    
    this.dbg_full = false;
    //  false.
    //  See below (5) for effects.
    //  If set to true, then, later, the script changes this.dbg to true.
    
    this.dbg = false;
    //  false
    
    // The only way to easily test the various interstellar scenarios - given how my code is - is to set the probability for them to 1, and to stop other stuff firing first.
    
    
    /*
    ==========
    4   SOUNDS
    ==========
    */
    
    // The following two sounds are built into Oolite.
    // So they the 'customsounds' file need not declare them.
    
    this.mySound_warning = new SoundSource();
    this.mySound_warning.sound = 'warning.ogg';
    
    this.mySound_hullBang = new SoundSource();
    this.mySound_hullBang.sound = 'hullbang.ogg';
    
    
    /*
    =========================
    5   CONSTANTS & VARIABLES 
    =========================
    */
    
    this.escapeSequence = {
        prob_thargoidsGetPod: 0.08,
        rescueProb: 0.86
    };
    
    this.galNum = 0; // We set this in this.playerEnteredNewGalaxy and in 
    
    this.initialisedAtFirstLaunch = false;
    
    this.interstellar_data = {
        // origin, // Set on fly
        misJumpInducedByScript_prob: 0.03, // 0.03
        scenario: '',
        subScenario: '',
        scenarios: {
            all: {
                genShipsRemove_prob: 0.85, // 0.85. Applies to ALL scenarios. The generationShips OXP seems to spawn a genship on a 0.3 chance.
                thargoidBattleshipsLimit: {
                    prob: 0.87, // 0.87
                    maxShips: 3 // 3
                },
                wouldBeHelpee_remove_prob: 0.85 // 0.85
            },
            tweak_prob: 0.57, // 0.57; the chance of just about any tweak.
            versions: {
                modifiedOrNovel: {
                    derelict_range: 27000, // 27000; can be 'offset', though.
                    offset: {
                        distance_biasTowardsMin: 5,
                        // Oolite scanner range is 25600 meters
                        distance_max: 140000, // 140000
                        distance_min: 7000, // 7000
                        prob: 0.08, // 0.08
                    },
                    novel_prob: 0.59, // 0.59. As Vs 'modified'.
                    repopulation: {
                        num: 0,
                        num_max_current: 0,
                        num_max_ever: 50, // 50
                        prob: 0.12, // 0.12
                        probEver: 0.49 // 0.49
                    }, // Cf. scenarios.novel.repopulation
                },
                modified: {
                    battleOccured: {
                        prob: 0.6, // 0.6
                        prob_derelict: 0.14, // 0.14
                        prob_wreckageOnly: 0.25 // 0.25
                    },
                    probOverrideThargoidOXPs: 0.8,
                    /*
                    ^ 0.8. As a comment below says, this 'override' does not actually prevent ships being added.
                    See also above on `thargoidBattleshipsLimit`.
                    */
    
                    subScenarios: {
                        navyAdd: {
                            maxNum: 15 // 15
                        },
                        navyOnly: {
                            maxNum: 7, // 7
                            prob: 0.5 // 0.5
                        },
                        thargoidsOnly: {
                            baseNum: 6, // 6
                            prob: 0.6 // 0.6
                        },
                    },
                },
                novel: {
                    chosenSubScenario: null,
                    repopulation: {
                        navy: {
                            baseNum: 4,
                            maxMil: 15,
                            probIgnoreParity: 0.2, // 0.2
                            threeG: {
                                prob: 0.18, // 0.18
                                squadSize: 5
                            }
                        },
                        thargoid: {
                            maxThargs: 35, // 35
                            probIgnoreMax: 0.15 // 0.15
                        },
                    },
                    subScenarios_individual: {
                        aliens: {
                            maxNum: 13 // 13
                        },
                        asteroids: {
                            miners_prob: 0.18 // 0.18
                        },
                        derelict: {
                            lives_prob: 0.14 // 0.14
                        },
                        hunters: {
                            baseNum: 3 // 3
                        },
                        lobsters: {
                            num: 11 // 11
                        },
                        pirates: {
                            baseNum: 4 // 4
                        },
                        radiation: {
                            player: {
                                damagePerRun: {
                                    heatShield: 2.85, // 2.95
                                    standard: 3.8, // 3.8. If one changes this, then change 'heatshield' proportionally.
                                }, // Interval gets set by 'standard'
                            },
                            nonPlayer: {
                                damagePerRun: {
                                    heatShield: 33.75, // 33.75
                                    standard: 45, // 45. If one changes this, then change the other two values proportionally.
                                    thargoid: 30 // 30
                                }, // interval gets set by via 'standard' and calculated damage-to-player-per-second
                            },
                        },
                        thargoids: {
                            baseNum: 4, // 4
                            prob_genShip: 0.02, // 0.02
                        },
                        thargoidsRebels: {
                            baseNum: 1, // 1
                        }
                    },
    
                    // To force a subscenario, may need to set a probability to something like 100.
                    subScenarios: [{
                            key: 'aliens',
                            weight: 0.05, // 0.05
                            members: ['aliens']
                        }, {
                            key: 'aliens+asteroids',
                            weight: 0.01, // 0.01
                            members: ['aliens', 'asteroids']
                        }, {
                            key: 'aliens+hunters',
                            weight: 0.015, // 0.015
                            members: ['aliens', 'hunters']
                        }, {
                            key: 'aliens+pirates',
                            weight: 0.014, // 0.014
                            members: ['aliens', 'pirates']
                        }, {
                            key: 'aliens+hunters+pirates',
                            weight: 0.01, // 0.01
                            members: ['aliens', 'hunters', 'pirates']
                        }, {
                            key: 'asteroids',
                            weight: 0.06, // 0.06. Asteroid (sub)scenarios get inflected by galaxy.
                            members: ['asteroids']
                        }, {
                            key: 'asteroids+hunters',
                            weight: 0.01, // 0.01
                            members: ['asteroids', 'hunters']
                        }, {
                            key: 'derelicts',
                            weight: 0.04, // 0.04
                            members: ['derelicts']
                        }, {
                            key: 'derelicts+asteroids',
                            weight: 0.02, // 0.02
                            members: ['derelicts', 'asteroids']
                        }, {
                            key: 'empty',
                            weight: 0.19, // 0.19
                            members: ['empty']
                        }, {
                            key: 'hunters',
                            weight: 0.07, // 0.07
                            members: ['hunters']
                        }, {
                            key: 'lobsters',
                            weight: 0.04, // 0.04. Lobster (sub)scenarios get inflected by galaxy.
                            members: ['lobsters']
                        }, {
                            key: 'pirates',
                            weight: 0.02, // 0.02. Pirate (sub)scenarios get inflected by government levels.
                            members: ['pirates']
                        }, {
                            key: 'radiation',
                            weight: 0.05, // 0.05. Gets inflected by galaxy.
                            members: ['radiation']
                        }, {
                            key: 'thargoids',
                            weight: 0.32, // 0.32
                            members: ['thargoids']
                        }, {
                            key: 'thargoids+aliens',
                            weight: 0.05, // 0.05
                            members: ['thargoids', 'aliens']
                        }, {
                            key: 'thargoids+aliens+hunters',
                            weight: 0.02, // 0.02
                            members: ['thargoids', 'aliens', 'hunters']
                        }, {
                            key: 'thargoids+aliens+hunters+pirates',
                            weight: 0.01, // 0.01
                            members: ['thargoids', 'aliens', 'hunters', 'pirates']
                        }, {
                            key: 'thargoids+hunters',
                            weight: 0.09, // 0.09
                            members: ['thargoids', 'hunters']
                        }, {
                            key: 'thargoids+hunters+pirates',
                            weight: 0.01, // 0.01
                            members: ['thargoids', 'hunters', 'pirates']
                        }, {
                            key: 'thargoids+lobsters',
                            weight: 0.01, // 0.01
                            members: ['thargoids', 'lobsters']
                        }, {
                            key: 'thargoids+pirates',
                            weight: 0.02, // 0.02
                            members: ['thargoids', 'pirates']
                        }, {
                            key: 'thargoidsRebels',
                            weight: 0.012, // 0.012. Rebel (sub)scenarios get inflected by galaxy.
                            members: ['thargoidsRebels']
                        }, {
                            key: 'thargoidsRebels+aliens',
                            weight: 0.01, // 0.01
                            members: ['thargoidsRebels', 'aliens']
                        }, {
                            key: 'thargoidsRebels+thargoids',
                            weight: 0.015, // 0.015. Occasionally this combination will occur via other means.
                            members: ['thargoidsRebels', 'thargoids']
                        },
    
                        // NB: forbidden combinations are simply omitted.
                    ]
                    // See also: this.playerEnteredNewGalaxy
                },
                unmodified: {}
            }
        },
        scripts: {
            populators: {},
            repopulators: {}
        },
        timing: {
            // Values are in seconds (decimals allowed); the relevant routine fires that amount of seconds after the relevant event.
            runTweak_2: 0.25,
            /* 
                ^ 0.25. Needs to be high enough to: 1) be able to zap OXP ships added at WillExitWitchspace; 2) help (other) ships to get moving by the time of tweak_3.
            */
            runTweak_3: 0.25,
            /*
                ^ 0.25. Needs to be high enough to: 1) be able to zap OXP ships added at exitedWitchspace; 2) help ships get moving.
                Yet, needs to be low enough not to keep player waiting.
            */
        },
    };
    
    const script = this; // Needed (under Oolite's javascript engine) for this.interstellar_codeForNovel.
    this.interstellar_codeForNovel = {
        _chosenSubScenario: null,
        novelSubScenario_choose: function () {
            var data = script.interstellar_data.scenarios.versions.novel;
            var total = 0;
            var i;
            for (i = 0; i < data.subScenarios.length; i++) {
                total += data.subScenarios[i].weight;
            }
            var r = script.$randomOrPseudo(0.2, 700000) * total;
            var cumulative = 0;
            var chosenSubScenario = data.subScenarios[data.subScenarios.length - 1];
            for (i = 0; i < data.subScenarios.length; i++) {
                cumulative += data.subScenarios[i].weight;
                if (r < cumulative) {
                    chosenSubScenario = data.subScenarios[i];
                    break;
                }
            }
            data.chosenSubScenario = chosenSubScenario;
            return chosenSubScenario.key;
        },
        novelSubScenario_run: function (args) {
            var data = script.interstellar_data.scenarios.versions.novel;
            var subScenario = data.chosenSubScenario;
            if (!subScenario) return;
            var i;
            for (i = 0; i < subScenario.members.length; i++) {
                var key = subScenario.members[i];
                this.executors[key](args);
            }
            data.chosenSubScenario = null;
        },
        executors: {
            aliens: function (args) {
                const cfg = script.interstellar_data.scenarios.versions.novel.subScenarios_individual.aliens;
                script.$scenarioAdd_ships_aliens(
                    (1 + ~~(Math.random() * cfg.maxNum)),
                    args.origin,
                    26000,
                    false // rings
                );
            },
            asteroids: function (args) {
                const cfg = script.interstellar_data.scenarios.versions.novel.subScenarios_individual.asteroids;
                script.$scenarioAdd_ships_asteroids(
                    args.origin,
                    cfg.miners_prob
                );
            },
            derelict: function (args) {
                script.$scenarioAdd_ships_derelicts(
                    args.origin
                );
            },
            hunters: function (args) {
                const cfg = script.interstellar_data.scenarios.versions.novel.subScenarios_individual.hunters;
                script.$scenarioAdd_ships_hunters(
                    cfg.baseNum,
                    args.origin,
                    15500,
                    false // rings
                );
            },
            empty: function () {},
            lobsters: function (args) {
                const cfg = script.interstellar_data.scenarios.versions.novel.subScenarios_individual.lobsters;
                script.$scenarioAdd_ships_lobsters(
                    cfg.num,
                    args.origin,
                    15000, // range
                    false // rings
                );
            },
            pirates: function (args) {
                const cfg = script.interstellar_data.scenarios.versions.novel.subScenarios_individual.pirates;
                script.$scenarioAdd_ships_pirates(
                    cfg.baseNum,
                    args.origin,
                    15500, // range
                    false // rings
                );
            },
            // radiation: function (args) { // TEST
            radiation: function () {
                // script.$scenarioAdd_radiation(args.origin); // Argument: TEST
                script.$scenarioAdd_radiation();
            },
            thargoids: function (args) {
                const cfg = script.interstellar_data.scenarios.versions.novel.subScenarios_individual.thargoids;
                script.$scenarioAdd_ships_thargoids(
                    cfg.baseNum,
                    args.origin,
                    20000, // range
                    cfg.prob_genShip
                );
            },
            thargoidsRebels: function (args) {
                const cfg = script.interstellar_data.scenarios.versions.novel.subScenarios_individual.thargoidsRebels;
                script.$scenarioAdd_ships_thargoidsRebels(
                    cfg.baseNum,
                    args.origin,
                    20000 // range
                );
            },
        },
        // === Call this once after setup so the originals are remembered ===
        novelSubScenario_saveOriginalWeights: function () {
            var data = script.interstellar_data.scenarios.versions.novel;
            data._originalWeights = {};
            var i;
            for (i = 0; i < data.subScenarios.length; i++) {
                data._originalWeights[data.subScenarios[i].key] = data.subScenarios[i].weight;
            }
        },
        // === Change one specific subscenario by its key (e.g. 'radiation') ===
        novelSubScenario_multiplyWeight: function (key, multiplier) {
            var data = script.interstellar_data.scenarios.versions.novel;
            if (!data._originalWeights || data._originalWeights[key] === undefined) return;
            var i;
            for (i = 0; i < data.subScenarios.length; i++) {
                if (data.subScenarios[i].key === key) {
                    data.subScenarios[i].weight = data._originalWeights[key] * multiplier;
                    return;
                }
            }
        },
        // === Change ALL subscenarios that contain a given member (e.g. 'pirates') ===
        novelSubScenario_multiplyByMember: function (member, multiplier) {
            var data = script.interstellar_data.scenarios.versions.novel;
            if (!data._originalWeights) return;
            var i;
            for (i = 0; i < data.subScenarios.length; i++) {
                var sub = data.subScenarios[i];
                var j;
                for (j = 0; j < sub.members.length; j++) {
                    if (sub.members[j] === member) {
                        if (data._originalWeights[sub.key] !== undefined) {
                            sub.weight = data._originalWeights[sub.key] * multiplier;
                        }
                        break;
                    }
                }
            }
        },
        // === Revert one specific subscenario to its original weight ===
        novelSubScenario_revertWeight: function (key) {
            var data = script.interstellar_data.scenarios.versions.novel;
            if (!data._originalWeights || data._originalWeights[key] === undefined) return;
            var i;
            for (i = 0; i < data.subScenarios.length; i++) {
                if (data.subScenarios[i].key === key) {
                    data.subScenarios[i].weight = data._originalWeights[key];
                    return;
                }
            }
        },
        // === Revert ALL subscenarios that contain a given member to their original weight ===
        novelSubScenario_revertByMember: function (member) {
            var data = script.interstellar_data.scenarios.versions.novel;
            if (!data._originalWeights) return;
            var i;
            for (i = 0; i < data.subScenarios.length; i++) {
                var sub = data.subScenarios[i];
                var j;
                for (j = 0; j < sub.members.length; j++) {
                    if (sub.members[j] === member) {
                        if (data._originalWeights[sub.key] !== undefined) {
                            sub.weight = data._originalWeights[sub.key];
                        }
                        break;
                    }
                }
            }
        },
        // === Revert everything back to original ===
        novelSubScenario_revertAllWeights: function () {
            var data = script.interstellar_data.scenarios.versions.novel;
            if (!data._originalWeights) return;
            var i;
            for (i = 0; i < data.subScenarios.length; i++) {
                var key = data.subScenarios[i].key;
                if (data._originalWeights[key] !== undefined) {
                    data.subScenarios[i].weight = data._originalWeights[key];
                }
            }
        }
    };
    
    this.msgPrefix = 'Interstellar Tweaks: '; // Including a colon and trailing space
    
    this.oxp = {};
    
    this.oxp_overrides = {
        TCAT: {
            overrode: false
        },
        thargoidThreat: {
            overrode: false
            // ^ This is used to set the key for which that OXP looks, viz., this.threatOXP_modify.
        }
    };
    
    this.sickness = {
        isSick: false, // changed on-fly
        prob_fatal: 0.4, // 0.4
        prob_getting: 0, // changed on-fly
        prob_getting_current: 0, // changed on-fly
        willDie: false // changed on-fly
    };
    
    this.stellar = {
        misplace: {
            secsAfterWillExitWitchspace: 0.25,
            // ^ 0.83. Cf. this.interstellar_data.timing.runTweak_2, which fires on a delay after shipWillExitWitchspace
            prob: 0.025, // 0.025.
            prob_nearSun: 0.16 // 0.16
        }
    };
    
    
    /*
    ==============================
    6    DEBUGGING: apply switches
    ==============================
    */
    
    if (this.dbg_full) {
        // To disable these effects, comment out the relevant lines (thereby preventing them from overriding values above).
        this.dbg = true;
        this.dbg_ignorePlayerRank = false; // false // NB
        this.dbg_testWithMisjumps = true; // true // NB
        // this.interstellar_data.scenarios.tweak_prob = 1; // NB
    } else {
        // DEFAULT values are as in my comments.
        this.dbg_ignorePlayerRank = false;
        // false
        this.dbg_testWithMisjumps = false;
        // false
    }
    
    if (this.dbg_testWithMisjumps) {
        log('InterstellarTweaks', 'Testing with misjumps enabled.');
        this.interstellar_data.scenarios.misJumpInducedByScript_prob = 1;
    }
    
    /*
    ==================================================
    7   Start of wrapper for JSHINT 
        See also JSHint options above.
        The following should be: 'function(){' (_sic_)
    ==================================================
    */
    
    (
        function () {
    
            'use strict';
    
            /*
            ============================
            8   ERROR & MESSAGE HANDLING
            ============================
            */
    
            this.$msg_dbg = function (txt) {
                // Note in the function name the '$' and the 'this'.
                if (!txt) {
                    player.consoleMessage('Problem with debug message text!', 30);
                    return;
                }
                player.consoleMessage(this.msgPrefix + txt, 12);
                log('InterstellarTweaks', 'DEBUG: ' + txt);
            };
    
            this.$msg_err = function (txt) {
                txt = txt || 'Problem with error message text!';
                log('InterstellarTweaks', 'ERROR (please report): ' + txt);
                this.dbg && player.consoleMessage(this.msgPrefix + 'ERROR ' + txt, 30);
            };
    
            this.$msg_player = function (txt, displaySecs) {
                if (!txt || !displaySecs || displaySecs < 1) {
                    txt = 'Problem with message!';
                    this.dbg && player.consoleMessage(txt, 30);
                    log('InterstellarTweaks', 'ERROR (please report): ' + txt);
                    return;
                }
                player.consoleMessage(txt, displaySecs);
                /*
                    NB: Debug text can push console messages off the screen! If that happens, then, to test the console messages,
                    temporarily comment out, above,
                        player.consoleMessage(this.msgPrefix + txt, 12);
                */
            };
    
    
            /*
            =======================================
            9   FINDING & MOVING, ADDING & REMOVING
            =======================================
            */
    
            this.$isWithinArray = function (item, array) {
                return worldScripts.IST_masterScript._index_in_list(item, array) > -1;
            };
            /*
            ^ _Sic_ - see http://www.aegidian.org/bb/viewtopic.php?f=4&t=18837.
            See also this script's '_index_in_list'.
            */
    
            this.$is_thargoid = function (e) {
                return e.isShip &&
                    e.isThargoid &&
                    !e.isPlayer;
            };
    
            this.$is_thargoid_battleship = function (e) {
                return e.isShip &&
                    e.isThargoid &&
                    e.name === 'Thargoid Thargorn Battleship';
                // The battleship lacks a distinct role, so we have to use its name.
            };
    
            this.$is_thargoid_not = function (e) {
                return e.isShip &&
                    !e.isThargoid &&
                    !e.isPlayer;
            };
    
            this.$is_thargoid_obedient = function (e) {
                return e.isShip &&
                    e.isThargoid &&
                    e.primaryRole !== 'thargoidRebel';
            };
    
            this.$is_navyIncompatible = function (e) {
                return e.isShip &&
                    e.scanClass !== 'CLASS_MILITARY' &&
                    !e.isPlayer;
            }; // Is there something wrong with this, such that it is leaving thargoids?
    
            this.$is_navyIncompatible_allowFollowingHelpee = function (e) {
                return e.isShip &&
                    e.scanClass !== 'CLASS_MILITARY' &&
                    !e.isPlayer &&
                    (e.primaryRole !== 'interstellar_help_trader');
            };
    
            this.$is_ship = function (e) {
                return e.isShip;
            };
    
            // this.$is_ship_heavy = function (e) {
            //  return e.mass >= 120000000;
            // };
    
            // this.$is_ship_medium = function (e) {
            //  return e.mass > 1200000 && e.mass < 120000000;
            // };
    
            this.$is_unwantedInNovelInitially = function (e) {
                return e.isShip &&
                    !e.isPlayer &&
                    !e.hasRole('hiredGuns_escort') &&
                    !e.hasRole('ecl_escape_pod_beacon') &&
                    !e.hasRole('escape-capsule') &&
                    !e.hasRole('uberBuggly') &&
                    !e.hasRole('generationship');
            };
    
            this.$is_unwantedInNovelInitially_allowHelpees = function (e) {
                return e.isShip &&
                    !e.isPlayer &&
                    !e.hasRole('hiredGuns_escort') &&
                    e.primaryRole !== 'interstellar_help_trader' &&
                    !e.hasRole('ecl_escape_pod_beacon') &&
                    !e.hasRole('escape-capsule') &&
                    !e.hasRole('uberBuggly') &&
                    !e.hasRole('generationship');
            };
    
            this.$is_helpShip = function (e) {
                return e.isShip && e.primaryRole === 'interstellar_help_trader';
            };
    
            this.$is_nonPlayer_lowEnergy_notHiredGun = function (e) {
                return e.isShip &&
                    !e.isPlayer &&
                    !e.hasRole('hiredGuns_escort') &&
                    e.energy < 150;
            };
    
            this.$is_nonPlayer_lowEnergy = function (e) {
                return e.isShip &&
                    !e.isPlayer &&
                    e.energy < 150;
            };
    
            this.$remove_navyIncompatible = function () {
                // this.dbg && this.$debugMessage('Removing non-navy');
                var s;
                if (this.oxp.interstellarHelp && worldScripts.interstellar_help.interstellarHelp === 'FOLLOWING') {
                    s = system.filteredEntities(this, this.$is_navyIncompatible_allowFollowingHelpee);
                } else {
                    s = system.filteredEntities(this, this.$is_navyIncompatible);
                }
                var i = s.length;
                while (i--) {
                    if (s[i].isValid) {
                        s[i].remove(true);
                    }
                }
            };
    
            this.$remove_nonThargoids = function () {
                const s = system.filteredEntities(this, this.$is_thargoid_not);
                var i = s.length;
                while (i--) {
                    if (s[i].isValid) {
                        s[i].remove(true);
                    }
                }
            };
    
            // this.$remove_thargoids = function () {
            // var s = system.filteredEntities(this, $is_thargoid);
            //  var i = s.length;
            //  while (i--) {
            //     if (s[i].isValid) s[i].remove(true);
            // }
            // };
    
            // Argument - TEST; needs argument in calling function = 'radiation: function (args)'
            // this.$scenarioAdd_radiation = function (origin) {
            this.$scenarioAdd_radiation = function () {
                this.dbg && this.$msg_dbg('$scenarioAdd_radiation - entered');
                // Remove anything that would not have survived much radiation.
                var
                    l,
                    s;
    
                s = system.allShips;
                l = s.length;
                if (l > 0) {
                    if (this.oxp.hiredGuns) {
                        s = system.filteredEntities(this, this.$is_nonPlayer_lowEnergy_notHiredGun);
                    } else {
                        s = system.filteredEntities(this, this.$is_nonPlayer_lowEnergy);
                    }
                    l = s.length;
                    while (l--) {
                        if (s[l].isValid) {
                            s[l].remove(true);
                        }
                    }
                }
                if (Math.random() < 0.5) {
                    system.sendAllShipsAway(); // Will send this message, for definite, later and then repeatedly.
                }
                /*
                    Now implement the radiation, such that: players & NPCs get same damage (if we bracket heat shielding),
                    but with players timer running more frequently than NPCs - in order to avoid sudden player energy jumps.
                    And we need to do all that without passing an argument to a timer-run function (because one cannot),
                    though if we had to we could use a global variable.
                */
    
                const radiation = this.interstellar_data.scenarios.versions.novel.subScenarios_individual.radiation;
                const
                    nonPlayer_damagePerRun = radiation.nonPlayer.damagePerRun.standard,
                    player_damagePerRun = radiation.player.damagePerRun.standard,
                    player_interval = 0.35 + Math.round((system.scrambledPseudoRandomNumber(700003) * 10) / 10); // Rounds to 2 decimals
    
                const player_damagePerSecond = player_damagePerRun / player_interval;
                const player_damagePerSecond_rounded = Number(player_damagePerSecond.toFixed(2));
    
                const nonPlayer_interval = nonPlayer_damagePerRun / player_damagePerSecond;
                const nonPlayer_interval_rounded = Number(nonPlayer_interval.toFixed(2));
                // nonPlayer_interval: needs to come out, for efficiency, at > c. 4 seconds.
    
                const nonPlayer_damagePerSecond = nonPlayer_damagePerRun / nonPlayer_interval;
                const nonPlayer_damagePerSecond_rounded = Number(nonPlayer_damagePerSecond.toFixed(2));
    
                if (this.dbg) {
                    this.$msg_dbg('Player, radiation damage per run (preset): ' +
                        player_damagePerRun
                    );
                    this.$msg_dbg('Player, radiation run interval (preset + random): ' +
                        player_interval
                    );
                    this.$msg_dbg('Player, damage per second (calculated, rounded): ' +
                        player_damagePerSecond_rounded
                    );
                    this.$msg_dbg('NonPlayer, radiation damage per run (calculated): ' +
                        nonPlayer_damagePerRun
                    );
                    this.$msg_dbg('NonPlayer, radiation run interval (calculated; will round): ' +
                        nonPlayer_interval
                    );
                    this.$msg_dbg('Non-player, damage per second (calculated, rounded, should match the player): ' +
                        nonPlayer_damagePerSecond_rounded
                    );
                }
    
                this.timer_radiation_1 = new Timer(
                    this,
                    this.$radiation_timed_1,
                    player_interval,
                    player_interval
                );
    
                this.timer_radiation_2 = new Timer(
                    this,
                    this.$radiation_timed_2,
                    nonPlayer_interval_rounded,
                    nonPlayer_interval_rounded
                );
                this.timer_radiation_3 = new Timer(
                    this,
                    this.$radiation_timed_3,
                    3,
                    14
                ); // This is the timer that sends ships away, and increases the player's sickness chance.
                this.mySound_warning.play();
                this.$msg_player('WARNING - high levels of space radiation!', 30);
    
                // TEST:
                // s = system.addShips(
                //     'thargoid',
                //     4,
                //     origin, // Needs that argument passed to function.
                //     11000
                // );
            };
    
            // Always returns a _ship_ array, even if have added a _group_.
            this.$scenarioAdd_ships = function (
                role,
                count,
                p, // must be an initialised vector, on pain of 'TypeError: position.distanceTo is not a function'
                radius,
                toHaveRings,
                isGroup
            ) {
                const witchPoint = new Vector3D(); // Creates the vector and sets it to [0, 0, 0].
    
                // NB: Comment out the following when not debugging.
                if (this.dbg) {
                    this.$msg_dbg('$scenarioAdd_ships - role is ' + role);
                    this.$msg_dbg('$scenarioAdd_ships - count is ' + count);
                    this.$msg_dbg('$scenarioAdd_ships - p is ' + p);
                    // ^ If the number appears without parentheses then it is no vector!
                    this.$msg_dbg('$scenarioAdd_ships - toHaveRings is ' + toHaveRings);
                    this.$msg_dbg('$scenarioAdd_ships - isGroup is ' + isGroup);
                    // this.$msg_dbg('$scenarioAdd_ships - witchPoint is ' + witchPoint);
                }
                // NB: In 'foo.distanceTo(bar)', foo must be a vector but bar can be a vector or else an entity-that-has-a-postion.
    
                var
                    g,
                    i,
                    s;
    
                if (toHaveRings) {
                    if (p.distanceTo(witchPoint) < 25600) {
                        // The intended position is within range of the witchPoint; so, we merely add the ships.
                        if (isGroup) {
                            g = system.addGroup(
                                role,
                                count,
                                p,
                                radius
                            );
                            s = g.ships;
                        } else {
                            s = system.addShips(
                                role,
                                count,
                                p,
                                radius
                            );
                        }
                    } else {
                        // The intended position is without the witchPoint range.
                        // So: add them at the witchPoint; then move them (but with their range offsets from the position).
                        // So, first create.
                        if (isGroup) {
                            g = system.addGroup(
                                role,
                                count,
                                witchPoint,
                                radius
                            );
                            s = g.ships;
                        } else {
                            s = system.addShips(
                                role,
                                count,
                                witchPoint,
                                radius
                            );
                        }
                        this.dbg && this.$msg_dbg('$scenarioAdd_ships - moving ships from witchpoint');
                        i = s.length;
                        while (i--) {
                            if (s[i].isValid) {
                                s[i].position = s[i].position.add(p);
                                // this.dbg && this.$msg_dbg('$scenarioAdd_ships - moved a ship'); // TEST
                            }
                        }
                    }
                } else {
                    // RINGLESS
                    if (p.distanceTo(witchPoint) >= 25600) {
                        // The intended position is not near the witchPoint; so, merely add them.
                        if (isGroup) {
                            g = system.addGroup(
                                role,
                                count,
                                p,
                                radius
                            );
                            s = g.ships;
                        } else {
                            s = system.addShips(
                                role,
                                count,
                                p,
                                radius
                            );
                        }
                    } else {
                        // The intended position is near the witchPoint.
                        // So: create the ships further away; then move them.
                        // First: create.
                        const farPosition = new Vector3D([500000, 500000, 500000]);
                        if (isGroup) {
                            g = system.addGroup(
                                role,
                                count,
                                farPosition,
                                radius
                            );
                            s = g.ships;
                        } else {
                            s = system.addShips(
                                role,
                                count,
                                farPosition,
                                radius
                            );
                        }
                        this.dbg && this.$msg_dbg('$scenarioAdd_ships - moving ships to near witchpoint');
                        var offset = new Vector3D(p).subtract(farPosition);
                        i = s.length;
                        while (i--) {
                            if (s[i].isValid) {
                                s[i].position = s[i].position.add(offset);
                            }
                        }
                    }
                }
                return s;
            };
    
            this.$scenarioAdd_ships_aliens = function (
                n,
                p,
                range,
                toHaveRings
            ) {
                const s = this.$scenarioAdd_ships(
                    'scorpax',
                    n,
                    p,
                    range,
                    false, // rings
                    false // group
                );
            };
    
            this.$scenarioAdd_ships_asteroids = function (
                origin,
                minersProb
            ) {
                this.dbg && this.$msg_dbg('$scenarioAdd_ships_asteroids - entered');
    
                var
                    n,
                    p2,
                    r,
                    s;
    
                /*
                -------------------------------------------------------------------
                Rocks of various sorts
                Use system.addShips directly, because there is no question of rings.
                -------------------------------------------------------------------
                */
    
                this.dbg && this.$msg_dbg('$scenarioAdd_ships_asteroids - group 1');
                const
                    x = (Math.random() - 0.5) * 51200,
                    y = (Math.random() - 0.5) * 51200,
                    z = (Math.random() - 0.5) * 51200;
                p2 = new Vector3D();
                p2 = origin.add([x, y, z]);
    
                s = system.addShips(
                    'IST_asteroid',
                    26,
                    p2,
                    33000
                );
    
                r = Math.random(); // Used for groups 2 and 3.
    
                this.dbg && this.$msg_dbg('$scenarioAdd_ships_asteroids - group 2');
                n = 1 + ~~(r * 56); // Do not exceed Oolite's limit of 64.
                s = system.addShips(
                    'IST_asteroid',
                    n,
                    p2,
                    44000
                );
                this.dbg && this.$msg_dbg('$scenarioAdd_ships_asteroids - group 3');
                if (r < 0.5) {
                    n = 5 + ~~(1 + (r * 38));
                    s = system.addShips(
                        'IST_asteroid',
                        n,
                        p2,
                        60000
                    );
                    s = system.addShips(
                        'IST_asteroid',
                        6,
                        p2,
                        70000
                    );
                    if (r < 0.2) {
                        s = system.addShips(
                            'IST_asteroid',
                            8,
                            p2,
                            95000
                        );
                    }
                }
    
                /*
                ----------
                Billboards
                ----------
    
                The billboards OXP adds them near asteroids, so, remove any billboards.
                */
                if (this.oxp.YAH) {
                    s = system.shipsWithRole('asteroid-billboard');
                    var i = s.length;
                    while (i--) {
                        if (s[i].isValid) {
                            s[i].remove(true);
                        }
                    }
                }
    
                /*
                ------
                Miners
                ------
                */
    
                // minersProb = 1; // TEST
                r = Math.random();
                if (r > minersProb) return;
    
                this.dbg && this.$msg_dbg('$scenarioAdd_ships_asteroids - miners');
                var grouped;
                if (r < (minersProb / 3)) {
                    grouped = false;
                } else {
                    grouped = true;
                }
                n = ~~(1 + (Math.random() * 5));
                if (Math.random() < 0.45) {
                    s = system.addShips('splinter', n, p2, 10000);
                } else {
                    s = system.addShips('IST_boulder', n, p2, 28000);
                }
                n = ~~(1 + (Math.random() * 10));
                s = this.$scenarioAdd_ships(
                    'IST_miner',
                    n,
                    p2,
                    12000,
                    false, // rings
                    grouped
                );
                if (Math.random() < 0.45) {
                    s = this.$scenarioAdd_ships(
                        'miner', // A normal miner, as against one of ours
                        1,
                        p2,
                        12500,
                        false, // rings
                        false // group
                    );
                    this.dbg && this.$msg_dbg('$scenarioAdd_ships_asteroids - added normal miner');
                }
            };
    
            this.$scenarioAdd_ships_derelicts = function (p) {
    
                this.dbg && this.$msg_dbg('$scenarioAdd_ships_derelicts - entered');
    
                const
                    n = 1 + (~~(8 * Math.pow(Math.random(), 4))), // number between 1 and 9, with the '4' is the bias towards 1.
                    versions = this.interstellar_data.scenarios.versions;
    
                const s = this.$scenarioAdd_ships(
                    'IST_derelict',
                    n,
                    p,
                    versions.modifiedOrNovel.derelict_range,
                    false, // rings
                    false // group
                );
    
                this.dbg && this.$msg_dbg('$scenarioAdd_ships_derelicts - added ' + n + ' derelict(s)');
                if (
                    s[0] &&
                    s[0].isValid &&
                    (Math.random() <= versions.novel.subScenarios.derelict.lives_prob)
                ) {
                    if (s[0].hasRole('IST_derelict_thargoid')) {
                        this.dbg && this.$msg_dbg(
                            '$scenarioAdd_ships_derelicts - would have made a derelict live, but it was a thargoid'
                        );
                    } else {
                        s[0].switchAI('IST_derelict_live.plist');
                        s[0].setScript('IST_derelict_live.js');
                        this.dbg && this.$msg_dbg('$scenarioAdd_ships_derelicts - made a derelict live');
                    }
                }
    
                if (Math.random() < 0.2) this.$scenarioAdd_debris(p);
            };
    
            this.$scenarioAdd_ships_hunters = function (
                baseNum,
                p, // must be an initialised vector
                range,
                toHaveRings
            ) {
    
                if (this.dbg) {
                    this.$msg_dbg('$scenarioAdd_ships_hunters - entered');
                    this.$msg_dbg('$scenarioAdd_ships_hunters - p is <' + p + '>');
                }
    
                var
                    i,
                    n;
    
                // Place some of the hunters defined by this OXP (though those hunters depend heavily on OXPs).
                n = 2 + ~~(Math.random() * baseNum);
                this.dbg && this.$msg_dbg('$scenarioAdd_ships_hunters - adding <' + n + '> of my hunters');
                const s = this.$scenarioAdd_ships(
                    'IST_hunter',
                    n,
                    p,
                    range,
                    toHaveRings,
                    false // group
                );
    
                // Add some heavy-hunters, in a group (not the same group as the first), and set their AIs.
                n = baseNum;
                const g = this.$scenarioAdd_ships(
                    'hunter-heavy',
                    n,
                    p,
                    range,
                    toHaveRings,
                    true // group
                );
                if (!g || !g.ships) {
                    this.$msg_err('Problem adding second hunter group');
                    return;
                }
                i = g.ships.length;
                while (i--)
                    if (g.ships[i].isValid) {
                        g.ships[i].setAI('IST_hunter.plist');
                    }
            };
    
            this.$scenarioAdd_ships_lobsters = function (
                baseNum,
                p,
                range,
                toHaveRings
            ) {
                var
                    i,
                    n = baseNum;
    
                if (system.info.galaxyID === 7) {
                    n = baseNum * 2;
                }
                if (n > 64) n = 64;
                const s = this.$scenarioAdd_ships(
                    'IST_lobster',
                    n,
                    p,
                    range,
                    toHaveRings,
                    true // group
                );
                // Give first lobster an ECM, and, for each lobster, tell it which one has the ECM.
                if (s[0]) s[0].awardEquipment('EQ_ECM');
                i = s.length;
                while (i--) {
                    if (s[i].isValid) {
                        s[i].script.$shipWithECM = s[0];
                    }
                }
            };
    
            this.$scenarioAdd_ships_navy = function (
                n,
                p,
                range,
                toHaveRings
            ) {
                var
                    i,
                    s;
                // Prevent any extant military from leaving immediately.
                s = system.entitiesWithScanClass('CLASS_MILITARY');
                i = s.length;
                while (i--) {
                    if (
                        s[i].isValid &&
                        s[i].ai !== 'IST_mil.plist' &&
                        s[i].ai !== 'ferdelance3G_milInterstellarAI' &&
                        s[i].ai !== 'spectreAI.plist' &&
                        s[i].ai !== 'TCAT_raptorAI.plist' &&
                        s[i].ai !== 'militaryFiascoCarrierAI' &&
                        s[i].ai !== 'IST_escapePod.plist'
                    ) {
                        if (s[i].ai === 'behemothFighterInterceptAI.plist') {
                            s[i].sendAIMessage('TARGET_LOST');
                        } else {
                            s[i].setAI('IST_milPootle.plist');
                        }
                    }
                }
                // Add ships.
                s = this.$scenarioAdd_ships(
                    'IST_mil',
                    n,
                    p,
                    24500,
                    false, // rings - so only use this function if one wants the ships to lack rings.
                    false // group
                );
                // Return - and we do need to return _some_ ship    
                return s;
            };
    
            this.$scenarioAdd_ships_pirates = function (
                baseNum,
                p,
                range,
                toHaveRings
            ) {
                const s = this.$scenarioAdd_ships(
                    'IST_pirate',
                    baseNum,
                    p,
                    range,
                    toHaveRings,
                    true // group
                );
            };
    
            this.$scenarioAdd_ships_thargoids = function (
                baseNum,
                p,
                range,
                prob_thargGenShip
            ) {
                var
                    n,
                    s,
                    x,
                    y,
                    z;
    
                this.dbg && this.$msg_dbg('$scenarioAdd_ships_thargoids - entered');
    
                // THARGOID GENERATION SHIP
                if (this.oxp.genShips && Math.random() <= prob_thargGenShip) {
                    x = (Math.random() - 0.5) * 175000;
                    y = (Math.random() - 0.5) * 175000;
                    z = (Math.random() - 0.5) * 175000;
                    var offsetPosition = new Vector3D(p).subtract([x, y, z]);
                    this.$scenarioAdd_ships(
                        'IST_thargoid_genShip',
                        1,
                        offsetPosition,
                        0, // range
                        false, // rings
                        false // group
                    );
                    if (this.oxp.extraThargoids) {
                        n = 1 + ~~(1 + (Math.random() * 6));
                        this.$scenarioAdd_ships('extraThargoids_sweeper', n, offsetPosition, 34000, false);
                        if (Math.random() < 0.7) {
                            n = ~~(1 + (Math.random() * 5));
                            this.$scenarioAdd_ships(
                                'IST_thargoid_lurker',
                                n,
                                offsetPosition,
                                34000,
                                false, // rings
                                true // group
                            );
                        }
                    }
                }
    
                // GENERAL
                const situation = ~~(2 + (Math.random() * 10));
                this.dbg && this.$msg_dbg('$scenarioAdd_ships_thargoids: situation ' + situation);
                switch (situation) {
                case 1:
                    s = this.$scenarioAdd_ships(
                        'IST_thargoid',
                        baseNum,
                        p,
                        14000,
                        false, // rings
                        false // group
                    );
                    if (
                        (baseNum > 2) &&
                        (
                            (this.oxp.extraThargoids) && (Math.random() < 0.2)
                        )
                    ) {
                        s = this.$scenarioAdd_ships(
                            'extraThargoids_sweeper',
                            1,
                            p,
                            18000,
                            false, // rings
                            false // group
                        );
                    }
                    s = this.$scenarioAdd_ships(
                        'thargoid',
                        baseNum,
                        p,
                        14000,
                        false, // rings
                        false // group
                    );
                    break;
                case 2, 3:
                    s = this.$scenarioAdd_ships(
                        'thargoid',
                        baseNum,
                        p,
                        14000,
                        false, // rings
                        false // group
                    );
                    s = this.$scenarioAdd_ships(
                        'IST_thargoid',
                        baseNum,
                        p,
                        (~~(Math.random() * 60000)),
                        false, // rings
                        false // group
                    );
                    break;
                case 4:
                    if (Math.random() < 0.8) {
                        s = this.$scenarioAdd_ships(
                            'IST_thargoid',
                            baseNum,
                            p,
                            50000,
                            false,
                            false // group
                        );
                        s = this.$scenarioAdd_ships(
                            'thargoid',
                            2,
                            p,
                            50000,
                            false, // rings
                            false // group
                        );
                    } else {
                        n = baseNum * 5;
                        s = this.$scenarioAdd_ships(
                            'thargoid',
                            n,
                            p,
                            250000,
                            false, // rings
                            false // group
                        );
                        if (this.oxp.extraThargoids) {
                            s = this.$scenarioAdd_ships(
                                'extraThargoids_sweeper',
                                1,
                                p,
                                250000,
                                false, // rings
                                false // group
                            );
                        }
                        if (this.oxp.thargoidCarriers) {
                            s = this.$scenarioAdd_ships(
                                'thargoid_carrier',
                                1,
                                p,
                                250000,
                                false, // rings
                                false // group
                            );
                        }
                        if (Math.random() < 0.3) {
                            s = this.$scenarioAdd_ships(
                                'thargoid',
                                20,
                                p,
                                300000,
                                false, // rings
                                false // group
                            );
                            if (this.oxp.thargoidCarriers) {
                                s = this.$scenarioAdd_ships(
                                    'thargoid_carrier',
                                    2,
                                    p,
                                    250000,
                                    false, // rings
                                    false // group
                                );
                            }
                        }
                    }
                    break;
                case 5:
                    this.dbg && this.$msg_dbg('$scenarioAdd_ships_thargoids: offsetting');
                    var o = ~~(Math.random() * 36000);
                    x = (Math.random() - 0.5) * o;
                    y = (Math.random() - 0.5) * o;
                    z = (Math.random() - 0.5) * o;
                    var p2 = new Vector3D(p);
                    p2 = p.add([x, y, z]);
                    s = this.$scenarioAdd_ships(
                        'thargoid',
                        baseNum,
                        p2,
                        15500,
                        false, // rings
                        false // group
                    );
                    break;
                default:
                    // Make this scenario resemble the BBC Elite scenario.
                    if (Math.random() > 0.75) {
                        n = baseNum * 2;
                    } else {
                        n = baseNum;
                    }
                    if (n > 64) n = 64;
                    s = this.$scenarioAdd_ships(
                        'IST_thargoidWarship_standard',
                        n,
                        p,
                        16000,
                        false, // rings
                        false // grouo
                    );
                    return s; // NB
                }
                return s; // We need to return _some_ ship
            };
    
            this.$scenarioAdd_ships_thargoidsRebels = function (
                baseNum,
                p,
                range
            ) {
                // I'd like them in little groups, but that would require modifing not only range but position.
                var s;
    
                this.dbg && this.$msg_dbg('$scenarioAdd_ships_thargoidsRebels - entered');
    
                if ((baseNum === 1) && (Math.random() < 0.1)) {
                    s = this.$scenarioAdd_ships(
                        'thargoidRebel',
                        1,
                        p,
                        range,
                        false, // rings
                        false // group
                    );
                    return s;
                }
    
                var n = baseNum + 1;
                s = this.$scenarioAdd_ships(
                    'thargoidRebel',
                    n,
                    p,
                    range,
                    false, // rings
                    true // group
                );
                var r = Math.random();
                if (r > 0.7) return s;
    
                n = ~~(1 + (r * 15));
                s = this.$scenarioAdd_ships(
                    'thargoidRebel',
                    n,
                    p,
                    range,
                    false, // rings
                    true // group
                );
    
                return s;
            };
    
            this.$scenarioAdd_debris = function (p) {
                var
                    n,
                    r,
                    s;
    
                this.dbg && this.$msg_dbg('$scenarioAdd_debris: entered');
    
                r = ~~(1 + (Math.random() * 7));
                switch (r) {
                case 1:
                    s = system.addShips('IST_alloy', 1, p, 19000);
                    break;
                case 2:
                    s = system.addShips('IST_alloy', 2, p, 20000);
                    break;
                case 3:
                    n = 3 + ~~(1 + (Math.random() * 14));
                    s = system.addShips('IST_alloy', n, p, 30000);
                    break;
                default:
                    if (r === 4) {
                        s = system.addShips('cargopod', 1, p, 30000);
                    }
                    s = system.addShips('IST_alloy', r, p, 18000);
                }
                r = Math.random();
                if (r > 0.6) return;
                this.dbg && this.$msg_dbg('$scenarioAdd_debris: tharglets');
                n = ~~(1 + (Math.random() * 19));
                if (r < 0.15) {
                    s = system.addShips('cargopod', n, p, 10000);
                    n += 11;
                }
                s = system.addShips('IST_Tharglet', n, p, 29000);
            };
    
            /*
            ==================
            10  TIMER HANDLERS
            ==================
            */
    
            this.$timer_delete_all_exceptDie = function () {
                // We proceed through the timers in order of increasing interval.
                this.$timer_delete_runTweak_2();
                this.$timer_delete_radiation();
                this.$timer_delete_runTweak_3();
                this.$timer_delete_stellar();
                this.$timer_delete_misjumpWarning();
            };
    
            this.$timer_delete_all = function () {
                this.$timer_delete_die();
                this.$timer_delete_all_exceptDie();
            };
    
            this.$timer_delete_die = function () {
                if (!this.timer_sickness_die) return;
                (this.timer_sickness_die.isRunning) && this.timer_sickness_die.stop();
                delete this.timer_sickness_die;
            };
    
            this.$timer_delete_misjumpWarning = function () {
                if (!this.timer_misjumpWarning) return;
                (this.timer_misjumpWarning.isRunning) && this.timer_misjumpWarning.stop();
                delete this.timer_misjumpWarning;
            };
    
            this.$timer_delete_radiation = function () {
                if (this.timer_radiation_1) {
                    (this.timer_radiation_1.isRunning) && this.timer_radiation_1.stop();
                    delete this.timer_radiation_1;
                }
                if (this.timer_radiation_2) {
                    (this.timer_radiation_2.isRunning) && this.timer_radiation_2.stop();
                    delete this.playerIntervaltimer_radiation_nonPlayer_1;
                }
                if (this.timer_radiation_3) {
                    (this.timer_radiation_3.isRunning) && this.timer_radiation_3.stop();
                    delete this.timer_radiation_3;
                }
            };
    
            this.$timer_delete_runTweak_2 = function () {
                if (!this.timer_runTweak_2) return;
                (this.timer_runTweak_2.isRunning) && this.timer_runTweak_2.stop();
                delete this.timer_runTweak_2;
            };
    
            this.$timer_delete_runTweak_3 = function () {
                if (!this.timer_runTweak_3) return;
                (this.timer_runTweak_3.isRunning) && this.timer_runTweak_3.stop();
                delete this.timer_runTweak_3;
            };
    
            this.$timer_delete_stellar = function () {
                if (!this.timer_stellar) return;
                (this.timer_stellar.isRunning) && this.timer_stellar.stop();
                delete this.timer_stellar;
            };
    
    
            /*
            =================
            11  MISCELLANEOUS
            =================
            */
    
            this._index_in_list = function (item, list) {
                var k = list.length;
                while (k--) {
                    if (list[k] === item) return k;
                }
                return -1;
            };
    
            this.$randomOrPseudo = function (pseudoRandomChance, pseudoRandomSeed) {
                const r = Math.random();
                // if (this.dbg_full) return r; // TEST. COMMENT THIS OUT WHEN NOT USING
                return r <= pseudoRandomChance ?
                    system.scrambledPseudoRandomNumber(pseudoRandomSeed) :
                    r;
            };
    
            this.$shipDockedWithStation_hijacked = function (station) {
                // The respective values of 'choicesKey' and 'choice' must match values in missiontext.plist
                this.showScreen = 'rejectPage';
                mission.runScreen({
                        title: 'Generation Ship (Hijacked)',
                        color: 'greenColor',
                        message: 'You have docked at a highjacked generation ship.\num\nTen out of ten for style, but minus several million for good thinking, yeah?',
                        model: 'thargoid',
                        choicesKey: 'IST_thargoid_genShipDocked'
                    },
                    function (choice) {
                        if (choice !== '1_RETURN') return;
                        if (Math.random() < 0.3) {
                            player.commsMessage('The thargoids got you!');
                            player.ship.launch(); // have to have this before explode the ship
                            if (!this.timer_sickness_die) {
                                this.timer_sickness_die = new Timer(
                                    this,
                                    this.$sickness_die_timed,
                                    1.7
                                );
                                return;
                            }
                        } else {
                            player.ship.launch();
                            station.reactToAIMessage('ATTACKED');
                            var damaged = false;
                            if (Math.random() < 0.6) {
                                if (player.ship.equipment.length > 0) {
                                    var
                                        i,
                                        equipmentToEffect,
                                        l;
                                    const
                                        ps_equip_list = player.ship.equipment,
                                        ps_equip_status = player.ship.equipmentStatus;
                                    if (Math.random() < 0.6) {
                                        l = 1;
                                    } else {
                                        l = 1 + ~~(1 + (Math.random() * 5));
                                    }
                                    i = l;
                                    while (i--) {
                                        equipmentToEffect = ps_equip_list[~~(ps_equip_list.length * Math.random())];
                                        if (ps_equip_status(equipmentToEffect.equipmentKey) === 'EQUIPMENT_OK') {
                                            player.ship.setEquipmentStatus(equipmentToEffect.equipmentKey, 'EQUIPMENT_DAMAGED');
                                        }
                                    }
                                    damaged = true;
                                }
                            }
                            if (Math.random() < 0.5) {
                                const r = 50 + ~~(1 + (Math.random() * 150));
                                player.ship.energy = (player.ship.energy - r);
                                damaged = true;
                            }
                            if (damaged === true) this.mySound_hullBang.play();
                        }
                    } // End 'function choice'
                );
            };
    
            // Takes: nothing.
            // Moves the player ship.
            // Returns: new position of player ship.
            this.$shift_stellar_timed = function () {
                this.dbg && this.$msg_dbg('$shift_stellar_timed: entered');
                if (!player.ship.isValid) return;
                if (system.isInterstellarSpace) return;
                var
                    newPosition = new Vector3D(), // Creates the vector and sets it to [0, 0, 0]
                    x,
                    y,
                    z;
                this.$msg_player('Drive malfunction - missed witchPoint!', 10);
                if (Math.random() > this.stellar.misplace.prob_nearSun && !system.sun.hasGoneNova) {
                    x = (Math.random() * 4000000) + 50000;
                    y = (Math.random() * 4000000) + 50000;
                    z = (Math.random() * 4000000);
                    const oldPosition = new Vector3D([0, 0, 0]);
                    newPosition = oldPosition.add([x, y, z]);
                } else {
                    this.$msg_player('ALERT - Approaching the sun.', 10);
                    x = (Math.random() * 200000) - 50000;
                    y = (Math.random() * 200000) - 50000;
                    z = (Math.random() * 200000) - 50000;
                    newPosition = system.sun.position.subtract([x, y, z]);
                }
                player.ship.position = newPosition;
                this.mySound_warning.play();
            };
    
            this.$sickness_die_timed = function () {
                // The above format, with its repetition of the function name, is right. See http://aegidian.org/bb/viewtopic.php?position=257147#p257147.
                this.$timer_delete_die();
                // Do not automatically delete it, because it might not be running; other things can call this.
                if (!player.ship.isValid) return;
                const playerShip = player.ship;
                // Next two lines are to avoid auto-eject nonsense, etc.
                if (playerShip.equipmentStatus('EQ_AUTO_EJECT') === 'EQUIPMENT_OK') player.ship.removeEquipment('EQ_AUTO_EJECT');
                if (playerShip.equipmentStatus('EQ_EEU') === 'EQUIPMENT_OK') player.ship.removeEquipment('EQ_EEU');
                player.ship.explode();
            };
    
            this.$radiation_timed_1 = function () {
                if (!system.isInterstellarSpace || !player.ship.isValid) {
                    this.$timer_delete_radiation();
                    return;
                }
                const radiation = this.interstellar_data.scenarios.versions.novel.subScenarios_individual.radiation;
                if (player.ship.equipmentStatus('EQ_HEAT_SHIELD') === 'EQUIPMENT_OK') {
                    player.ship.energy -= radiation.player.damagePerRun.heatShield;
                } else {
                    player.ship.energy -= radiation.player.damagePerRun.standard;
                }
            };
    
            this.$radiation_timed_2 = function () {
                if (!system.isInterstellarSpace || !player.ship.isValid) {
                    this.$timer_delete_radiation();
                    return;
                }
                // this.dbg && this.$msg_dbg('$radiation_timed_nonPlayer.1 - entered');  // TEST
                // Treat only (up to 32) ships (powered or unpowered) within player's scanner range - for speed.
                const
                    radiation = this.interstellar_data.scenarios.versions.novel.subScenarios_individual.radiation,
                    s = player.ship.checkScanner();
                var
                    d,
                    i = s.length;
    
                while (i--) {
                    if (!s[i].isValid) {
                        continue;
                    }
                    if (s[i].isThargoid) {
                        d = radiation.nonPlayer.damagePerRun.thargoid;
                    } else {
                        if (s[i].heat_insulation > 1) {
                            d = radiation.nonPlayer.damagePerRun.heatShield;
                        } else {
                            d = radiation.nonPlayer.damagePerRun.standard;
                        }
                    }
                    if (s[i].energy > d) {
                        s[i].energy -= d;
                    } else {
                        if (s[i].isCargo && s[i].commodity === 'radioactives') {
                            s[i].becomeCascadeExplosion();
                        } else {
                            s[i].explode();
                        }
                    }
                    // this.dbg && this.$msg_dbg('dealt damage: ' + d); // TEST
                }
            };
    
            this.$radiation_timed_3 = function () {
                if (!system.isInterstellarSpace || !player.ship.isValid) {
                    this.$timer_delete_radiation();
                    return;
                }
                this.sickness.prob_getting_current += 0.001;
                system.sendAllShipsAway();
            };
    
            this.$playerStartedJumpCountdown_misjumpWarningTimed = function () {
    
                const
                    playerShip = player.ship;
    
                const
                    newPlace = playerShip.cursorCoordinates,
                    oldPlace = playerShip.galaxyCoordinates;
    
                const dx = newPlace.x - oldPlace.x;
                const dy = (newPlace.y - oldPlace.y) / 2;
                const d = ~~(0.5 + Math.sqrt(dx * dx + dy * dy) * 2) / 10; // half of jump distance.
                this.mySound_warning.play();
                player.commsMessage('Warning: witchspace disturbance detected at ' + d + ' LY ahead!');
                delete this.timer_misjumpWarning;
            };
    
    
            /*
            ===================
            12  EVENT HANDLERS
                alphabetically ordered and comprising only the top-level handlers,
                i.e. the ones that the game itself calls.
                But excluding (re)populator functions.
            ===================
            */
    
            this.escapePodSequenceOver = function () {
                // Is no player ship by this point.
                this.overrideThargoidOXPs(false)();
                if (!system.isInterstellarSpace || player.alertCondition === 0) return;
    
                // Latter condition = is docked already. NB: Apparently is no way of checking whether something else has already set the escape pod location. . So on we go!
    
                var s;
                /*
                ------------------
                Thargoids get pod?
                ------------------
                */
                s = system.filteredEntities(this, this.$is_thargoid_obedient);
                const l = s.length;
                if (l > 0) {
                    var actualChanceBugsGetPod = this.escapeSequence.prob_thargoidsGetPod;
                    s = system.filteredEntities(this, this.$is_thargoid_not);
                    if (l === 0) {
                        actualChanceBugsGetPod += (l / 60);
                    } else {
                        const difference = l - l;
                        actualChanceBugsGetPod *= (difference / 5);
                    }
                    if (actualChanceBugsGetPod > 0.85) actualChanceBugsGetPod = 0.99;
                    if (Math.random() <= actualChanceBugsGetPod) {
                        this.$timer_delete_die();
                        player.setEscapePodDestination(null); // = you die.
                        this.$msg_player('Thargoids destroyed your escape pod.', 15);
                        return;
                    }
                }
    
                /*
                --------------------
                Delete (most) timers
                --------------------
                */
    
                this.$timer_delete_all_exceptDie();
                // Let us ignore any radiation - which, anyway, will make the presence of ships very unlikely.
    
    
                /*
                -------------------------------
                Rescue (and deletion of timers)
                -------------------------------
                */
    
                var actualEscapeSequence_rescueProb;
                if (
                    player.bounty > 39 + ~~(1 + (Math.random() * 11))
                ) {
                    actualEscapeSequence_rescueProb = this.escapeSequence.rescueProb / 3;
                } else {
                    actualEscapeSequence_rescueProb = this.escapeSequence.rescueProb;
                }
                if (
                    (player.bounty > 39 + ~~(1 + (Math.random() * 11))) &&
                    (Math.random() > 0.2)
                ) {
                    // No chance of rescue, because legally too dirty.
                    player.setEscapePodDestination(null);
                    return;
                }
    
                s = system.shipsWithRole('behemoth');
                const s2 = system.shipsWithRole('leviathan');
                s = s.concat(s2);
                if (s.length > 0) {
                    if (Math.random() <= actualEscapeSequence_rescueProb) {
                        player.setEscapePodDestination(s[0]);
                        return;
                    }
                }
    
                /*
                    By this stage of the function/game, it is certain - or nearly - that the player will not get picked up.
                    If there was radiation, we could display a message about that, but let us not.
                */
            };
    
            this.playerCancelledJumpCountdown = function () {
                this.jumpIsGalactic = false; // I think that do not _really_ need this.
                this.$timer_delete_misjumpWarning();
            };
    
            this.playerEnteredNewGalaxy = function (galaxyNumber) {
                this.galNum = galaxyNumber;
                // Store galaxy number (because we use it in $tweak_1_beforeWillExit_choose_move_setOrigin_InitRepop).
                // Adjust various probabilities by galaxy
                const code = script.interstellar_codeForNovel;
    
                // Galaxies are numbered 0-7.
                switch (galaxyNumber) {
                case 0:
                    code.novelSubScenario_multiplyWeight('thargoids', 0.85);
                    code.novelSubScenario_multiplyWeightByMember('thargoidsRebels', 0);
                    code.novelSubScenario_revertWeight('asteroids');
                    code.novelSubScenario_revertWeight('empty');
                    break;
                case 1:
                    code.novelSubScenario_multiplyWeightByMember('thargoidsRebels', 0);
                    code.novelSubScenario_revertWeight('asteroids');
                    code.novelSubScenario_revertWeight('empty');
                    code.novelSubScenario_revertWeight('thargoids');
                    break;
                case 2:
                    code.novelSubScenario_revertWeight('asteroids');
                    code.novelSubScenario_revertWeight('empty');
                    code.novelSubScenario_revertWeight('thargoids');
                    break;
                case 3:
                    code.novelSubScenario_multiplyWeight('empty', 1.15);
                    code.novelSubScenario_multiplyWeight('thargoids', 0.9);
                    code.novelSubScenario_multiplyWeightByMember('thargoidsRebels', 2);
                    code.novelSubScenario_revertWeight('asteroids');
                    break;
                case 4:
                    code.novelSubScenario_multiplyWeight('radiation', 1.05);
                    code.novelSubScenario_multiplyWeightByMember('thargoidsRebels', 1.5);
                    code.novelSubScenario_revertWeight('asteroids');
                    code.novelSubScenario_revertWeight('empty');
                    code.novelSubScenario_revertWeight('thargoids');
                    break;
                case 5:
                    code.novelSubScenario_multiplyByMember('asteroids', 1.23);
                    code.novelSubScenario_revertWeight('empty');
                    code.novelSubScenario_revertWeight('thargoids');
                    break;
                case 6:
                    code.novelSubScenario_multiplyWeightByMember('thargoidsRebels', 0);
                    code.novelSubScenario_revertWeight('asteroids');
                    code.novelSubScenario_revertWeight('empty');
                    code.novelSubScenario_revertWeight('thargoids');
                    break;
                case 7:
                    code.novelSubScenario_multiplyWeight('radiation', 1.3);
                    code.novelSubScenario_multiplyWeightByMember('thargoidsRebels', 0);
                    code.novelSubScenario_revertWeight('empty');
                    code.novelSubScenario_revertWeight('thargoids');
                    if (this.oxp.missionaries) {
                        script.interstellar_codeForNovel.novelSubScenario_multiplyByMember('lobsters', 1.2);
                        // The adding function itself - this.$scenarioAdd_ships_lobsters - increases the number of lobsters in this galaxy.
                        // TO DO: REMOVE LOBSTERS etc. IF NO OXP
                    }
                }
            };
    
            this.playerStartedJumpCountdown = function (jump) {
                if (system.isInterstellarSpace) {
                    this.isIntraInterstellar = true;
                } else {
                    this.isIntraInterstellar = false;
                }
                if (this.oxp.thargoidDrive && worldScripts['murphy-thargoid-drive.js'].jumping) return;
                if (
                    jump === 'galactic' ||
                    (this.oxp.galacticMisJump && worldScripts['Galactic Misjump'].gmj_active === 1)
                ) {
                    this.jumpIsGalactic = true;
                    return;
                }
                this.jumpIsGalactic = false;
                // Proceed only if a misjump scripted.
                if (!player.ship.scriptedMisjump) return;
    
                // MisJump Analyzer: make it do something if it is not doing something already.
                if (
                    this.oxp.misJumpAnalyzer &&
                    !worldScripts.misjump_analyser.misjump &&
                    (player.ship.equipmentStatus('EQ_MISJUMP_ANALYSER') == 'EQUIPMENT_OK')
                ) {
                    var prob;
                    if (system.isInterstellarSpace) {
                        prob = 0.8;
                    } else {
                        prob = 0.9;
                    }
                    var r = Math.random();
                    if (r > prob) return;
                    // Otherwise . .
                    if (r < 0.5) {
                        this.timer_misjumpWarning = new Timer(
                            this,
                            this.$playerStartedJumpCountdown_misjumpWarningTimed,
                            5
                        );
                        return;
                    }
                    // Otherwise . .
                    this.timer_misjumpWarning = new Timer(
                        this,
                        this.$playerStartedJumpCountdown_misjumpWarningTimed,
                        6
                    );
                }
            };
    
            this.playerWillEnterWitchspace = function () {
                this.dbg && this.$msg_dbg('playerWillEnterWitchspace - entered');
                /*
                    The shipWillEnterWitchspace handler is called immediately before a witchspace jump, while the player is still in the starting system.
                */
                this.$timer_delete_radiation();
                this.sickness.willDie = false;
                this.govLevelLeaving = system.government;
    
            };
    
            this.shipDockedWithStation = function (station) {
                if (!player.ship.docked) return;
                this.$timer_delete_all();
                this.sickness.prob_getting_current = this.sickness.prob_getting;
                if (player.ship.dockedStation.name === 'Generation Ship (Hijacked)') {
                    this.$shipDockedWithStation_hijacked(station);
                    return;
                }
                // Otherwise . .
                if (player.ship.dockedStation.isMainStation) {
                    this.sickness.isSick = false;
                    return;
                }
                // Otherwise . .
                if (Math.random() < 0.2) {
                    this.sickness.isSick = false;
                }
            };
    
            this.shipExitedWitchspace = function () {
                // this.dbg && this.$msg_dbg('shipExitedWitchspace');
                if (
                    !player.ship.isValid ||
                    (this.oxp.thargoidDrive && worldScripts['murphy-thargoid-drive.js'].jumping)
                ) {
                    return;
                }
                if (this.jumpIsGalactic) {
                    this.jumpIsGalactic = false;
                    // TO DO: recompute weights
                    return;
                }
                this.jumpIsGalactic = false;
            };
    
            // this.shipLaunchedFromStation = function (station) {
            // };
    
            this.shipWillDockWithStation = function (station) {
                if (station.name === 'Generation Ship (Hijacked)') {
                    station.suppressArrivalReports = true;
                }
            };
    
            this.shipWillExitWitchspace = function () {
                // From the wiki: 'The shipWillExitWitchspace handler is called as a witchspace jump concludes. When it is called, the player is (from a program perspective) in the destination system, but the tunnel effect has not yet been shown.'
                this.dbg && this.$msg_dbg('shipWillExitWitchspace: entered');
                if (system.isInterstellarSpace) return;
                /*
                =============================
                TWEAK 2: STELLAR - misplacing
                =============================
                */
                var realProb = this.stellar.misplace.prob;
                if (player.ship.energy < 60) {
                    realProb += 0.02;
                }
                if (
                    this.breakableDrive &&
                    player.ship.equipmentStatus('EQ_BREAKABLE_WITCHDRIVE') === 'EQUIPMENT_DAMAGED'
                ) {
                    realProb += 0.15;
                }
                if (Math.random() > realProb) return;
                this.$timer_delete_stellar();
                this.timer_stellar = new Timer(
                    this,
                    this.$shift_stellar_timed,
                    this.stellar.misplace.secsAfterWillExitWitchspace
                );
            };
    
            this.shipWillLaunchFromStation = function () {
                var i;
                /*
                ===================
                GAME INITIALISATION
                ===================
                */
                if (!this.initialisedAtFirstLaunch) {
                    /*
                    --------------
                    Fuel collector
                    --------------
                    */
                    if (this.oxp.fuelCollector) {
                        // this.dbg && this.$msg_dbg('Hacking fuel collector ..');
                        // Remove the role 'shuttle' from the Fuel Collector's list.
                        // For, it allows at least some shuttles to be hacked *while still flying*.
                        if (this.$isWithinArray('shuttle', worldScripts['Fuel Collector'].legitDerelictRoles)) {
                            worldScripts['Fuel Collector'].legitDerelictRoles.splice('shuttle');
                        }
                        // Add some roles to Fuel Collector's list of hackable entities.
                        const rolesCopy = worldScripts['Fuel Collector'].legitDerelictRoles.slice(0);
                        // The above copies worldScripts['Fuel Collector'].legitDerelictRoles to rolesCopy.
                        const roles = new Array(
                            'assassin-heavy',
                            'assassin-light',
                            'assassin-medium',
                            'escort-heavy',
                            'escort-medium',
                            'hiredGuns_escortHigh',
                            'hiredGuns_escortLow',
                            'hiredGuns_escortMid',
                            'hunter-heavy',
                            'hunter-medium',
                            'IST_derelict',
                            'IST_derelict_thargoid',
                            'IST_derelict_viper',
                            'IST_derelict_live',
                            'IST_mil',
                            'IST_miner',
                            'IST_pirate',
                            'pirate-aegis-raider',
                            'pirate-heavy-fighter',
                            'pirate-heavy-freighter',
                            'pirate-interceptor',
                            'pirate-light-fighter',
                            'pirate-light-freighter',
                            'pirate-medium-fighter',
                            'pirate-medium-freighter',
                            'trader-courier',
                            'trader-smuggler'
                        );
                        // ^ FIX ME: somehow, the fuel collector cannot hack the live derelict.
                        i = roles.length;
                        while (i--) {
                            if (!this.$isWithinArray(roles[i], rolesCopy))
                                worldScripts['Fuel Collector'].legitDerelictRoles.push(roles[i]);
                        }
                        log(
                            this.description,
                            'FOR INFO: fuel-collector elligible roles are now: ' +
                            worldScripts['Fuel Collector'].legitDerelictRoles
                        );
                    }
                    /*
                    ----
                    Rank
                    ----
                    */
                    if (!this.dbg_ignorePlayerRank) {
    
                        const score = player.score;
    
                        if (score > 2560) {
    
                            // 'Deadly'
    
                            const
                                modified = this.interstellar_data.scenarios.versions.modified,
                                novel = this.interstellar_data.scenarios.versions.novel;
    
                            if ((modified.probOverrideThargoidOXP > 0) && (modified.probOverrideThargoidOXP < 1)) {
                                modified.probOverrideThargoidOXP *= 0.2;
                            }
                            this.interstellar_codeForNovel.novelSubScenario_multiplyByMember('thargoids', 1.1);
    
                            if (score > 6400) {
    
                                // 'Elite'
    
                                novel.subScenarios_individual.pirates.baseNum += 1;
                                novel.subScenarios_individual.thargoids.baseNum += 1;
    
                                if (score > 19200) {
    
                                    // Way above 'Elite'
    
                                    const podProb = this.escapeSequence.prob_thargoidsGetPod;
                                    if ((podProb > 0) && (podProb < 1)) {
                                        this.escapeSequence.prob_thargoidsGetPod *= 1.1;
                                    }
                                    if (
                                        (modified.subScenarios.thargoidsOnly.sweeper_prob > 0) &&
                                        (modified.subScenarios.thargoidsOnly.sweeper_prob < 1)
                                    ) {
                                        modified.subScenarios.thargoidsOnly.sweeper_prob *= 1.11;
                                    }
                                    const battleShipLimitProb = this.interstellar_data.scenarios.all.thargoidBattleshipsLimit.prob;
                                    if ((battleShipLimitProb > 0) && (battleShipLimitProb < 1)) {
                                        this.interstellar_data.scenarios.all.thargoidBattleshipsLimit.prob *= 0.75;
                                    }
    
                                } // End above-Elite
                            } // End Elite
                        } // End Deadly
                    } // End `if (!this.dbg_ignorePlayerRank)`
    
    
                    /*
                    -------------------------------------------
                    Save probabilities for novel scenarios.
                    If debugging then print possible scenarios.
                    -------------------------------------------
                    */
                    script.interstellar_codeForNovel.novelSubScenario_saveOriginalWeights;
                    // ^ This saves the probabilities in their current form, i.e. including any rank-inflection.
    
                    if (this.dbg) {
                        var data = script.interstellar_data.scenarios.versions.novel;
                        var names = [];
                        for (i = 0; i < data.subScenarios.length; i++) {
                            if (data.subScenarios[i].weight !== 0) {
                                names.push(data.subScenarios[i].key);
                            }
                        }
                        var output = 'Active novel subscenarios: ' + names.join('; ');
                        this.$msg_dbg(output);
                    }
    
                    /*
                    -----------------
                    Get galaxy number
                    -----------------
                    */
                    this.galNum = system.info.galaxyID;
                    /*
                    -----------------------------
                    Mark that we have initialised
                    -----------------------------
                    */
                    this.initialisedAtFirstLaunch = true;
                    // this.dbg && this.$msg_dbg('Initialisation end');
    
                }
    
                /*
                =======
                MISJUMP
                =======
                */
                if (
                    (!player.ship.scriptedMisjump) &&
                    (Math.random() <= this.interstellar_data.scenarios.misJumpInducedByScript_prob)
                ) {
                    player.ship.scriptedMisjump = true;
                    // The wiki on ship.scriptedMisjump: 'When true, the next hyperspace jump will be a misjump. The scriptedMisjump flag will remain true during the shipWillExitWitchspace() event handler, and will be set to false after the shipExitedWitchspace() event.' 
                    const r = Math.random();
                    if (r <= 0.3) player.ship.scriptedMisjumpRange = r + 0.2;
                    // ship.scriptedMisjumpRange: again, an Oolite value; traditional misjump value is 0.5.
                    this.dbg && this.$msg_dbg('Misjump scheduled');
                }
    
                /*
                ========
                SICKNESS
                ========
                */
                if (this.sickness.isSick) {
                    this.$msg_player('You still have WITCHSPACE SICKNESS. Dock at a main station! Do not jump!', 15);
                    this.mySound_warning.play();
                }
            };
    
            /*
            --------------------------
            AUTORUN 'startUp' function
            and a helper function
            --------------------------
            */
    
            this.$roleExists = function (role) {
                const s = system.addShips(role, 1, [1000000, 1000000, 1000000]);
                if (s && (s.length == 1) && s[0].isValid) {
                    s[0].remove(true);
                    return true;
                }
                return false;
            };
    
    
            this.startUp = function () {
                /*
                ============================================
                This function is run automatically by Oolite
                ============================================
                */
                const
                    thisVeryScript = worldScripts.IST_masterScript;
                var
                    didExcludeSelf = false,
                    i,
                    populators = [],
                    repopulators = [],
                    worldScriptNames_local_barOwn = [],
                    worldScriptNames_local = worldScriptNames;
    
                var worldScripts_local = worldScripts; // worldScripts is an Oolite global.
    
                this.dbg && this.$msg_dbg('startUp');
    
                /*
                ========================================================================================
                Populate our global array this.worldScriptNames_local_barOwn.
                We do so by making a copy of the Oolite global 'worldScripts' and
                removing the name of our own script from it.
    
                WorldScriptNames and worldScripts are Oolite global.
    
                (Some?) properties with worldScripts, and thus within the worlds scripts of other OXPs,
                are editable, allowing us to hack them here.
                =======================================================================================
                */
    
                i = worldScriptNames.length;
                while (i--) {
                    if (
                        !didExcludeSelf &&
                        worldScriptNames_local[i] == this.name
                    ) {
                        didExcludeSelf = true;
                        continue;
                    }
                    const names = worldScriptNames_local[i];
                    worldScriptNames_local_barOwn.push(names);
                    this.dbg && this.$msg_dbg('found worldscript ' + names);
                    if (worldScripts_local[names]) {
                        if (
                            worldScripts_local[names].interstellarSpaceWillPopulate &&
                            worldScripts_local[names].interstellarSpaceWillPopulate !== undefined
                        ) {
                            populators.push(names);
                            this.dbg && this.$msg_dbg('  - script HAS interstellar populator');
                        } else {
                            this.dbg && this.$msg_dbg('  - script lacks interstellar populator');
                        }
                        if (
                            worldScripts_local[names].interstellarSpaceWillRepopulate &&
                            worldScripts_local[names].interstellarSpaceWillRepopulate !== undefined
                        ) {
                            repopulators.push(names);
                            this.dbg && this.$msg_dbg('  - script HAS interstellar re-populator');
                        } else {
                            this.dbg && this.$msg_dbg('  - script lacks interstellar re-populator');
                        }
                    } else {
                        this.dbg && this.$msg_dbg('  - script lacks detectable (re)populator');
                    }
                }
    
                // Now make the local arrays global.
                this.interstellar_data.scripts.populators.length = 0;
                Array.prototype.push.apply(this.interstellar_data.scripts.populators, populators);
                this.interstellar_data.scripts.repopulators.length = 0;
                Array.prototype.push.apply(this.interstellar_data.scripts.repopulators, repopulators);
    
                /*
                NB: Without checking that worldScripts[names] is valid, Missionaries throws an exception, perhaps because it lacks a manifest file.
                TO DO: the list includes Oolite's own populator!
                TO DO: see whether can remove routines that try to undo populators.
                /*
    
                /*
                ====================================================================================================
                Record which OXPS are installed
                (In the case of most but not all of those OXPs, we do so via the array of worldscripts just created.
                Note though that only some worldscripts end with '.js'!)
                ====================================================================================================
                */
    
                const oxp = this.oxp;
                oxp.aliens = this.$isWithinArray('aliens_world_script', worldScriptNames_local_barOwn);
                oxp.behemoth = this.$isWithinArray('behemoth', worldScriptNames_local_barOwn);
                oxp.bombs = this.$isWithinArray('Missiles & Bombs', worldScriptNames_local_barOwn);
                oxp.breakableDrive = this.$isWithinArray('Breakable_WitchDrive', worldScriptNames_local_barOwn);
                oxp.escapePodLocator = this.$isWithinArray('escapePodLocator.js', worldScriptNames_local_barOwn);
                oxp.genShips = this.$isWithinArray('Generation Ships', worldScriptNames_local_barOwn);
                oxp.extraThargoids = this.$isWithinArray('extraThargoids_populator', worldScriptNames_local_barOwn);
                oxp.ferDeLance3G = this.$isWithinArray('ferdelance3G_populator', worldScriptNames_local_barOwn);
                // oxp.fiasco = this.$isWithinArray('military_fiasco', worldScriptNames_local_barOwn); // we use its ships, but need no key
                oxp.fuelCollector = this.$isWithinArray('Fuel Collector', worldScriptNames_local_barOwn);
                oxp.galacticMisJump = this.$isWithinArray('Galactic Misjump', worldScriptNames_local_barOwn);
                oxp.hiredGuns = this.$isWithinArray('hiredGuns_system', worldScriptNames_local_barOwn);
                oxp.interstellarHelp = this.$roleExists('interstellar_help_trader');
                oxp.misJumpAnalyzer = this.$isWithinArray('misjump_analyser', worldScriptNames_local_barOwn);
                oxp.missionaries = this.$roleExists('lobsterworshipper');
                oxp.nexus = this.$roleExists('EQ_NEXUS_BUG_MISSILEb');
                oxp.secondWave = this.$roleExists('secondWave_toughTharglet');
                oxp.spectre = this.$roleExists('spectre');
                // oxp.starDestroyer = this.$isWithinArray('stardestroyer');
                oxp.thargoidDrive = this.$isWithinArray('murphy-thargoid-drive.js', worldScriptNames_local_barOwn);
                oxp.TCAT = this.$isWithinArray('TCAT_masterScript', worldScriptNames_local_barOwn);
                oxp.thargoidCarriers = this.$roleExists('thargoid_carrier');
                oxp.thargoidThreat = this.$isWithinArray('Thargorn_Witchspace_Battle', worldScriptNames_local_barOwn);
                oxp.wormDrones = this.$isWithinArray("Mauiby's Wormhole Drones", worldScriptNames_local_barOwn);
                oxp.YAH = this.$roleExists('asteroid-billboard');
    
                if (this.dbg) {
                    this.$msg_dbg('=== Detection of relevant OXPs ===');
                    var
                        key,
                        keys = Object.keys(oxp),
                        value;
                    for (i = 0; i < keys.length; i++) {
                        key = keys[i];
                        value = oxp[key];
                        this.$msg_dbg('- ' + key + ': ' + value);
                    }
                }
    
    
                /*
                ------------------
                React to some OXPs
                ------------------
                */
    
                const data = this.interstellar_data;
    
                if (!oxp.aliens) {
                    novelSubScenario_multiplyByMember('aliens', 0);
                }
                if (oxp.escapePodLocator) {
                    worldScripts['escapePodLocator.js'].ecl_interstellarSpaceRange = 15;
                    worldScripts['escapePodLocator.js'].ecl_normalSpaceRange = 20;
                }
                if (oxp.misJumpAnalyzer && !this.dbg_testWithMisjumps) {
                    data.misJumpInducedByScript_prob *= 0.2;
                }
                if (!oxp.missionaries) {
                    novelSubScenario_multiplyByMember('lobsters', 0);
                }
                if (oxp.spectre) {
                    data.scenarios.prob_tweak += 0.05;
                    data.scenarios.prob_modifiedAsVsNew -= 0.05;
                }
                if (oxp.thargoidCarriers) {
                    /* 
                    thargoidCarriers OXP spawns carriers on shipExitedWitchspace, at the witchPoint, but only on a (score-inflected) probability.
                    So, if that OXP is installed, then:
                    - substantially increase the chance of (what is now only so-called) onlyNavy;
                    - increase the likely number of naval ships in onlyNavy and in addNavy;
                    - make the novel scenario, which avoids the carriers altogether (or puts them very far away) more likely.
                    */
                    data.scenarios.versions.modified.subScenarios.navyOnly.prob *= 1.25;
                    data.scenarios.versions.modifiedOrNovel.novel_prob *= 1.1;
                }
    
    
                /*
                --------------
                More DEBUGGING
                --------------
                */
    
                this.dbg_full && this.$msg_dbg('Full test mode.');
            };
    
    
            /*
            ------------------------------------------------------------------------------------------------------------
            
            ON 'OVERRIDING' THE THARGORN THREAT OXP
    
            Due to decisions that I now regret, overriding the thargoidThreat OXP does not actually stop it adding ships.
            Rather, it makes it less likely that it does, though it adds a chance of adding them a bit later.
            And where it adds them is always near the witchpoint.
            So, it makes sense, in our 'novel' scenario, always to 'override' the OXP 
            (though in that situation the OXP might still add ships to the witchpoint).
            The relevant code in the Threat OXP is in ThargornWitchspaceBattle.js.
    
            ------------------------------------------------------------------------------------------------------------
            */
    
            this.overrideThargoidOXPs = function (override) {
                if (override) {
                    // DO the override
                    if (this.oxp.thargoidThreat) {
                        this.dbg && this.$msg_dbg('interstellarSpaceWillPopulate: overriding Threat');
                        this.threatOXP_modify = true;
                        this.oxp_overrides.thargoidThreat.overrode = true;
                        // this.threatOXP_modify is the variable that thargoidThreat actually looks for and acts on.
                        // We ourselves do nothing but set (and unset) it.
                    }
                    if (this.oxp.TCAT && missionVariables.otherOXPs_TCAT_override) {
                        this.dbg && this.$msg_dbg('interstellarSpaceWillPopulate: overriding TCAT');
                        missionVariables.otherOXPs_TCAT_override = true;
                        this.oxp_overrides.TCAT.overrode = true;
                    }
                } else {
                    // UNDO any override
                    if (this.oxp.thargoidThreat) {
                        this.dbg && this.$msg_dbg('interstellarSpaceWillPopulate: allowing Threat');
                        this.threatOXP_modify = false;
                        this.oxp_overrides.thargoidThreat.overrode = false;
                        // this.threatOXP_modify is the variable that thargoidThreat actually looks for and acts on.
                        // We ourselves do nothing but set (and unset) it.
                    }
                    if (this.oxp_overrides.TCAT.overrode) {
                        this.dbg && this.$msg_dbg('interstellarSpaceWillPopulate: allowing TCAT');
                        missionVariables.otherOXPs_TCAT_override = false;
                        this.oxp_overrides.TCAT.overrode = false;
                    }
                }
            };
    
    
            /*
            =====================================================================
            13  MAIN TWEAKING ROUTINES, including official (re)populator routines
            =====================================================================
            */
    
            this.interstellarSpaceWillPopulate = function _$tweak_1_beforeWillExit_choose_move_setOrigin_InitRepop() {
                /*
                This function runs immediately before this.playerWillExitWitchspace.
                The function's name must match the one given in planetinfo.plist, only without this script's 'this.' prefix.
                Moreover, only if the function is called (this.)interstellarSpaceWillPopulate will it be detected by other scripts.
                My '=' in the function is only for reading (and/or profiling) purposes.
    
                The function as I implement it does not actually do any population, except for, in some circumstances,
                running Oolite's inbuilt populator and/or the populators of other OXPs.
                (For we, via the plist, we have taken over population of interstellar space.)
                */
    
                this.dbg && this.$msg_dbg('$tweak_1: entered');
    
                /*
                ------------------------------------------------------------
                TWEAK 1: SET SCENARIO
                & some preliminaries, including at least one abort condition
                ------------------------------------------------------------
                */
    
                if (this.jumpIsGalactic) return;
    
                const data = this.interstellar_data;
    
                var scenarioToSet;
    
                if (
                    (
                        (this.oxp.thargoidDrive && worldScripts['murphy-thargoid-drive.js'].jumping) ||
                        (this.oxp.wormDrones && missionVariables.mauiby_multipleJumps)
                    ) ||
                    (this.$randomOrPseudo(0.4, 700001) > data.scenarios.tweak_prob)
                ) {
                    /*
                    //////////////////////
                    Tweak 1: set unchanged
                    //////////////////////
                    */
                    scenarioToSet = 'U';
                    if (Math.random() <= this.interstellar_data.scenarios.versions.modified.probOverrideThargoidOXP) {
                        this.overrideThargoidOXPs(true);
                    } else {
                        this.overrideThargoidOXPs(false);
                    }
                } else {
                    if (this.$randomOrPseudo(0.6, 700002) > data.scenarios.versions.modifiedOrNovel.novel_prob) {
                        /*
                        //////////////////////
                        Tweak 1: set modified
                        //////////////////////
                        */
                        scenarioToSet = 'M';
                        if (Math.random() <= this.interstellar_data.scenarios.versions.modified.probOverrideThargoidOXP) {
                            this.overrideThargoidOXPs(true);
                        } else {
                            this.overrideThargoidOXPs(false)();
                        }
                    } else {
                        /*
                        //////////////////
                        Tweak 1: set novel
                        //////////////////
                        */
                        scenarioToSet = 'N';
                        this.overrideThargoidOXPs(true); // It makes no sense not to do this here. See a comment above.
                    }
                } // End scenario chooser. Now, actually set it.
                data.scenario = scenarioToSet;
    
                /*
                -------------------------
                TWEAK 1: SET SUB-SCENARIO
                -------------------------
                */
    
                var subScenarioToSet;
                switch (scenarioToSet) {
                case 'U':
                    subScenarioToSet = '[none]';
                    break;
                case 'M':
                    /*  
                    //////////////////////////////////////
                    Tweak 1: set sub-scenario for modified
                    //////////////////////////////////////
                    */
                    const modified_subScenarios = data.scenarios.versions.modified.subScenarios;
                    if (this.$randomOrPseudo(0.45, 700003) <= modified_subScenarios.thargoidsOnly.prob) {
                        // Only thargoids
                        subScenarioToSet = 'OT';
                    } else if (Math.random() <= modified_subScenarios.navyOnly.prob) {
                        // Only navy
                        subScenarioToSet = 'ON';
                    } else {
                        /*
                            Add navy (our default)
                        */
                        subScenarioToSet = 'NA';
                    }
                    break;
                case 'N':
                    /*  
                    -----------------------
                    NOVEL - set subscenario
                    -----------------------
                    */
    
                    // PIRATES
                    const multiplier = 1.25 -
                        (
                            (this.govLevelLeaving + System.infoForSystem(this.galNum, player.ship.targetSystem).government) / 14
                        ) * 0.25;
    
                    script.interstellar_codeForNovel.novelSubScenario_multiplyByMember('pirates', multiplier);
                    // ^ We revert this after implementing the subscenario.
    
                    // ACTUALLY SET
                    subScenarioToSet = this.interstellar_codeForNovel.novelSubScenario_choose();
                    // ^ e.g., 'aliens+asteroids'
                    break;
                default:
                    this.$msg_err('Bad scenario for this.$tweak_1');
                }
    
                data.subScenario = subScenarioToSet;
                this.dbg && this.$msg_dbg('$tweak_1: set scenario <' + scenarioToSet + '>, subScenario <' + subScenarioToSet + '>');
    
                /*
                ------------------------------------------
                TWEAK 1: CONSIDER RUNNING OTHER POPULATORS
                ------------------------------------------
                */
    
                if (this.interstellar_data.scenario !== 'N') {
    
                    const
                        populators = this.interstellar_data.scripts.populators,
                        worldScripts_local = worldScripts; // worldScripts is an Oolite global.
    
                    var i;
    
                    worldScripts_local['oolite-populator'].interstellarSpaceWillPopulate();
                    i = populators.length;
                    while (i--) {
                        if (!worldScripts_local[populators[i]]) {
                            this.$msg_err('this.interstellarTweaks_populate_interstellar - undefined populator');
                        } else {
                            try {
                                worldScripts_local[populators[i]].interstellarSpaceWillPopulate();
                            } catch (err) {
                                this.$msg_err(
                                    'interstellarTweaks_populate_interstellar: problem with populator ' +
                                    worldScripts_local[populators[i]]
                                );
                            }
                        }
                    }
                }
    
                /*
                -------------------------------------------
                TWEAK 1: MOVE PLAYER, SET ORIGIN, SET TIMER
                -------------------------------------------
                */
    
                const scenario = this.interstellar_data.scenario;
    
                var origin; // Scenarios M and N define this; scenario U leaves it undefined
    
                this.dbg && this.$msg_dbg('$tweak_1: scenario <' + scenario + '>');
    
                /*
                In scenarios N and M, we move the player to a temporary location; we should do this as soon as possible.
                Set the 'origin' to [0, 0, 0] (which happens just by creating a new 'Vector3D').
                Or rather set the function variable 'origin'; at the end of the function we assign that variable to a global.
                The origin is where the player will go later and where we will populate with NPCs.
                */
    
                if (!player.ship.isValid) {
                    this.$msg_err('$tweak_1: problem with player.ship');
                    return;
                }
    
                var playerPositionTemporary = new Vector3D();
                switch (scenario) {
                case 'M':
                    // Move player & set origin (or rather leave origin at the [0,0,0] that, via 'new Vector3D', it was created with).
                    playerPositionTemporary = ([350000000, 350000000, 350000000]);
                    player.ship.position = playerPositionTemporary;
                    origin = new Vector3D([0, 0, 0]);
                    this.dbg && this.$msg_dbg(
                        '$tweak_1: scenario <M> - moved player temporarily to <' +
                        playerPositionTemporary +
                        '>; set local origin to <(0,0,0)>'
                    );
                    break;
                case 'N':
                    /*
                    As in 'M'. move player to temporary location & set local origin - except that the origin we set is not that of 'M'.
                    */
                    const x = 150000 + ~~(Math.random() * 300000000);
                    const y = 150000 + ~~(Math.random() * 300000000);
                    const z = 150000 + ~~(Math.random() * 300000000);
                    playerPositionTemporary = ([350000000, 350000000, 350000000]);
                    origin = new Vector3D([x, y, z]);
                    player.ship.position = playerPositionTemporary;
                    this.dbg && this.$msg_dbg(
                        '$tweak_1: scenario <N> - moved player to temporary location ' +
                        playerPositionTemporary +
                        '; have set local origin to' +
                        origin
                    );
                    break;
                case 'U':
                    // Leave the origin at default.
                    break;
                default:
                    this.$msg_err('$tweak_1: bad scenario <' + scenario + '>');
                    // Do not return, because if we do not, below, set the origin, then we will have other problems.
                }
    
                /*
                ///////////////////////////////////////////
                TWEAK 1: MOVE PLAYER, SET ORIGIN, SET TIMER
                ///////////////////////////////////////////
                */
    
                // SET GLOBAL ORIGIN, if we've defined the local origin.
                if (origin) {
                    this.interstellar_data.origin = origin;
                    this.dbg && this.$msg_dbg(
                        '$tweak_1: have set global origin to: ' +
                        origin
                    );
                }
    
                // SET TIMER - which we should run even in 'U', because the timed routine treats, inter alia, sickness.
                this.timer_runTweak_2 = new Timer(
                    this,
                    this.$tweak_2_afterWillExit_populate,
                    this.interstellar_data.timing.runTweak_2
                );
            };
    
    
            this.$tweak_2_afterWillExit_populate = function () {
    
                this.dbg && this.$msg_dbg('$tweak_2: entered');
    
                this.$timer_delete_runTweak_2(); // Delete the timer that called the current function.
    
                if (!player.ship.isValid) {
                    this.$msg_err('$tweak_2: problem with player.ship');
                    return;
                }
    
                /*
                =====================
                TWEAK 2: INTERSTELLAR
                =====================
                */
    
                const
                    scenario = this.interstellar_data.scenario,
                    scenarios = this.interstellar_data.scenarios,
                    subScenario = this.interstellar_data.subScenario;
                var
                    i,
                    n,
                    s;
    
                if (this.dbg) {
                    this.$msg_dbg('$tweak_2: further implementing <' + scenario + '-' + subScenario + '>');
                }
    
                /*
                ----------------------------------------
                Tweak 2: ANY INTERSTELLAR - ship removal
                ----------------------------------------
                */
    
                var scenariosAll = scenarios.all;
    
                // GENERATION SHIPS (but not thargoid ones)
                if (this.oxp.genShips) {
                    if (Math.random() <= scenariosAll.genShipsRemove_prob) {
                        this.dbg && this.$msg_dbg('$tweak_2: removing any genShips');
                        s = system.shipsWithRole('generationship');
                        i = s.length;
                        while (i--) {
                            if (s[i].isValid) {
                                s[i].remove(true); // 'true' = suppress any death event
                            }
                        }
                    } else if (this.dbg) {
                        this.$msg_dbg('$tweak_2: leaving any genShips');
                    }
                }
    
                /*
                THARGOID BATTLESHIPS
                Ignore here the so-called 'overriding' .
                */
                if (this.oxp.thargoidThreat) {
                    if (Math.random() <= scenariosAll.thargoidBattleshipsLimit.prob) {
                        s = system.filteredEntities(this, this.$is_thargoid_battleship);
                        n = scenariosAll.thargoidBattleshipsLimit.maxShips;
                        if (s.length > n) {
                            i = s.length - n;
                            while (i--) s[i].remove(true);
                        }
                        this.dbg && this.$msg_dbg('$tweak_2: limited any battleships to ' + n);
                    } else if (this.dbg) {
                        this.$msg_dbg('$tweak_2: ignored battleships');
                    }
                }
    
                // HELPERS / HELPEES
                if (this.oxp.interstellarHelp) {
                    if (
                        (Math.random() <= scenariosAll.wouldBeHelpee_remove_prob) &&
                        worldScripts.interstellar_help.interstellarHelp !== 'FOLLOWING'
                    ) {
                        this.dbg && this.$msg_dbg('$tweak_2: removing any would-be helpees');
                        s = system.filteredEntities(this, this.$is_helpShip);
                        i = s.length;
                        while (i--) {
                            if (s[i].isValid) {
                                s[i].remove(true); // 'true' = suppress any death event
                            }
                        }
                    } else if (this.dbg) {
                        this.$msg_dbg('$tweak_2: left any would-be helpees alone');
                    }
                }
    
                /*
                ---------------------------
                Tweak 2: SPECIFIC SCENARIOS
                ---------------------------
                */
    
                /*
                ///////////////////////////
                Tweak 2: UNCHANGED SCENARIO
                ///////////////////////////
                */
    
                if (scenario === 'U') {
                    this.dbg && this.$msg_dbg('$tweak_2: scenario is <U>; ending here');
                    return; // NB
                }
    
                /*
                //////////////////////////
                Tweak 2: MODIFIED OR NOVEL
                //////////////////////////
                */
    
                /* Initialise repopulation */
                const repopulation = this.interstellar_data.scenarios.versions.modifiedOrNovel.repopulation;
                if (Math.random() > repopulation.probEver) {
                    repopulation.num_max_current = 0;
                } else {
                    repopulation.num_max_current = ~~(
                        1 + (Math.random() * repopulation.num_max_ever)
                    );
                }
                repopulation.num = 0;
                this.dbg && this.$msg_dbg(
                    '$tweak_2: repopulation.num_max_current set to ' +
                    repopulation.num_max_current +
                    ' of a possible ' +
                    repopulation.num_max_ever
                );
    
                /* Set up origin alias */
                const origin = this.interstellar_data.origin;
    
                if (scenario === 'M') {
                    /*
                    //////////////////////////
                    Tweak 2: MODIFIED SCENARIO
                    //////////////////////////
                    */
                    const
                        battleOccured = this.interstellar_data.scenarios.versions.modified.battleOccured,
                        subScenarios = this.interstellar_data.scenarios.versions.modified.subScenarios;
    
                    switch (subScenario) {
                    case 'ON':
                        /*
                        **************************************
                        Tweak 2: modified scenario - only navy
                        **************************************
                        */
                        this.$remove_navyIncompatible();
                        if (system.countEntitiesWithScanClass('CLASS_MILITARY') < 4) {
                            s = this.$scenarioAdd_ships_navy(
                                subScenarios.navyOnly.maxNum,
                                origin,
                                24500,
                                false // rings
                            );
                        }
                        break;
                    case 'OT':
                        /*
                        *******************************************
                        Tweak 2: modified scenario - only thargoids
                        *******************************************
                        */
                        this.$remove_nonThargoids();
                        if (system.countEntitiesWithScanClass('CLASS_THARGOID') === 0) {
                            s = this.$scenarioAdd_ships_thargoids(
                                subScenarios.thargoidsOnly.baseNum,
                                origin,
                                25000,
                                0, // = rebel chance; add rebels only in the novel scenario
                                0, // = thargoid genShip chance; add a thargoid genShip only in the novel scenario
                                false // rings
                            );
                        }
                        break;
                    case 'NA':
                        /*
                        *************************************
                        Tweak 2: modified scenario - add navy
                        *************************************
                        */
                        s = this.$scenarioAdd_ships_navy(
                            subScenarios.navyAdd.maxNum,
                            origin,
                            21500,
                            false // rings
                        );
                        break;
                    default:
                        this.$msg_err(
                            '$tweak_3_afterExit_tempShift_setOrigin_populate_setTimer: bad modified subScenario <' +
                            subScenario +
                            '>'
                        );
                    } // End 'switch (subScenario)'
    
                    if (Math.random() <= battleOccured.prob) {
                        /*
                        ************************************
                        Tweak 2: modified scenario - battle?
                        ************************************
                        */
                        this.dbg && this.$msg_dbg('$tweak_2: battle occurred');
    
                        /*
                        The following needs us to have defined some 's', which we use to (!) scan the area.
                        (If we were to do it without that, then it would be hard and expensive.)
                        */
                        if (
                            s &&
                            s[0] &&
                            s[0].isValid
                        ) {
                            if (Math.random() > battleOccured.prob_wreckageOnly) {
                                var l;
                                this.dbg && this.$msg_dbg('$tweak_2: damaging');
                                s = s[0].checkScanner(true);
                                l = s.length;
                                i = ~~(Math.random() * l);
                                while (i--) {
                                    if (s[i].isValid) {
                                        s[i].energy -= 40 + (i * 40);
                                    }
                                }
                            }
                        } // End if-s-etc.
                        this.$scenarioAdd_debris(origin);
                        if (Math.random() <= battleOccured.prob_derelict) {
                            this.dbg && this.$msg_dbg('$tweak_2: derelict');
                            s = this.$scenarioAdd_ships(
                                'IST_derelict',
                                1,
                                origin,
                                50000,
                                false, // rings
                                false // group
                            );
                        }
                    } // End if-battle
                } else if (scenario === 'N') {
                    /*
                    ///////////////////////
                    TWEAK 2: NOVEL SCENARIO
                    ///////////////////////
    
                    /*
                    /////////////////////////////////////////
                    Tweak 2: novel scenario - remove unwanted
                    /////////////////////////////////////////
                    */
                    if (
                        this.oxp.interstellarHelp && (
                            worldScripts.interstellar_help.interstellarHelp === 'FOLLOWING' ||
                            Math.random() > 0.75
                        )
                    ) {
                        s = system.filteredEntities(this, this.$is_unwantedInNovelInitially_allowHelpees);
                    } else {
                        s = system.filteredEntities(this, this.$is_unwantedInNovelInitially);
                    }
                    i = s.length;
                    this.dbg && this.$msg_dbg('$tweak_2: removing any unwanted');
                    while (i--) {
                        if (s[i].isValid) {
                            s[i].remove(true);
                        }
                    }
    
                    /*
                    ////////////////////////////////////////////////////////
                    Tweak 2: novel scenario - execute pre-chosen subScenario
                    ////////////////////////////////////////////////////////
                    */
    
                    this.interstellar_codeForNovel.novelSubScenario_run({
                        origin: origin
                    });
                    /*
                        If need to pass > 1 argument, do:
                            var myArgs = { origin: origin };
                            this.interstellar_codeForNovel.novelSubScenario_run(myArgs);
                    */
    
                    /*
                    ///////////////////////////////////////////////////////////////////////////////
                    Tweak 2: novel scenario - revert any per-interstellar-jump probability changes.
                    ///////////////////////////////////////////////////////////////////////////////
                    */
    
                    this.interstellar_codeForNovel.novelSubScenario_revertByMember('pirates');
    
                } // End 'if scenario'
    
                /*
                /////////////////////////////////////////////////////////////////////////////////////////////
                Tweak 2: set timer
                (Were we to move the player - if the player has moved - to the permanent location immediately,
                then any ships at the latter have little time to get busy.
                Also, tweak_3 removes anything from 0,0,0, if the player is not there.
                //////////////////////////////////////////////////////////////////////////////////////////////
                */
                this.dbg && this.$msg_dbg('$tweak_2: setting timer for tweak3');
                this.timer_runTweak_3 = new Timer(
                    this,
                    this.$tweak_3_afterExitedWitchspace_move_sickness_clearWP,
                    this.interstellar_data.timing.runTweak_3
                );
            };
    
    
            this.$tweak_3_afterExitedWitchspace_move_sickness_clearWP = function () {
                /*
                This function is to run, only, on a timer started by shipExitedWitchspace.
                But that timer should be started only if we are in the 'novel scenario' and only if 'wormDrones' is not installed.
                (For, I fear the latter OXP could cause problems here. Probably though I could handle the OXP better here.)
                What the function does: revove everything near the witchPoint.
                */
    
                this.dbg && this.$msg_dbg('$tweak_3: entered');
    
                this.$timer_delete_runTweak_3(); // Delete the timer that called the current function.
    
                const scenario = this.interstellar_data.scenario;
    
                if (system.isInterstellarSpace) {
                    if (scenario !== 'U') {
                        /*
                        //////////////////////////////////////////////////////////////
                        Tweak 3: MODIFIED OR NOVEL - move player to permanent location
                        //////////////////////////////////////////////////////////////
                        */
    
                        const
                            offset = this.interstellar_data.scenarios.versions.modifiedOrNovel.offset,
                            origin = this.interstellar_data.origin,
                            scenarios = this.interstellar_data.scenarios;
    
                        // OFFSETTING
                        if (Math.random() < offset.prob) {
                            // Displace, ensuring that the vector is above a particular magnitude.
                            const countPerSide = offset.distance_max - offset.distance_min + 1;
                            const total = 2 * countPerSide;
    
                            var
                                index,
                                newPlayerPos = new Vector3D(),
                                x,
                                y,
                                z;
    
                            // x
                            index = ~~(Math.pow(Math.random(), offset.biasTowardsMin) * total);
                            if (index < countPerSide) {
                                x = -(offset.distance_max - index);
                            } else {
                                x = offset.distance_min + (index - countPerSide);
                            }
    
                            // y
                            index = ~~(Math.pow(Math.random(), offset.biasTowardsMin) * total);
                            if (index < countPerSide) {
                                y = -(offset.distance_max - index);
                            } else {
                                y = offset.distance_min + (index - countPerSide);
                            }
                            index = ~~(Math.pow(Math.random(), offset.biasTowardsMin) * total);
    
                            // z
                            index = ~~(Math.pow(Math.random(), offset.biasTowardsMin) * total);
                            if (index < countPerSide) {
                                z = -(offset.distance_max - index);
                            } else {
                                z = offset.distance_min + (index - countPerSide);
                            }
    
                            // Apply the new co-ordinates
                            newPlayerPos = origin.add([x, y, z]);
                            player.ship.position = newPlayerPos;
                            this.dbg && this.$msg_dbg(
                                '$tweak_3: scenario ' + scenario + '-' + this.interstellar_data.scenario.subScenario +
                                ' moved player to permanent location ' +
                                newPlayerPos +
                                ', OFFSET from <' +
                                origin
                            );
                        } else {
                            // No offset.
                            player.ship.position = origin;
                            this.dbg && this.$msg_dbg(
                                '$tweak_3: moved player to permanent location ' +
                                origin
                            );
                        }
    
    
                        if (scenario === 'N') {
                            /*
                            ///////////////////////////////////////////////////////////////
                            Tweak 3: interstellar - clear witchpoint, if scenario  is novel
                            ///////////////////////////////////////////////////////////////
                            */
    
                            // One hopes there is a better way than this.
                            const dummy = system.addShips('alloy', 1, [0, 0, 0]);
                            if (dummy && (dummy.length == 1) && dummy[0].isValid) {
                                var toRemove = system.filteredEntities(this, this.$is_ship, dummy[0], 51200);
                                // Scanner range: 25600.
                                // Genship max possible distance from witchpoint: 250000 = 10 x scanner range.
                                dummy[0].remove(true);
                                var i = dummy.length;
                                while (i--) {
                                    if (dummy[i].isValid) {
                                        dummy[i].remove(true);
                                    }
                                }
                            }
                            this.dbg && this.$msg_dbg('$tweak_3: cleared witchpoint area');
                        }
                    }
                } // End if-interstellar
    
                /*
                //////////////////////////////////////////
                Tweak 3: stellar | interstellar - sickness
                //////////////////////////////////////////
                */
    
                if (!this.sickness.willDie) {
                    // Is not currently set to die.
                    const sickness = this.sickness;
                    if (sickness.isSick) {
                        // But is sick
                        if (Math.random() > sickness.prob_fatal) {
                            this.$msg_player('You still have WITCHSPACE SICKNESS. Dock at a main station! Do not jump!', 15);
                            this.mySound_warning.play();
                        } else {
                            sickness.willDie = true;
                            this.$msg_player('You have TERMINAL WITCHSPACE SICKNESS!', 15);
                            this.mySound_warning.play();
                        }
                    } else {
                        // Is not sick
                        var r;
                        if (this.oxp.wormDrones) {
                            if ((missionVariables.mauiby_multipleJumps) && (sickness.prob_getting_current > 0)) {
                                r = sickness.prob_getting_current / 1.7;
                            } else {
                                r = sickness.prob_getting_current;
                            }
                        } else {
                            r = sickness.prob_getting_current;
                        }
                        // this.dbg && this.$msg_dbg('Sickness prob: '+r);
                        if (Math.random() < r) {
                            sickness.isSick = true;
                            this.$msg_player(
                                'You have developed WITCHSPACE SICKNESS. Jumping before docking at a main station may kill you!',
                                15
                            );
                            this.mySound_warning.play();
                            sickness.prob_getting_current = sickness.prob_getting; // Let us, thusly, reset this here.
                        }
                    }
                }
                // Now check whether STILL not scheduled to die from sickness.
                if (sickness.willDie) {
                    /* Destroy the player ship _after_ other OXPs have prob to do their stuff.
                    Also, need delay to allow user to read messages.
                    */
                    if (
                        player.ship.equipmentStatus('EQ_ESCAPE_POD') === 'EQUIPMENT_OK' ||
                        player.ship.equipmentStatus('EQ_ESCAPE_POD') === 'EQUIPMENT_DAMAGED'
                    ) {
                        player.ship.removeEquipment('EQ_ESCAPE_POD');
                    }
                    if (!this.timer_sickness_die) {
                        // this.dbg && this.$msg_dbg('setting die timer');
                        this.timer_sickness_die = new Timer(
                            this,
                            this.$sickness_die_timed,
                            4
                        );
                        // this.dbg && this.$msg_dbg('set die timer OK');
                    }
                } else if (!sickness.isSick && sickness.prob_getting_current < 0.5) {
                    sickness.prob_getting_current += 0.007;
                    // 0.007. This is where, through withchspace jumps, we increase the prob of the player getting sick.
                }
            };
    
    
            this.interstellarSpaceWillRepopulate = function () {
                /*
                    The function's name must match the one given in planetinfo.plist, only without this script's 'this.' prefix.
                    Moreover, only if the function is called (this.)interstellarSpaceWillRepopulate will it be detected by other scripts.
    
                    For (Wiki): 'OXPs which wish to 'take over' a system or systems should use planetinfo.plist to set a different populator function which is unique to their worldscript(s). This then prevents normal system population, including modifications from other more generic OXPs (provided those OXPs have been written to use this new population method, of course).'
                    Wiki: 'The repopulator function is called every twenty seconds of game real time.'
                */
    
                var
                    i,
                    repopulators,
                    worldScripts_local;
    
                this.dbg && this.$msg_dbg('interstellarSpaceWillRepopulate: entered');
    
                if (!player.ship.isValid) return;
    
    
                this.dbg && !this.interstellar_data.scenario && this.$msg_err('this.$repopulateInterstellar: scenario undefined');
    
                var scenario = this.interstellar_data.scenario;
                if (scenario === 'U') {
                    /*
                    -----------------------
                    REPOPULATION: UNCHANGED
                    -----------------------
                    */
    
                    repopulators = this.interstellar_data.scripts.repopulators;
    
                    worldScripts_local = worldScripts;
    
                    this.dbg && this.$msg_dbg('$repopulateInterstellar: repopulating unchanged scenario');
    
                    // Run repopopulators from other worldscripts (and those repopulators include Oolite's own)
                    i = repopulators.length;
                    while (i--) {
                        try {
                            worldScripts_local[repopulators[i]].interstellarSpaceWillRepopulate();
                        } catch (err) {
                            this.msg_err(
                                'Problem with repopulator <' +
                                worldScripts_local[repopulators[i]] +
                                '> : ' +
                                err.message);
                        }
                    }
                    return; // NB
                }
    
    
                /*
                ----------------------------------------
                REPOPULATION: MODIFIED OR NOVEL SCENARIO
                ----------------------------------------
                */
    
                const repopulation = this.interstellar_data.scenarios.versions.modifiedOrNovel.repopulation;
    
                // Increment run count; we have initialised it at zero.
                repopulation.num++;
    
                if (repopulation.num > repopulation.num_max_current) {
                    this.dbg && this.$msg_dbg(
                        '$repopulateInterstellar, modified/novel scenario: repopulation count beyond the maximum of <' +
                        repopulation.num_max_current +
                        '>; aborting');
                    return;
                    // Seeming some OXP(s) still adds thargoids.
                }
    
                this.dbg && this.$msg_dbg(
                    '$repopulateInterstellar, modified/novel scenario: repopulation ' + repopulation.num +
                    ' of ' + repopulation.num_max_current
                );
    
                if (Math.random() > repopulation.prob) {
                    this.dbg && this.$msg_dbg('$repopulateInterstellar: aborting, by probability');
                    return;
                }
    
    
                if (scenario === 'M') {
                    /*
                    -------------------------------
                    REPOPULATION: MODIFIED SCENARIO
                    -------------------------------
                    */
    
                    if (repopulation.num === 1) {
                        this.dbg && this.$msg_dbg('$repopulateInterstellar, modified scenario: skipping first re-pop');
                        return; // The repopulation comes too early.
                    }
    
                    worldScripts_local = worldScripts;
                    // The above two entities do need to be variables, not constants.
    
                    this.dbg && this.$msg_dbg('$repopulateInterstellar: repopulating modified scenario');
    
                    /*  
                        Run repopulators from other worldscripts (including Oolite's own)
                    */
                    repopulators = this.interstellar_data.scripts.repopulators;
                    i = repopulators.length;
                    while (i--) {
                        try {
                            worldScripts_local[repopulators[i]].interstellarSpaceWillRepopulate();
                        } catch (err) {
                            this.msg_err(
                                'Problem with repopulator <' +
                                worldScripts_local[repopulators[i]] +
                                '> : ' +
                                err.message);
                        }
                    }
                    return; // NB
                }
    
                /*
                ----------------------------
                REPOPULATION: NOVEL SCENARIO
                ----------------------------
                */
    
                this.dbg && this.$msg_dbg('$repopulateInterstellar: novel scenario');
    
                // For variety of experience, let us _not_ skip the first repopulation here in the novel scenario.
    
                if (
                    this.timer_radiation_1 &&
                    this.timer_radiation_1.isRunning &&
                    (Math.random() < 0.8)
                ) {
                    this.dbg && this.$msg_dbg('$repopulateInterstellar: aborting radioactive repop');
                    return;
                }
    
                if (this.dbg && !this.interstellar_data.origin) {
                    this.$msg_err('$repopulateInterstellar: origin unset');
                    return;
                }
    
                const
                    novelRepopulation = this.interstellar_data.scenarios.versions.novel.repopulation,
                    playerShip = player.ship;
                var
                    n,
                    numMil,
                    numTharg,
                    r,
                    s,
                    whereAdd; // needs setting later as 'new Vector3D(vector_value_in_some_format)'
    
                numMil = system.countEntitiesWithScanClass('CLASS_MILITARY', playerShip, 500000);
                numTharg = system.countEntitiesWithScanClass('CLASS_THARGOID', playerShip, 500000);
    
                /*
                ////////////////////
                Consider adding navy
                ////////////////////
                */
                r = this.$randomOrPseudo(0.6, 700004);
                if (
                    (numTharg >= numMil) ||
                    (r <= novelRepopulation.navy.probIgnoreParity)
                ) {
                    if (r < (novelRepopulation.navy.probIgnoreParity / 3)) {
                        whereAdd = new Vector3D(playerShip.position);
                        this.dbg && this.$msg_dbg('$repopulateInterstellar: whereAdd is playerShipPosition: <' + whereAdd + '>');
                    } else {
                        whereAdd = new Vector3D(this.interstellar_data.origin);
                        this.dbg && this.$msg_dbg('$repopulateInterstellar: whereAdd is origin: <' + whereAdd + '>');
                    }
                    if (
                        this.oxp.ferDeLance3G && (Math.random() < novelRepopulation.navy.threeG.prob)
                    ) {
                        // The 3G's own populator script adds only to the witchPoint.
                        this.dbg && this.$msg_dbg('$repopulateInterstellar: adding navy (3G)');
                        s = this.$scenarioAdd_ships(
                            'ferdelance3G_milInterstellar',
                            novelRepopulation.navy.threeG.squadSize,
                            whereAdd,
                            10000, // range
                            true, // rings
                            true // group
                        );
                    } else {
                        this.dbg && this.$msg_dbg('$repopulateInterstellar: adding navy');
                        n = novelRepopulation.navy.baseNum + ~~(1 + (Math.random() * 6));
                        s = this.$scenarioAdd_ships(
                            'IST_mil',
                            n,
                            whereAdd,
                            10000, // range
                            true, // rings
                            false // group
                        );
                    }
                    return; // NB
                }
    
                /*
                /////////////////////////
                Consider adding thargoids
                /////////////////////////
                */
    
                r = this.$randomOrPseudo(0.6, 700005);
                if (
                    (numTharg > novelRepopulation.thargoid.maxThargs) &&
                    (r > novelRepopulation.thargoids.probIgnoreMax)
                ) {
                    return;
                }
                this.dbg && this.$msg_dbg('$repopulateInterstellar: adding thargoid');
                r = Math.random();
                if (
                    (
                        playerShip.equipmentStatus('EQ_ELECTRONIC_THUMB') === 'EQUIPMENT_OK' &&
                        system.countShipsWithPrimaryRole('interstellar_helper')
                    ) || Math.random() < 0.3
                ) {
                    whereAdd = new Vector3D(playerShip.position);
                } else {
                    whereAdd = new Vector3D(this.interstellar_data.origin);
                }
                const u = Math.random();
                const x = Math.pow(u, 2);
                n = 1 + ~~(1 + x * 9); // Number 2-9, biased towards low.
                s = this.$scenarioAdd_ships(
                    'thargoid',
                    n,
                    whereAdd,
                    10000,
                    true, // rings
                    false // group
                );
            };
    
    
            /*
            ==================================
            14  End of wrapper for JSHINT
                See also JSHint options above.
            ==================================
            */
    
        }).call(this);
    
    
    // EOF
    Scripts/IST_pirate.js
    /*
    ///////////////////////////////////////////////////////////////////////////////////
    
    This file:          IST_pirate.js
    Associated AI:      IST_pirate.plist
    Part of:            Interstellar Tweaks OXP
    Author:             UK_Eliter
    License:            2017 Creative Commons: attribution, non-commercial, sharealike
    
    ///////////////////////////////////////////////////////////////////////////////////
    */
    
    
    /*
    ===================
    JSLINT: set options
    ===================
    */
    
    /*jshint esversion: 6*/
    /*jshint sub:true*/
    /*jshint -W030 */
    
    
    
    this.name = 'IST_pirate.js';
    this.author = 'UK_Eliter';
    this.copyright = '2014 Creative Commons: attribution, non-commercial, sharealike.';
    this.licence = 'CC-NC-SA 3';
    this.description = 'A ship script for the Interstellar Tweaks OXP';
    
    
    /*
    ------------------
    DEBUGGING SWITCHES
    ------------------
    */
    
    // this.logging = true;
    
    var consoleDebugMessages = false;
    // Do NOT comment this out. Rather, to disable, set to false.
    
    this.dbg = false; // NB
    
    
    /*
    ------------------------
    JSLINT: start of wrapper
    ------------------------
    */
    
    (function () {
    	'use strict';
    
    	/*
    	--------------
    	EVENT HANDLERS
    	--------------
    	*/
    
    	this.shipSpawned = function () {
    		const r = Math.random();
    		if (r < 0.6) {
    			// Try to replace a normal missile with a more interesting type.
    			this.ship.removeEquipment('EQ_MISSILE');
    			var missileDeficit = this.ship.missileCapacity - this.ship.missiles.length;
    			if (worldScripts['CT_masterScript'] !== undefined) {
    				this.ship.awardEquipment('EQ_ARMOURY_CUTPURSE_MISSILE');
    			} else {
    				this.ship.awardEquipment('EQ_HARDENED_MISSILE');
    			}
    		}
    		// Bounty
    		if (r < 0.2) return;
    		if (r > 0.94) {
    			this.ship.bounty = 50 + Math.ceil(r * 200);
    		} else {
    			this.ship.bounty = 7 + Math.ceil(r * 90);
    		}
    	};
    
    	this.entityDestroyed = function () {
    		this.$decloak_timer_deleteAny();
    	};
    
    	this.shipDied = function () {
    		this.$decloak_timer_deleteAny();
    	};
    
    	this.playerWillEnterWitchspace = function () {
    		this.$decloak_timer_deleteAny();
    	};
    
    	this.shipBeingAttackedByCloaked = function () {
    		// From the wiki: 'There is no parameter provided to identify the cloaked ship.'
    		if (!this.cloakedTarget)
    			this.cloakedTarget = system.filteredEntities(
    				this,
    				this.$isCloaked,
    				this.ship,
    				25600
    			)[0];
    
    		if (this.cloakedTarget && this.cloakedTarget.isCloaked) {
    			if (this.timer_detectDeCloak) {
    				if (!this.timer_detectDeCloak.isRunning)
    					this.timer_detectDeCloak.start();
    			} else {
    				var num = 0.2 + Math.random();
    				this.timer_detectDeCloak = new Timer(
    					this,
    					$detectDeCloak_timed,
    					num,
    					2.65
    				);
    			}
    		}
    	};
    
    	this.$decloak_timer_deleteAny = function () {
    		if (!this.timer_detectDeCloak) return;
    		this.timer_detectDeCloak.isRunning && this.timer_detectDeCloak.stop();
    		delete this.timer_detectDeCloak;
    	}; // Identical to this.$_decloak_timer_deleteAny
    
    
    	/*
    	---------------------
    	ROUTINES CALLED BY AI
    	---------------------
    	*/
    
    	this.$_decloak_timer_deleteAny = function () {
    		if (!this.timer_detectDeCloak) return;
    		this.timer_detectDeCloak.isRunning && this.timer_detectDeCloak.stop();
    		delete this.timer_detectDeCloak;
    	};
    
    	// if (this.timer_detectDeCloak.isRunning) this.timer_detectDeCloak.stop();
    
    	this.$_timer_detectDeCloak_stop = function () {
    		if (this.timer_detectDeCloak && this.timer_detectDeCloak.isRunning) this.timer_detectDeCloak.stop();
    	};
    
    	// This function called by AI
    	this.$_seekVictim = function () {
    		if (!this.ship || !this.ship.isPiloted) return;
    
    		var potentialTargets = system.filteredEntities(this, this.$isPotentialTarget, this.ship, 25600);
    		var numPotentialTargets = potentialTargets.length;
    		if (numPotentialTargets === 0) {
    			this.ship.reactToAIMessage('NOTHING_FOUND');
    			return;
    			// No need to scan for dead tharglets, 'cos they'll be got via AI's SCAN_FOR_LOOT.
    		}
    		// Else
    		var r = Math.random();
    		if ((numPotentialTargets === 1) || (r < 0.5)) {
    			this.ship.target = potentialTargets[0];
    		} else {
    			if ((numPotentialTargets === 2) || (r > 0.6)) {
    				this.ship.target = potentialTargets[1];
    			} else {
    				this.ship.target = potentialTargets[2];
    			}
    		}
    		this.ship.reactToAIMessage('TARGET_FOUND');
    	};
    
    
    	/*
    	--------------
    	OTHER ROUTINES
    	--------------
    	*/
    
    	this.$isCloaked = function (e) {
    		return e.isShip && e.isCloaked;
    	};
    
    	this.$isPotentialTarget = function (e) {
    		return e.isShip &&
    			!e.isCloaked &&
    			e.isPiloted &&
    			(
    				(
    					(e.isPirateVictim || e.hasRole('hiredGuns_escort') || e.isPlayer || e.primaryRole === 'IST_lobster') &&
    					(e.bounty < 50 || e.primaryRole === 'IST_lobster')
    				) ||
    				(e.isPlayer && this.ship.script.$playerAttackedPirates)
    			);
    	};
    	// Cf. this.shipSpawned and this.shipTakingDamage
    
    	this.$detectDeCloak_timed = function () {
    		if (!this.ship || !this.ship.isPiloted) {
    			if (this.timer_detectDeCloak.isRunning) this.timer_detectDeCloak.stop();
    			return;
    		}
    		if (!this.cloakedTarget || !this.cloakedTarget.isValid) {
    			if (this.timer_detectDeCloak.isRunning) this.timer_detectDeCloak.stop();
    			this.ship.reactToAIMessage('TARGET_LOST');
    			return;
    		}
    		if (this.ship.position.distanceTo(this.cloakedTarget) > 25600) {
    			if (this.timer_detectDeCloak.isRunning) this.timer_detectDeCloak.stop();
    			this.ship.reactToAIMessage('TARGET_LOST');
    			return;
    		}
    		if (this.cloakedTarget.isCloaked) {
    			return;
    		}
    		if (this.timer_detectDeCloak.isRunning) this.timer_detectDeCloak.stop();
    		this.ship.target = this.cloakedTarget;
    		this.ship.reactToAIMessage('TARGET_DECLOAKED');
    	};
    
    
    	/*
    	--------------
    	DEBUGGING CODE
    	--------------
    	*/
    
    	// this.$debugMessage = function(debugTxt)
    	// {
    	// 	if ( !this.dbg ) { return; } // NB. THIS ROUTINE DOES NOTHING IF this.dbg IS NOT TRUE
    	// 	// else
    	// 	if ( !debugTxt ) {
    	// 		player.consoleMessage( 'Problem with debug message text!', 30 );
    	// 		return;
    	// 	}
    	// 	player.consoleMessage( 'Interstellar Tweaks, PIRATES - '+debugTxt, 12 );
    	// 	log( 'InterstellarTweaks, PIRATES - debugging', debugTxt );
    	// };
    
    
    	/*
    	----------------------
    	JSHINT: end of wrapper
    	----------------------
    	*/
    
    }).call(this);
    
    
    // EOF
    Scripts/IST_thargoid.js
    /*
    ///////////////////////////////////////////////////////////////////////////////////
    
    This file:          IST_thargoid.js
    Associated AI:      IST_thargoid.plist
    Part of:            Interstellar Tweaks OXP
    Author:             UK_Eliter
    License:            2017 Creative Commons: attribution, non-commercial, sharealike
    
    ///////////////////////////////////////////////////////////////////////////////////
    */
    
    
    /*
    ===================
    JSLINT: set options
    ===================
    */
    
    /*jshint esversion: 6*/
    /*jshint sub:true*/
    
    
    /*
    ======
    HEADER
    ======
    */
    
    this.name = 'IST_thargoid.js';
    this.author = 'UK_Eliter';
    this.copyright = '2014 Creative Commons: attribution, non-commercial, sharealike.';
    this.licence = 'CC-NC-SA 3';
    this.description = 'A script for InterstellarTweaks OXP';
    
    
    /*
    ==================
    DEBUGGING SWITCHES
    ==================
    */
    
    this.logging = false; // Corresponds to, or needs, settings in logcontrol.plist?
    this.dbg = false; // false
    var consoleDebugMessages = false; // do NOT comment this out. Rather, to disable, set to false.
    
    
    /*
    ========================
    JSLINT: start of wrapper
    ========================
    */
    
    (function() {
    	'use strict';
    
    	/*
    	--------------
    	EVENT HANDLERS
    	--------------
    	*/
    
    	// Toggle out the following when not debugging.
    
    	// this.shipSpawned = function() {
    	// 	this.ship.reportAIMessages = true;
    	// }
    
    
    	/*
    	---------------------
    	ROUTINES CALLED BY AI
    	---------------------
    	*/
    
    	this.$_findTarget = function() {
    		const near = system.filteredEntities(
    			this,
    			this.$isEligibleTarget,
    			this.ship,
    			25600
    		);
    		// The above will exclude escorts marked as CLASS_ROCK. But if those attack the ship, it will respond.
    		const l = near.length;
    		if (l > 0) {
    			if (l > 1) {
    				this.ship.target = near[(~~(Math.random() * l))];
    			} else {
    				this.ship.target = near[0];
    			}
    			if (Math.random() < 0.16) this.$cursePlayer();
    			this.ship.reactToAIMessage('TARGET_FOUND');
    			return;
    		}
    		// Otherwise
    		const farArray = system.filteredEntities(this, $isEligibleTarget, this.ship, 75000);
    		if (farArray.length > 0) {
    			if (Math.random() < 0.1) {
    				this.$cursePlayer();
    			}
    			this.ship.target = farArray[0]; // set to the closest target off-scanner but in range
    			this.ship.reactToAIMessage('INTERCEPT');
    		} else {
    			this.ship.reactToAIMessage('NOTHING_FOUND');
    		}
    	};
    
    	this.$_validateAttackerAsTarget = function() {
    		if (
    			this.ship.target &&
    			this.ship.target.isValid &&
    			this.$isEligibleTarget(this.ship.target)
    		) {
    			this.ship.reactToAIMessage('TARGET_VALID');
    			return;
    		}
    		this.ship.reactToAIMessage('TARGET_INVALID');
    	};
    
    
    	/*
    	---------------
    	OTHER FUNCTIONS
    	---------------
    	*/
    
    	this.$cursePlayer = function() {
    		this.ship.commsMessage(expandDescription('[thargoid_curses]', player.ship));
    	};
    
    	this.$isEligibleTarget = function(e) {
    		return !e.isCloaked &&
    		(
    			e.isPlayer ||
    			(
    				e.isShip && e.ispiloted && (!e.isThargoid || e.primaryRole === 'thargoidRebel')
    			)
    		);
    	};
    
    
    	/*
    	---------
    	DEBUGGING
    	---------
    	*/
    
    	// this.$debugMessage = function( debugTxt )
    	// {
    	// 	if (!this.dbg) { return; } // NB. THIS ROUTINE DOES NOTHING IF this.dbg IS NOT TRUE
    	// 	// else
    	// 	if ( !debugTxt ) {
    	// 		player.consoleMessage( 'Problem with debug message text!', 30 );
    	// 		return;
    	// 	}
    	// 	player.consoleMessage( 'Interstellar Tweaks - '+debugTxt, 12 );
    	// 	log( 'InterstellarTweaks - debugging', debugTxt );
    	// };
    
    
    	/*
    	----------------------
    	JSHINT: end of wrapper
    	----------------------
    	*/
    
    }).call(this);
    
    
    // EOF
    Scripts/IST_thargoid_defenceShip.js
    /*
    ///////////////////////////////////////////////////////////////////////////////////
    
    This file:          IST_thargoid_defenceShip.js
    Associated AI:      IST_thargoid_defenceShip.plist
    Part of:            Interstellar Tweaks OXP
    Author:             UK_Eliter
    License:            2017 Creative Commons: attribution, non-commercial, sharealike
    
    ///////////////////////////////////////////////////////////////////////////////////
    */
    
    
    /*
    ====================
    JSLINT: set options
    ====================
    */
    
    /*jshint esversion: 6*/
    /*jshint sub:true*/
    
    
    this.name = 'IST_thargoid_defenceShip.js';
    this.author = 'UK_Eliter';
    this.copyright = '2014 Creative Commons: attribution, non-commercial, sharealike.';
    this.licence = 'CC-NC-SA 3';
    this.description = 'A script for InterstellarTweaks OXP';
    
    
    /*
    ==================
    DEBUGGING SWITCHES
    ==================
    */
    
    this.logging = false; // Corresponds to, or needs, settings in logcontrol.plist?
    this.dbg = false; // false
    var consoleDebugMessages = false; // do NOT comment this out. Rather, to disable, set to false.
    
    
    /*
    ========================
    JSHINT: start of wrapper
    ========================
    */
    
    (function() {
    	'use strict';
    
    
    	/*
    	--------------
    	EVENT HANDLERS
    	--------------
    	*/
    
    	// Toggle out the following when not debugging.
    
    	// this.shipSpawned = function() {
    	// 	this.ship.reportAIMessages = true;
    	// 	delete this.shipSpawned;
    	// }
    
    
    	/*
    	---------------------
    	ROUTINES CALLED BY AI
    	---------------------
    	*/
    
    	this.$_findTarget = function() {
    		const nearby = this.ship.CheckScanner(true);
    		var i = nearby.length;
    		while (i--) {
    			if (
    				nearby[i].isShip &&
    				nearby[i].isPiloted && 
    				!nearby[i].isThargoid &&
    				!nearby[i].isCloaked 
    			) {
    				this.ship.reactToAIMessage('TARGET_FOUND');
    				return;
    			}
    		}
    	};
    
    	this.$_validateAttackerAsTarget = function() {
    		if (
    			this.ship.target &&
    			this.ship.target.isValid && (
    				(!this.ship.target.isThargoid) ||
    				(this.ship.target.primaryRole === 'thargoidRebel')
    			)
    		) {
    			this.ship.reactToAIMessage('TARGET_VALID');
    			return;
    		}
    		this.ship.reactToAIMessage('TARGET_INVALID');
    	};
    
    	/*
    	----------------------
    	JSHINT: end of wrapper
    	----------------------
    	*/
    
    }).call(this);
    
    
    // EOF
    Scripts/IST_thargoid_genShip.js
    /*
    ///////////////////////////////////////////////////////////////////////////////////
    
    This file:          IST_thargoid_genShip.js
    Associated AI:      IST_thargoid_genShip.plist
    Part of:            Interstellar Tweaks OXP
    Author:             UK_Eliter
    License:            2017 Creative Commons: attribution, non-commercial, sharealike
    
    ///////////////////////////////////////////////////////////////////////////////////
    */
    
    
    /*
    ===================
    JSLINT: set options
    ===================
    */
    
    /*jshint esversion: 6*/
    /*jshint sub:true*/
    
    
    /*
    ======
    HEADER
    ======
    */
    
    this.name = 'IST_thargoid_genShip';
    this.author = 'UK_Eliter';
    this.copyright = '2014 Creative Commons: attribution, non-commercial, sharealike.';
    this.licence = 'CC-NC-SA 3';
    this.description = 'A ship script for the Interstellar Tweaks OXP';
    
    /*
    ==================
    DEBUGGING SWITCHES
    ==================
    */
    
    this.logging = false;
    //	Corresponds to, or needs, settings in logcontrol.plist?
    
    this.dbg = false;
    //	false
    
    var consoleDebugMessages = false;
    //	Do NOT comment this out. Rather, to disable, set to false.
    
    
    /*
    ======
    SOUNDS
    ======
    */
    
    var mysound_Warning = new SoundSource();
    mysound_Warning.sound = 'warning.ogg';
    //	This sound built-in to Oolite - and does not need to be declared in the 'customsounds' file.
    
    /*
    ========================
    JSLINT: start of wrapper
    ========================
    */
    
    (function () {
    	'use strict';
    
    	/*
    	---------
    	DEBUGGING
    	---------
    	*/
    
    	this.$debugMessage = function (debugTxt) {
    		if (!debugTxt) {
    			player.consoleMessage('Problem with debug message text!', 30);
    			return;
    		}
    		player.consoleMessage('Interstellar Tweaks - ' + debugTxt, 12);
    		log('InterstellarTweaks - debugging', debugTxt);
    	};
    
    
    	/*
    	----
    	MISC
    	----
    	*/
    
    	this.$isEligibleTarget = function (e) {
    		return !e.isCloaked &&
    			(
    				(e.isPlayer && !e.docked) || (
    					(
    						e.isShip && e.isPiloted && (
    							!e.isThargoid || e.primaryRole === 'thargoidRebel')
    					)
    				)
    			);
    	};
    
    
    	/*
    	---------------------
    	ROUTINES CALLED BY AI
    	---------------------
    	*/
    
    	this.$_seekTarget_near = function () {
    
    		var near = this.ship.checkScanner(true);
    		var l = near.length;
    		while (l--) {
    			if (
    				near[l].isShip &&
    				near[l].isPiloted &&
    				(
    					(!near[l].isThargoid) || (near[l].primaryRole === 'thargoidRebel')
    				) &&
    				!near[l].isCloaked
    			) {
    				// this.dbg && this.$debugMessage('Near target');
    				this.ship.reactToAIMessage('TARGET_FOUND');
    				return;
    			}
    		}
    		this.ship.reactToAIMessage('NOTHING_FOUND');
    	};
    
    	this.$_seekTarget_far = function () {
    		const far = system.filteredEntities(
    			this,
    			this.$isEligibleTarget,
    			this.ship,
    			70000
    		);
    		if (far.length === 0) {
    			this.ship.reactToAIMessage('NOTHING_FOUND');
    			// this.dbg && this.$debugMessage('NO far target');
    			return;
    		}
    		this.ship.target = far[0];
    		this.ship.reactToAIMessage('TARGET_FOUND');
    		// this.dbg && this.$debugMessage('Far target');
    	};
    
    	this.$_validateAttackerAsTarget = function () {
    		if (
    			this.ship.target &&
    			this.ship.target.isValid &&
    			this.$isEligibleTarget(this.ship.target)
    		) {
    			this.ship.reactToAIMessage('TARGET_VALID');
    			return;
    		}
    		this.ship.reactToAIMessage('TARGET_INVALID');
    	};
    
    	this.$_curse = function () {
    		this.ship.commsMessage(expandDescription('[thargoid_curses]'), player.ship);
    	};
    
    
    	/*
    	======================
    	JSLINT: end of wrapper
    	======================
    	*/
    
    }).call(this);
    
    
    // EOF
    Scripts/IST_thargoid_rebel.js
    /*
    ///////////////////////////////////////////////////////////////////////////////////
    
    This file:          IST_thargoid_rebel.js
    Associated AI:      IST_thargoid_rebel.plist
    Part of:            Interstellar Tweaks OXP
    Author:             UK_Eliter
    License:            2017 Creative Commons: attribution, non-commercial, sharealike
    
    ///////////////////////////////////////////////////////////////////////////////////
    */
    
    
    /*
    ===================
    JSLINT: set options
    ===================
    */
    
    /*jshint esversion: 6*/
    /*jshint sub:true*/
    
    
    /*
    ======
    HEADER
    ======
    */
    
    this.name = 'IST_thargoid_rebel.js';
    this.author = 'UK_Eliter';
    this.copyright = '2014 Creative Commons: attribution, non-commercial, sharealike.';
    this.licence = 'CC-NC-SA 3';
    this.description = 'A script for InterstellarTweaks OXP';
    
    
    /*
    ==================
    DEBUGGING SWITCHES
    ==================
    */
    
    // this.logging = true;
    var consoleDebugMessages = false;
    // do NOT comment out the above line. Rather, to disable, set to false.
    
    
    /*
    ========================
    JSHINT: start of wrapper
    ========================
    */
    
    (function () {
    	'use strict';
    
    
    	/*
    	----------------
    	EVENT HANDLER(S)
    	----------------
    	*/
    
    	// The AI ensures that a rebel ship can never use its own drive to enter normal space.
    	// But rebels can enter each others' wormwholes and thereby (despite the above) enter normal space. Hence this function.
    	this.shipSpawned = function () {
    		// Fix witchspace.
    		this.ship.scriptedMisjump = true;
    	};
    
    
    	/*
    	----
    	MISC
    	----
    	*/
    
    	this.filterForTargets = function (e) {
    		return e.isShip &&
    			e.isPilolted &&
    			e.isThargoid &&
    			e.primaryRole !== 'thargoidRebel' &&
    			!e.isCloaked;
    	};
    
    	/*
    	---------------------
    	ROUTINES CALLED BY AI
    	---------------------
    	*/
    
    	this.$_rebelSeekTarget = function () {
    		const near = this.ship.checkScanner(true);
    		var i = near.length;
    		while (i--) {
    			if (
    				near[i].isShip &&
    				near[i].isPiloted &&
    				near[i].isThargoid &&
    				near[i].primaryRole !== 'thargoidRebel' &&
    				!near[i].isCloaked
    			) {
    				// this.debug && this.$debugMessage('Near target');
    				this.ship.target = near[i];
    				this.ship.reactToAIMessage('TARGET_FOUND');
    				return;
    			}
    		}
    		const far = system.filteredEntities(this, filterForTargets, this.ship, 60000);
    		if (far.length) {
    			this.ship.target = far[0];
    			this.ship.reactToAIMessage('INTERCEPT_TARGET');
    			return;
    		}
    		this.ship.reactToAIMessage('NOTHING_FOUND');
    	};
    
    	this.$_validateTarget = function () {
    		if (
    			(!this.ship.target) ||
    			(!this.ship.target.isValid) ||
    			this.ship.target.isCloaked
    		) {
    			this.ship.reactToAIMessage('TARGET_INVALID');
    			return;
    		}
    		if (
    			this.ship.target.isThargoid &&
    			(this.ship.target.primaryRole != 'thargoidRebel')
    		) {
    			this.ship.reactToAIMessage('TARGET_VALID');
    			return;
    		}
    		if (
    			(this.ship.target.isPolice) ||
    			this.ship.target.scanClass == 'CLASS_MILITARY') {
    			this.ship.reactToAIMessage('TARGET_GALCOP');
    			return;
    		}
    		this.ship.reactToAIMessage('TARGET_INVALID');
    	};
    
    
    	/*
    	======================
    	JSLINT: end of wrapper
    	======================
    	*/
    
    }).call(this);
    
    
    // EOF