Back to Index Page generated: May 24, 2025, 12:46:18 PM

Expansion Equal rights for Shrews.

Content

Warnings

  1. http://wiki.alioth.net/index.php/Equal%20rights%20for%20Shrews. -> 404 Not Found

Manifest

from Expansion Manager's OXP list from Expansion Manifest
Description Adds the Equal Rights for Shrews, mission for galaxy 2. Adds the Equal Rights for Shrews, mission for galaxy 2.
Identifier oolite.oxp.Zafrusteria.ShrewsRights oolite.oxp.Zafrusteria.ShrewsRights
Title Equal rights for Shrews. Equal rights for Shrews.
Category Missions Missions
Author Zafrusteria Zafrusteria
Version 0.8 0.8
Tags Mission, galaxy 2, ambience Mission, galaxy 2, ambience
Required Oolite Version
Maximum Oolite Version
Required Expansions
  • oolite.oxp.Svengali.GNN:1.2
  • oolite.oxp.Svengali.GNN:1.2
  • Optional Expansions
  • oolite.oxp.phkb.BountySystem:0.13
  • oolite.oxp.DrNil.YAH:4.5
  • oolite.oxp.spara.market_ads:1.0
  • oolite.oxp.spara.station_ads:1.1
  • oolite.oxp.phkb.BountySystem:0.13
  • oolite.oxp.DrNil.YAH:4.5
  • oolite.oxp.spara.market_ads:1.0
  • oolite.oxp.spara.station_ads:1.1
  • Conflict Expansions
    Information URL https://wiki.alioth.net/img_auth.php/8/88/Oolite.oxp.Zafrusteria.ShrewsRights.oxz n/a
    Download URL https://wiki.alioth.net/img_auth.php/8/88/Oolite.oxp.Zafrusteria.ShrewsRights.oxz n/a
    License CC-BY-NC-SA 4.0 CC-BY-NC-SA 4.0
    File Size n/a
    Upload date 1713946543

    Documentation

    readme.txt

    Shrews Rights OXP 0.8 by Zafrusteria.
    
    The mission takes place in Galaxy 2 when the player has reached at least dangerous levels and finds the system with "vicious shrews". Just dock at the main station to get an initial meeting and further details. You should have GNN OXP installed, to keep up with the latest news. Also, you will get some hints about where the mission starts. As you progress through, there will be a square the colour of most shews (brown) on the galactic chart to indicate your next destination. You should dock at the main station of a system when arriving at your destination.
    
    You will need to be capable of defending yourself to finish this mission. A few addons to help protect yourself and attack your enemies will make life less like that of a rabbit and more like that of a fox. You will be attacked, so be prepared. A fuel scoop and an advanced space compass will come in handy, but they are not necessary. But what dangerous commander does not have those already.
    
    The bad guys will be in an Ask Mk II, Cobra Mk. III, Krait or Moray Star Boat, all of which are vanilla game ships. They will have front and possibly rear pulse lasers, which they may have upgraded to front beam lasers. Their accuracy, strength and number will be adjusted depending on how many kills you have and how well your ship is equipped. The bag guys will get slightly harder and more numerous as the mission progresses. Get ready for a fight. You are fighting on the side of the minority and against the status quo.
    
    You will need to install the extension Galactic News Network, oolite.oxp.Svengali.GNN.oxp. This gives a bit more background to what is happening as you progress through the mission.
    
    Optional: This OXP will add some advertisements if you have YAH installed for stations, buoys or markets.
    
    --------------------------------------------------------------
    
    Licence:
    
    This OXP is released under the Creative Commons Attribution - Non-Commercial - Share Alike 4.0 licence:
    
    * While you are free (and encouraged) to re-use any of the scripting, models or texturing in this OXP, the usage must be distinct from that within this OXP.
    * I would appreciate an IM through the Oolite forum, if you are re-using any sizeable or recognisable piece of this OXP, please let me know.
    
    --------------------------------------------------------------
    
    Instructions:
    
    Unzip the file, and then move the folder "ShrewsRights_0.8.oxp" to the AddOns directory of your Oolite installation. Then start the game while holding down the shift key (until the spinning Cobra Mk II screen appears), and the mission should be added and available when you arrive at the right system.
    
    This OXP requires Oolite version 1.90 or higher to run.
    
    --------------------------------------------------------------
    
    Credits:
    
    The mission Shrews Rights was created by Zafrusteria.
    
    My thanks to (in no particular order):
    * CaptSolo's Cobra Mk II-XT for the AI.
    * Cholmondely, for encouragement and pointing out loads of documentation.
    * cim's Black Box Coding and for setting comms role and personalities coding from oolite.oxp.cim.comms-pack-a.oxz
    * DaddyHoggy, Svengali, Drew and Disembodied for GNN, Rooters and Snoopers.
    * Eric Walsh's DeepSpaceDredger OXP.
    * mossfoot and Norby for the Collector mission code.
    * phkb for Gal Cop missions, GalCopBB_Derelict(), Death comms and Bulletin Board System.
    * Thargoid for retro rockets.
    
    Resources and websites:
    
    Mission background images from pixabay.com are licensed. https://pixabay.com/service/license-summary/
    Sounds from Soundbible https://soundbible.com/
    Spell and grammar are checked with https://www.scribbr.co.uk/grammar-checker/
    Anagram generator https://new.wordsmith.org/
    Translate English to Latin. https://lingvanex.com/translation/english-to-latin
    
    --------------------------------------------------------------
    Version history:
    
    
    0.8, Remove comms from bad guys as it was not varied enough, typos.
    0.7, Fixes for upper limit on  bad guys, typos.
    0.5, Initial playtest release.
    

    Equipment

    Name Visible Cost [deci-credits] Tech-Level
    Black box from Liberate Sorex yes 0 100+
    Encrypted Data Crystal yes 0 100+
    Retrovirus Dispersal System yes 0 100+
    White-spot, Shrew Scientist (Passenger) yes 0 100+

    Ships

    Name
    Asp Mark II
    Cobra Mark III
    Cobra Mark III: Whiskers
    Krait
    Moray Star Boat
    shrewsrights-blackbox
    Black Box (Liberate Sorex)

    Models

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

    Scripts

    Path
    Scripts/ShrewsRights.js
    "use strict";
    this.author    = "Zafrusteria";
    this.copyright = "© 2024 Zafrusteria.";
    this.license   = "Creative Commons: attribution, non-commercial,  CC BY-NC-SA 4.0 sharealike - see readme.txt";
    this.version   = "0.8";
    
    this.name = "ShrewsRights";
    
    this.description = "A mission, in Galaxy 2, should you decide to take it. Will you do what it takes to help out a new, evolving species gain the rights of full citizens?";
    
    this.$debug = false;
    
    /*
       The original structure of this mission js file was copied from The Collector
       by mossfoot and Norby. It made for a good template to follow through but
       now its a bit different. It seemed like a good idea at the time. :)
    */
    
    
    /*  ****************************************************************
                  * * *  SPOILER ALERT  * * *
    
         Reading any further down this file will give away the mission!
    
                  * * *  SPOILER ALERT  * * *
        ****************************************************************
    */
    
    this.$missionStage     = null; // Where the player is in the mission, 0 means it has not been started.
    this.$missionStageSub  = null;
    this.$missionJumpCount = null; // Number of jumps since last message
    this.$missionTotalJmp  = null; // number of jumps since accepting the mission
    this.$missionNewsCount = null; // Which News update are we on
    this.$missionResume    = null; // Player chose an option to take a break  "jN" in a redirect.
    this.$missionWhatName  = null; // Name of what was attacked
    this.$missionHow       = null; // "a" attacked or "d" destroyed
    this.$missionNextSys   = null; // Sets text where you are going for next stage
    this.$missionPastNames = {};
    this.$missionTSSystem  = null; // systemID where last kill was made
    this.$missionTSSysName = null; // Name if the system
    this.$missionTSDead    = [];   // [0] == 1, captured TS || 2, killed TS || 3, lost in space || 4, not dead yet
                                   // [1] == 1 W derelict || 2, npc derelict || 3, you destroyed || 4, npc destroyed || 5, just attacked
    this.$displayScreen  = false;  // Only run through missionScreenOpportunity() once per docking.
    this.$aegisEntered   = false;
    this.$orgThrust      = null;
    this.$orgVelocity    = null;
    this.$orgMaxSpeed    = null;
    this.$blackBox       = null;
    this.$blackBoxTimer  = null;
    this.$proximityTimer = null;
    this.$escapeTimer    = null;
    this.$freeDocking    = null;
    
    this.$GNN     = null;
    this.$dockFee = null;   // Docking fees OXP
    
    this.$messagePing = null;
    
    this.$regexNumber = /^-?[0-9]+$/;
    this.$regexpEscPod = /escape (pod|capsule)|space suit/i;
    
    //-------------------------------------------------------------------------------------------------------------
    this.startUp = function _ShrewsRights_startUp() {
        this.$missionStage = missionVariables.$ShrewsRightsStage || 0;
    
        // only in Galaxy 2 and 512+ kills,  not interstella space or no data found for mission
        if (galaxyNumber != 1 || player.score < 512) {
            if (this.$stage[this.$missionStage].complete == true) {
                // clean up as we are not going to be doing anything. We can play
                // nice and remove these event handlers.
                delete this.startUpComplete;
                delete this.missionScreenOpportunity;
                delete this.shipExitedWitchspace;
                delete this.systemWillPopulate;
                delete this.shipScoopedOther;
                delete this.shipAttackedOther;
                delete this.shipTargetDestroyed;
                delete this.shipKilledOther;
                delete this.shipEnteredStationAegis;
                delete this.shipWillDockWithStation;
                delete this.guiScreenChanged;
            }
            return;
        };
    
        this.$missionJumpCount = missionVariables.$ShrewsRightsJumpCount || 0;
        this.$missionTotalJmp  = missionVariables.$ShrewsRightsTotalJmp || 0;
        this.$missionNewsCount = missionVariables.$ShrewsRightsNewsCount || 0;
        this.$missionTSDead    = missionVariables.$ShrewsRightsTSDead || [];
        this.$missionResume    = missionVariables.$ShrewsRightsResume;
        this.$missionWhatName  = missionVariables.$ShrewsRightsWhatName;
        this.$missionHow       = missionVariables.$ShrewsRightsHow;
        this.$blackBox         = missionVariables.$ShrewsRightsBlackBox;
        this.$freeDocking      = missionVariables.$ShrewsRightsFreeDocking;
        this.$missionTSSystem  = missionVariables.$ShrewsRightsTSSystem;
        this.$missionTSSysName = missionVariables.$ShrewsRightsTSSysName;
    
        let mv = missionVariables.$ShrewsRightsPastNames;
        if (mv == null)
            this.$missionPastNames = {};
        else {
            let data = JSON.parse(mv);
            for (key in data) this.$missionPastNames[key] = data[key];
        }
    
        this.$messagePing = new SoundSource(); // makes a sound when a new mission message is displayed.
        this.$messagePing.loop = false;
    }
    
    this.startUpComplete = function _ShrewsRights_startUpComplete() {
        // Check to see if GNN is installed.
        if (worldScripts.GNN) {
            this.$GNN = worldScripts.GNN;
    //        let pics = [];
    //        for (let i = 0; i < 4; i++) {
    //            pics[i] = [];
    //            for (let j = 0; j < 3; j++){
    //                pics[i][j] = "ShrewsRights_GNN_"+ (i+1) + (j+1) + ".png"
    //            }
    //        }
    //        let st = this.$GNN._addExtImages(pics, this.name);
        }
        if (worldScripts["Docking Fees"]) this.$dockFee = worldScripts["Docking Fees"];
    
        // put back updated system descriptions at startup.
        let len = (this.$missionNewsCount <= this.$news.length ? this.$missionNewsCount : this.$news.length);
        for (let i = 0; i < len; i++) {
            if (this.$news[i].desc && this.$news[i].desc[0] > 0) this._setSystemDesc(this.$news[i].desc);
        }
    
        // reset variable location of TS
        if (this.$missionTSSystem && this.$missionTSSysName &&
                         (this.$stage[this.$missionStage].findTS || this.$stage[this.$missionStage].spawnTS)) {
            this.$stage[this.$missionStage].sysName  = this.$missionTSSysName;
            this.$stage[this.$missionStage].systemID = this.$missionTSSystem;
        }
    }
    
    this.playerWillSaveGame = function _ShrewsRights_playerWillSaveGame(reason) {
        missionVariables.$ShrewsRightsStage       = this.$missionStage;
        missionVariables.$ShrewsRightsJumpCount   = this.$missionJumpCount;
        missionVariables.$ShrewsRightsTotalJmp    = this.$missionTotalJmp;
        missionVariables.$ShrewsRightsNewsCount   = this.$missionNewsCount;
        missionVariables.$ShrewsRightsResume      = this.$missionResume;
        missionVariables.$ShrewsRightsWhatName    = this.$missionWhatName;
        missionVariables.$ShrewsRightsHow         = this.$missionHow;
        missionVariables.$ShrewsRightsBlackBox    = this.$blackBox;
        missionVariables.$ShrewsRightsFreeDocking = this.$freeDocking;
        missionVariables.$ShrewsRightsTSSystem    = this.$missionTSSystem;
        missionVariables.$ShrewsRightsTSSysName   = this.$missionTSSysName;
        missionVariables.$ShrewsRightsTSDead      = this.$missionTSDead;
    
        if (this.$missionPastNames)
            missionVariables.$ShrewsRightsPastNames = JSON.stringify(this.$missionPastNames);
    }
    
    this.missionScreenOpportunity = function _ShrewsRights_missionScreenOpportunity() {
        // only in Galaxy 2 and 512+ kills,  not interstella space or no data found for mission
        if (system.ID == -1 || ! this.$stage[this.$missionStage] || this.$stage[this.$missionStage].complete == true) return;
    
        if (this.$displayScreen == false) {
            if (this.$debug) log(this.name, "already displayed mission screen");
            return;
        }
        this.$displayScreen = false;
    
        let stage = this.$stage[this.$missionStage];
    
        // Taking a time out.
        if (this.$missionResume != null) return;
    
        let ps = player.ship;
        if (ps && ps.docked && ps.dockedStation == system.mainStation) {
            if (this.$debug)
                log(this.name, "Docked at " + system.name + " (" + system.ID + ") Stage: "+ this.$missionStage + " News: " + this.$missionNewsCount);
    
            if (system.ID == stage.systemID) {
                if (this.$debug)
                    log(this.name, "Running stage "+ this.$missionStage + " at " + stage.sysName + "(" + system.ID + ")");
    
                // Time to find Tiaras Skeets within 7ly.
                // find new systemID. and F5F5 mission where to go next.
                if (stage.findTS) {
                    if (this.$debug) log(this.name, "Calling  _systemNearBy(7, " + stage.mission + ") by missionScreenOpportunity");
                    this._systemNearBy(7, this.$missionStage + 1);
                }
    
                this._displayInstructions(stage.message);
    
                if (this.$debug) log(this.name, "After _displayInstructions, stage "+ this.$missionStage);
            }
    
            // End mission tests.
            if (this.$missionStage > 18) {
                if (this.$missionTSDead[0] == 4) {
                    // TS was spawned but not destroyed or derelict, TS is still free
                    // and flying in circles
                    this._displayInstructions(19);
    
                } else {
                    if (this.$missionTSDead[0] == 3) {
                        // TS is lost in space
                        this._displayInstructions(20);
                    } else {
                        // TS is dead or captured, yay!
                        this._displayInstructions(21);
                    }
                }
            }
        }
    }
    
    this.shipExitedWitchspace = function _ShrewsRights_shipExitedWitchspace() {
        // only in Galaxy 2 and 512+ kills,  not interstella space or no data found for mission
        if (galaxyNumber != 1 || player.score < 512 || system.ID == -1 ||
            ! this.$stage[this.$missionStage] || this.$stage[this.$missionStage].complete == true) {
            return;
        }
    
        this.$missionTotalJmp++;
        this.$missionJumpCount++;
        this._stopBBTimer();
    
        let rnd  = Math.random();  // This is here so we can log it if required.
        if (this.$debug) log(this.name, "shipExitedWitchspace Stage: " + this.$missionStage + " News: " + this.$missionNewsCount +
                                        " Jumps: " + this.$missionJumpCount + " Total jmp: " + this.$missionTotalJmp +
                                        (this.$missionResume != null ? " Resume: " + this.$missionResume + " > " + rnd : ""));
    
        if (this.$missionResume != null) {
            this.$missionResume += 0.03; // reduce the chance of waiting at each jump.
    
            if (this.$missionResume > 1.0 || this.$missionResume > rnd) {
                // wait a few seconds of the message will not be heard.
                this.$missionStage++;
                new Timer(this, this._finishedWaiting.bind(this), 10);
                this.$missionResume = null;
            }
        }
    
        let stage = this.$stage[this.$missionStage];
        // nothing more to do once finished.
        if (this.$stage[this.$missionStage].complete) return;
    
        // Travelled to Veriar and should contact the Liberate Sorex. Create the derelict and black box.
        if (this.$missionResume == null) {
            if (stage.award === "BLACK_BOX" && stage.systemID == system.ID && this.$blackBox == null) {
                // Only spawn one derelict and black box for a player.
                if (this.$debug) log(this.name, "Will add derelict and blackbox, Flag: " + this.$blackBox);
                this._addDerelictShip();
            }
        }
    
        let news = this.$news[this.$missionNewsCount];
        if (news != null) {
            news.params = {};
    
            if (this.$missionResume == null && news.antishrew == true) {
                // Add anti-shrew ships to attack player when bounty has been announced,
                // Stop before finding the Liberate Sorex.
                this._addAntiShrew();
            }
    
            if (this.$missionResume == null && news.findTS == true) {
                // Time to find where Tiaras Skeets.
                // This sets up stage 17 with a systemID. and F5F5 mission where to go next.
                if (this.$debug) log(this.name, "Calling  _systemNearBy(13, " + news.mission + ") by shipExitedWitchspace");
                this._systemNearBy(13, news.mission);
                news.params.ShrewsRights_TSSysName = this.$missionTSSysName;
            }
    
            if (this.$missionStage == news.stage) {
                // Update the mission status on F5F5
                if (this.$missionResume == null && news.mission > 0) {
                    if (this.$debug) log(this.name, "mission text F5F5 update " + news.mission);
    
                    let msg = expandMissionText("ShrewsRights_msg" + news.mission, news.params);
                    if (msg != null || msg.length == 0) {
                        if (msg.length == 0) msg = null;
                        if (this.$debug) log(this.name, "shipExitedWitchspace F5F5 ShrewsRights_msg" + news.mission);
                        mission.setInstructions(msg, this.name);
                    }
                }
    
                // When there is a news update to process
                if ((news.jumps > 0 && this.$missionJumpCount >= news.jumps) ||
                    (news.tJumps > 0 && this.$missionTotalJmp  >= news.tJumps)) {
                    this._updateNewsDesc(news);
                }
    
            } else if (this.$missionStage > news.stage) {
                // When the news update got missed for some reason. Hmm.
                if (this.$debug) log(this.name, "news update got missed while testing " + this.$missionNewsCount);
                this._updateNewsDesc(news);
            }
    
            // Using news.mission to redirect to a new stage.
            if (news.mission > 0) this.$missionStage = news.mission;
        }
    
        if (this.$debug) {
            log(this.name, "shipExitedWitchspace end, Stage: " + this.$missionStage + " News: " + this.$missionNewsCount +
                                    " Jumps: " + this.$missionJumpCount + " Total jmp: " +this.$missionTotalJmp);
        }
    }  // end of this.shipExitedWitchspace
    
    this.systemWillPopulate = function _ShrewsRights_systemWillPopulate() {
       if (this.$stage[this.$missionStage].spawnTS && this.$missionTSSysName &&
                       this.$missionTSSystem && this.$missionTSSystem == system.ID) {
            if (this.$debug) log(this.name, "systemWillPopulate Will add TS in this system");
    
            // Assume you kill and capture TS, unmark the system on F6
            mission.unmarkSystem({system: this.$missionTSSystem, name: this.name});
    
            // Move mission stage to one of the possible endings.
            this.$missionStage = 19;
    
            // spawn the system as usual
            worldScripts["oolite-populator"].systemWillPopulate();
    
            // Spawn Tiaras Skeets in Whiskers at the witchpoint
            system.setPopulator("ShrewsRights_Tiaras_Skeets", {
                priority: 100,
                location: "WITCHPOINT",
                callback: function(pos) {
                    let ts = system.addShips("sr_extremist_ts", 1, pos, 0);
    
                    ts[0].setScript("ShrewsRights_Whiskers.js");
                    ts[0].setAI("ShrewsRights_extremist_AI.js");
                    ts[0].bounty   = ~~(Math.random() * 15 + 201);
                    ts[0].energy   = ts[0].maxEnergy;
    
                    // Add TS as the pilot
                    ts[0].setCrew({
                        name: "Tiaras Skeets",
                        origin: 82,   // She is from Vezaaes
                        short_description: expandDescription("[ShrewsRights_TS_description]"),
                        insurance: 0,
                        randon_seed: "0 0 0 0 " + 82 + " 1",
                    });
                },
            });
        }
    }
    
    this.shipScoopedOther = function _ShrewsRights_shipScoopedOther(whom) {
        if (whom && whom.isCargo && system.ID > -1) {
            if (whom.primaryRole === "shrewsrights_blackbox") {
                this._stopBBTimer();
    
                let ps = player.ship;
                whom.remove(true);
                ps.awardEquipment("EQ_SHREWSRIGHTS_BLACK_BOX");
                this.$missionStage = 10;
                this._displayInstructions(this.$missionStage);
    
            } else if (this.$regexpEscPod.test(whom.name) && whom.crew[0].name === "Tiaras Skeets") {
                if (this.$debug) log(this.name, "scooped escape pod with TS");
                this.$missionStage++;
                this.$missionTSDead[0] = 1;  // captured TS
                player.commsMessage(expandDescription("[ShrewsRights_scooped_TS]"), 10);
                // Timer in case TS escapes the players hold
                this._stopEscTimer();
                this.$escapeTimer = new Timer(this, this._escapePod.bind(this), 20, 10);
            }
        }
    }
    
    this.shipAttackedOther = function _ShrewsRights_shipAttackedOther(other) {
        if (this.$stage[this.$missionStage].buoy && system.ID == 5 && other.scanClass === "CLASS_BUOY") {
            // We only need to do this once
            if (this.$missionHow == null) {
                this.$missionHow      = "a";
                this.$missionWhatName = other.displayName;
                missionVariables.$ShrewsRightsWhatName = this.$missionWhatName;
                this.$missionStage++;
                if (this.$debug)
                    log(this.name, "fired laser at (" + this.$missionStage + ") Class: " + other.scanClass + " Name: " + other.displayName + " How: " + this.$missionHow + " Text: " + this.$missionWhatName);
    
                let msg = expandMissionText("ShrewsRights_msg" + this.$missionStage);
                if (msg != null || msg.length == 0) {
                    if (msg.length == 0) msg = null;
                    mission.setInstructions(msg, this.name); //show in F5F5 screen
                }
            }
        }
    }
    
    this.shipTargetDestroyed = function _ShrewsRights_shipTargetDestroyed(target) {
        if (this.$stage[this.$missionStage - 1] && this.$stage[this.$missionStage - 1].buoy &&
                   this.$stage[this.$missionStage - 1].systemID == system.ID &&  target.scanClass === "CLASS_BUOY") {
            if (this.$debug)
                log(this.name, "shipTargetDestroyed " + " (" + this.$missionStage + ") Class:" + target.scanClass + " Name: " + target.displayName+ " isDere " + target.isDerelict);
    
            // 'destroyed' takes precedence over 'attacked' for the displays on news and messages.
            this.$missionWhatName = target.displayName;
            this.$missionHow      = "d";
            this.$missionWhatName = target.displayName;
            missionVariables.$ShrewsRightsWhatName = this.$missionWhatName;
            if (this.$debug)
                log(this.name, "shipTargetDestroyed (" + this.$missionStage + ") Class:" + target.scanClass + " Name: " + target.displayName  + " How: " + this.$missionHow + " Text: " + this.$missionWhatName);
        }
    }
    
    this.shipKilledOther = function _ShrewRights_shipKilledOther(whom, damageType) {
        if (this.$stage[this.$missionStage] && this.$stage[this.$missionStage].spawnTS &&
                                               this.$stage[this.$missionStage].systemID == system.ID) {
            if (whom.crew && whom.crew[0].name === "Tiaras Skeets") {
                if (this.$debug)
                    log(this.name, "shipKilledOther (" + this.$missionStage + ") Class:" + whom.scanClass + " Name: " + whom.displayName + " Crew:" + whom.crew[0].name + " Damage: " + damageType);
    
                // When TS was killed in her ship by you.
                if (this.$debug) log(this.name, "TS was killed in " + whom.displayName);
                this.$missionStage++;
                this.$missionTSDead[0] = 2;   // TS is dead
                player.commsMessage(expandDescription("[ShrewsRights_killed_TS] in her escape pod."), 10);
    
            } else if (whom.isDerelict && whom.name == "Cobra Mark III: Whiskers") {
                if (this.$debug) log(this.name, "shipKilledOther Derelict Whiskers destroyed");
                this.$missionTSDead[1] = 3;   // You destroyed the derelict, TS will be lost in space if not scooped
            }
        }
    }
    
    this.shipEnteredStationAegis = function _ShrewsRights_shipEnteredStationAegis(station) {
        // enter Aegis at Vezaaes 82 after attacking the bouy
        if (this.$stage[this.$missionStage].aegis && this.$stage[this.$missionStage].systemID == system.ID) {
            this.$aegisEntered = true;
            this._stopBBTimer();
    
            if (this.$debug) log(this.name, "Entered aegis " + station.name + " System: " + system.ID + " Stage: " + this.$missionStage);
    
            // player is probably flying full speed at the station. We don't
            // want them to crash into it while they read the message.
            let ps = player.ship;
            this.$orgThrust   = ps.thrust;
            this.$orgMaxSpeed = ps.maxSpeed;
            this.$orgVelocity = ps.velocity;
            ps.velocity = [0,0,0];
            ps.thrust   = 0;
            ps.maxSpeed = 0;
            // Setting velocity before and after thrust and maxSpeed helps stop the ship when torus drive is on.
            ps.velocity = [0,0,0];
            if (this.$debug) {
                log(this.name, "Coming to a full stop. maxSpeed: " + this.$orgMaxSpeed + " thrust: " + this.$orgThrust + " Vel: " + this.$orgVelocity + " Forward Vel: " + ps.vectorForward + " Vel Now: " + ps.velocity);
            }
            this._displayInstructions(this.$missionStage);
        }
    }
    
    this.shipWillDockWithStation = function _ShrewsRights_shipWillDockWithStation(station) {
        // flip flag so we show any missionScreenOpportunity()
        this.$displayScreen = true;
    
        if (this.$dockFee && this.$freeDocking != null && this.$freeDocking.indexOf(system.name) != -1) {
            let txt = "";
            // turn off the docking fees for some stations, grab description if OXP was loaded.
            if (this.$dockFee) {
                txt = "\n\n" + expandDescription("[dockfee_" + system.techLevel + "]", {fee_amt: "0 credits"});
                this.$dockFee._simulator = true;
            }
            player.addMessageToArrivalReport(expandDescription("[ShrewsRights_freeDocking]") + txt);
        }
    }
    
    this.guiScreenChanged = function _ShrewsRights_guiScreenChanged(to, from) {
        if (from === "GUI_SCREEN_STATUS" && to === "GUI_SCREEN_MAIN" && this.$aegisEntered) {
            if (this.$debug)
    
                log(this.name, "guiScreenChanged from " + from + " -> " + to + " Stage: " + this.$missionStage + " ID: " + system.ID);
    
            if (this.$stage[this.$missionStage].aegis && this.$stage[this.$missionStage].systemID == system.ID) {
                let ps = player.ship;
                ps.thrust   = this.$orgThrust;
                ps.maxSpeed = this.$orgMaxSpeed;
                ps.velocity = this.$orgVelocity;
    
                if (this.$debug) log(this.name, "Returning engines to original value. Stage " + this.$missionStage);
            }
        } else if (to === "GUI_SCREEN_SYSTEM_DATA") {
            if (this.$debug) log(this.name, "GUI screen changed to GUI_SCREEN_SYSTEM_DATA from: " + from);
        }
    }
    
    this._displayInstructions = function _ShrewRights_displayInstructions(step) {
        let wsSR = worldScripts.ShrewsRights;
        if (wsSR.$debug) log(wsSR.name, "_displayInstructions for: " + step + "  $missionStage: " + wsSR.$missionStage);
        this._stopBBTimer();
    
        if (step > 0) { // display screen for step mission
            let stage = wsSR.$stage[wsSR.$missionStage];
            let overlayImg = { width: 495,
                               height: 500,
                               name: "ShrewsRights_ui_background_msg.png",
                             };
            if (stage && stage.img) {
                overlayImg.name = "ShrewsRights_ui_background_" + stage.img[0] + ".png";
            }
    
            // Add / remove EQ
            if (stage.award  != null) player.ship.awardEquipment("EQ_SHREWSRIGHTS_" + stage.award);
            if (stage.remove != null) player.ship.removeEquipment("EQ_SHREWSRIGHTS_" + stage.remove);
    
            // Mark and unmark Systems that you travel to.
            if (stage.mark > 0) {
                mission.markSystem({system: stage.mark,
                                    name: wsSR.name,
                                    markerColor: "brownColor",
                                    markerScale: 1,
                                    markerShape: "MARKER_SQUARE"});
            }
            if (stage.unmark > 0) mission.unmarkSystem({system: stage.unmark, name: wsSR.name});
    
            if (wsSR.$debug) log(wsSR.name, "_displayInstructions calling mission screen for " + step);
            wsSR.$missionStage = step;
            wsSR.$missionStageSub = "m";
            wsSR._missionScreen(wsSR, overlayImg);
            this.$missionJumpCount = 0;
        }
    
        //show text on F5F5 screen
        let params = {};
        if (wsSR.$missionTSSysName) {
            params.ShrewsRights_TSSysName = wsSR.$missionTSSysName;
            if (wsSR.$debug) log(wsSR.name, "_displayInstructions Where is TS: " + params.ShrewsRights_TSSysName);
        }
    
        let msg = expandMissionText("ShrewsRights_msg" + wsSR.$missionStage, params);
        if (msg != null || msg.length == 0) {
            if (msg.length == 0) msg = null;
            if (wsSR.$debug)
                log(wsSR.name, "_displayInstructions will call setInstructions on ShrewsRights_msg" + wsSR.$missionStage);
            mission.setInstructions(msg, wsSR.name);
        }
    }
    
    this._missionScreen = function _ShrewRights_missionScreen(wsSR, overlayImg) {
        let ps     = player.ship
        let mt     = wsSR.$missionStage + wsSR.$missionStageSub;
        let msgKey = "ShrewsRights_msg" + mt
        let stage  = wsSR.$stage[wsSR.$missionStage];
    
        if (wsSR.$debug)
            log(wsSR.name, "_missionScreen, Stage/Sub :" + mt + "  Key: " + msgKey + "  overlayImg: " + overlayImg.name);
    
        // Where are we going to next, mark on galactic chart
        wsSR.$missionNextSys = null;
        if (stage.mark > 0) {
            wsSR.$missionNextSys = System.systemNameForID(stage.mark);
            missionVariables.$ShrewsRightsNextSys = wsSR.$missionNextSys;
            if (wsSR.$debug) log(wsSR.name, "Next system: " + wsSR.$missionNextSys);
        }
    
        // Grab some text to describe what was attacked or destroyed at Veriar.
        let params = {};
        if (stage.how && wsSR.$missionHow) {
            params.ShrewsRights_what_was_attacked = expandDescription("[ShrewsRights_how_msg" + wsSR.$missionHow + "]");
            if (wsSR.$debug) log(wsSR.name, "What How: " + params.ShrewsRights_what_was_attacked);
        }
        if (wsSR.$missionTSSysName) {
            params.ShrewsRights_TSSysName = wsSR.$missionTSSysName;
            if (wsSR.$debug) log(wsSR.name, "_missionScreen Where is she: " + params.ShrewsRights_TSSysName);
        }
    
        if (stage.lost == true || stage.dead == true) {
            if (stage.lost == true) {
                params.ShrewsRights_destroyed_derelict = expandDescription("[ShrewsRights_state_ship" + this.$missionTSDead[1] + "]");
    
            } else if (stage.dead == true) {
                params.ShrewsRights_capture_death      = expandDescription("[ShrewsRights_state_TS" + this.$missionTSDead[0] + "]");
                params.ShrewsRights_destroyed_derelict = expandDescription("[ShrewsRights_state_ship" + this.$missionTSDead[1] + "]");
            }
        }
    
        // Set some of the Shrew home worlds to give free docking, after your trial
        // clean offender status or bounty.
        if (stage.clean) {
                if (this.$dockFee) wsSR._whichDockingFees(wsSR);
    
            // If the bounty system is installed remove any offences from Veriar.
            let wsB = worldScripts.BountySystem_Core;
            if (wsB && wsB._offences) {
                for (let i = 0; i < wsB._offences.length; i++) {
                    // Only clear the offences committed in Veriar.
                    if (wsB._offences[i].systemName === "Veriar") wsB._offences.splice(i, 1);
                }
            } else {  // Player has a vanila system bounty so we can clear that.
                ps.bounty = 0;
            }
        }
    
        // Grab the full text for the message 'ShrewsRights_msg1m'
        let msg = expandMissionText(msgKey, params);
        if (msg == null || msg.length == 0) return;
    
        if (wsSR.$debug) log(wsSR.name, "key: " + msgKey + "  msg: " + msg.substr(0, 40) + "...");
    
        // Read any choices from missiontext.plist 'ShrewsRights_msg1m + j'
        if (wsSR.$debug) log(wsSR.name, "Choices for Key: " + msgKey);
        let c = [];
        for (let j = 1; j < 10; j++) {
            let txt = expandMissionText(msgKey + j);
            if (txt && txt.length > 0) {
                   c[j] = txt;
            } else break;
        }
    
        if (c.length < 1) c[0] = "Close Message"; //default choice if none are defined
        if (wsSR.$debug) log(wsSR.name, "    c.length: " + c.length + " c: "+ c);
    
        wsSR.$messagePing.playSound("[ShrewsRights_msg_ping]");
    
        mission.runScreen({
            title: "Equal rights for Shrews",
            screenID: "ShrewsRights_mission_screen",
            message: msg,
            exitScreen: "GUI_SCREEN_MANIFEST",
            overlay: overlayImg,
            choices: { "_0" : c[0], "_1" : c[1], "_2" : c[2], "_3" : c[3], "_4" : c[4],
                       "_5" : c[5], "_6" : c[6], "_7" : c[7], "_8" : c[8], "_9" : c[9],
                     },
        }, function(choice) {
            let selection = 0;
            if (choice) selection = Number(choice.substr(1)); // remove the leading "_" to give 0-9
    
            if (wsSR.$debug) {
                log(wsSR.name, "runScreen, chosen: " + selection + "  c[" + selection + "] = " + c[selection]);
                log(wsSR.name, "Stage: " + wsSR.$missionStage + " Sub: " + wsSR.$missionStageSub + "  " + stage.sysName);
            }
    
            // Are we getting paid or paying for anything?
            let cash = expandMissionText("ShrewsRights_msg" + mt + selection + "c");
            if (cash != null && wsSR.$regexNumber.test(cash)) {
                cash = Number(cash);
                // Add a bonus for your fuel usage after your trial
                if (stage && stage.fuel == true)  cash += Math.min(wsSR.$missionTotalJmp * 3, 500);
                player.credits += cash;
                let msg = expandMissionText("ShrewsRights_msg" + mt + selection + "cm", {ShrewsRights_cash: cash});
                if (msg) player.commsMessage(msg);
            }
    
            // is there a redirect to a new stage here 'ShrewsRights_msg1m2r'
            let redirValue = expandMissionText("ShrewsRights_msg" + mt + selection + "r");
            if (wsSR.$debug) log(wsSR.name, "Redir Key: " + "ShrewsRights_msg" + mt + selection + "r   Value: '" + redirValue + "'");
    
            if (redirValue && redirValue.length > 0) {
                redirValue = redirValue.toLowerCase()
                if (redirValue.indexOf("j") == 0) {
                    // Player choose to take a break, redir is 'jN'. Where ask again
                    // will be when Math.random() < (N /10);
                    wsSR.$missionResume = Number(redirValue.substr(1) / 10);
                    // Update F5F5
                    let msg = expandDescription("[ShrewsRights_waiting_msg]",
                                                 {ShrewsRights_SystemName: wsSR.$stage[wsSR.$missionStage + 1].sysName});
                    mission.setInstructions(msg, wsSR.name);
    
                } else {
                        //redir will have 'nN' or 'dN' & 'dNm'
                    let nextStage = redirValue.substr(1);
                    if (wsSR.$regexNumber.test(nextStage)) wsSR.$missionStage = nextStage;
    
                    if (redirValue.indexOf("n") == 0) {
                        //redir into a new stage, do NOT do choices
                        if (wsSR.$debug)
                            log(wsSR.name, "Will call _displayInstructions(0) Stage: " + wsSR.$missionStage);
    
                        wsSR._displayInstructions(0);    // update F5F5 only, and for the next $missionStage
    
                    } else if (redirValue.indexOf("d") == 0) {
                        //redir into a new sub stage, DO choices
    
                        // Is there more than one background img or more
                        let overlayImg = { width:  495,
                                           height: 500,
                                           name: "ShrewsRights_ui_background_msg.png", // default img
                                         };
                        if (stage && stage.img) {
                            if (stage.img.length > 1)
                                overlayImg.name = "ShrewsRights_ui_background_" + stage.img[1] + ".png";
                            else
                                overlayImg.name = "ShrewsRights_ui_background_" + stage.img[0] + ".png";
                        }
                        wsSR.$missionStageSub = nextStage.replace(wsSR.$missionStage, "") + "m";
    
                        if (wsSR.$debug)
                            log(wsSR.name, "call _missionScreen on ShrewsRights_msg" + wsSR.$missionStage + wsSR.$missionStageSub);
    
                        wsSR._missionScreen(wsSR, overlayImg);
                    }
                }
    
                if (wsSR.$debug) {
                    log(wsSR.name, "End, Redir: '" + redirValue + "' Stage: " + wsSR.$missionStage + " Sub: " + wsSR.$missionStageSub);
                }
            }
    
            // If we have reached the end remove the text on F5F5
            if (stage.complete) mission.setInstructions(null, wsSR.name);
    
            if (wsSR.$debug) log(wsSR.name, "End, function(choice)");
        });  // End of mission.runScreen({...
    
        if (wsSR.$debug) log(wsSR.name, "return from end _missionScreen");
    } // end of this._missionScreen()
    
    this._updateNewsDesc = function _ShrewsRights_updateNewsDesc(news) {
        if (this.$GNN) {
            if (this.$debug)
                log(this.name, "_publishNews Num: " + this.$missionNewsCount + " Agency: " + news.agency + " How: " + this.$missionHow);
    
            // Grab some text to describe what was attacked or destroyed at Veriar.
            if (news.how && this.$missionHow) {
                let whatHow = null;
                whatHow = expandDescription("[ShrewsRights_how" + this.$missionNewsCount + this.$missionHow + "]");
                news.params.ShrewsRights_what_was_attacked = whatHow;
                if (this.$debug) log(this.name, "What How: " + whatHow);
            }
            this.$GNN._insertNews({
                ID:       this.name,
                Message:  expandMissionText("ShrewsRights_news" + this.$missionNewsCount, news.params),
                Agency:   news.agency,
                Priority: 1,
    //            Pic: {name: "ShrewsRights_GNN_" + news.agency + ~~(Math.random() * 3 + 1) + ".png", height: 512},
    //            PicExt: this.name,
                });
        }
    
        if (news.mark > 0) {
            mission.markSystem({system: news.mark,
                                name: this.name,
                                markerColor: "brownColor",
                                markerScale: 1,
                                markerShape: "MARKER_SQUARE"});
        }
    
        if (news.desc && news.desc[0] > 0) this._setSystemDesc(news.desc);
        this.$missionNewsCount++;
    }
    
    this._setSystemDesc = function _ShrewsRights_setSystemDesc(update) {
        let desc = System.infoForSystem(1, update[0]).description;
        let txt  = expandMissionText("ShrewsRights_desc_" + update[1])
    
        // If feudal states OXP is loaded it may have changed some descriptions. Kepp those changes.
        if (worldScripts["feudal-challenge.js"] != null && desc.indexOf("Note that this system" > 0))
            txt += " Note that this system is a member of the Feudal States.";
    
        if (worldScripts["System Features: Rings"] != null && desc.indexOf("This planet has rings" > 0))
            txt += " This planet has rings.";
    
        System.infoForSystem(1, update[0]).description = txt;
    }
    
    this._addAntiShrew = function _ShrewsRights_addAntiShrew() {
        let Rnd = Math.random;
        let ps  = player.ship;
    
        // Start with one or two bad guys and then Add extra bad guys depending
        // how many kills the player has made. This is also used in whether the
        // ships a re spawned or not.
        let shipCount = ~~(Rnd() * 2 + 1);
        shipCount = Math.min(shipCount + ~~(player.score / 1200), 5);
    
        // Chance of getting attacked, depends on: stage and how many ships could be spawned.
        let chance = 0.2 + (((this.$missionStage - 3) / 2) + (shipCount / 2)) / 10;
        let check = Rnd();
        if (this.$debug) log(this.name, "bad guys chance " + chance + " >= Check " + check);
    
        if (chance >= check || this.$debug) {
            if (this.$debug) log(this.name, shipCount + " bad guys to be spawned.");
    
            let as = null;
            chance /= 2;
            // Two ways to add ships, first at witchpoint beacon may not be in scanner of player
            // Seems easier as player is not attacked right away.
            if (player.score < 1000 || Rnd() >= 0.65)
                as = system.addShips("sr_extremist", shipCount);
            else
                as = system.addShips("sr_extremist", shipCount, ps.position, Rnd() * 2000 + 10000);
    
            if (as) {
                let baseEq = 0;
                if (ps.equipmentStatus("EQ_SHIELD_BOOSTER")   === "EQUIPMENT_OK") baseEq += 0.175;
                if (ps.equipmentStatus("EQ_SHIELD_ENHANCER")  === "EQUIPMENT_OK") baseEq += 0.1;
                if (ps.equipmentStatus("EQ_IRONHIDE_MIL")     === "EQUIPMENT_OK") baseEq += 0.15;
                else if (ps.equipmentStatus("EQ_IRONHIDE")    === "EQUIPMENT_OK") baseEq += 0.1;
                if (ps.equipmentStatus("EQ_MISSILE_ANALYSER") === "EQUIPMENT_OK") {
                    baseEq += 0.025;
                    if (ps.equipmentStatus("EQ_AMS") === "EQUIPMENT_OK") baseEq += 0.1;
                }
                if (ps.equipmentStatus("EQ_AUTOECM") === "EQUIPMENT_OK")  baseEq += 0.075;
                else if (ps.equipmentStatus("EQ_ECM") === "EQUIPMENT_OK") baseEq += 0.05;
    
                let i = as.length;
                while (i--) {
                    let debugTxt = "";
                    let crewName = randomName() + " " + randomName();
                    // origin is one of the anti-shrew worlds but never Arries [0]
                    let whichSys = ~~(Rnd() * (this.$systemIDs.length - 1) + 1);
                    let crewSystemID = this.$systemIDs[whichSys];
                    let crewSystem   = this.$systems[whichSys];
                    if (this.$debug)
                        log(this.name, "Picking System ID: " + crewSystemID + ", " + crewSystem + "  CrewName: " + crewName);
    
                   as[i].setCrew({
                        name: crewName,
                        origin: crewSystemID,
                        short_description: expandDescription("[ShrewsRights_badguy_desc]", {ShrewsRights_crewSystem: crewSystem,}),
                        insurance: 0,
                        randon_seed: "0 0 0 0 " + crewSystemID + " 1",
                    });
    
                    // Pick a ship's name and add roman numerals.
                    let shipName = expandDescription("[ShrewsRights_ship_names]");
                    if (this.$missionPastNames[shipName]) ++this.$missionPastNames[shipName];
                    else this.$missionPastNames[shipName] = 1;
    
                    as[i].shipUniqueName = shipName + " " + this._toRoman(this.$missionPastNames[shipName], true);
                    if (this.$debug)
                        log(this.name, "    Name: " + shipName + " " + this._toRoman(this.$missionPastNames[shipName], true) + " " + as[i].primaryRole);
    
                    as[i].target = player.ship; // Attack the player
                    as[i].bounty = ~~(Rnd() * 5 + 1) + Math.max((shipCount * 8), 25);
                    as[i].missileLoadTime = 2.0;
    
                    let eq = baseEq;
    
                    if (player.score > 2000 && i > 3 && Rnd() > (0.75 - eq)) {
                        as[i].awardEquipment("EQ_SHIELD_BOOSTER");
                        as[i].bounty += ~~(Rnd() * 5 + 7);
                        if (this.$debug) debugTxt += " shield booster";
                    }
    
                    if (i > 2 && Rnd() > (0.55 - eq)) {
                        as[i].awardEquipment("EQ_SHIELD_ENHANCER");
                        as[i].bounty += ~~(Rnd() * 5 + 5);
                        if (this.$debug) debugTxt += " shield enhancer";
                    }
    
                    // give some bad guys get better weapons
                    if (player.score > 2500 && i > 4  && Rnd() > (0.9 - eq)) {
                        as[i].forwardWeapon = "WEAPON_MILITARY_LASER";
                        as[i].bounty += ~~(Rnd() * 20 + 30);
                        if (this.$debug) debugTxt += " military laser.";
    
                    } else if ((shipCount > 3 && Rnd() > (0.7 - eq)) || (shipCount > 2 && Rnd() > (0.85 - eq))) {
                        as[i].forwardWeapon = "WEAPON_BEAM_LASER";
                        as[i].bounty += ~~(Rnd() * 5 + 5);
                        if (this.$debug) debugTxt += " beam laser.";
                    }
    
                    if (this.$debug) {
                        log(this.name, debugTxt + " Bounty: " + as[i].bounty + " EQ: " + (~~(eq * 1000) / 1000));
                    }
                }
            }
        }
    }
    
    /*
      This collection of functions for the derelict and black box were taken from: Eric Walsh's DeepSpaceDredger OXP
      phkb's GalCop missiions - GalCopBB_Derelict OXP;
      cim's Black Box coding
    */
    this._addDerelictShip = function _ShrewRights_addDerelictShip(whom, who) {
        // derelict ship creation code from Eric Walsh's DeepSpaceDredger OXP
    
        let derelict = system.addShips("trader", 1, player.ship.position, ~~(Math.random() * 2000) + 16000)[0];
    
        if (! derelict) {
            if (this.$debug) log(this.name, "!!ERROR: Derelict ship not spawned!");
    
        } else {
            derelict.shipUniqueName = "Liberate Sorex";
            derelict.bounty = 0;
            // set script to default, to avoid a special script for the trader doing stuff.
            derelict.setScript("oolite-default-ship-script.js");
            derelict.switchAI("oolite-nullAI.js");
            // remove any escorts that came with the ship
            if (derelict.escorts) {
                for (let j = derelict.escorts.length - 1; j >= 0; j--) derelict.escorts[j].remove(true);
            }
    
            // function to remove the escape pod after launch when calling derelict.abandonShip()
            derelict.script.shipLaunchedEscapePod = this._derelict_shipLaunchedEscapePod;
            // must have an escape pod, so we can delete it in _derelict_shipLaunchedEscapePod(), or ship will not spawn
            if (derelict.equipmentStatus("EQ_ESCAPE_POD") === "EQUIPMENT_UNAVAILABLE") derelict.awardEquipment("EQ_ESCAPE_POD");
    
            // make sure no pilot is left behind and this command turns the ship into cargo.
            derelict.abandonShip();
            derelict.primaryRole = "shrewsrights_derelict"; // to avoid pirate attacks
            derelict.displayName += " (derelict)";
            derelict.lightsActive = false;
            derelict.throwSpark();  // not sure if this does anything.
    
            if (this.$debug) log(this.name, "Derelict ship has been spawned!");
    
            if (player.ship.equipmentStatus("EQ_FUEL_SCOOPS") !== "EQUIPMENT_OK") {
                // Go to the main station in Veriar and dock.
                // stat timer, delay for 5 second and repeat every 2 seconds.
                this._stopBBTimer();
                this.$proximityTimer = new Timer(this, this._derelictProximity.bind(this), 5, 2);
    
            } else {
                // Player has fuel scoops and the derelict ship was spawned.
                // We can spawn the black box
                this.$blackBox = derelict.spawnOne("shrewsrights_blackbox");
                if (this.$blackBox) {
                    this.$blackBox.setScript("oolite-default-ship-script.js");
    
                    this.$blackBox.maxSpeed  = 7;
                    this.$blackBox.maxThrust = 10;
                    this.$blackBox.thrust    = 1;
    
                    // Give the blackbox a heading and velocity
                    this.$blackBox.orientation = Quaternion.random();
                    this.$blackBox.desiredSppeed = this.$blackBox.maxSpeed;
    
                    if (this.$debug) log(this.name, "Black box has been spawned");
    
                    // start timer, after 5 seconds and then repeat at 15 second
                    // intervals, so the player gets the idea.
                    this._stopBBTimer()
                    this.$blackBoxTimer = new Timer(this, this._blackBoxMsg.bind(this), 5, 15);
                }
            }
        }
    } // end of this._addDerelictShip
    
    this._derelict_shipLaunchedEscapePod = function _ShrewsRights_derelict_shipLaunchedEscapePod(escapepod, passengers) {
        // we don't want floating escapepods around but need them initially to create the derelict.
        // Note: this does not always work :(
        escapepod.remove(true);
    }
    
    this._blackBoxMsg = function _ShrewsRights_blackBoxMsg() {
        // Black box within scanner range, send message to the player.
        if (system.countShipsWithPrimaryRole("shrewsrights_blackbox", player.ship, 25000) === 1)
            player.commsMessage("Black box detected on scanner.", 10);
    }
    
    this._derelictProximity = function _ShrewsRights_derelictProximity() {
        // When player gets close to derelict ship.
        if (system.countShipsWithPrimaryRole("shrewsrights_blackbox", player.ship, 25000) === 1)
            player.commsMessage("Black box detected on scanner.", 10);
    }
    
    this._escapePod = function _ShrewsRights_escapePod() {
        let escPod = system.shipsWithPrimaryRole("escape-capsule", player.ship, 25000);
        if (escPod.length > 0) {
            let eLen = escPod.length;
            for (let i = 0; i < eLen; i++) {
                if (escPod[i].crew) {
                    for (let c = 0; c < escPod[i].crew.length; c++) {
                        if (escPod[i].crew[c] && escPod[i].crew[c].name == "Tiaras Skeets") {
                            // TS is lost in space again.
                            player.commsMessage(expandDescription("[ShrewsRights_escaped_TS]"));
                            this.$missionTSDead[0] = 3;
                            this._stopEscTimer();
                            break
                        }
                    }
                }
            }
        }
    }
    
    this._toRoman = function _ShrewsRights_toRoman(num, first) {
        let roman = [[1000, 'M'], [900, 'CM'], [500, 'D'], [400, 'CD'], [100, 'C'],
                     [90, 'XC'],  [50, 'L'],   [40, 'XL'], [10, 'X'],   [9, 'IX'],
                     [5, 'V'],    [4, 'IV'],   [1, 'I'],
                    ];
    
        // Do not use use 'shipname 0' or 'shipname I'.
        if (num === 0 || (first && num === 1)) return '';
    
        for (let i = 0; i < roman.length; i++) {
            if (num >= roman[i][0]) return roman[i][1] + this._toRoman(num - roman[i][0], false);
        }
    }
    
    this._stopBBTimer = function _ShrewsRights_stopBBTimer() {
        if (this.$blackBoxTimer)  this.$blackBoxTimer.stop();
        if (this.$proximityTimer) this.$proximityTimer.stop();
    }
    
    this._stopEscTimer = function _ShrewsRights_stopEscTimer() {
        if (this.$escapeTimer)  this.$escapeTimer.stop();
    }
    
    
    // Award the player free docking fees after trial.
    this._whichDockingFees = function _ShrewsRights_whichDockingFees(wsSR) {
        if (this.$dockFee) {
            wsSR.$freeDocking = [];
            for (let i = 0; i < wsSR.$systems.length; i++) {
                if (Math.random() > 0.4) wsSR.$freeDocking.push(wsSR.$systems[i]);
            }
        }
    }
    
    this._systemNearBy = function _ShrewsRights_systemNearBy(range, nextStage) {
        let count = (! this.$missionTSSystem ? 1 : 2);
    
        // When testing always find TS within 1 jump :)
    //    if (this.$debug) range = 6.8;
    
        // unmark the first system when you arrive there
        if (count == 2 && this.$missionTSSystem) {
            mission.unmarkSystem({system: this.$missionTSSystem, name: this.name,});
        }
    
        let possible = this._findCloseBySystem(range);
        let i = possible.length;
        if (this.$debug) log(this.name, "Found " + i + " systems within " + range + "ly");
        if (i > 0) {
            let actual = ~~(Math.random() * i);
            if (this.$debug && actual)
                log(this.name, "Actual system will be "  + possible[actual].name  + " (" + possible[actual].systemID + ")");
    
            this.$stage[nextStage].unmark   = possible[actual].systemID;
            this.$stage[nextStage].systemID = this.$missionTSSystem  = possible[actual].systemID;
            this.$stage[nextStage].sysName  = this.$missionTSSysName = possible[actual].name;
    
            // Display new F5F5 message
            let msg = expandDescription("[ShrewsRights_tsf5_" + count + "]", {ShrewsRights_TSSysName: possible[actual].name});
            if (msg != null || msg.length == 0) {
                if (msg.length == 0) msg = null;
                player.commsMessage(msg, 10);
                mission.setInstructions(msg, this.name);
            }
    
            mission.markSystem({system: this.$missionTSSystem,
                                name: this.name,
                                markerColor: "brownColor",
                                markerScale: 1,
                                markerShape: "MARKER_SQUARE"});
    
            if (count == 2 && this.$missionTSSystem) {
                this.$stage[nextStage + 1].mark = this.$missionTSSystem;
                this.$stage[nextStage + 2].mark = this.$missionTSSystem;
            }
    
        }
    }
    
    // returns a list of sysInfos where earthquakes are a problem
    this._findCloseBySystem = function _ShrewsRights_findCloseBySystem(range) {
       range = range || 7;
    
       return SystemInfo.filteredSystems(this, function(other) {
           return (other.systemID != system.ID &&
                   other.distanceToSystem(System.infoForSystem(galaxyNumber,system.ID)) <=  range &&
                   other.distanceToSystem(System.infoForSystem(galaxyNumber,system.ID)) >= (range - 7));
       });
    }
    
    this._finishedWaiting = function _ShrewsRights_finishedWaiting() {
        // Update F5F5 as the player has waiting long enough
        let msg = expandDescription("[ShrewsRights_deferred_msg]", {ShrewsRights_SystemName: this.$stage[this.$missionStage].sysName});
        mission.setInstructions(msg, this.name);
        player.commsMessage(msg, 10);
    }
    
    
    /*
                   *** spoiler alert ***
      Raw mission data below, if you don't want to know then don't look.
    
      The data was placed down here at the end in functions so it was not visible
      by someone being inquisitive and looking at the code.
      Something else that seemed like a good idea at the time.
    */
    
    
    
    /*
        Usings these objects made it simpler to insert new stages and news updates
        without recoding, e.g. if stage > 5  etc.
        We only need to update the value in news.stage.
     */
    
    /*
    Dest            Img     Possible Route
    ------------    ---     ------------------------------------------------------------------
     2 238 Arries   1       Ceerdiza, Titequ, Isxees, Anve, Leaza,
     3 161 Ramaan   2       Leaza, Anve, Isxees, Titequ, Ceerdiza,
     4 177 Envebe   3       Laatre, Esceso, Anusa, Esbete, Ausar, Atzaxe,
     5 204 Zatebiso 4       Ceiner, Zaaxeve, Rixees, Isoned, Aanan,
     6 150 Xeatxe   5       Aanan, Isoned,
     7 143 Vesozaxe 6       Zaaxeve, Ceiner, Erlaened, Ausar, Esbete, Anusa, Esceso, Laatre,
     8 238 Arries   1g      Laatre,Esceso, Anusa, Laribebi, Dixeza, Usdive, Solaed, Cerare,
     9  33 Diti     7g      Erlage, Anesce, Israra, Andiri,
    10   5 Veriar   8       Ansoreat, Reveabe,
    11  82 Vezaaes  10      Reerqu, Israra, Anesce, Erlage,
    12  33 Diti     7 9 9a
    */
    this._stageData = function _ShrewsRights_stageData() {
        /*
         * aegis                When true and you fly into the aegis it will trigger the stage.
         * award, remove        Give or take away EQ from a player.
         * buoy                 When true, marks the stage to attack a buoy in the system
         * clean                When true remove some of your offender/fugitive status.
         * circle               TS is in whiskers circling the witchpoint buoy.
         * dead                 TS is either captured or dead.
         * findTS               Triggers search for planet within 7ly.
         * how                  Send some additional text of what was attacked and its name.
         * img                  Used to make up the name for the background image default is ShrewsRights_ui_background_msg.png.
         * lost                 Whiskers was destroyed or left derelict but the escap pod was not scooped or did not contain TS.
         * mark, unmark         Add a brown square or remove it from a system. Mark is also used to name the next system, in msgs.
         * message              This is used to find the mission text ShrewsRights_msg[message].
         * sysName, systemID    Name and systemID where a step happens.
         */
        return [
        //  0 This is not used
        {sysName: "anywhere", systemID: 0, message: 0,},
        //  1  Dock at Arries to see what is going on.
        {sysName: "Arries",   systemID: 238, message: 1, mark: 161, unmark: 238, img: ["1"],},
        //  2
        {sysName: "Ramaan",   systemID: 161, message: 2, mark: 177, unmark: 161, img: ["2"], award: "CRYSTAL",},
        //  3
        {sysName: "Envebe",   systemID: 177, message: 3, mark: 204, unmark: 177, img: ["3"],},
        //  4 may be attacked by anti-shrew bad guys, after a feww jumps
        {sysName: "Zatebiso", systemID: 204, message: 4, mark: 150, unmark: 204, img: ["4"],},
        //  5
        {sysName: "Xeatxe",   systemID: 150, message: 5, mark: 143, unmark: 150, img: ["5"], award: "DEVICE",
                                                                                             remove: "CRYSTAL",},
        //  6
        {sysName: "Vesozaxe", systemID: 143, message: 6, mark: 238, unmark: 143, img: ["6"],},
        //  7
        {sysName: "Arries",   systemID: 238, message: 7, mark: 33, unmark: 238, img: ["1g"], award: "WHITESPOT"},
        //  8
        {sysName: "Diti",     systemID: 33, message: 8, mark: 5, unmark: 33, img: ["7g"], remove: "WHITESPOT"},
        //  9 Dock at Veriar without scooping the blackBox.
        {sysName: "Veriar",   systemID: 5, message: 9, mark: 5, unmark: 5, img: ["8"], award: "BLACK_BOX",},
        //  10 find the derelict at Veriar scoop the blackBox, this action is scripted
        {sysName: "Veriar",   systemID: 5, message: 10, mark: 5, unmark: 5, img: ["msg"],},
        //  11 Message contained in the black box from Short-leg
        {sysName: "Veriar",   systemID: 5, message: 11, mark: 5, unmark: 5, img: ["bb"],},
        //  12 only used for the F5F5 update go to Vezaaes after attacking the buoy at Veriar
        {sysName: "Veriar",   systemID: 0, message: 0, mark: 82, unmark: 5, buoy: true,},
        //  13 jump to Vezaaes enter aegis, this is scripted
        {sysName: "Vezaaes",  systemID: 82, message: 13, mark: 82, unmark: 82, aegis: true, how: true,},
        //  14 Dock at Vezaaes
        {sysName: "Vezaaes",  systemID: 82, message: 14, mark: 33, unmark: 82, img: ["10"], remove: "DEVICE", how: true,},
        //  15 Arrive for your trial, then read trial records.
        {sysName: "Diti",     systemID: 33, message: 15, unmark: 33, img: ["9a", "9b"], remove: "BLACK_BOX",
                                                                                           how: true, clean: true,},
        //  16 Go and find Tiaras Skeets, get this after reading the trial records.
        {sysName: "no where", systemID: 0, message: 16},
        //  17 Triggered by news update[28], which sets the System name & ID and F5F5
        {sysName: "tba", systemID: 0, message: 17, findTS: true,},
        //  18 jump into the system chosen above and fight TS
        {sysName: "tba", systemID: 0, message: 18, spawnTS: true,},
        //  19 Landed at a main station without making Whiskers a derelict.
        {sysName: "doesn't matter", systemID: 0, message: 19, circle: true,},
        //  20  Whiskers is derelict/destroyed but no escape pod.
        {sysName: "doesn't matter", systemID: 0, message: 20, lost: true,},
        //  21 The End, player killed/captured TS
        {sysName: "doesn't matter", systemID: 0, message: 21, dead: true,},
        //  22 The End, player gave up.
        {sysName: "doesn't matter", systemID: 0, message: 22, complete: true,},
        ];
    }
    
    // Code for which News org to use 1=GNN, 2=Rooters, 3=Snoopers, 4=Chronicle.
    this._newsData = function _ShrewsRights_newsData() {
        /*
         * agency         Which news agency publishes the news.
         * antishrew      Get attacked by anti-shrew bad guys
         * desc           Update planetary description[0]  for system[1] on F7
         * findTS         Triggers planet search within 13ly and sets mission 17.
         * how            Send some additional text of what was attacked and its name.
         * jumps          Hyperdrive jumps since the stage
         * tJumps         Total hyperspace jumps since player started
         * mark, unmark   Add a brown square or remove it from a system.
         * mission        Display stage F5F5 and then redirect to mission stage
         * stage          Which mission stage triggers this news
         */
        return [
    
        //  0  Hint 1, Something beginning in Arries
        {stage: 0, tJumps: 2, agency: 4,},
        //  1  Hint 2, Something happening in Arries, mark Arries and update F5F5
        {stage: 0, tJumps: 3, agency: 4, mark: 238, mission: 1,},
        //  2  Hint 3. Naval Fleet to Arries
        {stage: 1, tJumps: 5, agency: 3, mark: 238,},
        //  3  Shrews are lobbying Arries govenment
        {stage: 2, jumps: 2, agency: 2, desc: [238, "arries1"]},
        //  4  Diti, V & V are trying to stop shrews
        {stage: 2, jumps: 4, agency: 1,},
        //  5 bounty for anyone helping
        {stage: 3, jumps: 4, agency: 2,},
        //  6  Shrews at Arries get full citizenship, and start getting attacked by bad guys.
        {stage: 3, jumps: 2, agency: 1, antishrew: true,},
        //  7  Veriarian Economic Minister
        {stage: 4, jumps: 5, agency: 2, antishrew: true,},
        //  8  Restaurant owners are pleading
        {stage: 5, jumps: 2, agency: 3, antishrew: true,},
        //  9  Reporter at Arries parties are still on going
        {stage: 5, jumps: 4, agency: 3, antishrew: true,},
        // 10  Shrews on Arries to full citizenship, ratified galaxy wide
        {stage: 5, jumps: 5, agency: 1, antishrew: true, desc: [238, "arries2"]},
        // 11  a galactic-wide publicity campaign.
        {stage: 6, jumps: 2, agency: 3, antishrew: true,},
        // 12  Roumors from Veriar of culling.
        {stage: 7, jumps: 3, agency: 4, antishrew: true,},
        // 13  V & V reprisals from Symperthisers
        {stage: 7, jumps: 4, agency: 2, antishrew: true,},
        // 14  Whiskers Estate, one of the largest shrew
        {stage: 7, jumps: 6, agency: 1, antishrew: true,},
        // 15  Full citizenship on Envebe
        {stage: 8, jumps: 2, agency: 1, antishrew: true, desc: [204, "envebe"]},
        // 16  Shrews killed in 1000's on Veriar
        {stage: 8, jumps: 4, agency: 4, antishrew: true, desc: [5, "veriar1"]},
        // 17  Full citizenship on Zatebiso
        {stage: 8, jumps: 6, agency: 3, antishrew: true, desc: [177, "zatebiso"]},
        // 18   Wide-mouth has stated publicly
        {stage: 8, jumps: 8, agency: 2, antishrew: true,},
        // 19  Full citizenship on Diti
        {stage: 9, jumps: 2, agency: 3, antishrew: true, desc: [33, "diti"]},
        // 20  Full citizenship on Rave
        {stage: 9, jumps: 4, agency: 4, antishrew: true, desc: [54, "rave"]},
        // 21  There was an unprovoked attack on Veriar
        {stage: 10, jumps: 1, agency: 2, how: true,},
        // 22  The Vesozaxe government has confirmed there was a vote
        {stage: 10, jumps: 2, agency: 2,},
        // 23  players ship named
        {stage: 13, jumps: 2, agency: 2, desc: [82, "vesozaxe"], how: true,},
        // 24  Shrews are full galactic citizens
        {stage: 14, jumps: 2, agency: 1, desc: [82, "vezaaes"]},
        // 25  You have been summoned to Diti
        {stage: 15, jumps: 2, agency: 1, how: true, fuel: true, desc: [5, "veriar2"]},
        // 26  Player named by the media.
        {stage: 15, jumps: 3, agency: 4, how: true, },
        // 27  After Trial news report
        {stage: 16, jumps: 2, agency: 1,},
        // 28  Tiaras Skeets was hiding but spotted jumping out of XXX
        {stage: 16, jumps: 10, agency: 4, mission: 17, findTS: true,},
        ];
    }
    
    this.$stage = this._stageData();
    this.$news = this._newsData();
    
    this.$systems   = ["Arries", "Diti", "Envebe", "Rave", "Veriar", "Vesozaxe",  "Vezaaes", "Zatebiso",];
    this.$systemIDs = [238, 33, 177, 54, 5, 143, 82, 204,];
    
    if (this.$debug) {
        // Dock at Vezaaes after getting the Aegis update
        this.$stage[13].systemID = 154; // Inisza
        this.$stage[14].systemID = 154;
    
        this.$news[28].jumps = 3;
    }
    
    Scripts/ShrewsRights_Whiskers.js
    "use strict";
    this.author    = "Zafrusteria";
    this.copyright = "© 2024 Zafrusteria.";
    this.license   = "Creative Commons: attribution, non-commercial,  CC BY-NC-SA 4.0 sharealike - see readme.txt";
    this.version   = "0.8";
    
    this.name        = "ShrewsRights_Whiskers";
    this.description = "Handlers for Tiaras Skeets flying Whiskers.";
    
    this.$debug = false;
    
    // [0] == 1, captured TS || 2, killed TS || 3, lost in space || 4, not dead yet
    // [1] == 1 Whiskers left derelict || 2, npc left derelict || 3, you destroyed || 4, npc destroyed || 5, just attacked
    this.shipDied = function _ShrewsRights_Whiskers_shipDied(killer) {
        if (this.$debug) log(this.name, "shipDied Killer: " + killer);
    
        if (this.ship.crew && this.ship.crew[0].name === "Tiaras Skeets") {
            if (this.$debug) log(this.name, "shipDied Crew: " + this.ship.crew[0].name);
            if (killer.isPlayer) {
                worldScripts.ShrewsRights.$missionTSDead[0] = 2;
                worldScripts.ShrewsRights.$missionTSDead[1] = 3;
               player.commsMessage(expandDescription("[ShrewsRights_killed_TS]"), 10);
            } else {
                worldScripts.ShrewsRights.$missionTSDead[0] = 2;
                worldScripts.ShrewsRights.$missionTSDead[1] = 4;
                killer.commsMessage(expandDescription("[ShrewsRights_npc_killed_TS]"), player);
            }
        } else {
            if (this.$debug) log(this.name, "shipDied Derelict destroyed");
            worldScripts.ShrewsRights.$missionTSDead[0] = 3;
    
            if (killer.isPlayer) {
                player.commsMessage(expandDescription("[ShrewsRights_derelict_killed]"), 10);
                worldScripts.ShrewsRights.$missionTSDead[1] = 3;
            } else {
                killer.commsMessage(expandDescription("[ShrewsRights_npc_derelict_killed]"), player);
                worldScripts.ShrewsRights.$missionTSDead[1] = 4;
            }
        }
    }
    
    this.shipTakingDamage = function _ShrewsRights_Whiskers_shipTakingDamage() {
        worldScripts.ShrewsRights.$missionTSDead[0] = 4;
        if (this.ship.isDerelict) {
            if (this.ship.AIPrimaryAggressor.scanClass === "CLASS_PLAYER")
                worldScripts.ShrewsRights.$missionTSDead[1] = 1;
            else
                worldScripts.ShrewsRights.$missionTSDead[1] = 2;
        } else
            worldScripts.ShrewsRights.$missionTSDead[1] = 5;
    }
    
    Scripts/ShrewsRights_ads_horz.js
    "use strict";
    this.author    = "Zafrusteria";
    this.copyright = "© 2024 Zafrusteria.";
    this.license   = "Creative Commons: attribution, non-commercial,  CC BY-NC-SA 4.0 sharealike - see readme.txt";
    this.version   = "0.8";
    
    this.name      = "ShrewsRights_ads_horz";
    this.description = "ShrewsRights for Market Ads";
    
    this.$antiShrew = false;
    this.$proShrew  = false;
    this.$wanted    = false;
    
    this.$debug = false;
    
    this.startUpComplete = function _ShrewsRights_horz_startUpComplete() {
        if (galaxyNumber != 1 || player.score < 512) return;
    
        let SR = worldScripts.ShrewsRights;
        if (this.$debug) log(this.name, "Startup horz SR missionStage: " + SR.$missionStage)
    
        this._addAntiShrewAds(SR.$missionStage);
        this._addProShrewAds(SR.$missionStage);
        this._addWantedPosters(SR.$missionNewsCount);
    
        let wsSt = worldScripts.station_ads;
        let wsSe = worldScripts.market_ads_service;
        // just adverts, always available
        if (this.$debug) log(this.name, "add horz ads");
        for (let i = 0; i < 14; i++) {
            if (wsSt) wsSt._setHorzAd("ShrewsRights_horz_" + (150 + i) + ".png");
            if (wsSe) wsSe._setHorzAd("ShrewsRights_horz_" + (150 + i) + ".png");
        }
    }
    
    this.missionScreenOpportunity = function _ShrewsRights_horz_missionScreenOpportunity() {
        if (galaxyNumber != 1 || player.score < 512) return;
    
        let SR = worldScripts.ShrewsRights;
        if (this.$debug) log(this.name, "missionScreenOpportunity testing horz ads " + SR.$missionStage)
    
        this._delAntiShrewAds(SR.$missionStage);
        this._addProShrewAds(SR.$missionNewsCount);
        this._addWantedPosters(SR.$missionNewsCount);
    }
    
    this._addAntiShrewAds = function _ShrewsRights_horz_addAntiShrewAds(stage) {
        if (! this.$antiShrew && stage > 0 && stage < 14) {
            if (this.$debug) log(this.name, "_addAntiShrewAds horz");
            this.$antiShrew = true;
    
            let wsSt = worldScripts.station_ads;
            let wsSe = worldScripts.market_ads_service;
            // Ads for eating Shrews, these stop when Shrews are welcomed as citizens
            // the outer loop so they will appear more often.
            for (let j = 0; j < 4; j++) {
                for (let i = 1; i < 11; i++) {
                    if (wsSt) wsSt._setHorzAd("ShrewsRights_horz_" + i + ".png");
                    if (wsSe) wsSe._setHorzAd("ShrewsRights_horz_" + i + ".png");
                }
            }
            // Take these new ads into use as we added them after setup.
            // ** Probably not the best way to do this. **
            wsSt._initHorzPool();
            wsSe._initHorzPool();
        }
    }
    
    this._delAntiShrewAds = function _ShrewsRights_horz_delAntiShrewAds(stage) {
        let regexKey =  new RegExp("ShrewsRights_horz_[1-4]?[0-9].png");
        function checkAntiShrew(key) { return ! regexKey.test(key);}
    
        if (this.$antiShrew && stage >= 14) {
            if (this.$debug) log(this.name, "_delAntiShrewAds horz");
            this.$antiShrew = false;
    
            let wsSt = worldScripts.station_ads;
            let wsSe = worldScripts.market_ads_service;
            if (wsSt && wsSt.$horzAdPool) {
                wsSt.$horzAdPool = wsSt.$horzAdPool.filter(checkAntiShrew);
                // remove these ads from the service
                wsSt._initHorzPool();
    
            }
            if (wsSe && wsSe.$horzAdPool) {
                wsSe.$horzAdPool = wsSe.$horzAdPool.filter(checkAntiShrew);
                // remove these ads from the service
                wsSe._initHorzPool();
            }
        }
    }
    
    this._addProShrewAds = function _ShrewsRights_horz_addProShrewAds(news) {
        if (! this.$proShrew && news >= 11) {
            if (this.$debug) log(this.name, "_addProShrewAds horz");
            this.$proShrew = true;
    
            let wsSt = worldScripts.station_ads;
            let wsSe = worldScripts.market_ads_service;
            for (let i = 0; i < 12; i++) {
                if (wsSt) wsSt._setHorzAd("ShrewsRights_horz_" + (50 + i) + ".png");
                if (wsSe) wsSe._setHorzAd("ShrewsRights_horz_" + (50 + i) + ".png");
            }
            // Take these new ads into use
            wsSt._initHorzPool();
            wsSe._initHorzPool();
        }
    }
    
    this._addWantedPosters = function _ShrewsRights_horz_addWantedPosters(news) {
        if (! this.$wanted && news >= 16) {
            if (this.$debug) log(this.name, "_addWantedPosters horz");
            this.$wanted = true;
    
            let wsSt = worldScripts.station_ads;
            let wsSe = worldScripts.market_ads_service;
            for (let i = 0; i < 4; i++) {
                if (wsSt) wsSt._setHorzAd("ShrewsRights_horz_" + (100 + i) + ".png");
                if (wsSe) wsSe._setHorzAd("ShrewsRights_horz_" + (100 + i) + ".png");
            }
            // Take these new ads into use
            wsSt._initHorzPool();
            wsSe._initHorzPool();
        }
    }
    Scripts/ShrewsRights_ads_yah.js
    "use strict";
    this.author    = "Zafrusteria";
    this.copyright = "© 2024 Zafrusteria.";
    this.license   = "Creative Commons: attribution, non-commercial,  CC BY-NC-SA 4.0 sharealike - see readme.txt";
    this.version   = "0.8";
    
    this.name      = "ShrewsRights_yah";
    this.description = "ShrewsRights YAH additions";
    
    this.$antiShrew = false;
    this.$proShrew  = false;
    this.$wanted    = false;
    
    this.$debug = false;
    
    this.startUpComplete = function _ShrewsRights_yah_startUpComplete() {
        if (galaxyNumber != 1) return;
    
        let SR = worldScripts.ShrewsRights;
        if (this.$debug) log(this.name, "Startup yha SR missionStage: " + SR.$missionStage)
    
        this._addAntiShrewAds(SR.$missionStage);
        this._addProShrewAds(SR.$missionStage);
        this._addWantedPosters(SR.$missionNewsCount);
    
        let ws = worldScripts.yah_ad_service;
        if (ws) {
            if (this.$debug) log(this.name, "add yah default ads");
            // just adverts, always available
            for (let i = 0; i < 7; i++) {
                ws._setAd("ShrewsRights_yah_" + (150 + i) + ".png");
            }
        }
    }
    
    this.missionScreenOpportunity = function _ShrewsRights_yah_missionScreenOpportunity() {
        if (galaxyNumber != 1 || player.score < 512) return;
    
        let SR = worldScripts.ShrewsRights;
        if (this.$debug) log(this.name, "missionScreenOpportunity testing yha ads " + SR.$missionStage)
    
        this._delAntiShrewAds(SR.$missionStage);
        this._addProShrewAds(SR.$missionNewsCount);
        this._addWantedPosters(SR.$missionNewsCount);
    }
    
    this._addAntiShrewAds = function _ShrewsRights_yah_addAntiShrewAds(stage) {
        if (! this.$antiShrew && stage > 0 && stage < 14) {
            if (this.$debug) log(this.name, "_addAntiShrewAds yah");
    
            this.$antiShrew = true;
            let ws = worldScripts.yah_ad_service;
            if (ws) {
                // Ads for eating Shrews, these stop when Shrews are welcomed as citizens
                // the outer loop so they will appear more often.
                for (let j = 0; j < 4; j++) {
                    for (let i = 1; i < 6; i++) {
                        ws._setAd("ShrewsRights_yah_" + i + ".png");
                    }
                }
                // Take these new ads into use as we added them after setup.
                // ** Probably not the best way to do this. **
                ws._initPool();
            }
        }
    }
    
    this._delAntiShrewAds = function _ShrewsRights_yah_delAntiShrewAds(stage) {
        let regexKey = new RegExp("ShrewsRights_yah_[1-4]?[0-9].png");
        function checkAntiShrew(key) { return ! regexKey.test(key);}
    
        if (this.$antiShrew && stage >= 14) {
            if (this.$debug) log(this.name, "_delAntiShrewAds yah");
    
            this.$antiShrew = false;
            let ws = worldScripts.yah_ad_service;
            if (ws && ws.$adPool) {
                ws.$adPool = ws.$adPool.filter(checkAntiShrew);
                // remove these ads from the service
                ws._initPool();
            }
        }
    }
    
    this._addProShrewAds = function _ShrewsRights_yah_addProShrewAds(news) {
        if (! this.$proShrew && news >= 11) {
            if (this.$debug) log(this.name, "_addProShrewAds yah");
            this.$proShrew = true;
            let ws = worldScripts.yah_ad_service;
            if (ws) {
                for (let i = 0; i < 7; i++) {
                    ws._setAd("ShrewsRights_yah_" + (50 + i) + ".png");
                }
                // Take these new ads into use
                ws._initPool();
            }
        }
    }
    
    this._addWantedPosters = function _ShrewsRights_yah_addWantedPosters(news) {
        if (! this.$wanted && news >= 16) {
            if (this.$debug) log(this.name, "_addWantedPosters yah");
            this.$wanted = true;
            let ws = worldScripts.yah_ad_service;
            if (ws) {
                for (let i = 0; i < 2; i++) {
                    ws._setAd("ShrewsRights_yah_" + (100 + i) + ".png");
                }
                // Take these new ads into use
                ws._initPool();
            }
        }
    }
    Scripts/ShrewsRights_comms.js
    notYetParsed
    Scripts/ShrewsRights_comms_not_used.js
    "use strict";
    this.author    = "Zafrusteria";
    this.copyright = "© 2024 Zafrusteria.";
    this.license   = "Creative Commons: attribution, non-commercial,  CC BY-NC-SA 4.0 sharealike - see readme.txt";
    this.version   = "0.8";
    this.name = "ShrewsRights_comms_not_used";
    
    // This file was taken from cim's oolite.oxp.cim.comms-pack-a.oxz with a few changes
    // some of these functions are a copy/paste from deathcomms by phkb with some changes for ShrewsRights.
    
    this.description = "Comms messages for antishrew extremists that attack shrews rights sympathisers and also specific deathcomms.";
    
    // The generic prefix used for this OXP's comms entries in descriptions.plist
    this.$commsPrefix = "ShrewsRights";
    this.$includeRole = ["sr_extremist"]; // From shipdata.plist
    
    this.$debug = false;
    
    this.startUpComplete = function _ShrewsRightsAI_startUpComplete() {
    
    
        return;
    
    
    
    
        for (let i = 0 ; i < this.$includeRole.length; i++) {
            // Add "sr_extremist" to the list that Deathcomms ignores, this is from shipdata.plist.
            if (worldScripts.DeathComms) worldScripts.DeathComms._excludeRoles.push(this.$includeRole[i]);
        }
    
        /* See https://wiki.alioth.net/index.php/Oolite_Javascript_Reference:_PriorityAI_Documentation#Usage
         * for a list of Standard Communications Keys.
         * and for a usage example see cim's oolite.oxp.cim.comms-pack-a.oxz
         */
    
        // ensure the priority AI library is started
        if (worldScripts["oolite-libPriorityAI"].startUp) worldScripts["oolite-libPriorityAI"].startUp();
    
        /*  The comms structure below should result in an entry in worldScripts["oolite-libPriorityAI"].$commsSettings
            similar to the following:
                // 'role' from shipdata.plist
                sr_extremist: {
                    // 'personality' for anti-shrew extremists, set in the AI script with ai.setCommunicationsPersonality
                    ShrewsRights_extremist: {
                        oolite_surrender: "[ShrewsRights_extremist_surrender]",
                        oolite_beginningAttack: "[ShrewsRights_extremist_beginningAttack]",
                        oolite_attackLowEnergy: "[ShrewsRights_extremist_attackLowEnergy]"
                    }
                }
        */
        let comms = {
            sr_extremist: { // Shipdata roles
                /* The _makeComms function saves a bit of typing on
                 * defining key/value entries like
                 * "oolite_attackLowEnergy": "[ShrewsRights_extremist_attackLowEnergy]",
                 *
                 *  Comms keys/values - see
                 * http://wiki.alioth.net/index.php/Oolite_PriorityAI_Documentation#Usage
                 * for a list of available keys.
                 *
                 * this._makeComms will generate keys for descriptions.plist such as:
                 * ShrewsRights_extremist_surrender & ShrewsRights_extremist_attackLowEnergy
                 *
                 * They are made up from this.$commsPrefix and the two parameters passed into _makeComms.
                 */
    
                // The name part can be anything we like but should include an OXP prefix. I just added that to the personality.
                // Personality for 'extremist' is set in the AI script
                ShrewsRights_extremist: this._makeComms("extremist",
                    [
                        "attackLowEnergy",
                        "beginningAttack",
                        "makeDistressCall",
                        "surrender",
                        "thargoidAttack",
                    ]),
            },
        };
    
        // This loads the communications settings into the library
        worldScripts["oolite-libPriorityAI"]._setCommunications(comms);
    }
    
    this.shipSpawned = function _ShrewsRights_shipSpawned(ship) {
        if (ship.primaryRole == this.$includeRole) {
             if (this.$debug)
                 log(this.name, "ShipSpawned '" + ship.displayName + "' - '" + ship.shipUniqueName + "' - '" + ship.primaryRole + "'");
            // is there an existing script in shipDied?
            // move it to a new spot
            ship.script._dmShipDied = ship.script.shipDied;
            ship.script.shipDied = this._dmShipDied;
        }
    }
    
    this._dmShipDied = function _ShrewsRights__dmShipDied(whom, why) {
        if (this.$debug) log(this.name, "_dmShipDied '" + this.ship.displayName + "' - '" + this.ship.shipUniqueName + " " + why);
        if (this.ship.script._dmShipDied) this.ship.script._dmShipDied(whom, why);
    
        // Was way to chatty.
        if (why != "removed"  && Math.random() > 0.95) {
            if (this.ship.isDerelict == false) {
                if (this.$debug) log(this.name, "ShrewsRights_extremist_beginningAttack - sending comms");
                this.ship.commsMessage(expandDescription("[ShrewsRights_extremist_beginningAttack]"), player.ship);
            } else {
                if (this.$debug) log(this.name, "ShrewsRights_Derelict message - sending comms");
                this.ship.commsMessage(expandDescription("[ShrewsRights_extremist_derelictMessage]"), player.ship);
            }
        }
    }
    
    /* This function makes a set of descriptions.plist entries, to save a little
     * typing. this.$commsPrefix plus specific is the personality we're writing
     * for and 'entries' is an array of the communications keys that are
     * generated with "oolite_" appended to them.
     */
    this._makeComms = function _ShrewsRightsAI_makeComms(specific, entries) {
        let result = {};
        let i = entries.length;
        while (i--) {
            let entry = entries[i];
            result["oolite_" + entry] = "[" + this.$commsPrefix + "_" + specific + "_" + entry + "]";
        }
        return result;
    }
    
    Scripts/ShrewsRights_death.js
    "use strict";
    this.author    = "Zafrusteria";
    this.copyright = "© 2024 Zafrusteria.";
    this.license   = "Creative Commons: attribution, non-commercial,  CC BY-NC-SA 4.0 sharealike - see readme.txt";
    this.version   = "0.8";
    this.name        = "ShrewsRights_death";
    this.licence     = "CC BY-NC-SA 4.0";
    this.description = "Adds one last message when the antishrew extremists die.";
    
    this.$includeRole = "sr_extremist"; // From shipdata.plist
    
    this.$debug = false;
    
    // These functions ware a copy/paste from deathcomms by phkb with some changes for ShrewsRights.
    
    this.startUpComplete = function _ShrewsRights_startUpComplete() {
        // Add "sr_extremist" for the list Death comms ignores.
    //    if (worldScripts.DeathComms) worldScripts.DeathComms._excludeRoles.push(this.$includeRole);
    }
    
    this.shipSpawned = function _ShrewsRights_shipSpawned(ship) {
        if (ship.primaryRole == this.$includeRole) {
             if (this.$debug)
                 log(this.name, "ShipSpawned '" + ship.displayName + "' - '" + ship.shipUniqueName + "' - '" + ship.primaryRole + "'");
            // is there an existing script in shipDied?
            // move it to a new spot
            ship.script._dm_shipDied = ship.script.shipDied;
            ship.script.shipDied = this._dm_shipDied;
        }
    }
    
    this._dm_shipDied = function _ShrewsRights__dm_shipDied(whom, why) {
        if (this.$debug) log(this.name, "_dm_shipDied '" + this.ship.displayName + "' - '" + this.ship.shipUniqueName);
        if (this.ship.script._dm_shipDied) this.ship.script._dm_shipDied(whom, why);
    
        if (why != "removed"&& Math.random() > 0.75) {
            if (this.ship.isDerelict == false) {
                 if (this.$debug) log(this.name, "ShrewsRights_extremist_beginningAttack - sending comms");
                this.ship.commsMessage(expandDescription("[ShrewsRights_extremist_deathMessage]"), player.ship);
            } else {
                 if (this.$debug) log(this.name, "ShrewsRights_Derelict message - sending comms");
                this.ship.commsMessage(expandDescription("[ShrewsRights_extremist_derelictMessage]"), player.ship);
            }
        }
    }