| Scripts/escortdeck.js | "use strict";
this.name        = "escortdeck";
this.version     = "1.12"
this.author      = "Norby";
this.copyright   = "2015 Norbert Nagy";
this.description = "Buy and hold escort ships on external deck";
this.licence     = "CC BY-NC-SA 4.0";
this.$debug = false; // debug verbose log
this.$logging = true; // verbose log
this.$Derelicts = false; //add derelict ships near the nav buoy of the starting station
this.$EscortDeckAutoLaunch = false; // automatically launch/callback when leaving/entering green condition
this.$EscortDeckBigTraderLaunch = false; //launch Anaconda from Carriers automatically or not
this.$EscortDeckCMaxMass = 800000; //escorts must below this mass on XL deck of Carriers
this.$EscortDeckExtremeMass = 10*this.$EscortDeckCMaxMass; //so much so don't display message
this.$EscortDeckJumpDist = 50; //escorts jump this m farther from the main ship at launch
this.$EscortDeckMassEffect = true; //slower steering and acceleration with more mass on deck
this.$EscortDeckMassFactor = 1; // fraction of steering maneuverability to retain
this.$EscortDeckMinMass = 30000; //which ships can tow the deck (30t not small in ShipVersion)
this.$EscortDeckLandingDist = 1500; //escorts will be added to the deck within this m if possible
this.$EscortDeckSpinModel = true;//spinning model in the interface screen
//this.$EscortDeckLargeMass = 130000; //max. escort mass, border of large ships in ShipVersion OXP
//this.$EscortDeckWMass = 2 * this.$EscortDeckLargeMass; //which ships can tow wide deck (2*large)
this.$EscortDeckLargeMass = 205000; //max. escort mass, border of large ships in ShipVersion OXP
this.$EscortDeckWMass = 1.5 * this.$EscortDeckLargeMass; //which ships can tow wide deck (2*large)
this.$EscortDeckXLMass = 400000; //which can tow XL deck (400t mean xl ships in Telescope OXP)
this.$EscortDeckBasicUnusableProb = 0.9; //probability of damage (0-1) to base systems (not equipment) of a derelict, which would render it unusable
//internal properties, should not touch
this.$EscortDeckEqs = []; //buyable escort ship sizes and masses
this.$EscortDeckEqs["EQ_ESCORTDECK_adder"] = [28.02, 14.9, 51, 16000];
this.$EscortDeckEqs["EQ_ESCORTDECK_asp"] = [64.73, 23.52, 83.69, 75882];
this.$EscortDeckEqs["EQ_ESCORTDECK_gecko"] = [61.48, 14.64, 51, 24670];
this.$EscortDeckEqs["EQ_ESCORTDECK_sidewinder"] = [63.23, 14.43, 44, 25088];
this.$EscortDeckEqs["EQ_ESCORTDECK_viper"] = [50, 18.12, 65.199, 34000];
this.$EscortDeckPad = { //landing pad max. x,y,z sizes and max. mass
            escort:[70,29,135,this.$EscortDeckLargeMass], //for Asp SG, Arachnid, S8
//            escort:[70,45,135,this.$EscortDeckLargeMass], //for Asp SG, Arachnid, S8
            mini:[39,15,65,30000], //Adder, Ophidian
            wide:[95,29,135,this.$EscortDeckLargeMass], //wide ship: Krait SG, Tiger Ray
//            wide:[95,45,135,this.$EscortDeckLargeMass], //wide ship: Krait SG, Tiger Ray
            xl:[70,29,95,60000], //sizes like heavy escorts but smaller mass, no Asp
//            xl:[70,45,95,60000], //sizes like heavy escorts but smaller mass, no Asp
            //Carrier specific pads
            center:[65,29,95,130000], //small center pad for Asp, Fer-De-Lance, S8
            main:[95,64,170,this.$EscortDeckCMaxMass], //Anaconda, D.T.T. Atlas, Krait SG
                        side:[70,29,95,this.$EscortDeckLargeMass], //heavy escorts: Asp, Arachnid, S8
                        small:[65,19,66,40000], //aft side pad for Gecko, Ophidian, Sonoran, Viper
            top:[140,35,135,this.$EscortDeckCMaxMass] } //Cobra Mark III, King Cobra, Krait SG
this.$EscortDeckLPos = [[-36,6,-25], [36,6,-25], //landing pad positions: top left-right
            [-36,-6,-25], [36,-6,-25], //bottom left-right
            [-43,0,-25], [43,0,-25]];  //center left-right (Adder only)
//            [34,0,-43], [-34,0,-43],   //aft left, aft right (facing backward) - disabled
this.$EscortDeckLVec = [[0,1,0], [0,1,0], //vectors from landing pad to ship center: up
            [0,-1,0], [0,-1,0], //down
            [-1,0,0], [1,0,0]]; //left, right
//            [0,0,-1], [0,0,-1], //back - disabled
this.$EscortDeckWPos = [[-48,6,-25], [48,6,-25], //Wide deck landing pad positions: top left-right
            [-48,-6,-25], [48,-6,-25], //bottom left-right
            [-54,0,-25], [54,0,-25]];  //center left-right (Adder only)
//            [34,0,-43], [-34,0,-43],   //aft left, aft right (facing backward)
this.$EscortDeckWVec = [[0,1,0], [0,1,0], //vectors from landing pad to ship center: up
            [0,-1,0], [0,-1,0], //down
            [-1,0,0], [1,0,0]]; //left, right
//            [0,0,-1], [0,0,-1], //back
this.$EscortDeckXLPos = [[-8,0,-15], [8,0,-15], //XL deck landing pad positions: left, right
            [-8,0,-44], [8,0,-44], //left, right
            [-8,0,-73], [8,0,-73], //left, right
            [-8,0,-102], [8,0,-102]];  //left, right
this.$EscortDeckXLVec = [[-1,0,0], [1,0,0], //XL deck vectors from landing pad to ship center
            [-1,0,0], [1,0,0], //left, right
            [-1,0,0], [1,0,0], //left, right
            [-1,0,0], [1,0,0]]; //left, right
this.$EscortDeckCPos = [[0,15,215], [0,15,135], //Carrier pad positions: top front and center
            [0,15,15], [-67,16,15], [67,16,15], //aft center, left, right
            [-52,15,46], [52,15,46], //main deck left, right
            [-30,0,233], [30,0,233]];  //mini front deck left, right
this.$EscortDeckCSize = [this.$EscortDeckPad.top, //Carrier max. sizes and mass, top front pad
            this.$EscortDeckPad.center, //center: Asp, Fer-De-Lance, S8
            this.$EscortDeckPad.top, //top aft for Cobra Mark III and King Cobra
            this.$EscortDeckPad.small, this.$EscortDeckPad.small, //aft sides
            this.$EscortDeckPad.main, this.$EscortDeckPad.main, //left-right main deck
            this.$EscortDeckPad.mini, this.$EscortDeckPad.mini]; //front pads
this.$EscortDeckCVec = [[0,1,0], [0,1,0], //Carrier vectors from landing pad to ship center: up
            [0,1,0], //up
            [0,1,0], [0,1,0], //up
            [0,0,1], [0,0,1], //forward
            [0,0,0], [0,0,0]]; //center
this.$EscortDeckCXPos = [[0,15,215], [0,15,115],  //Carrier XL deck positions: top front, center
            [-48,16,15], [48,16,15], //aft left, right
            [-11,0,151], [11,0,151], //left, right
            [-11,0,81], [11,0,81], //left, right
            [-30,0,233], [30,0,233]];  //front left, right
this.$EscortDeckCXSize = [this.$EscortDeckPad.top, this.$EscortDeckPad.top, //top front and center
            this.$EscortDeckPad.wide, this.$EscortDeckPad.wide, //top aft wide pads
            this.$EscortDeckPad.side, this.$EscortDeckPad.side, //4 heavy escorts
            this.$EscortDeckPad.side, this.$EscortDeckPad.side, //on main decks
            this.$EscortDeckPad.mini, this.$EscortDeckPad.mini]; //front pads
            
this.$EscortDeckCXVec = [[0,1,0], [0,1,0], //Carrier XL deck vectors from landing pad to ship center: up
            [0,1,0], [0,1,0], //up
            [-1,0,0], [1,0,0], //left, right
            [-1,0,0], [1,0,0], //left, right
            [0,0,0], [0,0,0]]; //center
/* Carrier deck positions for 10 heavy escorts, removed to be less powerful
this.$EscortDeckCXXPos = [[0,14,220], [0,14,23], //landing pad positions: top front-aft
            [-11,0,170], [11,0,170], //left, right
            [-11,0,135], [11,0,135], //left, right
            [-11,0,100], [11,0,100], //left, right
            [-11,0,65], [11,0,65]];  //left, right
this.$EscortDeckCXXSize = [this.$EscortDeckPad.top, this.$EscortDeckPad.top, //top front and aft
            this.$EscortDeckPad.escort, this.$EscortDeckPad.escort, //8 heavy escorts
            this.$EscortDeckPad.escort, this.$EscortDeckPad.escort, //on main decks
            this.$EscortDeckPad.escort, this.$EscortDeckPad.escort,
            this.$EscortDeckPad.escort, this.$EscortDeckPad.escort];
this.$EscortDeckCXXVec = [[0,1,0], [0,1,0], //vectors from landing pad to ship center: up
            [-1,0,0], [1,0,0], //left, right
            [-1,0,0], [1,0,0], //left, right
            [-1,0,0], [1,0,0], //left, right
            [-1,0,0], [1,0,0]]; //left, right
*/
this.$EscortDeckVersion = null; //version of EscortDeck installed on the ship
this.$EscortDeckCWS = null; //carriers worldscript if Carriers OXP is installed
this.$EscortDeckDockingTunnel = false; //player in the tunnel
this.$EscortDeckEWS = null; //escortdeck_escort worldscript
this.$EscortDeckFCB = null; //FrameCallBack updating escort ship positions
this.$EscortDeckFCBDelta = 0; //delta counter in FrameCallBack
this.$EscortDeckFirstMSO = true; //first missionScreenOpportunity after docked
this.$EscortDeckGWS = null; //Gallery worldscript
this.$EscortDeckI = false; //start on the first page within EscortDeck Interface
this.$EscortDeckLaunch = false; //launch or call back via primeable equipment
this.$EscortDeckLastTooLargeTarget = null; //prevent repeated too large messages
this.$EscortDeckLocked = []; //which positions will not launch with the other escorts
this.$EscortDeckMaxs = null; //store maxThrust, maxPitch, maxRoll and maxYaw
this.$EscortDeckNPCEqDamage = false; // NPC Equipment Damage OPC is installed
this.$EscortDeckPadPos = []; //landing pad positions
this.$EscortDeckPadSize = []; //landing pad sizes
this.$EscortDeckPadVec = []; //vectors from landing pad to ship center
this.$EscortDeckPlayableDataKeys = null;  //playable ships
this.$EscortDeckPWS = null; //escortpack worldscript if escortpack OXP is installed
this.$EscortDeckSelectedPad = -1; //the pad selected via primeable equipment
this.$EscortDeckShip = []; //store the ship objects of escorts
this.$EscortDeckShipData = []; //store the data of escort ships
this.$EscortDeckShipPos = []; //positions of escort ships on board
this.$EscortDeckSitInto = false; //sit into the selected escort when launch it from a Carrier
this.$EscortDeckSitIntoWithMode = false; //allow sit into with mode key when deck is primed
this.$EscortDeckSound = null; //sound of escort landing
this.$EscortDeckSpawnedShips = []; //stack for restoring ship data in FCB after shipSpawned
this.$EscortDeckSGWS = null; //snipergun worldscript if Sniper Gun OXP is installed
this.$EscortDeckTWS = null; //telecope worldscript if telecope OXP is installed
this.$EscortDeckToWS = null; //towbar worldscript if towbar OXP is installed
this.$EscortDeckV = null; //visualeffect of the escort deck of the player ship
this.$EscortDeckXL = false; //the current deck is xl or not
this.$EscortDeckZV = 0; //the z offset of deck position from the center of the player ship
// configuration settings for use in Lib_Config
this.$newUsableProb = 1 - this.$EscortDeckBasicUnusableProb;
this.$newAutoLaunch = false;
this._compConfig = {
    Name:this.name, Alias:"EscortDeck", Display:"Config", Alive:"_compConfig", Notify:"$onChange", Reset:true,
    Bool:   {
                B0: {Name:"$newAutoLaunch", Def:true, Desc:"Automatic Launch"},
                Info:"Automatic launch/callback on Red condition transition (0:no, 1:yes)"
            },
    SInt:   {
                S0:{Name:"$newUsableProb", Min:0, Max:1, Float:true, Def:0.1, Desc:"Usable derelict probability"},
                Info:"0 - Value between 0 (all unusable) and 1 (all usable)"
            },
};
//
//equipment events
//
//-----------------------------------------------------------------------------------------------//
this.activated = function  _escortdeck_activated() { //launch or call back one or all escorts
    var w = worldScripts.escortdeck;
    w.$EscortDeck_EQActivated(w);
}
//-----------------------------------------------------------------------------------------------//
this.mode = function _escortdeck_mode() { //select the next pad on escort deck
    var w = worldScripts.escortdeck;
    w.$EscortDeck_EQMode(w);
}
//
//worldscript events
//
//-----------------------------------------------------------------------------------------------//
this.startUp = function _escortdeck_startUp() {
    var hh = worldScripts.HyperspaceHangar;
    if (!player.ship.addCollisionException) { //before v1.81 need more room
        for (var i = 0; i < this.$EscortDeckLPos.length; i++) 
            this.$EscortDeckLPos[i][2] -= 30;
        for (var i = 0; i < this.$EscortDeckWPos.length; i++)
            this.$EscortDeckWPos[i][2] -= 30;
        for (var i = 0; i < this.$EscortDeckXLPos.length; i++)
            this.$EscortDeckXLPos[i][2] -= 20;
    }
    this.$EscortDeck_SetPads(player.ship, this);
    
    this.$EscortDeckSound = new SoundSource;
    this.$EscortDeckSound.sound = "escortdeck.ogg";
    this.$EscortDeckSound.loop = false;
    this.$EscortDeckSound.repeatCount = 1;
    this.$EscortDeckSound2 = new SoundSource;
    if (worldScripts["BGS-M"]) this.$EscortDeckSound2.sound = "bgs-c_sell.ogg";
    else this.$EscortDeckSound2.sound = "sell.ogg"; //Oolite default sell sound
    this.$EscortDeckSound2.loop = false;
    this.$EscortDeckSound2.repeatCount = 1;
    this.$EscortDeckSound3 = new SoundSource;
    this.$EscortDeckSound3.sound = "escortdeckhitderelict.ogg";
    this.$EscortDeckSound3.loop = false;
    this.$EscortDeckSound3.repeatCount = 1;
    this.$EscortDeckPlayableDataKeys = Ship.keysForRole( "player" );  //playable ships
    
    if (worldScripts.escortdeck_escort) this.$EscortDeckEWS = worldScripts.escortdeck_escort;
    if (worldScripts.carriers) this.$EscortDeckCWS = worldScripts.carriers;
    if (worldScripts.gallery) this.$EscortDeckGWS = worldScripts.gallery; 
    if (worldScripts.escortpack) this.$EscortDeckPWS = worldScripts.escortpack;
    if (worldScripts.snipergun) this.$EscortDeckSGWS = worldScripts.snipergun;
    if (worldScripts.telescope) this.$EscortDeckTWS = worldScripts.telescope;
    if (worldScripts.NPC_Equipment_Damage) this.$EscortDeckNPCEqDamage = true;
    var h = worldScripts.hudselector;
    if( h && h.$HUDSelectorAddMFD ) h.$HUDSelectorAddMFD(this.name);
    var s = missionVariables.$EscortDeckShipData;
    if( s && s.length > 0 )    this.$EscortDeckShipData = JSON.parse(s);
    var s = missionVariables.$EscortDeckLocked;
    if( s && s.length > 0 )    this.$EscortDeckLocked = JSON.parse(s);
    var t = worldScripts.towbar;
    if (t) {
        this.$EscortDeckToWS = t; //save Towbar worldscript
        //inject code to do not lock on derelicts of escort deck
        eval("t.$EscortDeckToWS_IsDerelictOrig = "+t.$Towbar_IsDerelict);
        eval("t.$Towbar_IsDerelict = function(entity) { \
            var w = worldScripts.escortdeck; \
            if ((!w.$EscortDeckXL || \
                w.$EscortDeckCWS && player.ship && player.ship.isValid \
                && player.ship.dataKey.indexOf('carrier') > -1 ) && \
                worldScripts.towbar.$EscortDeckToWS_IsDerelictOrig(entity) \
                && ( !w.$EscortDeckShip \
                || w.$EscortDeckShip.indexOf(entity) == -1 )) \
                 return true; \
            return false; \
        }");
        eval("t.$EscortDeckToWS_shipCollidedOrig = "+t.shipCollided);
        eval("t.shipCollided = function(otherShip) { \
            var w = worldScripts.escortdeck; \
            if( (!w.$EscortDeckXL || \
                w.$EscortDeckCWS && player.ship && player.ship.isValid && \
                player.ship.dataKey.indexOf('carrier') > -1 ) && \
                !w.$EscortDeckShip || w.$EscortDeckShip.indexOf(otherShip) == -1 ) \
                worldScripts.towbar.$EscortDeckToWS_shipCollidedOrig(otherShip); \
        }");
        if (hh) {
            //makes Towbar not salvage usable derelicts
            eval("t.$EscortDeckToWS_missionScreenOpportunityOrig = "+t.missionScreenOpportunity);
            eval("t.missionScreenOpportunity = function _Towbar_missionScreenOpportunity_escortdeck() { \
                var w = worldScripts.towbar; \
                if (w.$TowbarShip && w.$TowbarShip.script && w.$TowbarShip.script.$EscortDeckUsable != 1) \
                    w.$EscortDeckToWS_missionScreenOpportunityOrig(); \
            }");
            //makes Towbar's Tug Drone not salvage usable derelicts
            eval("t.$Towbar_TugDrone_ProcessingOrig = "+t.$Towbar_TugDrone_Processing);
            eval("t.$Towbar_TugDrone_Processing = "+worldScripts.escortdeck.$EscortDeck_TugDrone_Processing)
        }
    }
}
//-----------------------------------------------------------------------------------------------//
this.startUpComplete = function _escortdeck_startUpComplete() {
    // register our settings, if Lib_Config is present
    if (worldScripts.Lib_Config) worldScripts.Lib_Config._registerSet(this._compConfig);
    if (missionVariables.$EscortDeck_unusable_derelic_prob) {
        this.$EscortDeckBasicUnusableProb = parseFloat(missionVariables.$EscortDeck_unusable_derelic_prob)
    } else {
        if (worldScripts.NPC_Equipment_Damage) this.$EscortDeckBasicUnusableProb -= 0.1
    }
    this.$newUsableProb = 1 - this.$EscortDeckBasicUnusableProb;
    log(this.name, "Base unusable derelict probability: "+this.$EscortDeckBasicUnusableProb)
    this.$EscortDeck_SaveMaxs( player.ship, this );
    //call after towbar and ccl startups finished
    this.$EscortDeck_SalvageBadEscorts(player.ship);
    this.$EscortDeckI = false; //start on the first page
    this.$EscortDeck_Interface();
    this.$EscortDeckVersion = this.$EscortDeck_Version();
}
//-----------------------------------------------------------------------------------------------//
this.alertConditionChanged = function _escortdeck_alertConditionChanged(newCondition, oldCondition) {
    if (!this.$EscortDeckVersion) return;
    if( !player.ship || !player.ship.isValid || !this.$EscortDeckAutoLaunch) return;
     if( newCondition == 3 && player.alertHostiles ) {
        this.$EscortDeck_ControlAll(true, false, this); //launch all escorts
    } else if( newCondition == 1 ) { //call back escorts in green alert
        this.$EscortDeck_ControlAll(false, false, this);
    }
}
//-----------------------------------------------------------------------------------------------//
this.guiScreenChanged = function _escortdeck_guiScreenChanged(to, from) {
    if( to == "GUI_SCREEN_INTERFACES" ) this.$EscortDeck_Interface(); //update displayed ship names
}
//-----------------------------------------------------------------------------------------------//
this.missionScreenOpportunity = function _escortdeck_missionScreenOpportunity() {
    var that = _escortdeck_missionScreenOpportunity;
    var hh = (that.hh = that.hh || worldScripts.HyperspaceHangar);
    var t = this.$EscortDeckToWS; //use Towbar salvage if available
    if (t && t.$TowbarShip && t.$TowbarShip.isValid
        && !t.$Towbar_IsHeavyShip(t.$TowbarShip)) { //avoid heavy ships in aegis with autodock
        if (t.$TowbarShip.script && t.$TowbarShip.script.$EscortDeckUsable > 0 && hh) {
            var displayName = t.$TowbarShip.displayName;
            log("escortdeck", "Towed ship "+displayName+" is usable, storing in Hyperspace Hangar");
            // towed ship is usable and HH is installed, transfer it to HH
            this.$EscortDeck_HHStoreShip(t.$TowbarShip)
            player.consoleMessage("Usable ship "+displayName+" stored into Hyperspace Hangar", 10);
            player.commsMessage("Usable ship "+displayName+" stored into Hyperspace Hangar", 10);
            t.$TowbarShip.remove(true);
            t.$TowbarShip = null;
        } else {
            log("escortdeck", t.$TowbarShip.displayName+" is not usable, salvaging it!");
            t.$Towbar_Payout(t.$TowbarShip); //call payout in towbar oxp
        }
    }
    if( player.ship.docked && this.$EscortDeckFirstMSO
        && this.$EscortDeckShip && this.$EscortDeckShip.length > 0 ) {
        this.$EscortDeckFirstMSO = false; //do only once
        for(var i = 0; i < this.$EscortDeckPadPos.length; i++ ) { //salvage unusables
            var s = this.$EscortDeckShip[i]; //the ship on this pad
            if( s && s.isValid ) {
                if( !s.script || !( s.script.$EscortDeckUsable > 0 ) ) { //unusable
                    this.$EscortDeck_Salvage(i, player.ship); //salvage
                    if(this.$EscortDeckToWS) {
                        this.$EscortDeckFirstMSO = true; //do one at a time
                        i = this.$EscortDeckPadPos.length;
                    }
                }
            } else { //inside stations check saved ship data
                var d = this.$EscortDeckShipData[i];
                if( d && d[1] && ( !d[14] || !(d[14].usable > 0) ) ) {//unusable
                    this.$EscortDeck_Salvage(i, player.ship);
                    if(this.$EscortDeckToWS) {
                        this.$EscortDeckFirstMSO = true; //do one at a time
                        i = this.$EscortDeckPadPos.length;
                    }
                }
            }
        }
    }
    if( t && t.$TowbarRunScreens.length > 0 ) {
        var rs = t.$TowbarRunScreens.shift(); //get and remove the oldest element
        if(this.$debug) log(this.name, "MSO "+t.$TowbarRunScreens.length+" "+guiScreen+" "+rs );
        t.$Towbar_RunScreen(rs.place, rs.msg, rs.model); //show sell salvage screen
    }
}
//-----------------------------------------------------------------------------------------------//
this.playerBoughtEquipment = this.equipmentAdded = function _escortdeck_playerBoughtEquipment(equipment) {
    var p = player.ship;
    var w = this; //w is used below
    if( equipment.indexOf("EQ_ESCORTDECK_" ) == 0 ) {
        p.removeEquipment(equipment);
        var k = equipment.substring(14); //extract datakey from the end of the eq key
        var s = this.$EscortDeck_SpawnOne( "[escortdeck-"+k+"]", this, false );
        if(this.$debug) log(this.name, k+" "+s); //debug
        if( !s || !s[0] || !s[0].isValid ) { //possible bug
            this.$EscortDeck_RollBack(equipment, "You can't buy "+k+".");
            return;
        } else s = s[0];
        var c = EquipmentInfo.infoForKey(equipment).price/10; //escort price
                if( p && p.dockedStation ) { //Rock Hermits ask more
                        var e = p.dockedStation.equipmentPriceFactor;
                        if( e > 0 ) c = e * c;
                }
        var en = EquipmentInfo.infoForKey(equipment).name; //escort name
        w.$EscortDeck_AwardEQs(s);
        var d = this.$EscortDeck_MakeShipData(s);
        if(d[14]) d[14].usable = 1; //usable
        var added = false;
        var reason = this.$EscortDeck_TooLarge(s, p, this);
        if( !reason && !this.$EscortDeckXL && this.$EscortDeck_isLarge(p, this)
            && this.$EscortDeck_isMini(s) ) { //Adder or smaller, try side decks first
            for( var i = 0; !added && i < w.$EscortDeckPadPos.length; i++ ) {
                if( w.$EscortDeck_isMiniPad(i, p, w) ) {
                    if( !this.$EscortDeckShipData[i] ) {
                        this.$EscortDeckShipData[i] = d;
                        added = i+1; //=pad+1
                    }
                }
            }
        }
        var len = this.$EscortDeckPadPos.length;
        if( !reason && !added ) for(var i = 0; i < len; i++ ) {
            if( !this.$EscortDeckShipData[i] 
                && !w.$EscortDeck_TooLarge2(s, p, w, i) ) {
                this.$EscortDeckShipData[i] = d;
                added = i+1;
                i=10;
            }
        }
        s.remove(true);
        if( added ) player.consoleMessage("You paid "+c+" credits for "+en+" placed to landing pad "+added, 10);
        else {
            if(!reason) reason = "no room on your deck";
            this.$EscortDeck_RollBack( equipment, "You can't buy "+en+", "+reason );
        }
    }
    if( equipment == "EQ_ESCORTDECK" ) {
        this.$EscortDeckXL = false;
        if( w && w.$EscortDeckCWS && p.dataKey.indexOf("carrier") > -1
            && p.equipmentStatus("EQ_ESCORTDECKXL") == "EQUIPMENT_OK" ) {
            //salvage pad 8, exchange pad 2-3 and shift pad 9-10 to 8-9
            w.$EscortDeck_Salvage(7, p); //salvage a ship to reduce from 10 to 9
            var d = [], s = [];
            d[0] = w.$EscortDeckShipData[0]; s[0] = w.$EscortDeckShip[0];
            d[1] = w.$EscortDeckShipData[2]; s[1] = w.$EscortDeckShip[2];
            d[2] = w.$EscortDeckShipData[1]; s[2] = w.$EscortDeckShip[1];
            d[3] = w.$EscortDeckShipData[3]; s[3] = w.$EscortDeckShip[3];
            d[4] = w.$EscortDeckShipData[4]; s[4] = w.$EscortDeckShip[4];
            d[5] = w.$EscortDeckShipData[5]; s[5] = w.$EscortDeckShip[5];
            d[6] = w.$EscortDeckShipData[6]; s[6] = w.$EscortDeckShip[6];
            d[7] = w.$EscortDeckShipData[8]; s[7] = w.$EscortDeckShip[8];
            d[8] = w.$EscortDeckShipData[9]; s[8] = w.$EscortDeckShip[9];
            w.$EscortDeckShipData = d;
            w.$EscortDeckShip = s;
        }
        p.removeEquipment("EQ_ESCORTDECKXL"); //replace
        this.$EscortDeck_SalvageBadEscorts(p);
    }
    if( equipment == "EQ_ESCORTDECKXL" ) {
        this.$EscortDeckXL = true;
        if( w && w.$EscortDeckCWS && p.dataKey.indexOf("carrier") > -1
            && p.equipmentStatus("EQ_ESCORTDECK") == "EQUIPMENT_OK" ) {
            //exchange pad 2-3 and shift pad 8-9 to 9-10
            var d = [], s = [];
            d[0] = w.$EscortDeckShipData[0]; s[0] = w.$EscortDeckShip[0];
            d[1] = w.$EscortDeckShipData[2]; s[1] = w.$EscortDeckShip[2];
            d[2] = w.$EscortDeckShipData[1]; s[2] = w.$EscortDeckShip[1];
            d[3] = w.$EscortDeckShipData[3]; s[3] = w.$EscortDeckShip[3];
            d[4] = w.$EscortDeckShipData[4]; s[4] = w.$EscortDeckShip[4];
            d[5] = w.$EscortDeckShipData[5]; s[5] = w.$EscortDeckShip[5];
            d[6] = w.$EscortDeckShipData[6]; s[6] = w.$EscortDeckShip[6];
            d[7] = null; s[7] = null;
            d[8] = w.$EscortDeckShipData[7]; s[8] = w.$EscortDeckShip[7];
            d[9] = w.$EscortDeckShipData[8]; s[9] = w.$EscortDeckShip[8];
            w.$EscortDeckShipData = d;
            w.$EscortDeckShip = s;
        }
        p.removeEquipment("EQ_ESCORTDECK"); //replace
        this.$EscortDeck_SalvageBadEscorts(p);
    }
    if( equipment == "EQ_FUEL" ) {
        var d = this.$EscortDeckShipData;
        var f = 0;
        var s = "";
        var len = this.$EscortDeckPadPos.length;
        if(d) for(var i = 0; i < len; i++ ) {
            if( d[i] && d[i][9] < 7 ) {
                if( f > 0 ) s = "s"; //more than one escort ship need refuel
                f += 7 - d[i][9];
            }
        }
        if( f > 0 ) {
            var c = f * 3;//3cr/ly
            if( player.credits > c ) {
                for(var i = 0; i < len; i++ ) {
                    if( d[i] && d[i][9] < 7 ) d[i][9] = 7;
                }
                player.credits -= c;
                player.consoleMessage("Your escort"+s+" got "+(Math.round(f*10)/10)+" ly fuel for "+(Math.round(c*10)/10)+" credits", 10);
            }
        }
    }
}
//-----------------------------------------------------------------------------------------------//
this.playerBoughtNewShip = function _escortdeck_playerBoughtNewShip(ship) {
    this.$EscortDeck_SaveMaxs( player.ship, this );
    if( !this.$EscortDeckCWS || !this.$EscortDeckCWS.$CarriersPlayerInEscort ) {
        this.$EscortDeck_SalvageBadEscorts(player.ship);
        //setup new pads after SalvageBadEscorts only!
        this.$EscortDeck_SetPads( player.ship, this );
    }
}
//-----------------------------------------------------------------------------------------------//
this.playerStartedJumpCountdown = function _escortdeck_playerStartedJumpCountdown(type, seconds) {
    if (!this.$EscortDeckVersion) return;
    //fix for Granite ships to the too close high mass do not prevent the jump
    if( !this.$EscortDeckCWS || player.ship.dataKey.indexOf("carrier") == -1 ) {
        for( var i = 0; i < this.$EscortDeckPadPos.length; i++ )
            if( this.$EscortDeckShipPos[i] )
                this.$EscortDeckShipPos[i].z -= 40; //move backward on non-Carriers
    } else { //on Carriers move escort up
        for( var i = 0; i < this.$EscortDeckPadPos.length; i++ )
            if( this.$EscortDeckShipPos[i] )
                this.$EscortDeckShipPos[i].y += 100;
    }
}
//-----------------------------------------------------------------------------------------------//
this.playerWillSaveGame = function _escortdeck_playerWillSaveGame(message) {
    missionVariables.$EscortDeckShipData = JSON.stringify(this.$EscortDeckShipData);
    missionVariables.$EscortDeckLocked = JSON.stringify(this.$EscortDeckLocked);
    missionVariables.$EscortDeck_unusable_derelic_prob = this.$EscortDeckBasicUnusableProb
}
//-----------------------------------------------------------------------------------------------//
this.shipKilledOther = function _escortdeck_shipKilledOther(whom, damageType) {
    var _npcShip = this.ship; // set if called from an escort's ship script, unidefined if called as a world script for the playership
    var _ship = (_npcShip ? _npcShip : player.ship);
    if (!_npcShip && !this.$EscortDeckVersion) return;
    log("escortdeck", _ship.displayName+" killed "+whom.displayName+" with "+damageType);
    if (_npcShip && this.$EscortDeckUsable === 1)
        _ship.commsMessage("Mama, "+whom.displayName+" is no more!", player.ship);
}
//-----------------------------------------------------------------------------------------------//
this.shipAttackedOther = function _escortdeck_shipAttackedOther(other) { //player hits other
    // this function is both a world script event handler for the playership and a ship event handler for the escorts
    var that = _escortdeck_shipAttackedOther;
    var wc = (that.wc = that.wc || worldScripts.escortdeck);
    var tb = (that.tb = that.tb || worldScripts.towbar);
    var weak_lasers = (that.weak_lasers = that.weak_lasers || ["EQ_WEAPON_EBEAM_LASER","EQ_WEAPON_BEAM_LASER"]);
    var npcShip = this.ship; // set if called from an escort's ship script, unidefined if called as a world script for the playership
    var _ship = (npcShip ? npcShip : player.ship);
    var current_weapon = _ship.currentWeapon;
    var weapon_info = current_weapon.weaponInfo;
    var hit_energy = (weapon_info && weapon_info.damage ?
                                                weapon_info.damage :
                                                (weak_lasers.indexOf(current_weapon.equipmentKey) >= 0 ? 14 : 23)
                    );
    var fname = "shipAttackedOther";
    var debug = this.$debug;
    var logging = this.$logging;
    if (!npcShip && !this.$EscortDeckVersion) return;
    if (!other || !other.isValid || other.isPlayer) return; //for sure
    if (npcShip && logging) log("escortdeck", _ship.displayName+" attacked "+other.displayName+" with "+other.energy.toFixed(1)+" energy at "+_ship.position.distanceTo(other).toFixed(1));
    if( !other.script ) other.script = "oolite-default-ship-script.js"; //for sure
    if( other.script ) other.script.$EscortDeck_Target = 1; //flag for escort AI
    if (!other.isThargoid && !other.isDerelict && other.energy > 0 && other.energy < 2 * hit_energy) {
        //the next Military Laser hit can destroy the target
        if (logging) log("escortdeck", _ship.displayName+": forcing target '"+other.displayName+"' to abandon ship");
        other.fireMissile();//last resort
        if( other.equipmentStatus("EQ_ESCAPE_POD") === "EQUIPMENT_OK"
            && other.abandonShip()) {//eject to make more derelict ship but only if has Escape Pod
            var dn;
            if( worldScripts["detectors"] ) {
                dn = other.script.$Detectors_Origname;//short name with version
            } else {
                dn = other.displayName;
                other.displayName = "Derelict " + dn;
            }
            if (!npcShip) {
                player.consoleMessage(dn+" ejected!");
                log("escortdeck", _ship.displayName+" turned "+other.displayName+" derelict");
                if( other.script && !other.script.$AlreadyScored ) {
                    other.script.$AlreadyScored = true;
                    player.score++;
                }
            } else if (npcShip && this.$EscortDeckUsable === 1) {
               _ship.commsMessage("Mama, look, I got "+other.displayName+" !", player.ship);
               log("escortdeck", "Escort "+_ship.displayName+" turned "+other.displayName+" derelict");
            }
        } else log("escortdeck", _ship.displayName+" "+other.displayName+" has no functional escape pod");
    }
    if( other.isDerelict && !other.isThargoid ) {
        if (!npcShip) this.$EscortDeckSound3.play(); //audio feedback if derelict
        if( other.script ) {
            if( !other.script.$TowbarDerelictAttack ) { 
                other.script.$TowbarDerelictAttack = 1;
            } else {
                if (++other.script.$TowbarDerelictAttack >= tb.$TowbarMaxReduct)
                   log("escortdeck", _ship.displayName+", Laser Reductor disabled for "+other.displayName+", used too many times:"+other.script.$TowbarDerelictAttack);
            }
        }
        if (_ship.equipmentStatus("EQ_LASERREDUCTOR") === "EQUIPMENT_OK" && tb.$TowbarLaserReductorOn &&
            (!other.script || other.script.$TowbarDerelictAttack <= tb.$TowbarMaxReduct) ) { //prevent invulnerability
            // there are too many special cases for weapons and weapons positions (like shipdata's weapon_energy field)
            // so let's err on the conservative side...
            if (debug) log("escortdeck", _ship.displayName+" giving "+2*hit_energy+" back to derelict "+other.displayName+", "+other.script.$TowbarDerelictAttack+" attacks");
            other.energy += 2 * hit_energy;
        }
        if (npcShip) return;
        var reason = wc.$EscortDeck_TooLarge(_ship, other, this); //fit into the deck limits?
        if (other.script && !(other.script.$EscortDeckUsable > 0) && !(other.script.$EscortDeckUsable < 0) ) {
            if (wc.$isUsable(other, fname) ) { //fit into the deck limits and lucky
                log(this.name, _ship.displayName+": "+fname+": '"+other.displayName+"' is usable");
                other.script.$EscortDeckUsable = 1;
                other.script.$TowbarUsableShip = true;
                other.script.$TowbarShipHealth = 1;
                wc.$EscortDeck_AwardEQs(other);
            } else {
                log(this.name, _ship.displayName+": "+fname+": '"+other.displayName+"' is NOT usable");
                other.script.$EscortDeckUsable = -1; //unusable as escort
                other.script.$TowbarUsableShip = false;
            }
        }
    }
    if (!npcShip) this.$EscortDeck_Bounty( other );
}
//-----------------------------------------------------------------------------------------------//
this.shipCollided = function _escortdeck_shipCollided(otherShip) {
    var _fname = "shipCollided";
    var p = player.ship;
    if (!this.$EscortDeckVersion) return;
    if( !p || !p.isValid ) return;
    var w = this;
    var i = w.$EscortDeckShip.indexOf(otherShip);
    if( i > -1 ) {
        if( w.$EscortDeckShipPos[i] ) { //move escort until avoid collision
            if( !w.$EscortDeckCWS || p.dataKey.indexOf("carrier") == -1 ) {
                w.$EscortDeckShipPos[i].z -= 2; //move backward on non-Carriers
            } else { //on Carriers move escort up until avoid collision
                w.$EscortDeckShipPos[i].y += 5;
//                if( w.$EscortDeckXL ) {
//                    if( i < 4 ) w.$EscortDeckShipPos[i].y += 5; //top pads up
//                    else w.$EscortDeckShipPos[i].y -= 5; //others move down
//                    else if( Math.round(i/2) != i/2 )
//                        w.$EscortDeckShipPos[i].x += 5; //move right
//                    else w.$EscortDeckShipPos[i].x -= 5; //move left
//                } else {
//                    if( i < 5 ) w.$EscortDeckShipPos[i].y += 5; //on top move up
//                    else w.$EscortDeckShipPos[i].y -= 5; //others move down
//                }
            }
        }
        if( otherShip.AIState == "LANDING" ) {
            var r = w.$EscortDeck_PutShip(false, otherShip, i, p, true, true);
            if( !r ) otherShip.AIState == "DECK"; //landed
            else {
                otherShip.AIState == "FOLLOW"; //no room?
                if(w.$debug) log(w.name, "Can't add "+t.displayName +" to Escort Deck pad "+i+", "+r); //debug
            }
        }
    } else {
        if (this.$debug) log(this.name, _fname+": bumped '"+otherShip.dislayName+"'");
        w.$EscortDeck_AddEscort( otherShip, w.$EscortDeckShip, p, w, true );
    }
}
//-----------------------------------------------------------------------------------------------//
this.shipDied = function _escortdeck_shipDied() {
        var p = player.ship;
        //var e = system.addVisualEffect("escortdeckxl", p.position);e.orientation = p.orientation;
        //this.$EscortDeckV.remove();
}
//-----------------------------------------------------------------------------------------------//
this.shipDockedWithStation = function _escortdeck_shipDockedWithStation(station) {
    this.$EscortDeck_RestoreMaxs(player.ship, this);
    this.$EscortDeckDockingTunnel = false;
    this.$EscortDeckI = false; //start on the first page
    this.$EscortDeck_Interface();
}
//-----------------------------------------------------------------------------------------------//
this.shipExitedWitchspace = function _escortdeck_shipExitedWitchspace() {
    if (!this.$EscortDeckVersion) return;
    this.$EscortDeck_Show(player.ship);
    this.$EscortDeckDockingTunnel = false;
}
//-----------------------------------------------------------------------------------------------//
this.shipLaunchedFromStation = function _escortdeck_shipLaunchedFromStation() {
    if (!this.$EscortDeckVersion) return;
    this.$EscortDeck_SaveMaxs( player.ship, this );
    this.$EscortDeckDockingTunnel = false;
    if( this.$EscortDeckCWS && this.$EscortDeckCWS.$CarriersPlayerInEscort ) return;
    if (this.$debug) {
        log(this.name, "Launching, PadSize:"+JSON.stringify(this.$$EscortDeckPadSize));
        this.$printDeckLoad();
    }
    this.$EscortDeck_Show(player.ship);
}
//-----------------------------------------------------------------------------------------------//
this.shipTargetAcquired = function _escortdeck_shipTargetAcquired(t) {  //make some derelicts usable
    var _fname = "shipTargetAcquired";
    if( !t || !t.isValid ) return;
    this.$EscortDeckLastTooLargeTarget = null;
    var p = player.ship;
    var pad = this.$EscortDeckShip.indexOf(t);
//    if( pad > -1 ) this.$EscortDeckSelectedPad = pad; - do not use, cause disturbing changes in selected pad
    if( !t.script ) t.script = "oolite-default-ship-script.js"; //for sure
    if( t.script.$TowbarMinedShip ) t.script.$EscortDeckUsable = -1; //unusable as escort
    if ( t.isDerelict && !(t.script.$EscortDeckUsable > 0) && !(t.script.$EscortDeckUsable < 0) ) {
        //determine usability once only
        if (this.$isUsable(t, _fname)) { //limits and lucky
            log(this.name, "'"+t.displayName+"' is usable");
            t.displayName = t.displayName.replace("Derelict", "Usable");
            t.script.$EscortDeckUsable = 1;
            t.script.$TowbarUsableShip = true;
            t.script.$TowbarShipHealth = 1;
            this.$EscortDeck_AwardEQs(t);
        } else {
            log(this.name, "'"+t.displayName+"' is NOT usable");
            t.script.$EscortDeckUsable = -1; //unusable 
            t.script.$TowbarUsableShip = false;
            if(this.$debug) log(this.name, t.displayName+" is set to unusable: "+t);
        }
    }
    if( t.script && pad == -1  && ( t.isDerelict || t.script.$EscortDeckUsable > 0 )
        && (!this.$EscortDeck_isLarge(t, this) //exclude ships over 130t except for Carriers
            || this.$EscortDeckCWS && p.dataKey.indexOf("carrier") > -1
            && t.mass < this.$EscortDeckCMaxMass ) ) { //must below this mass on Carriers
        var shipmass = t.mass/1000;
        //remove extra density from HardShips and Granite ships
        if( t.scriptInfo && t.scriptInfo.hardarmour > 1 )
            shipmass = shipmass / 2;
        var reason = this.$EscortDeck_TooLarge(t, p, this); //fit into the deck limits?
        if( t.script.$EscortDeckUsable > 0 ) {
            t.script.$TowbarUsableShip = true; //show Usable! flag in CombatMFD
            var f = "Found an usable "+t.name+" ("+Math.ceil(shipmass)+"t) ";
            var g = null;
            if( p.equipmentStatus("EQ_ESCORTDECK") != "EQUIPMENT_OK"
                 && p.equipmentStatus("EQ_ESCORTDECKXL") != "EQUIPMENT_OK" )
                g = "You need an Escort Deck equipment to use as your escort";
            else if( reason ) f = t.name+" is "+reason+" for your Escort Deck";
            else if( t.position.distanceTo(p.position) > this.$EscortDeckLandingDist
                 + t.collisionRadius )
                g = "Go near to pick up to the Escort Deck";
            if(this.$debug) log(this.name, f+" "+g); //debug
            player.consoleMessage(f, 10);
            if(g) player.consoleMessage(g, 10);
        } else if( t.script.$EscortDeckUsable < 0 ) {
            t.script.$TowbarUsableShip = false;
            player.consoleMessage("Salvage "+t.displayName+" ("+Math.ceil(shipmass)+"t)", 4.5);
        }
    }
    if( pad == -1 && t.script && t.script.$EscortDeckUsable > 0 )
        p.awardEquipment("EQ_DTAUSA"); //show Usable label on NumericHUD
    else p.removeEquipment("EQ_DTAUSA"); //remove Usable label on NumericHUD
}
//-----------------------------------------------------------------------------------------------//
this.shipTargetLost = function _escortdeck_shipTargetLost() {
    if (!this.$EscortDeckVersion) return;
    player.ship.removeEquipment("EQ_DTAUSA"); //remove Usable label on NumericHUD
}
//-----------------------------------------------------------------------------------------------//
this.shipWillDockWithStation = function _escortdeck_shipWillDockWithStation(station) {
    if (!this.$EscortDeckVersion) return;
    if (this.$debug) {
        log(this.name, "Will Dock");
        this.$printDeckLoad();
    }
    this.$EscortDeckDockingTunnel = true;
    if( station.dataKey != "carriers-vdock" 
        && station.dataKey.indexOf("carrier-player") == -1 )
        this.$EscortDeck_VClear(this);
    else this.$EscortDeckFirstMSO = false; //do not salvage in vdock
    player.ship.removeEquipment("EQ_DTAUSA"); //remove Usable label on NumericHUD
}
//-----------------------------------------------------------------------------------------------//
this.shipWillEnterWitchspace = function _escortdeck_shipWillEnterWitchspace(cause) {
    if (!this.$EscortDeckVersion) return;
    this.$EscortDeckDockingTunnel = true;
    this.$EscortDeck_VClear(this);
    player.ship.removeEquipment("EQ_DTAUSA"); //remove Usable label on NumericHUD
}
//-----------------------------------------------------------------------------------------------//
this.shipWillLaunchFromStation = function _escortdeck_shipWillLaunchFromStation() {
    this.$EscortDeckVersion = this.$EscortDeck_Version();
    if (!this.$EscortDeckVersion) return;
    this.$EscortDeckDockingTunnel = true;
//    this.$EscortDeck_Show(player.ship); - lose escorts here, must call in shipLaunchedFromStation
    var ps = player.ship; //show mfd as early as other mfds but avoid no escorts message
    if( ps.setMultiFunctionText ) ps.setMultiFunctionText(this.name, "", false);
    
    if( this.$EscortDeckCWS && this.$EscortDeckCWS.$CarriersPlayerInEscort )
        return;
    
    this.$EscortDeckFirstMSO = true;
    if( this.$Derelicts && system && !system.isInterstellarSpace ) { //derelict targets
        var m = system.mainStation;
        var r = 300; //spread ship in this radius
        var ships = system.addShips("escort", 16, //random escorts over the nav buoy
            m.position.add(m.vectorForward.multiply( 11000 )), 3*r);
                if( this.$EscortDeckSGWS ) {
                        var a = system.addShips("[asp-sg]", 2,
                                m.position.add(m.vectorForward.multiply( 1800 )), r);
                        ships.push(a[0]); ships.push(a[1]);
                        var a = system.addShips("[cobra3-sg]", 2,
                                m.position.add(m.vectorForward.multiply( 2100 )), r);
                        ships.push(a[0]); ships.push(a[1]);
                        var a = system.addShips("[ferdelance-sg]", 2,
                                m.position.add(m.vectorForward.multiply( 2400 )), r);
                        ships.push(a[0]); ships.push(a[1]);
                        var a = system.addShips("[krait-sg]", 2,
                                m.position.add(m.vectorForward.multiply( 2700 )), r);
                        ships.push(a[0]); ships.push(a[1]);
                }
                var a = system.addShips("[escortdeck-adder]", 4,
                        m.position.add(m.vectorForward.multiply( 3000 )), r);
        ships.push(a[0]); ships.push(a[1]); ships.push(a[2]); ships.push(a[3]);
/*        var a = system.addShips("[escortdeck-asp]", 4,
            m.position.add(m.vectorForward.multiply( 3500 )), r);
        ships.push(a[0]); ships.push(a[1]); ships.push(a[2]); ships.push(a[3]);
        var a = system.addShips("[escortdeck-gecko]", 4,
            m.position.add(m.vectorForward.multiply( 4000 )), r);
        ships.push(a[0]); ships.push(a[1]); ships.push(a[2]); ships.push(a[3]);
        var a = system.addShips("[escortdeck-sidewinder]", 4,
            m.position.add(m.vectorForward.multiply( 4500 )), r);
        ships.push(a[0]); ships.push(a[1]); ships.push(a[2]); ships.push(a[3]);
        var a = system.addShips("[escortdeck-viper]", 4,
            m.position.add(m.vectorForward.multiply( 5000 )), r);
        ships.push(a[0]); ships.push(a[1]); ships.push(a[2]); ships.push(a[3]);
        
        var a = system.addShips("[cobramk1]", 4,
            m.position.add(m.vectorForward.multiply( 6000 )), r);
        ships.push(a[0]); ships.push(a[1]); ships.push(a[2]); ships.push(a[3]);
        var a = system.addShips("[ferdelance]", 4,
            m.position.add(m.vectorForward.multiply( 7000 )), r);
        ships.push(a[0]); ships.push(a[1]); ships.push(a[2]); ships.push(a[3]);
        var a = system.addShips("[moray]", 4,
            m.position.add(m.vectorForward.multiply( 8000 )), r);
        ships.push(a[0]); ships.push(a[1]); ships.push(a[2]); ships.push(a[3]);
        var a = system.addShips("[cobra3-player]", 4,
            m.position.add(m.vectorForward.multiply( 6500 )), r);
        ships.push(a[0]); ships.push(a[1]); ships.push(a[2]); ships.push(a[3]);
        var a = system.addShips("[krait]", 4,
            m.position.add(m.vectorForward.multiply( 7500 )), r);
        ships.push(a[0]); ships.push(a[1]); ships.push(a[2]); ships.push(a[3]);
*/
        if( this.$EscortDeckPWS ) {
            var a = system.addShips("[escortdeck-ophidian]", 4,
                m.position.add(m.vectorForward.multiply( 8000 )), r);
            ships.push(a[0]); ships.push(a[1]); ships.push(a[2]); ships.push(a[3]);
/*            var a = system.addShips("[escortdeck-ghavial]", 2,
                m.position.add(m.vectorForward.multiply( 8000 )), r);
            ships.push(a[0]); ships.push(a[1]);
            var a = system.addShips("[escortdeck-wolf]", 2,
                m.position.add(m.vectorForward.multiply( 8500 )), r);
            ships.push(a[0]); ships.push(a[1]);
            var a = system.addShips("[escortdeck-sniper-wolf]", 2,
                m.position.add(m.vectorForward.multiply( 8500 )), r);
            ships.push(a[0]); ships.push(a[1]);
*/            var a = system.addShips("[escortdeck-arachnid]", 2,
                m.position.add(m.vectorForward.multiply( 9000 )), r);
            ships.push(a[0]); ships.push(a[1]);
            var a = system.addShips("[escortdeck-sniper-arachnid]", 2,
                m.position.add(m.vectorForward.multiply( 9000 )), r);
            ships.push(a[0]); ships.push(a[1]);
            var a = system.addShips("[escortdeck-s8]", 2,
                m.position.add(m.vectorForward.multiply( 9500 )), r);
            ships.push(a[0]); ships.push(a[1]);
            var a = system.addShips("[escortdeck-sniper-s8]", 2,
                m.position.add(m.vectorForward.multiply( 9500 )), r);
            ships.push(a[0]); ships.push(a[1]);
        }
        for(var i=0; i<ships.length; i++) {
            if( ships[i] ) {
                ships[i].orientation = ps.orientation;
                ships[i].switchAI("nullAI.plist"); //need to deactivate escortdeck AI
                if( worldScripts["shipversion"] ) {
                    worldScripts["shipversion"].shipSpawned(ships[i]);
                }
                ships[i].awardEquipment("EQ_ESCAPE_POD");
                if(i == 0) { //make first ship to weak offender without injectors, need a few shot only to eject it
                    ships[i].removeEquipment("EQ_FUEL_INJECTION");
                    ships[i].bounty=33;
                    ships[i].energy=33;
                } else ships[i].abandonShip(); //other ships derelict
            } else if( this.$debug ) log(this.name, "ships["+i+"] is undefined ");
        }
        if( this.$debug ) log(this.name, "test derelict ships: "+ships);
    }
}
//
// Internal functions
//
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_Version = function _escortdeck_EscortDeck_Version() {
    var that = _escortdeck_EscortDeck_Version;
    var eq_list = (that.eq_list = that.eq_list || [ "EQ_ESCORTDECK", "EQ_ESCORTDECKXL" ]);
    var ship = player.ship;
    var i = eq_list.length;
    while (i--)
        if (ship.equipmentStatus(eq_list[i]) === "EQUIPMENT_OK")
            return eq_list[i];
    return null;
}
//-----------------------------------------------------------------------------------------------//
this.$onChange = function _escortdeck_onChange() {
    if (this.$newUsableProb != (1-this.$EscortDeckBasicUnusableProb) && this.$newUsableProb >= 0 && this.$newUsableProb <= 1) {
        this.$EscortDeckBasicUnusableProb = (1-this.$newUsableProb);
        log(this.name, "Unusable derelict probability changed to "+this.$EscortDeckBasicUnusableProb);
    }
    if (this.$newAutoLaunch != this.$EscortDeckAutoLaunch) {
        this.$EscortDeckAutoLaunch = this.$newAutoLaunch;
        log(this.name, "AutoLaunch changed to "+this.$EscortDeckAutoLaunch);
    }
    log(this.name,"Config changed, newUsableProb:"+this.$newUsableProb+", newAutoLaunch:"+this.$newAutoLaunch+", $EscortDeckBasicUnusableProb:"+this.$EscortDeckBasicUnusableProb+", $EscortDeckAutoLaunch:"+this.$EscortDeckAutoLaunch);
}
//-----------------------------------------------------------------------------------------------//
this.$damageRatio = function _escortdeck_damageRatio(s) {
    var fname = "damageRatio";
    var shipEquipments = s.equipment;
    var i = shipEquipments.length;
    var ok = 0;
    var damaged = 0;
    var eqpKey, eqpStatus, damaged_ratio;
    while (i--) {
        eqpKey = shipEquipments[i].equipmentKey;
        eqpStatus = s.equipmentStatus(eqpKey);
        if (this.$debug) log(this.name, fname+": '"+s.displayName+"' has "+eqpKey+": "+eqpStatus);
        if (eqpStatus === "EQUIPMENT_OK") {
            ok++;
        } else if (eqpStatus === "EQUIPMENT_DAMAGED") {
            damaged++;
        }
    }
    if (ok + damaged === 0) {
        damaged_ratio = 0;
    } else if (ok === 0 && damaged > 0) {
        damaged_ratio = 1;
    } else {
        damaged_ratio =  damaged / (damaged + ok);
    }
    if (this.$debug) log(this.name, fname+": '"+s.displayName+"' has "+damaged+" damaged of "+(damaged+ok)+" equipments, ratio "+damaged_ratio+", $EscortDeckUsable: "+s.script.$EscortDeckUsable);
    return damaged_ratio
}
//-----------------------------------------------------------------------------------------------//
this.$isUsable = function _escortdeck_isUsable(s, from_fname) {
    var fname = "isUsable";
    var unusable_prob, rnd;
    if (s.script && s.script.$EscortDeckUsable && s.script.$EscortDeckUsable === -1) return false;
    var damaged_ratio = this.$damageRatio(s);
    if (this.$EscortDeckNPCEqDamage) {
        // NPC Equipment Damage OPC is installed, and it adds _a lot_ of equipment damage
        damaged_ratio = damaged_ratio / 6.7;
    }
    if (this.$debug) log(this.name, fname+"("+from_fname+"): '"+s.displayName+"', adjusted damage_ratio: "+damaged_ratio+", $EscortDeckUsable: "+s.script.$EscortDeckUsable);
    // probability of damaged to basic systems + equipment damage 
    // (it's not a probem being larger than 1)    
    unusable_prob = this.$EscortDeckBasicUnusableProb + damaged_ratio;
    rnd = Math.random();
    if (this.$debug) log(this.name, fname+": unusable probability: "+unusable_prob+", random: "+rnd);
    return (rnd > unusable_prob);
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_HHStoreShip = function _escortdeck_HHStoreShip(ship) {
    var that = _escortdeck_HHStoreShip;
    var ws = (that.ws = that.ws || worldScripts.escortdeck);
    if (!ship) return;
    var shipdata = $EscortDeck_MakeShipData(ship);
    if (shipdata && shipdata[0]) {
        ws.$EscortDeck_HHStoreShipData(shipdata);
    } else {
        log(ws.name, "Trouble creating shipData for "+ship.displayName);
    }
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_HHStoreShipData = function _escortdeck_HHStoreShipData(shipdata) {
    var that = _escortdeck_HHStoreShipData;
    var ws = (that.ws = that.ws || worldScripts.escortdeck);
    var hh = (that.hh = that.hh || worldScripts.HyperspaceHangar);
    if (shipdata && shipdata[0]) {
        var k = clock.absoluteSeconds;
        while (hh._shipsStored[k]) k++;
        var displayName = shipdata[0][3];
        shipdata[0][0] = galaxyNumber;
        shipdata[0][1] = system.ID;
        hh._shipsStored[k] = JSON.stringify(shipdata);
        hh._currentNames[k] = displayName;
        if (hh._currentNames["0_EXIT"] === "EMPTY") hh._currentNames["0_EXIT"] = "Exit";
    }
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_AddEscort = function _escortdeck_AddEscort( t, e, owner, w, msg ) { //add t into the deck of ship if possible
    var _fname = "$EscortDeck_AddEscort";
//    if (this.$debug) log(this.name, _fname+": examining "+(t.displayName ? t.displayName : t)+", mass:"+t.mass); 
    if( t && t.isValid && t.script &&
        ( t.isDerelict || t.script.$EscortDeckUsable === -1 || t.script.$EscortDeckUsable > 0 )
        && t.mass > 1000 && t.mass < this.$EscortDeckExtremeMass && !t.isVisualEffect && !t.isPlayer
        && !t.isDock && !t.isStation && !t.isCargo && !t.isRock && !t.isThargoid && !t.isWeapon
        && owner && owner.isValid
        && ( owner.equipmentStatus("EQ_ESCORTDECK") == "EQUIPMENT_OK"
            || owner.equipmentStatus("EQ_ESCORTDECKXL") == "EQUIPMENT_OK" )
        && owner.mass >= w.$EscortDeckMinMass
        && owner.position.distanceTo(t.position) < w.$EscortDeckLandingDist + t.collisionRadius ) {
        var p = e.indexOf(t);
        if( p > -1 && t.AIState != "LANDING" ) return false;//owned escort is on way, do not land
    
        var r = this.$EscortDeck_TooLarge(t, owner, w);
        if( r ) { //reason
                        w.$EscortDeckLastTooLargeTarget = t; //prevent repeated message
            if( msg && owner == player.ship ) {
                player.consoleMessage("You can't pick up "+t.displayName+" to your Escort Deck, "+r, 10);
            }
            return false;
        }
        if (t.script.$EscortDeckUsable != 1 && t.script.$EscortDeckUsable != -1 ) {
            // derelict was not examined for usability
            if (this.$isUsable(t, _fname)) { //fit into the deck limits and lucky
                log(this.name, t.displayName+" is usable");
                t.script.$EscortDeckUsable = 1;
                t.script.$TowbarUsableShip = true;
                t.script.$TowbarShipHealth = 1;
                this.$EscortDeck_AwardEQs(t);
            } else {
                log(this.name, t.displayName+" is NOT usable");
                t.script.$EscortDeckUsable = -1; //unusable as escort
                t.script.$TowbarUsableShip = false;
                if(this.$debug) log(this.name, t.displayName+" is set to unusable: "+t);
            }
        }
        var diff = owner.velocity.subtract( t.velocity ).magnitude();
        if( diff > 100 ) {
            log(this.name, "Velocity difference ("+(Math.round(diff/10)*10)+"m/s) too big to load into EscortDeck");
            if( owner == player.ship && msg && p == -1 ) //not in auto landing
                player.consoleMessage(Math.round(diff/10)*10+" m/s speed difference");
            return false;
        }
        //put into the selected pad if fit into
        //and regardless of occupied (replace) if this is the current target
        var spad = w.$EscortDeckSelectedPad;
        if( spad > -1 ) {
            if(this.$debug) log(w.name, "Try "+t.displayName+" to selected pad "+(spad+1)); //debug
            var f = false; //force replace
            if( owner.target == t ) f = true;
            r = w.$EscortDeck_PutShip(false, t, spad, owner, msg, f);
            if( !r ) return true; //report successfully added
            else if(this.$debug) log(w.name, "Can't add "+t.displayName+" to selected pad "+(spad+1)+", "+r); //debug
        }
        //put owned escorts back to the correct pad
        if( p > -1 ) {
            if(this.$debug) log(w.name, "Try "+t.displayName+" to old pad "+(p+1)); //debug
            r = w.$EscortDeck_PutShip(false, t, p, owner, msg, true);
            if( !r ) return true; //report successfully added
            else { //if faliled the remove and try other pads as new ships
                if(this.$debug) log(w.name, "Can't add "+t.displayName+" back to pad "+(p+1)+", "+r); //debug
                e[p] = null;
                p = -1;
            }
        }
        if(this.$debug) log(w.name, "Try "+t.displayName+" to empty pads"); //debug
        //if Adder or smaller then try Adder pads first
        var large = w.$EscortDeck_isLarge(owner, w);
        if( large && //side pads need large ship
            w.$EscortDeck_AddMini(t, owner, w, msg, false) )
            return true; //report successfully added
        //non-small escort need large ship
        for(var i = 0; i < w.$EscortDeckPadPos.length; i++ ) {
            if( !w.$EscortDeck_isMiniPad(i, owner, w) ) {
                //find the first empty pad except Adder only pads
//                log(w.name, i+". free:"+w.$EscortDeck_isPadFree(i, w)); //debug
                r = w.$EscortDeck_PutShip(false, t, i, owner, msg, false);
                if( !r ) i = w.$EscortDeckPadPos.length;//exit from the for cycle
            }
        }
        if( e.indexOf(t) > -1 ) return true; //report successfully added
        //replace first flying escort if no more empty pad and none selected
        if(this.$debug) log(w.name, "Try "+t.displayName+" to replace a flying escort");
        for(var i = 0; i < w.$EscortDeckPadPos.length; i++ ) {
            if( !w.$EscortDeck_isMiniPad(i, owner, w)
                || large && w.$EscortDeck_isMini(t) ) {
                if( !w.$EscortDeckShipPos[i] ) { //on the way
                    r = w.$EscortDeck_PutShip(false, t, i, owner, msg, true);
                    if( !r ) return true;
                }
            }
        }
        if( e.indexOf(t) > -1 ) return true; //report successfully added
        else {
            if( msg && owner == player.ship ) {
                player.consoleMessage("No room on Escort Deck for "+t.displayName, 10);
            }
            if(this.$debug) log(w.name, "Can't add "+t.displayName+" to Escort Deck, "+r); //debug
            return false;
        }
    } //else log(this.name, _fname+": ship "+(t.displayName ? t.displayName : t)+" didn't meet basic condition, ignoring");
    return false; //can not add escort to the deck
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_AddMini = function _escortdeck_AddMini( t, owner, w, msg, land ) { //add mini escort to a small pad if possible
    if( !w.$EscortDeck_isMini(t) ) return false; //need like an Adder or smaller
    for( var i = 0; i < w.$EscortDeckPadPos.length; i++ ) {
        if( w.$EscortDeck_isMiniPad(i, owner, w) ) {
            if( !w.$EscortDeck_PutShip(false, t, i, owner, msg, land ) )
                return true;
        }
    }
    return false;
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_AwardEQs = function _escortdeck_AwardEQs(ship) {
    var logging = this.$logging;
    var debug  = this.$debug;
    var LN10 = Math.LN10;
    var damage_ratio = this.$damageRatio(ship);
    function escortdeck_awardEQ(eqKey) {
        var eqInfo = EquipmentInfo.infoForKey(eqKey);
        var rnd = Math.random();
        var threshold = 0.03 * eqInfo.techLevel + 0.05 * Math.log(eqInfo.calculatedPrice/10)/LN10;
        if (debug) log("escortdeck", "usable "+ship.displayName+", "+eqKey+": tl:"+eqInfo.techLevel+" ("+0.03 * eqInfo.techLevel+"), price: "+eqInfo.calculatedPrice/10+" ("+0.05 * Math.log(eqInfo.calculatedPrice/10)/LN10+"), threshold: "+threshold+", rnd: "+rnd);
        if (rnd > threshold) {
            var damage_prob = damage_ratio * eqInfo.damageProbability;
            ship.awardEquipment(eqKey);
            if (Math.random() < damage_prob) ship.setEquipmentStatus(eqKey, "EQUIPMENT_DAMAGED");
            if (logging) log("escortdeck", "awarding "+eqKey+"("+ship.equipmentStatus(eqKey)+") to usable "+ship.displayName);
            return true;
        }
        return false;
    }
    //award player specific equipments, useful when sitting in from a Carrier
    if (ship && ship.isValid) {
        escortdeck_awardEQ("EQ_ADVANCED_COMPASS");
        if(worldScripts.combat_MFD) escortdeck_awardEQ("EQ_COMBATMFD");
        if (worldScripts.MFDFastConfiguration) escortdeck_awardEQ("EQ_MFD_FAST_CONFIG");
        if (ship.hasHyperspaceMotor) escortdeck_awardEQ("EQ_ADVANCED_NAVIGATIONAL_ARRAY");
        escortdeck_awardEQ("EQ_MULTI_TARGET");
        escortdeck_awardEQ("EQ_SCANNER_SHOW_MISSILE_TARGET");
        if (worldScripts.targetAutolock) //requires EQ_SCANNER_SHOW_MISSILE_TARGET so add after
            escortdeck_awardEQ("EQ_TARGET_AUTOLOCK");
        escortdeck_awardEQ("EQ_TARGET_MEMORY");
        escortdeck_awardEQ("EQ_INTEGRATED_TARGETING_SYSTEM");//requires Target Memory so add after
        if (worldScripts.telescope) escortdeck_awardEQ("EQ_TELESCOPE");
        if (worldScripts.sniperlock) escortdeck_awardEQ("EQ_SNIPERLOCK");
        if (worldScripts.towbar) escortdeck_awardEQ("EQ_LASERREDUCTOR");
        if (worldScripts.traildetector) escortdeck_awardEQ("EQ_TRAIL_DETECTOR");
        if (worldScripts.Police_Scanner_Upgrade) escortdeck_awardEQ("EQ_POLICE_SCANNER_UPGRADE");
        if (worldScripts.hudselector) escortdeck_awardEQ("EQ_HUDSELECTOR");
        if (worldScripts.BarrelRoll) escortdeck_awardEQ("EQ_BARREL_ROLL");
        escortdeck_awardEQ("EQ_WORMHOLE_SCANNER");
        escortdeck_awardEQ("EQ_HEAT_SHIELD");//protection for sunskimming, requested by QCS
    }
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_Bounty = function _escortdeck_Bounty( ship ) {
    if( ship.isDerelict && ship.bounty > 0 ) {
        player.credits += ship.bounty;
        player.consoleMessage("Bounty: "+formatCredits(ship.bounty, true, true));
        player.consoleMessage("Total: "+formatCredits(player.credits, true, true));
        ship.bounty = 0;
    }
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_ControlAll = function _escortdeck_ControlAll( launch, manual, w ) { //launch or call back all escorts
    if( launch && w.$EscortDeck_ControlFast(manual) ) return(false);
    var c = 0;
    var carrier = false;
    if( w.$EscortDeckCWS && player.ship.dataKey.indexOf("carrier") > -1 )
        carrier = true;
    for(var i = 0; i < w.$EscortDeckPadPos.length; i++ ) {
        if( !w.$EscortDeckLocked[i] &&
            ( !launch || !carrier || w.$EscortDeckXL || manual ||
            //can prevent auto launch of Anaconda from Carrier
            w.$EscortDeckCBigTraderLaunch ||
            w.$EscortDeckShip && w.$EscortDeckShip[i]
            && w.$EscortDeckShip[i].mass < w.$EscortDeckXLMass ) ) {
            if( w.$EscortDeck_Control1(launch, i, false, manual)) //launch without message
                c++; //count usable escorts
        }
    }
    if( c > 0 && ( manual || !launch ) ) { //do not message autolaunch when entering red alert
        var e = c+" Escorts ";
        if( c == 1 ) e = "1 Escort ";
        var m = "called back";
        if( launch ) m = "on the way";
        player.consoleMessage(e+m, 10);
    }
    w.$EscortDeck_MFD(w); //update MFD
    return(c);
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_ControlFast = function _escortdeck_ControlFast(manual) {
    var max = player.ship.maxSpeed; //no launch over max.speed
//    if( !manual ) max = max * 0.999; //no autolaunch at max.speed
    if( player.ship.speed > max ) {
        if( manual ) player.consoleMessage("You travel too fast to launch escorts", 3);
//        else player.consoleMessage("No autolaunch of escorts at maximal speed", 3);
        return(true);
    }
    return(false);
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_Control = function _escortdeck_Control( launch, pad, msg, w ) { //launch or call back an escort
    w.$EscortDeck_Control1(launch, pad, msg, true);
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_Control1 = function _escortdeck_Control1(launch, pad, msg, manual) { //launch or call back an escort
    var that = _escortdeck_Control1;
    var w = (that.w = that.w || worldScripts.escortdeck);
    if (launch && this.$EscortDeck_ControlFast(manual)) return false;
    var s = null;
    var p = player.ship;
    if (w.$EscortDeckShip) s = w.$EscortDeckShip[pad];
    if (s && s.isValid) {
        if (!s.script || !(s.script.$EscortDeckUsable > 0)) { //unusable
            if (msg) w.$EscortDeck_Release(p, pad, w); //msg mean manual and one ship
            return(false);
        }
        var m = null; //the carrier can't launch
        if (launch && !w.$EscortDeckLocked[pad]) {
            var seen = [];
            s.script.shipAttackedOther = this.shipAttackedOther;
            s.script.shipKilledOther = this.shipKilledOther;
            s.script.$debugHarderWay = true;
            s.script.$debug = false;
            s.script.$logging = true;
            log(this.name, "Launching "+s.displayName+" from Pad "+pad+", escortdeckUsable "+s.script.$EscortDeckUsable+" script: "+JSON.stringify(s.script, function(key,val) {
                                if (val != null && typeof val == "object") {
                                    if (seen.indexOf(val) >= 0) {return;}
                                    seen.push(val);
                                }
                                return val;
                               }));
            if (p.removeCollisionException) //from v1.81
                p.removeCollisionException(s);
            w.$EscortDeckShipData[pad] = null; //prevent recreation in dock
            w.$EscortDeckShipPos[pad] = null; //do not update position in FCB
            //increase steering and acceleration in proportion with the smaller mass on deck
            w.$EscortDeck_SetMaxs(p, w);
            if (s.AI != "EscortDeck_AI.plist") s.switchAI("EscortDeck_AI.plist");
            s.AIState = "GOTO"; //switch back from waypoint AI
            s.scannerDisplayColor1 = [0, 0.7, 0]; //green
            s.velocity = p.velocity; //prevent collision if player travel at speed
            if (w.$EscortDeckPadVec) {//help to leave the owner: jump 20m far
                var j = w.$EscortDeckJumpDist;
                var x = j * w.$EscortDeckPadVec[pad][0];
                var y = j * w.$EscortDeckPadVec[pad][1];
                var z = j * w.$EscortDeckPadVec[pad][2];
                if (p.dataKey.indexOf("carrier") > -1) {
                    if (w.$EscortDeck_isMiniPad(pad, p, w)) {
                        z = j; //Carrier mini pads jump forward
                    } else if(!w.$EscortDeckXL) { //traders jump sideways
                        if (pad == 6) {x = j; z = 0;}
                        else if (pad == 5) {x = -j; z = 0;}
                    }
                }
                var o = p.orientation;
                var sp = s.position.add(o.vectorRight().multiply(x))
                    .add(o.vectorUp().multiply(y))
                    .add(p.heading.multiply(z));
                if (sp.magnitude() > 500) s.position = sp;
            }
            s.script.$EscortDeckOnDeck = false;
            m = "on the way";
        } else { //call back
            if (s.AIState != "DECK") { //isn't in deck already
//            if( !w.$EscortDeckShipPos[pad] ) {
                if (s.AI != "EscortDeck_AI.plist") s.switchAI("EscortDeck_AI.plist");
                s.AIState = "LANDING";  //switch back from waypoint AI
                m = "called back";
            }
        }
        if (m && msg) {
            player.consoleMessage(s.displayName+" "+m, 5);
            w.$EscortDeck_MFD(w); //update MFD
        }
        if (w.$debug) log(w.name, pad+". launch:"+launch+" "+m); //debug
        if (m) return true;
    }
    return false;
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_EQActivated = function _escortdeck_EQActivated(w) { //launch or call back one or all escorts
    var p = player.ship;            //called from carriers-escort-eq.js also!
    if( !p || !p.isValid ) return;
    if( w.$EscortDeckSelectedPad == w.$EscortDeckPadPos.length && w.$EscortDeckToWS ) {
        if( w.$EscortDeckToWS.$TowbarShip ) w.$EscortDeckToWS.$Towbar_Release();
        else player.consoleMessage("No ship on Towbar");
    } else if( w.$EscortDeckSelectedPad == -2 ) { //setup sit into mode
        w.$EscortDeckSitInto = !w.$EscortDeckSitInto; //flip-flop
         w.$EscortDeck_EQDisplayMode(w);
    } else if( w.$EscortDeckSelectedPad == -1 ) { //launch or call back all escorts
        w.$EscortDeckLaunch = !w.$EscortDeckLaunch; //flip-flop
        w.$EscortDeck_ControlAll(w.$EscortDeckLaunch, true, w);
    } else { //launch or call back a selected escort with message
        var s = w.$EscortDeckShip[w.$EscortDeckSelectedPad];
        if( s && s.isValid ) {
            if( w.$EscortDeckShipPos[w.$EscortDeckSelectedPad] ) {
                if( w.$EscortDeckLocked[w.$EscortDeckSelectedPad] ) {
                    w.$EscortDeckLaunch = true; //launch if on deck and locked
                    w.$EscortDeckLocked[w.$EscortDeckSelectedPad] = false;
                } else {
                    w.$EscortDeckLaunch = false;
                    w.$EscortDeckLocked[w.$EscortDeckSelectedPad] = true; //lock
                }
                w.$EscortDeck_MFD(w); //update MFD
            } else {
                w.$EscortDeckLocked[w.$EscortDeckSelectedPad] = false;
                if( s.AIState == "LANDING" ) w.$EscortDeckLaunch = true;
                else {
                    w.$EscortDeckLaunch = false;
                    if( s.dataKey.indexOf("carrier-player") > -1 ) {
                        if( p.target == s ) {//target is the player's carrier?
                            player.consoleMessage("Approach the Carrier "+
                                "within "+w.$EscortDeckLandingDist+
                                "m with small speed difference "+
                                " for landing",10);
                        } else { player.consoleMessage("Hold ident lock on"+
                                " your Carrier to keep up"+
                                " landing clearance",5);
                        }
                    }
                }
            }
            if( w.$EscortDeckSitInto //deck is in the "sit into the escort" mode
                && w.$EscortDeckCWS //Carriers OXP is installed
                && p.dataKey.indexOf("carrier-player") > -1 //player in the Carrier
                && s.AIState == "DECK" //selected a landed, usable and playable escort
                && s.missileCapacity > 0 //bugfix against a core bug with 0 max_missiles
                && w.$EscortDeckPlayableDataKeys.indexOf(s.dataKey) > -1 ) {
                w.$EscortDeckCWS.$Carriers_SitIntoAnEscort(s); //launch player
            } else w.$EscortDeck_Control( w.$EscortDeckLaunch,
                w.$EscortDeckSelectedPad, true, w);
        } else { //no ship in this pad
            if( w.$EscortDeckLocked[w.$EscortDeckSelectedPad] )
                w.$EscortDeckLocked[w.$EscortDeckSelectedPad] = false;
            else w.$EscortDeckLocked[w.$EscortDeckSelectedPad] = true;
        }
    }
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_EQDisplayMode = function _escortdeck_EQDisplayMode(w) {
     if( w.$EscortDeckSelectedPad == -1 ) { //launch or callback mode
        var l = "Launch all non-locked";
        if( w.$EscortDeckLaunch ) l = "Call back all flying";
        player.consoleMessage(l+" escorts when EscortDeck activated",5);
    } else if( w.$EscortDeckSelectedPad == -2 ) { //setup sit into mode
        var l = "avoid";
        if( w.$EscortDeckSitInto ) l = "allow";
        player.consoleMessage("EscortDeck "+l+" to sit into a selected escort",5);
    }
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_EQMode = function _escortdeck_EQMode(w, playable) { //select the next pad on escort deck
     var p = player.ship;         //called from carriers-escort-eq.js also!
    if( p && p.isValid ) {
        w.$EscortDeckSelectedPad++;
/*        if( playable ) { - removed to allow select any ship to drop salvage, etc.
            var start = false;
            if( w.$EscortDeckSelectedPad == 0 ) start = true; //start from the first pad
            var found = -1;
            while( found < 0 && w.$EscortDeckSelectedPad < w.$EscortDeckPadPos.length ) {
                var s = w.$EscortDeckShip[w.$EscortDeckSelectedPad];
                if( s && s.isValid && s.script && s.script.$EscortDeckUsable > 0
                    && w.$EscortDeckPlayableDataKeys.indexOf(s.dataKey) > -1 )
                    found = w.$EscortDeckSelectedPad;
                w.$EscortDeckSelectedPad++;
            }
            w.$EscortDeckSelectedPad = found;
            if( start && found == -1 )
                player.consoleMessage( "You have not any playable ships on board", 5 );
        }
*/
//        while( w.$EscortDeckSelectedPad < w.$EscortDeckPadPos.length
//            && !w.$EscortDeckShip[w.$EscortDeckSelectedPad] )  //skip empty pads
//            w.$EscortDeckSelectedPad++; - no skip to be able to lock to a specific pad
        if( !w.$EscortDeckToWS || w.$EscortDeckSelectedPad <= w.$EscortDeckPadPos.length ) {
            if( w.$EscortDeckSelectedPad >= w.$EscortDeckPadPos.length ) {
            if( w.$EscortDeckSitIntoWithMode //allowing sit into enabled with mode key
                && p.dataKey.indexOf("carrier") > -1 )//and player in a Carrier
                w.$EscortDeckSelectedPad = -2; //setup sit into mode
            else w.$EscortDeckSelectedPad = -1; //none selected
            var ws = w.$EscortDeckTWS; //Telescope worldScript
            if( ws ) ws.$TelescopeTargetSet = true;
            p.target = null;
            if( ws ) ws.$TelescopeTargetSet = false;
            }
            if( w.$EscortDeckSelectedPad > -1 ) {
            var s = w.$EscortDeckShip[w.$EscortDeckSelectedPad];
            if( s && s.isValid ) {
                var ws = w.$EscortDeckTWS; //Telescope worldScript
                if( ws ) {
                    var mi = ws.$TelescopeList.indexOf( s );
                    if( mi == -1 ) ws.$Telescope_Scan(); //forced rescan
                    if( mi > -1 ) {
                        if(  ws.$TelescopeListi != mi + 1 ) {
                            ws.$TelescopeListi = mi + 1;
                            ws.$Telescope_Show2( false ); //lock target
                        }
                    }
                } else p.target = s; //target the selected ship
            }
            } else w.$EscortDeck_EQDisplayMode(w);
        }
        w.$EscortDeck_MFD(w); //update MFD
    }
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_FCB = function _escortdeck_FCB(delta) { //FrameCallBack updating positions of escort ships
    var that = _escortdeck_FCB;
    var owner = (that.owner = that.owner || player.ship); //modify this for NPCs
    var w = (that.w = that.w || worldScripts.escortdeck);
    var wSV = (that.wSV = that.wSV || worldScripts.shipversion); // ShipVersion
    var tb = (that.tb = that.tb || w.$EscortDeckToWS); // Towbar
    var oldSV = (that.oldSV = that.oldSV || (wSV && wSV.version && wSV.version > "1.24" ? false : true));
    if (!owner || !owner.isValid) { //test with PS.explode() in debug console
            var v = w.$EscortDeckV;
            if (!v || !v.position) return; //exit if player died
            if (!w.$EDH) w.$EDH = v.heading.multiply(-4).add(Vector3D.randomDirection());
            v.position = v.position.add(w.$EDH.multiply(100*delta)); //deck drift away
            v.orientation = v.orientation.rotateX(delta).rotateY(delta).rotateZ(delta/2);
            //log(w.name, w.$EscortDeckV.position.x);
            return;
        }
    var zv = w.$EscortDeckZV;
    var eship = w.$EscortDeckShip;
    var epos = w.$EscortDeckShipPos;
    if (owner != player.ship && owner.script && owner.script.$EscortDeckZV) { //NPC
        zv = owner.script.$EscortDeckZV;
        eship = owner.script.$EscortDeckShip;
        epos = owner.script.$EscortDeckShipPos;
    } else if (w.$EscortDeckCWS && w.$EscortDeckCWS.$CarriersPlayerInEscort) {
        owner = w.$EscortDeckCWS.$CarriersCarrier; //deck is no more on player ship but on Carrier
        if (!owner || !owner.isValid) return; //exit if carrier died
    }
    if (!zv) {
        if (!zv) zv = Vector3D(0,0,-50);
        zv.x = 0;
        var hl = owner.boundingBox.z * 0.5; //half of the ship's length
        if (zv.z < hl) zv.z = -hl; //correct z position for carriers
        if (zv.y > 0) zv.y = 0; //do not raise over the center line
        w.$EscortDeckZV = zv;
    }
    var d = w.$EscortDeckShipData;
    var sh = w.$EscortDeckSpawnedShips.pop();
    if ( sh && sh.isValid && oldSV) { 
        // restore ship data after ShipVersion's shipSpawned has its way with the ship
        // ShipVersion 1.25+ looks for ship.script.$ShipVersionIgnore, which we set, and ignores the ship
        var pad = eship.indexOf(sh);
        if (w.$debug) log(w.name, "FCB pad:"+pad+" d:"+d[pad]);
        if (pad > -1 && d[pad]) {
            var seen = [];
            w.$EscortDeck_RestoreShipData(sh, d[pad], w, owner);
            log(w.name, "restored ship: "+JSON.stringify(sh, function(key,val) {
                                        if (val != null && typeof val == "object") {
                                            if (seen.indexOf(val) >= 0) {return;}
                                            seen.push(val);
                                        }
                                        return val;
                                    }));
        }   
    }
//    if(w.$debug) log(w.name, "FCB pad:1 d:"+d[1]);
    //set the new position of the deck if exist (carriers haven't added deck)
    var dp = owner.position.add(owner.heading.multiply(zv.z)); //deck position, used at escorts also
    if (zv.y != 0) dp = dp.add(owner.orientation.vectorUp().multiply(zv.y)); //move lower if needed
    if (w.$EscortDeckV) { //set the new position of the deck if exist (carriers haven't added deck)
        w.$EscortDeckV.position = dp;
        w.$EscortDeckV.orientation = owner.orientation;
    }
    
    //set the new positions of escorts
    for (var pad = 0; pad < w.$EscortDeckPadPos.length; pad++) {
        var s = eship[pad];
        if (s) {
            if(s.isValid) {
                var sp = epos[pad]; //on deck and is not a Carrier?
                if (sp && s.dataKey.indexOf("carrier") === -1) {
                    var h = owner.heading;
                    var o = owner.orientation;
                    var r = o.vectorRight();
                    var u = o.vectorUp();
                    var pos = dp.add(h.multiply(sp.z)).add(r.multiply(sp.x)).add(u.multiply(sp.y));
                    if (pos.magnitude() > 500) s.position = pos;
                    if (w.$EscortDeckCWS
                        && owner.dataKey.indexOf("carrier") > -1) {
                        w.$EscortDeck_FCBC(s, owner, pad, o, h, r, u, w);
                    } else if (w.$EscortDeckXL) {
                        var left = -0.5; //left side of the xl deck
                        if (Math.round(pad/2) != pad/2) left = 0.5; //right
                        var p = -Math.PI;
                        s.orientation=o.rotate(r,p*0.5001).rotate(h,left*p);
                    } else s.orientation = o.rotate(r, 0.0001);
                    //if(pad==6 || pad==7) reverse ori
                }
            } else { //escort ship destroyed
                if (w.$EscortDeckShip) {
                    if (owner != player.ship && (!w.$EscortDeckCWS
                        || owner != w.$EscortDeckCWS.$CarriersCarrier))
                        owner.script.$EscortDeckShip[pad] = null; //NPC escort
                    else w.$EscortDeckShip[pad] = null; //player escort
                }
            }
        }
    }
    
    w.$EscortDeckFCBDelta += delta;
    if (w.$EscortDeckFCBDelta > 0.5) { //update AI and MFD twice in a second
        w.$EscortDeckFCBDelta = 0;
        //check nearby usable derelict for pickup
        var t = owner.target;
//        log(w.name, "target:"+t); //debug
        if (t && w.$EscortDeckLastTooLargeTarget != t //prevent repeated too large message
            && eship.indexOf(t) === -1 //and must not already owned
            && !(tb && tb.$TowbarShip && t == tb.$TowbarShip) ) //and not being towed
            w.$EscortDeck_AddEscort(t, eship, owner, w, true);
        
        //AI helper part, keep in sync with the UPDATE parts of EscortDeck_AI.plist
        for (var pad = 0; pad < w.$EscortDeckPadPos.length; pad++) {
            var s = eship[pad];
            if (s && s.isValid && s.AI === "EscortDeck_AI.plist") { //skip waipoint AIs
                var e = s.script;
                if (s.AIState === "FOLLOW") e._locatePlayer2(s);
                else if (s.AIState === "GOTO") e._findPlayerHostiles2(s);
                else if (s.AIState === "ATTACK" || s.AIState == "INJECT"
                    || s.AIState === "SNIPER") e._combatCheck2(s);
                else if (s.AIState === "FLEE") e._fleeCheck2(s);
                else if (s.AIState === "LANDING") e._landing2(s);
            }
        }
        w.$EscortDeck_MFD(w); //update MFD
    }
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_FCBC = function _escortdeck_FCBC(s, owner, pad, o, h, r, u, w) { //FrameCallBack rotate ships on Carriers
    var left = -0.5001; //left side of the xl deck
    if( Math.round(pad/2) != pad/2 ) left = 0.5001; //right side
    var p = -Math.PI;
    if( w.$EscortDeck_isMiniPad(pad, owner, w) ) {
        if( w.$EscortDeckXL ) left = -left;
        s.orientation = o.rotate( h, left*p ).rotate( u, left*p*0.6 ).rotate( r, 0.0001 );
    } else if( w.$EscortDeckXL && pad > 3 )
        s.orientation = o.rotate( u, left*p ).rotate( r, 0.0001 );
    else s.orientation = o.rotate( r, 0.0001 ); //fix blinking shaders bug
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_HHGet = function _escortdeck_HHGet(k, pad, w) { //get a ship from Hyperspace Hangar into the given pad
    var that = _escortdeck_HHGet;
    var w = (that.w = that.w || worldScripts.escortdeck);
    var hh = (that.hh = that.hh || worldScripts.HyperspaceHangar);
    var d = w.$EscortDeckShipData[pad];
    if( !d || !d[1] ) {
       log(this.name, "Parsing ship "+k+" "+hh._shipsStored[k]);
        w.$EscortDeckShipData[pad] = JSON.parse(hh._shipsStored[k]);
        delete hh._shipsStored[k];
        delete hh._currentNames[k];
        if( Object.keys(hh._currentNames).length < 1 ) //this was the last ship
            hh._currentNames["1_EXIT"] = "EMPTY";
    }
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_Interface = function _escortdeck_Interface() {
    var ps = player.ship; if( !ps || !ps.isValid || !ps.docked ) return;
    var n = 0, m = 0, h = this.$EscortDeckShipData, p = this.$EscortDeckPadPos;
    if( h ) n = h.length;
    if( p ) m = p.length;
    var s = "";
    if( n > 0 ) {
        n = 0;
        for(var i = 0; i < m; i++) {
            s += "                                                  Deck "+(i+1)+": ";
            var d = h[i];
            if( d && d[0] ) {
                n++;
                if( d[0][3] ) s += d[0][3]+"\n"; //displayName
                else {
                    if(d[0] && d[0][7]) { //shipClassName
                        s += d[0][7];
                        if(d[0][8]) s += d[0][8]; //shipUniqueName
                        s += "\n";
                    } else if(d[1]) s += d[1]+"\n"; //dataKey at last resort
                }
            } else s += "empty\n"
        }
        n = " ("+n+" of "+m+" ships)";
        //s = s.substr( 0, s.length - 2 ); //cut last comma
    } else {
        n = "";
        s = "You have no ships on Escort Deck.";
    }
    if( ps.equipmentStatus("EQ_ESCORTDECKXL") == "EQUIPMENT_OK" ) n = " XL"+n;
    ps.dockedStation.setInterface(this.name,{
        title:"Escort Deck"+n,
        category:"Ship Systems",
        summary:s,
        callback:this.$EscortDeck_InterfaceCallBack.bind(this)
    });
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_InterfaceCallBack = function _escortdeck_InterfaceCallBack(lastchoice) {
    var that = _escortdeck_InterfaceCallBack;
    var w = (that.w = that.w || worldScripts.escortdeck);
    var hh = (that.hh = that.hh || worldScripts.HyperspaceHangar);
    var ps = player.ship; if( !ps || !ps.isValid ) return;
    player.ship.hudHidden = true;//to shift down the menu
    var d = w.$EscortDeckShipData;
    var dkm = null;
    if( !w.$EscortDeckI ) w.$EscortDeckSpinModel = false; //rotate only in the subpage
    var curChoices = {};
    var exitscr = "GUI_SCREEN_INTERFACES";
    var ex = "99_EXIT";
    var ic = ex;
    if( lastchoice === "51_SPIN" ) ic = lastchoice;
    var mp = 0;
    var msg = "";
    var oc = "orangeColor";
    var j = 0;
    var getHH = false;
    var len = w.$EscortDeckPadPos.length;
    if( w.$EscortDeckSelectedPad < 0 ) w.$EscortDeckSelectedPad = 0;
    if( ps.equipmentStatus("EQ_ESCORTDECK") !== "EQUIPMENT_OK"
        && ps.equipmentStatus("EQ_ESCORTDECKXL") !== "EQUIPMENT_OK" ) {
        msg = "You have no Escort Deck";
        if( ps.mass > w.$EscortDeckMinMass ) msg += ", buy it in the Equipment shop.";
        else msg += ", moreover your ship is too small to hold it."
        exitscr = "GUI_SCREEN_EQUIP_SHIP";
    } else {
        var pad = w.$EscortDeckSelectedPad;
        if( w.$EscortDeckI ) { //show submenu when an escort ship is selected
            if( d[pad] && d[pad][1] ) {
//                curChoices["31_EQUIP"] = {text:"Equip", color:oc };
                curChoices["41_RENAME"] = {text:"Rename", color:oc };
            
                if( w.$EscortDeckSpinModel )
                    curChoices["51_SPIN"] = {text:"Stop rotation", color:oc };
                else curChoices["51_SPIN"] = {text:"Rotate", color:oc };
                if (d[pad][9] < 7) {
                    curChoices["61_REFUEL"] = {text:"Refuel", color:oc };
                }
                if (hh) {
                    curChoices["71_HH"] = {text:"Put into Hyperspace Hangar", color:oc };
                } else curChoices["72_NHH"] = {text:"No Hyperspace Hangar installed",
                    unselectable:true };
                let salvage_price = w.$EscortDeck_SalvagePadPrice(pad);
                curChoices["81_SALVAGE"] = {text:"Salvage"+(salvage_price ? " and get about "
                    +Math.round(salvage_price/1000)*1000+" credits" : ""),
                    color:"redColor" };
            } else if (hh) {
                var fitinto=false;
                var sID = system.ID;
                for( var k in hh._shipsStored ) {
                    var sh = JSON.parse(hh._shipsStored[k]); //ship in hangar
                    if (sh[0][1] === sID) {
                        if( sh && !w.$EscortDeck_TooLarge2b(ps, w, pad, sh)) {
                            fitinto=true;
                            var n = sh[0];
                            if( n ) n = n[3]; else n = sh[1]; //show dataKey if no name
/*                            if( sh[14] ) {
                                n += " ("+Math.floor(sh[14].mass/1000)+"t, "
                                    +Math.round(sh[14].x)+"x"
                                    +Math.round(sh[14].y)+"x"
                                    +Math.round(sh[14].z)+"m)";
                            }*/
                            curChoices["22_"+k] = "Get "+n+" from Hyperspace Hangar into Deck "+(pad+1);
                        }
                    } else { log(this.name, "HyperSpaceHangar ship "+sh[0][3]+" is in system "+sh[0][4]); }
                }
                if( !fitinto ) {
                    curChoices["21_PHH"] = {
                        text:"No ship in Hyperspace Hangar which fit into Deck "
                            +(pad+1),
                        unselectable:true };
                } else {
                    getHH = true;
                }
            } else curChoices["20_NHH"] = { text:"No Hyperspace Hangar installed", unselectable:true };
//            curChoices["71_EMPTY_LINE"] = {text:""};
            
        } else { //in the first screen show the list of pads with escort names
            for( pad = 0; pad < len && j++ < 20; pad++ ) {
                var co = "orangeColor";
                var s = d[pad];
                var tx = "Deck "+(pad+1)+" ";
                var x = 0, y = 0, z = 0, maxmass = 0;
                var si = w.$EscortDeckPadSize;
                if( si && si[0] ) {
                    var e = null;
                    var len = si.length-1;
                    if( pad < 0 || pad > len ) e = si[len]; //the last line contain the maximums
                    else e = si[pad];
                    if( e && e[0] ) {
                        x = e[0];
                        y = e[1];
                        z = e[2];
                        maxmass = e[3];
                    }
                }
                tx += "("+Math.floor(maxmass/1000)+"t, "+x+"x"+y+"x"+z+"m): ";
                if( s && s[0] && s[0][3] ) {
                    tx += s[0][3].substring(0,38);// + " Fuel: "+Math.round(s[9]*10)/10+"ly";
//                    msg += w.$EscortDeck_MFDAlign(tx, s.AIState)+"\n";
                } else {
                    tx += "Empty";
                    co = "grayColor";
                }
                tx += "\n";
                var cc = "01_" + ( pad < 10 ? "0" : "" ) + pad;
                curChoices[cc] = {text:tx, color:co, alignment:"LEFT"};
                if( pad == w.$EscortDeckSelectedPad ) {
                    ic = cc; //initialChoicesKey
                }
            }
            pad = w.$EscortDeckSelectedPad;
        }
        msg = "Deck "+(pad+1)+": ";
        if( d[pad] && d[pad][1] ) {
            dkm = "["+d[pad][1]+"]";//dataKey
            mp = d[pad][13];//personality
            msg += w.$EscortDeck_InterfaceShipData(d[pad],
                w.$EscortDeckSpinModel ); //no eqs when spinning
        } else msg += "Empty";
    }
    if(w.$EscortDeckI) curChoices[ex] = {text:"Back", color:oc};
    else curChoices[ex] = {text:"Exit", color:oc, alignment:"LEFT"};
    
    var xl = "";
    if( ps.equipmentStatus("EQ_ESCORTDECKXL") == "EQUIPMENT_OK" ) xl = " XL";
    var opts = {
        screenID: "escortdeck",
        title: "Escort Deck"+xl,
        background: {name:"escortdeck_bg.png", height:512},//532//546
        allowInterrupt: false,
        exitScreen: exitscr,
        choices: curChoices,
        initialChoicesKey: ic,
        message: msg,
        model: dkm,
        modelPersonality: mp,
        spinModel: w.$EscortDeckSpinModel
    };
    if (getHH) {
        worldScripts["Paginated Screen"].$runScreen(opts, w.$EscortDeck_InterfaceChoice, w, true);
    } else {
        mission.runScreen(opts, w.$EscortDeck_InterfaceChoice, w);
    }
    
    // if Gallery or Towbar has been loaded and used, remove the frame callback
    var g = w.$EscortDeckGWS;
    if (g && isValidFrameCallback(g.$GalleryFCB)) removeFrameCallback(g.$GalleryFCB);
    var r = w.$EscortDeckToWS;
    if( r && isValidFrameCallback( r.$TowbarFCB2 ) ) removeFrameCallback( r.$TowbarFCB2 );
    
    var m = mission.displayModel;
    if( m && !w.$EscortDeckSpinModel ) {
        m.orientation = m.orientation.rotateZ(-Math.PI/2).rotateX(-Math.PI/4).rotateY(Math.PI/8);
        m.position = Vector3D(m.position.x, m.position.y, 150);
    }
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_InterfaceChoice = function _escortdeck_InterfaceChoice(choice) {
    var that = _escortdeck_InterfaceChoice;
    var w = (that.w = that.w || worldScripts.escortdeck);
//    log(this.name, choice+" "+choice.indexOf("01_")+" "+choice.substr(3));
    if (choice === "99_EXIT") {
        if( !w.$EscortDeckI ) { //selected on the first page
            w.$EscortDeckSelectedPad = -1; //clear pad selection
            player.ship.hudHidden = false;
            return;
        } else w.$EscortDeckI = false; //back to the first page
    }
    if (choice.indexOf("01_") === 0) {
        w.$EscortDeckSelectedPad = 1*choice.substr(3);
        w.$EscortDeckI = true; //go to the subpage
    } else if (choice.indexOf("22_") === 0) {  //get ship from HyperHangar
        var k = choice.substr(3);
        w.$EscortDeck_HHGet(k, w.$EscortDeckSelectedPad, w);
        w.$EscortDeckI = false; //back to the first page
    } else if (choice === "41_RENAME") {
        var pad = w.$EscortDeckSelectedPad;
        var d = w.$EscortDeckShipData[pad];
        if (d && d[1] ) {
            var msg = "Deck "+(pad+1)+": ";
            var dkm = "["+d[1]+"]";//dataKey
            var mp = d[13];//personality
            msg += w.$EscortDeck_InterfaceShipData(d, true)+
                "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nType the new name of "
                +d[0][3]+":";
            mission.runScreen({
                screenID: "escortdeck",
                title: "Escort Deck",
                background: {name:"escortdeck_bg.png", height:512},
                allowInterrupt: false,
                message: msg,
                model: dkm,
                modelPersonality: mp,
                spinModel: w.$EscortDeckSpinModel,
                textEntry: true
            },function(text) {
                if (text && text.length > 0) {
                    if( d && d[0] ) {
                        w.$EscortDeckShipData[pad][0][8]=text;//shipUniqueName
                        var t = "";
                        if( d[0][7] ) t = d[0][7]+": ";
                        w.$EscortDeckShipData[pad][0][3] = t + text;
                    }
                }
                w.$EscortDeckI = false; //back to the first page
                w.$EscortDeck_InterfaceCallBack(choice); //display the menu again
            });
        }
        return;
    } else if (choice === "51_SPIN") {
        if( w.$EscortDeckSpinModel ) w.$EscortDeckSpinModel = false;
        else w.$EscortDeckSpinModel = true;
    } else if (choice === "61_REFUEL") {
        this.$EscortDeck_RefuelEscort(w.$EscortDeckShipData[w.$EscortDeckSelectedPad]);
    } else if (choice === "71_HH") { //put ship into HyperHangar
        w.$EscortDeck_HHStoreShipData(w.$EscortDeckShipData[w.$EscortDeckSelectedPad]);
        player.consoleMessage(w.$EscortDeckShipData[w.$EscortDeckSelectedPad][0][3]+" from deck "+(w.$EscortDeckSelectedPad+1)+" stored into Hyperspace Hangar", 10);
        w.$EscortDeckShipData[w.$EscortDeckSelectedPad] = [];
        w.$EscortDeckI = false; //back to the first page
    } else if (choice === "81_SALVAGE") {
        //are you sure?
        w.$EscortDeck_Salvage(w.$EscortDeckSelectedPad, player.ship);
        w.$EscortDeckI = false; //back to the first page
    }
    w.$EscortDeck_InterfaceCallBack(choice); //display the menu again
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_InterfaceShipData = function _escortdeck_InterfaceShipData(d, noeq, all) {
    if (!d || !d[1]) return("No data");
    var psd = d[0][3]+"\n";//ship.displayName+
    var equip="";
    var s="";
    s=d[3]; if(s) s=EquipmentInfo.infoForKey(s);//ship.forwardWeapon;
    if (s!=null && s.equipmentKey !== "EQ_WEAPON_NONE") equip+="Forward "+s.name;
    s=d[2]; if(s) s=EquipmentInfo.infoForKey(s)//ship.aftWeapon;
    if (s!=null && s.equipmentKey !== "EQ_WEAPON_NONE") {
        if (equip.length > 0) equip+=", "; //Anaconda has aft weapon only
        equip+="Aft "+s.name;
    }
    s=d[4]; if(s) s=EquipmentInfo.infoForKey(s)//ship.portWeapon;
    if (s!=null && s.equipmentKey !== "EQ_WEAPON_NONE") equip+=", Port "+s.name;
    s=d[5]; if(s) s=EquipmentInfo.infoForKey(s)//ship.starboardWeapon;
    if (s!=null && s.equipmentKey !== "EQ_WEAPON_NONE") equip+=", Starboard "+s.name;
    if (equip.length > 0) equip+=""; //no newline, save space
    else equip+="No Laser";
    var eqs = "";
    var savedEQ = d[7], counter;
    if (!noeq && savedEQ) for (counter = 0; counter < savedEQ.length; counter++) {
        if (savedEQ[counter][1] !== "EQUIPMENT_DAMAGED" && savedEQ[counter][0].indexOf("EQ_NUMERIC3_") === -1 && savedEQ[counter][0].indexOf("EQ_ADDHUD_") === -1 && savedEQ[counter][0].indexOf("EQ_XENONHUD_") === -1) {
            s = EquipmentInfo.infoForKey(savedEQ[counter][0]);
            if (s) {
                var n = s.name;
                if (n.length > 0) eqs += n + ", ";
            }
        }
    }
    eqs = eqs.substr(0, eqs.length - 2); //cut last comma
    if (eqs.length > 0) equip+=", "+eqs+"\n";
    eqs = "";
    if (!noeq && savedEQ) for (counter = 0; counter < savedEQ.length; counter++) {
        if (savedEQ[counter][1] === "EQUIPMENT_DAMAGED" && savedEQ[counter][0].indexOf("EQ_NUMERIC3_") === -1 && savedEQ[counter][0].indexOf("EQ_ADDHUD_") === -1 && savedEQ[counter][0].indexOf("EQ_XENONHUD_") === -1) {
            s = EquipmentInfo.infoForKey(savedEQ[counter][0]);
            if (s) {
                var n = s.name;
                if (n.length > 0) eqs += n + ", ";
            }
        }
    }
    eqs = eqs.substr( 0, eqs.length - 2 ); //cut last comma
    if (eqs.length > 0) equip+="\nDamaged: "+eqs+"\n";
    var desc = "";
    var speed = d[0][19];
    psd += "Fuel: "+Math.round(d[9]*10)/10+" ly  ";
    if (speed > 0) psd += "Speed: "+speed+" mLS  ";
    if (d[0][17] > 0) psd += "Thrust: "+d[0][17]+"  ";
    if (d[0][15] > 0) psd += "Pitch: "+Math.round(d[0][15]*100)/100+"  ";
    if (d[0][16] > 0) psd += "Roll: "+Math.round(d[0][16]*100)/100+"  ";
    if (d[0][14] > 0) psd += "Yaw: "+Math.round(d[0][14]*100)/100+"  ";
    var en = "";
    var r = "";
    if (all && d[0][20] > 0) en = "Energy+Shields: "+d[0][20]+"  ";
    else {
        if (d[0][20] > 0) {
            var eb = Math.max(1, Math.floor(d[0][20]/64));
            en = "Energy+Shields";
            en += ": "+eb+"  ";
        }
    }
    var er = d[0][21];
    if (er > 0) {
        var ers = "";
        if (er < 2.5) ers = "Poor";
        else if (er < 3.5) ers = "Medium";
        else if (er < 4.5) ers = "Good";
        else if (er < 10) ers = "Excellent";
        else ers = "Extreme";
        if (all) ers += " ("+er+")";
        r = "Recharge: "+ers+"  ";
    }
    if (d[0][6] > 0) {
        var v = Math.round(d[0][6]/1333)*1000; //trade-in value is 75% of ship price
        psd += "Value: "+formatCredits(v, false, true)+"  ";
    }
    psd += "\n"+en+r;
//{usable:u, mass:ship.mass, x:b.x, y:b.y, z:b.z});//storedShipArray[14]
    if (d[14]) psd += "Mass: "+Math.max(Math.round(d[14].mass/100)/10, 1)+ //min.1t
        "t  Size: "+Math.round(d[14].x)+"*"+
        Math.round(d[14].y)+"*"+Math.round(d[14].z)+"m  ";
//        "m Radius: "+Math.round(ship.collisionRadius)+"m"+//" Sphere: "+sp+"m^2"+
    psd += "\n"+equip;
    psd = desc + psd;
    return(psd);
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_isFreePadFor = function _escortdeck_isFreePadFor(eqKey, owner, context, w, mini) { 
    //called from escortship equipment conditions
    //should check TooLarge but no ship nor mass,etc. data available
    //due to shouldn't spawn ships in equipment conditions
    //so must provide in the prefilled $EscortDeckEqs array for TooLarge3
    
    var len = w.$EscortDeckPadPos.length;
    for(var i = 0; i < len; i++ ) {
        if( !w.$EscortDeckShipData[i] && //find the first empty pad where fit
            ( !w.$EscortDeck_isMiniPad(i, owner, w) || mini ) ) {
            if( !w.$EscortDeckEqs || !w.$EscortDeckEqs[eqKey] ||
                !w.$EscortDeck_TooLarge3( owner, w,
                    w.$EscortDeckEqs[eqKey][3],
                    w.$EscortDeckEqs[eqKey][0],
                    w.$EscortDeckEqs[eqKey][1],
                    w.$EscortDeckEqs[eqKey][2], i ) )
                return true;
        }
    }
    return false;
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_isLarge = function _escortdeck_isLarge(ship, w) { //check if the ship is over the large border
    var m = ship.mass; //revert HardShips' double mass caused by density=2
    if( ship.scriptInfo && ship.scriptInfo.hardarmour > 1 ) m = m / 2;
    if( m >= w.$EscortDeckLargeMass ) return true;
    return false;
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_isMini = function _escortdeck_isMini(ship) { //check if the ship is like an Adder or smaller
    if( !ship || !ship.isValid ) return false;
    var b = ship.boundingBox;
    if( !b || ship.mass > this.$EscortDeckPad.mini[3]
        || b.x > this.$EscortDeckPad.mini[0]
        || b.y > this.$EscortDeckPad.mini[1]
        || b.z > this.$EscortDeckPad.mini[2] ) return false;
    return true;
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_isMiniPad = function _escortdeck_isMiniPad(pad, owner, w) { //check if this pad is for mini ships only
    if( w.$EscortDeckCWS && owner.dataKey.indexOf("carrier") > -1 ) {
        if( w.$EscortDeckXL ) {
            if( pad != 8 && pad != 9 ) return false;
        } else if( pad != 7 && pad != 8 ) return false;
    } else if( w.$EscortDeckXL || pad != 4 && pad != 5 ) return false;
    return true;
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_isPadFree = function _escortdeck_isPadFree(pad, owner, w) { //is this pad free for this ship?
    var s = null; //or the assigned ship is not valid or not in scanner range
    if( w && w.$EscortDeckShip ) s = w.$EscortDeckShip[pad];
    if( w && pad < w.$EscortDeckPadPos.length && ( !s || !s.isValid 
//        || owner && owner.isValid && owner.position.distanceTo(s.position) > owner.scannerRange 
        || pad == w.$EscortDeckSelectedPad ) ) { //pad is selected to replace ship?
        return(true);
    }
    return(false);
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_MakeShipData = function _escortdeck_MakeShipData(ship) {
    var that = _escortdeck_MakeShipData;
    var ssh = (that.ssh = that.ssh ||  worldScripts["Ship_Storage_Helper.js"]);
    if (!ship || !ship.isValid) return null;
    var storedShipArray = [];
    var entry_time = Date.now();
    if (ssh && ssh.storeCurrentShipToArray) {
        storedShipArray = ssh.storeCurrentShipToArray(ship);
    } else {
        var stationAttributes;
        if (ship.dockedStation) {
            stationAttributes = [ship.dockedStation.primaryRole,ship.dockedStation.dataKey, ship.dockedStation.position];
        } else {
            stationAttributes = [null,null,null];
        }
        storedShipArray.push([galaxyNumber, system.ID, clock.seconds, ship.displayName,
                system.name, stationAttributes, ship.price,
                ship.shipClassName, ship.shipUniqueName, ship.AIScriptWakeTime, ship.beaconLabel,
                ship.destinationSystem, ship.exhaustEmissiveColor, ship.homeSystem,
                ship.maxYaw, ship.maxPitch, ship.maxRoll, ship.maxThrust, ship.thrust,
                ship.maxSpeed, ship.maxEnergy, ship.energyRechargeRate, ship.cargoSpaceCapacity,
                ship.injectorBurnRate, ship.injectorSpeedFactor,
                ship.scanDescription, ship.hyperspaceSpinTime,
                ship.scannerHostileDisplayColor1, ship.scannerHostileDisplayColor2,
                ship.forwardShield, ship.maxForwardShield, ship.forwardShieldRechargeRate,
                ship.aftShield, ship.maxAftShield, ship.aftShieldRechargeRate,
                ship.passengerCapacity]); //storedShipArray[0]
        storedShipArray.push(ship.dataKey); // storedShipArray[1]
        if (!ship.aftWeapon) { // storedShipArray[2]
            storedShipArray.push(ship.aftWeapon);
        } else {
            storedShipArray.push(ship.aftWeapon.equipmentKey);
        } 
        if (!ship.forwardWeapon) { // storedShipArray[3]
            storedShipArray.push(ship.forwardWeapon);
        } else {
            storedShipArray.push(ship.forwardWeapon.equipmentKey);
        } 
        if (!ship.portWeapon) { // storedShipArray[4]
            storedShipArray.push(ship.portWeapon);
        } else {
            storedShipArray.push(ship.portWeapon.equipmentKey);
        }
        if (!ship.starboardWeapon) { // storedShipArray[5]
            storedShipArray.push(ship.starboardWeapon);
        } else {
            storedShipArray.push(ship.starboardWeapon.equipmentKey);
        }
        var missiles = [];
        if( ship.missileCapacity > 0 ) { //bugfix of "Tried to init array with nil object" in Oolite 1.81
            missiles = ship.missiles;
            var counter;
            for(counter = 0;counter<missiles.length;counter++)
                missiles.splice(counter,1,missiles[counter].equipmentKey);
        }
        storedShipArray.push(missiles); // storedShipArray[6]
        var equipment = ship.equipment;
        var tempArray;
        for(counter = 0;counter<equipment.length;counter++) {
            tempArray = [equipment[counter].equipmentKey, ship.equipmentStatus(equipment[counter])];
            equipment.splice(counter,1,tempArray);
        }
        storedShipArray.push(equipment); // storedShipArray[7]
        storedShipArray.push(ship.cargoList); // storedShipArray[8]
        storedShipArray.push(ship.fuel); //storedShipArray[9]
        storedShipArray.push(new Array); //passengers is player only, storedShipArray[10]
        var subEnts = new Array;
        if (ship.subEntityCapacity > 0 && ship.subEntityCapacity !== ship.subEntities.length) {
            // only store subEnts if at less than capacity
            for (counter = 0;counter < ship.subEntities.length; counter++) {
                subEnts.push([ship.subEntities[counter].dataKey,
                        ship.subEntities[counter].position.x,
                        ship.subEntities[counter].position.y,
                        ship.subEntities[counter].position.z]);
            }
        }
        storedShipArray.push(subEnts);//storedShipArray[11]
        if( ship == player.ship ) {
            storedShipArray.push(ship.serviceLevel);//storedShipArray[12]
            ship.script.$SSH_serviceLevel = ship.serviceLevel; //save for Carriers OXP
        } else if( ship.script ) storedShipArray.push(ship.script.$SSH_serviceLevel);
    
        storedShipArray.push(ship.entityPersonality);//storedShipArray[13]
    
        var u = 0; //escortdeck specific data
        if( ship.script && ship.script.$EscortDeckUsable > 0 )
            u = ship.script.$EscortDeckUsable;
        var b = ship.boundingBox; //mass for TooLarge and ship price, sizes needed in playerboughtnewship
        storedShipArray.push({usable:u, mass:ship.mass, x:b.x, y:b.y, z:b.z});//storedShipArray[14]
    }
    if (this.$debug) log(this.name, "Serializing ship "+ship.displayName+" took "+(Date.now()-entry_time).toFixed(0)+"ms");
    return(storedShipArray);
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_MassFactor = function _escortdeck_MassFactor(ship, w) { //compute thrust reduction due to extra mass in deck
    var m = 0; //total mass of ships on deck
    for(var i = 0; i < w.$EscortDeckPadPos.length; i++ ) {
        var d = w.$EscortDeckShipData[i]; //inside stations must use the saved data array
        var s = w.$EscortDeckShip[i];
        if( ( d || s ) && w.$EscortDeckShipPos[i] ) { //there is a ship in this deck
            if( d && d[14] && d[14].mass > 0 ) m += d[14].mass;
            else if( s && s.mass > 0 ) m += s.mass;
            else m += 50000; //guess
        }
    }
    //in proportion with the original mass (max. 1)
    return( ship.mass / ( ship.mass + m / 3 ) );
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_MFD = function _escortdeck_MFD(w) { //update MFD
    var ps = player.ship; if (!ps || !ps.isValid) return;
    var d = w.$EscortDeckShipData;
    var mfd = "";
    var j = 0;
    var len = w.$EscortDeckPadPos.length;
    if (w.$EscortDeckDockingTunnel) ; //show an empty MFD while the player travel in the tunnel
    else if (ps.equipmentStatus("EQ_ESCORTDECK") != "EQUIPMENT_OK"
        && ps.equipmentStatus("EQ_ESCORTDECKXL") != "EQUIPMENT_OK")
        mfd = "No Escort Deck\n";
    else for ( var pad = 0; pad < len && j++ < 10; pad++) {
        var s = w.$EscortDeckShip[pad];
        var state = "";
        if (w.$EscortDeckLocked[pad]) state = "LOCKED";
        var l = (pad+1)+": ";
        if (pad == w.$EscortDeckSelectedPad) l += "-> "; //mark selected pad
        if (s && s.isValid) {
            if (state.length == 0) state = s.AIState; //only if not locked
            if (s.script && s.script.$EscortDeckUsable > 0) { //show for usables only
                //l += s.shipUniqueName.substring(0,18)+" E"+Math.round(s.energy/64);
                l += (s.shipUniqueName ? s.shipUniqueName.substring(0,18) : s.shipClassName.substr(0,18))+" E"+s.energy.toFixed(0);
                    //+"/"+Math.round(s.maxEnergy/64);
                    //first 18 char to fit into but show Sidewinder Special
                //l += " E"+Math.round(s.energy);
//                var r = "";
//                var t = Math.round(s.maxEnergy/64); //total letters
//                for(var k = 0; k < t; k++ )
//                    if( k < Math.round(s.energy/64) ) r += "E";
//                    else r += "-"; //empty bank
//                l += " "+r;
                //if( s.equipmentStatus("EQ_FUEL_INJECTION") == "EQUIPMENT_OK" )
                l += " F"+Math.ceil(s.fuel-0.01);
                if (s.forwardShield)
                    l += " S"+Math.ceil(s.forwardShield/64)+Math.ceil(s.aftShield/64);
                if (s.forwardShield < 64 || s.aftShield < 64 )
                    log(this.name, "Escort "+s.displayName+" has low shields: "+s.forwardShield.toFixed(0)+"/"+s.aftShield.toFixed(0));
                if (!w.$EscortDeckShipPos[pad]) //not in deck
                    l += " "+Math.floor(ps.position.distanceTo(s.position)/1000)+"km "+w.$EscortDeck_MFDFrom(s.position);
            } else l += s.displayName+" ("+Math.round(s.mass/1000)+"t)";
            mfd += this.$EscortDeck_MFDAlign(l, state)+"\n";
        } else {
            var x = 0, y = 0, z = 0, maxmass = 0;
            var s = w.$EscortDeckPadSize;
            if (s && s[0]) {
                var e = null;
                var len = s.length-1;
                if (pad < 0 || pad > len) e = s[len]; //the last line contain the maximums
                else e = s[pad];
                if (e && e[0]) {
                    x = e[0];
                    y = e[1];
                    z = e[2];
                    maxmass = e[3];
                }
            }
            l += "("+Math.floor(maxmass/1000)+"t, "+x+"x"+y+"x"+z+"m)";
            if (state.length > 0) mfd += this.$EscortDeck_MFDAlign(l, state)+"\n";
            else mfd += l+"\n";
        }
    }
    var t = this.$EscortDeckToWS; //Towbar
    if (t && j < 10 && !w.$EscortDeckDockingTunnel) {
        var l = "T: ";
        if (j == w.$EscortDeckSelectedPad) l += "-> "; //mark selected pad
        var s = t.$TowbarShip;
        if (s) {
            l += s.displayName+" ("+Math.round(s.mass/1000)+"t)";
            mfd += this.$EscortDeck_MFDAlign(l, (s.script && s.script.$EscortDeckUsable && s.script.$EscortDeckUsable > 0 ? "Usable" : "Salvage"))+"\n";
        } else mfd += l+"("+Math.floor(ps.mass*1.6/1000)+"t)\n"; //max. mass on towbar
    }
        
    if (ps.setMultiFunctionText) ps.setMultiFunctionText(w.name, mfd, false);
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_MFDAlign = function _escortdeck_MFDAlign(left, right) { //insert as many spaces between as fill the line
    var width = 14.2;
    var space = " ";
    left += space;
    var t = left + right;
    while( defaultFont.measureString(t) < width ) {
        left += space;
        t = left + right;
    }
    //fine tune with hair spaces - from spara's marketObserver
    var hairSpace = String.fromCharCode(31);
    while ( defaultFont.measureString(t + hairSpace) < width ) {
        left += hairSpace;
        t = left + right;
    }
    return(t);
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_MFDFrom = function _escortdeck_MFDFrom(whomposition) { //direction mark
    var ship = player.ship;
    if( !ship || !ship.isValid || !whomposition || !whomposition.dot ) return ("");
    var v = ship.position.subtract(whomposition);
    var fw = ship.vectorForward.angleTo(v);
    var ri = ship.vectorRight.angleTo(v);
    var up = ship.vectorUp.angleTo(v);
    var s = ""; // refine if out of front 1 degree cone
    if( ri < 1.56 ) s += "<";
    if( up > 1.58 ) s += "^";
    else if( up < 1.56 ) s += "v";
    if( ri > 1.58 ) s += ">";
//    if( s.length > 0 || fw < 1 ) //do not exclude the aft 1 degree cone
//        s = Math.round( 180 * ( 1 - fw / Math.PI ) ) + "° " + s;
    return( s );
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_PutShip = function _escortdeck_PutShip(deckinit, ship, pad, owner, msg, land, xch ) { //put ship into the pad
    var that = _escortdeck_PutShip;
    var w = (that.w = that.w || worldScripts.escortdeck);
    var r = w.$EscortDeck_TooLarge2(ship, owner, w, pad);
    if (r) return (r); //too large
    if (ship && ship.isValid && owner && owner.isValid
        && (land || w.$EscortDeck_isPadFree(pad, owner, w))) { //landing back or new ship
        if (!w.$EscortDeckShip) w.$EscortDeckShip = [];
        var old = w.$EscortDeckShip[pad];
        var sp = w.$EscortDeckShip.indexOf(ship); //ship pad if already owned
        if (old && old.isValid && !xch) { //prevent recursion during exchange with old ship
            if (sp > -1 && sp != pad) { //put an owned ship to other pad
                //exchange with the old ship if old fit into the another pad
                //if( w.$EscortDeck_PutShip(old, sp, owner, w, msg, true, true ) )
                w.$EscortDeck_Release(owner, pad, w, //relock old if fit
                              w.$EscortDeckLandingDist-100, msg);
                w.$EscortDeckSelectedPad = -1; //prevent lock to the new pad
                owner.target = old; //for relock
            } else if (ship != old)
                w.$EscortDeck_Release(owner, pad, w, false, msg); //replace old ship
        }
        if (sp > -1) { //remove from the previous pad
            var d = [], s = [];
            for(var i = 0; i < w.$EscortDeckPadPos.length; i++ ) {
                if (i == sp) {
                    s[i] = null;
                    d[i] = null;
                } else {
                    s[i] = w.$EscortDeckShip[i];
                    d[i] = w.$EscortDeckShipData[i];
                }
            }
            w.$EscortDeckShipData = d;
            w.$EscortDeckShip = s;
        }
        w.$EscortDeckShip[pad] = ship;
        if (!deckinit) {
            w.$EscortDeckShipData[pad] = w.$EscortDeck_MakeShipData(ship);
            if (w.$debug) log(w.name, (pad+1)+". "+JSON.stringify(w.$EscortDeckShipData[pad]));
        }
        var usable = 0;
        if (ship.script && ship.script.$EscortDeckUsable > 0)
            usable = ship.script.$EscortDeckUsable;
        
        if (usable > 0 && ship.isDerelict) { //removing derelict flag need respawn
            var ship2 = w.$EscortDeck_SpawnShip(pad, w, owner);
            if (ship2 && ship2.isValid) {
                ship.remove(true);
                w.$EscortDeckShip[pad] = ship = ship2;
            } //else fallback to the derelict ship
        }
        if (ship.AI != "EscortDeck_AI.plist") ship.switchAI("EscortDeck_AI.plist");
//        var js = "escortdeck_escort";
//        if( !ship.script || ship.script.name != js ) {
//            var sl = 0;  //must set usable and sl again after setScript
//            if( ship.script ) sl = ship.script.$SSH_serviceLevel;
//            ship.setScript(js+".js");
//            if( ship.script ) {
//                ship.script.$EscortDeckUsable = usable;
//                ship.script.$SSH_serviceLevel = sl;
//            }
//        }
        ship.script.$EscortDeckOnDeck = true;
        if (usable > 0) {
            log(this.name, "Adding escort "+ship.displayName+" with accuracy "+ship.accuracy+" to pad "+pad+", escortdeckUsable:"+ship.script.$EscortDeckUsable);
            ship.AIState = "DECK";
            if (!owner.group) { //escort group for performEscort AI command
                owner.group = new ShipGroup();
                owner.group.addShip(owner);
                //from Oolite 1.84 the player can not be the leader of a group
                if (owner != player.ship) owner.group.leader = owner;
            }
            if (owner && owner.group.leader != owner && owner != player.ship) owner.group.leader = owner;
            if (!owner.group.containsShip(ship)) owner.group.addShip(ship);
        } else ship.AIState = "SALVAGE";
        ship.beaconCode = "E"+pad+" "+ship.name;
        ship.bounty = 0;
        ship.scanClass = "CLASS_CARGO"; //prevent masslock
        ship.scannerDisplayColor1 = [0,0,0]; //black
// 
        var carrier = false;
        if (w.$EscortDeckCWS && owner.dataKey.indexOf("carrier") > -1) carrier = true;
        var p = w.$EscortDeckPadPos[pad];
        if (!p) {
            w.$EscortDeck_SetPads(owner, w);
            p = w.$EscortDeckPadPos[pad];
            if (!p) p = [0,70*(pad+1),0];
        }
        var sp = Vector3D(p[0], p[1], p[2]);
        var x = ship.boundingBox.x * 0.5;
        var y = ship.boundingBox.y * 0.5;
        var z = ship.boundingBox.z * 0.5;
        if (w.$EscortDeckXL && (!carrier || pad > 3 && pad < 8)) {
            var left = -0.5; //left side of the xl deck
            if( Math.round(pad/2) != pad/2 ) left = 0.5; //right side
            sp = sp.add(Vector3D(left * 2 * z, 0, 0));
        } else {
            var v = w.$EscortDeckPadVec[pad];
            if (!v) v = [0,1,0];//up
            var vx = v[0] * x;
            if (!carrier && y > 12) y = 12 - ( y - 12 ); //sunk if taller than 24m
            var vy = v[1] * y;
            var vz = v[2] * z;
            sp = sp.add(Vector3D(vx, vy, vz));
        }
        if (owner.addCollisionException) owner.addCollisionException(ship); //from v1.81
        else if(carrier) {  //must move up to avoid kill by Injector speed
            var a = y + 25;
            if (w.$EscortDeck_isMiniPad(pad, owner, w)) a = 2 * x + 30;
            sp = sp.add(Vector3D(0, a, 0));
        }
        if (sp.magnitude() > 500) ship.position = sp; //prevent terminated by Witchpoint Beacon
        if (owner != player.ship && ship.script) {
            if (!ship.script.$EscortDeckShipPos) 
                ship.script.$EscortDeckShipPos = [];
            ship.script.$EscortDeckShipPos[pad] = sp; //NPC
        } else w.$EscortDeckShipPos[pad] = sp; //player ship
        //ship.orientation = owner.orientation;
        ship.velocity = owner.velocity;
        
        //slower steering and acceleration in proportion with the mass on deck
        w.$EscortDeck_SetMaxs(owner, w);
        
        if (ship.script) {
            ship.script.$Towbar_TimedBombCounter = -1; //disarm timed bomb
            if (ship.script.$TowbarUsableShip)
                ship.script.$TowbarUsableShip = null;
        }
        
        if (owner == player.ship) {
            if (msg) {
                var m = ship.displayName+" locked on deck "+(pad+1);
                player.consoleMessage(m, 10);
                if (w.$debug) log(w.name, m); //debug
                w.$EscortDeckSound.play();
            }
        }
        
        return false;
    }
    return("no room");
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_Release = function _escortdeck_Release(owner, pad, w, dist, nomsg) {
    if( !dist ) var dist = w.$EscortDeckLandingDist + 500; //drop distance over lock range
    var s = w.$EscortDeckShip[pad];
    if( s && s.isValid ) {
//        if (s.script.$EscortDeckUsable < 0) s.script.$EscortDeckUsable = 0;
        //move the old ship backward, over the relock range if on deck
        if( w.$EscortDeckShipPos[pad] ) {
            var sp = s.position.add(player.ship.heading.multiply(-dist));
            if( sp.magnitude() > 500 ) s.position = sp; //prevent terminated by Witchpoint Beacon
            w.$EscortDeckSound.play();
        }
        if( owner.removeCollisionException ) //from v1.81
            owner.removeCollisionException(s);
        w.$EscortDeckShipData[pad] = null;
        w.$EscortDeckShip[pad] = null;
        w.$EscortDeckShipPos[pad] = null;
        s.script.$EscortDeckOnDeck = false;
        if(!nomsg && s.dataKey.indexOf("carrier-player") == -1 ) //no msg for carrier
            player.consoleMessage( s.displayName+" released from deck "+(pad+1), 10 );
    }
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_RestoreShipData = function _escortdeck_RestoreShipData(s, d, w, owner) {
    var that = _escortdeck_RestoreShipData;
    var ssh = (that.ssh = that.ssh ||  worldScripts["Ship_Storage_Helper.js"]);
    if (s && s.isValid && d && d[0]) {
        var entry_time = Date.now();
        if (!s.script) s.setScript("escortdeck_escort.js");
        if (ssh && ssh.restoreStoredShipFromArray) {
            ssh.restoreStoredShipFromArray(d, s);
        } else {
            if( d[0][7] ) s.shipClassName = d[0][7];
            if( d[0][8] ) s.shipUniqueName = d[0][8];
            if( s != player.ship ) s.displayName = d[0][3];
            if(d[2]) s.aftWeapon = EquipmentInfo.infoForKey(d[2]);
            if(d[3]) s.forwardWeapon = EquipmentInfo.infoForKey(d[3]);
            if(d[4]) s.portWeapon = EquipmentInfo.infoForKey(d[4]);
            if(d[5]) s.starboardWeapon = EquipmentInfo.infoForKey(d[5]);
            //set new writable ship properties in Oolite 1.82
            var oolite182 = false;
            if (0 < oolite.compareVersion("1.82")) { ; }
            else oolite182 = true;
            if( oolite182 ) { //these are not writable before Oolite 1.82
                var a = d[0];
                if( a[9] ) s.AIScriptWakeTime = a[9];
                if( a[11] ) s.destinationSystem = a[11];
                if( a[12] ) s.exhaustEmissiveColor = a[12];
                if( a[13] ) s.homeSystem = a[13];
                if( a[14] ) s.maxYaw = a[14];
                if( a[15] ) s.maxPitch = a[15];
                if( a[16] ) s.maxRoll = a[16];
                if( a[17] ) s.maxThrust = a[17];
                if( a[18] ) s.thrust = a[18];
                if( a[19] ) s.maxSpeed = a[19];
                if( a[20] > 0 ) s.maxEnergy = a[20];
                if( a[20] > 0 ) {
                    s.maxEnergy = a[20];
                    if(s.energy < s.maxEnergy ) s.energy = s.maxEnergy;
                }
                if( a[21] > 0 ) s.energyRechargeRate = a[21];
                if( a[22] ) s.cargoSpaceCapacity = a[22];
                if( a[23] ) s.injectorBurnRate = a[23];
                if( a[24] ) s.injectorSpeedFactor = a[24];
                if( a[25] ) s.scanDescription = a[25];
                if( a[26] ) s.hyperspaceSpinTime = a[26];
                if( a[27] ) s.scannerHostileDisplayColor1 = a[27];
                if( a[28] ) s.scannerHostileDisplayColor2 = a[28];
                if( s == player.ship ) {
                    if( a[29] ) s.forwardShield = a[29];
                    if( a[30] ) s.maxForwardShield = a[30];
                    if( a[31] ) s.forwardShieldRechargeRate = a[31];
                    if( a[32] ) s.aftShield = a[32];
                    if( a[33] ) s.maxAftShield = a[33];
                    if( a[34] ) s.aftShieldRechargeRate = a[34];
                } else if( a[10] ) s.beaconLabel = a[10]; //read-only for player
            }
            var counter, counter1;
            var equipment = s.equipment;
            //remove all equipments
            var r = [];
            for(counter = 0;counter<equipment.length;counter++)
                r.push(equipment[counter].equipmentKey);
            for(counter = 0;counter<r.length;counter++) //separated to avoid problems during eq array shortening
                s.removeEquipment(r[counter]);
//        if(w.$debug) log(w.name, "RestoreShipData2 d:"+d);
//        if(w.$debug) log(w.name, "RestoreShipData2e "+equipment);
            var tempArray = [];
            var savedEQ = d[7];
            for(counter = 0; counter < savedEQ.length; counter++) {
                tempArray.push(savedEQ[counter][0]);
            }
            for(counter = 0; counter < equipment.length; counter++) {
                var e = equipment[counter].equipmentKey;
                var index = tempArray.indexOf(e);
                if( index == -1 ) s.removeEquipment(e);
            else if( savedEQ[index][1] == "EQUIPMENT_DAMAGED" )
                s.setEquipmentStatus( e, "EQUIPMENT_DAMAGED" );
            }
            var depth = 0;
            var retryArray = []; //try again if a required equipment comes later
            retryArray[depth] = savedEQ;
            do {
                var added = false;
                retryArray[depth+1] = [];
                for(counter = 0; counter < retryArray[depth].length; counter++) {
                    var e = retryArray[depth][counter][0]; //award if exists and has not
                    var ed = retryArray[depth][counter][1];
                    if( EquipmentInfo.infoForKey(e) ) {
                        if( !s.awardEquipment(e) ) //retry next time if failed
                            retryArray[depth+1].push([e,ed]);
                        else {
                            added = true;
                            if( ed == "EQUIPMENT_DAMAGED" )
                                s.setEquipmentStatus( e, "EQUIPMENT_DAMAGED" );
                        }
                    }
                }
                depth++;
//            if(w.$debug) log(w.name, "RestoreShipData3 depth:"+depth+" retryArray:"+retryArray[depth]);
            } while( retryArray[depth].length > 0 && added && depth < 100 ) //until no more added eq
        /* old code lose equipments due to make changes in the original d array
        for(counter = 0; counter<equipment.length; counter++) {
            tempArray = [equipment[counter].equipmentKey,
                s.equipmentStatus(equipment[counter])];
            for(counter1 = 0; counter1<savedEQ.length; counter1++) {
                if (savedEQ[counter1][0] === tempArray[0]) {
                    tempArray.push(true);
                    tempArray.push(counter1);
                    if (savedEQ[counter1][1] === "EQUIPMENT_DAMAGED")
                        s.setEquipmentStatus(tempArray[0],
                                     "EQUIPMENT_DAMAGED");
                    break;
                }
            }
            if (!tempArray[2]) {
                s.removeEquipment(tempArray[0]);
                continue;
            } else savedEQ.splice(tempArray[3],1);
        }
        for(counter = 0; counter<savedEQ.length; counter++) {
            if(EquipmentInfo.infoForKey(savedEQ[counter][0])) {
                s.awardEquipment(savedEQ[counter][0]);
                if(savedEQ[counter][1] === "EQUIPMENT_DAMAGED")
                    s.setEquipmentStatus(savedEQ[counter][0],savedEQ[counter][1]);
            }
        }*/
//        if(w.$debug) log(w.name, "RestoreShipData3 d[7]:"+d[7]);
//        if(w.$debug) log(w.name, "RestoreShipData3e "+s.equipment);
            //ship.cargoList is read-only and no ship.manifest so d[8] is skipped
            s.fuel = d[9];
            //passengers is player only, so d[10] is skipped
            var subEnts = d[11]; //will only exist if less subEnts than capacity when ship was stored.
            if (subEnts && subEnts.length>0) {
                var currentSubEnts = s.subEntities;
                for (counter = 0; counter<currentSubEnts.length; counter++) {
                    currentSubEnts[counter].toBeRemoved = true;
                    for(counter1 = 0; counter1<subEnts.length; counter1++) {
                        if(subEnts[counter1][0] === currentSubEnts[counter].dataKey &&
                             subEnts[counter1][1] === currentSubEnts[counter].position.x &&
                             subEnts[counter1][2] === currentSubEnts[counter].position.y &&
                             subEnts[counter1][3] === currentSubEnts[counter].position.z )
                            delete currentSubEnts[counter].toBeRemoved;
                    }
                    if (currentSubEnts[counter].toBeRemoved) {
                        delete currentSubEnts[counter].toBeRemoved;
                        currentSubEnts[counter].remove(true);
                    }
                }
            }
            //serviceLevel is player only, NPCs store it in script variable
            if( d[12] > 0 ) {
                if( s == player.ship && d[12] ) s.serviceLevel = d[12];
                else if( s.script ) s.script.$SSH_serviceLevel = d[12]; //must set after setScript
            }
            if (oolite.compareVersion("1.80") < 0) {// can only do this in Oolite 1.81 or greater
                s.entityPersonality = d[13];
            }
        
            if( s.script && d[14] )
                //s.script.$EscortDeckUsable = d[14].usable; //must set after setScript
                s.script.$EscortDeckUsable = 1; //must set after setScript
            
//        if(w.$debug) log(w.name, "missileCapacity:"+s.missileCapacity+" d[6]:"+d[6]);
            if( s.missileCapacity > 0 ) {
            //do missiles at the end due to there is a core bug in Oolite 1.80
            //when land back to the carrier from an Asp which has max_missiles=0:
            //Error: Native exception: Tried to init array with nil object
                var missiles = [];
                if( s.missiles && s.missiles.length > 0 ) {
                    missiles = s.missiles;
                    for(counter = 0;counter<missiles.length;counter++) {
                        s.removeEquipment(missiles[counter]);
                    }
                }
                missiles = d[6];
                if( missiles && missiles.length > 0 ) {    
                    for(counter = 0; counter<missiles.length; counter++)
                        if(EquipmentInfo.infoForKey(missiles[counter]))
                            s.awardEquipment(missiles[counter]);
                }
            }
        }
        if (s.script && s.script.$EscortDeckUsable === 1) {
            var js = "escortdeck_escort";
            if (!s.script || s.script.name != js) {
                // sanity check only, since we set ship.script as soon as we spawn it
                var sl = 0;  //must set usable and sl again after setScript
                if (s.script) sl = s.script.$SSH_serviceLevel;
                s.setScript(js+".js");
                if (s.script) {
                    s.script.$EscortDeckUsable = 1;
                    s.script.$SSH_serviceLevel = sl;
                }
            }
            // make sure the escorts can use LaserReductor if they have it - if the player wants derelicts, they will have it!
            s.script.shipAttackedOther = this.shipAttackedOther;
            s.script.shipKilledOther = this.shipKilledOther;
            // if running HarderWay, attach SolarWind to ship script if it hasn't been already to set fuel scooping/consumption
            if (worldScripts.SolarWind && typeof s.script.$debugHarderWay === 'undefined') worldScripts.SolarWind.shipSpawned(s);
            s.script.$debugHarderWay = true; // see log messages form the escorts in Latest.log
            // nobody would hire a novice as a escort pilot, so take out the lower accuracy range
            if (s.accuracy == null || s.accuracy < 6.1) s.accuracy = 6.1 + 4 * Math.random();
        }
        var now = Date.now();
        if (this.$debug) log(this.name, "Deserializing ship "+s.displayName+" took "+(now-entry_time).toFixed(0)+"ms");
    }
    if (this.$debug) log(this.name, s.displayName+": shipRestore returns");
    return (s);
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_RollBack = function _escortdeck_RollBack(eq, msg) {
    var f = 1;
    if( player.ship && player.ship.dockedStation ) {
        var e = player.ship.dockedStation.equipmentPriceFactor;
        if( e > 0 ) f = e;
    }
    player.credits += f * EquipmentInfo.infoForKey(eq).price/10;
    player.consoleMessage(msg, 5);
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_Salvage = function _escortdeck_Salvage( pad, owner ) { //salvage the escort on the given pad
    var that = _escortdeck_Salvage;
    var w = (that.w = that.w || worldScripts.escortdeck);
    var d = null;
    if( w.$EscortDeckShipData ) d = w.$EscortDeckShipData[pad];
    var s = null;
    if( w.$EscortDeckShip ) s = w.$EscortDeckShip[pad];
    var sn = "escort";
    var t = w.$EscortDeckToWS;
    if( t ) {
        var s = w.$EscortDeck_SpawnShip( pad, w, owner );
        if (s && s.isValid) {
            if (w.$debug) log(w.name, "Using Towbar_Payout to salvage "+s.displayName);
            t.$Towbar_Payout(s); //call payout in towbar oxp
        }
    } else { //pay salvage price like in towbar
        var cr = w.$EscortDeck_SalvagePadPrice(pad); //salvage payment
        log(w.name, "Salvage "+pad+". pad for "+cr+"cr: "+d);
        if( s && s.isValid ) sn = s.shipClassName; //must be short name to fit into a line
        else {
            if( !d || !d[1] ) return false; //no ship in this pad
            sn = d[0][7]; //shipClassName
        }
        player.consoleMessage("You got "+cr+" Cr for salvaging "+sn, 10);
        player.credits += cr;
        w.$EscortDeckSound2.play();
    }
    if( s && s.isValid ) s.remove(true);
    w.$EscortDeckShipData[pad] = null;
    w.$EscortDeckShip[pad] = null;
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_Salvage2 = function _escortdeck_Salvage2(pad, owner) { //salvage or save into Hyperspace Hangar
    var that = _escortdeck_Salvage2;
    var w = (that.w = that.w || worldScripts.escortdeck);
    var hh = (that.hh = that.hh || worldScripts.HyperspaceHangar);
    if (hh && w.$EscortDeckShipData[pad]) {
        if (w.$EscortDeckShipData[pad][0]) 
            player.consoleMessage(w.$EscortDeckShipData[pad][0][3]+" from deck "+(pad+1)+" stored into Hyperspace Hangar", 10);
        w.$EscortDeck_HHStoreShipData(w.$EscortDeckShipData[pad]);
        w.$EscortDeckShipData[pad] = [];//clear this deck
    } else w.$EscortDeck_Salvage(pad, owner); //salvage the escort on the given pad
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_SalvageBadEscorts = function _escortdeck_SalvageBadEscorts(owner) {
    var that = _escortdeck_SalvageBadEscorts;
    var w = (that.w = that.w || worldScripts.escortdeck);
    if (!owner) return;
    var oldlength = w.$EscortDeckPadPos.length; //save the old length before SetPads when sold a Carrier
    w.$EscortDeck_SetPads( owner, w );
    if (owner.mass < w.$EscortDeckMinMass) { //bought a too small ship
        owner.removeEquipment("EQ_ESCORTDECK");
        owner.removeEquipment("EQ_ESCORTDECKXL");
        for(var i = 0; i < oldlength; i++ ) //salvage all escorts
            w.$EscortDeck_Salvage2(i, owner); //or save into Hyperspace Hangar
    } else if (!w.$EscortDeck_isLarge(owner, w)) { //no side pads
        w.$EscortDeck_Salvage2(4, owner); //salvage or save left adder
        w.$EscortDeck_Salvage2(5, owner); //salvage or save right adder
    }
    if (owner.mass < w.$EscortDeckXLMass) { //bought a non-xl ship
        if (owner.equipmentStatus("EQ_ESCORTDECKXL") == "EQUIPMENT_OK") {
            owner.removeEquipment("EQ_ESCORTDECKXL"); //downgrade
            owner.awardEquipment("EQ_ESCORTDECK");
        }
    }
    if (owner.equipmentStatus("EQ_ESCORTDECKXL") == "EQUIPMENT_OK") w.$EscortDeckXL = true;
    else w.$EscortDeckXL = false; //must set XL for the following TooLarge call
    for(var i = 0; i < oldlength; i++ ) {
        var t = null;
        if (w.$EscortDeckShip) t = w.$EscortDeckShip[i]; //the ship on this pad
        if (i >= w.$EscortDeckPadPos.length || //salvage too much decks when bougth a smaller ship
            w.$EscortDeck_TooLarge2(t, owner, w, i)) //can use ShipData array if no ship
            w.$EscortDeck_Salvage2(i, owner); //salvage too large escorts or save into HH
    }
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_SalvagePadPrice = function _escortdeck_SalvagePadPrice(pad) { //salvage price of an escort on this pad
    var that = _escortdeck_SalvagePadPrice;
    var w = (that.w = that.w || worldScripts.escortdeck);
    var s = null;
    var t = w.$EscortDeckToWS;
    if (t) return null; // Towbar_Payout will be used to salvage, we don't know the salvage price
    if (w.$EscortDeckShip) s = w.$EscortDeckShip[pad]; //the ship on this pad if player is not docked
    if (s && s.isValid) return(w.$EscortDeck_SalvageShipPrice(s, w));
    var d = null;
    if (w.$EscortDeckShipData) d = w.$EscortDeckShipData[pad]; //inside stations must use the saved data array
    if (!d || !d[1]) return(0); //no price for a nonexistent ship
    var eq = [];
    for (var i = 0; i < d[7].length; i++) {
        var ep = 0;
        var e = EquipmentInfo.infoForKey(d[7][i][0]);
        if (e) ep = e.price;
        eq[i] = {price:ep};
        if (w.$debug) log(w.name, i+". "+e+" fullprice:"+eq[i].price); //debug
    }
    var mass = 30000;//guess
    if (d[14] && d[14].mass > 0) mass = d[14].mass;
    return(w.$EscortDeck_SalvagePrice(mass, eq, d[3], d[2], w));
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_SalvagePrice = function _escortdeck_SalvagePrice( mass, eq, fw, af, w ) {//price like in towbar OXP
    var hull = Math.round( 32.8 * Math.min(80, Math.round( //1t alloys = 32.8cr in average
        0.3 * mass * 0.001 )) * 0.1 ) * 10 //alloys from the hull
    var comp = 0; if( mass > 30000 ) comp = 84; //price of 1t computers
    var eqcr = 0; //payment for all equipments
    var rnd = 0.5;//Math.random(); - removed randomness due to repeated price listing in interface
    var i = -1;
    function weapon_value(eqKey) {
        var value = 0;
        if (eqKey) {
            var eqInfo = EquipmentInfo.infoForKey(eqKey);
            if (eqInfo) {
                value = Math.round(eqInfo.price/10*(0.2+rnd*0.2)/10)*10;
            }
        }
        return value;
    }
    while( eq[++i] ) {
        var p = eq[i].price * 0.04; //20% of the original cost in credits (not in credits*10)
        if( p > 300 ) {
//            rnd = rnd * ( 300 / p ); //costly eqs always damaged - removed, already cheap
            eqcr += Math.round( Math.max( 50, Math.round(p * rnd ))/10)*10; //min. 50cr
        } else eqcr += Math.round( Math.max( 10,
            Math.round( p * rnd ))/10)*10; //at least 10cr
    }
    //payment for fw and aft weapons, min. 20%, max 40% of original price, fixed rnd mean fix 30%
    var fc = weapon_value(fw);
    var ac = weapon_value(af);
    var cr = hull + comp + eqcr + fc + ac; //all payments but nothing for cargo
    return( cr ); //* 0.5 ); //deduct 50% commission - removed, an escort already cheap
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_SalvageShipPrice = function _escortdeck_SalvageShipPrice( ship, w ) { //salvage price of this ship
    return( w.$EscortDeck_SalvagePrice( ship.mass, ship.equipment,
                      ship.forwardWeapon, ship.aftWeapon, w ) );
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_SaveMaxs = function _escortdeck_SaveMaxs( ship, w ) {
    if(ship) 
        w.$EscortDeckMaxs = {   
                                maxThrust:ship.maxThrust,
                                maxPitch:ship.maxPitch,
                                maxRoll:ship.maxRoll,
                                maxYaw:ship.maxYaw 
                            };
    else w.$EscortDeckMaxs = { maxThrust:32, maxPitch:1, maxRoll:2, maxYaw:1 };
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_RestoreMaxs = function _EscortDeck_RestoreMaxs(ship, w) {
    var stored_max = w.$EscortDeckMaxs;
    if (stored_max) {
        ship.thrust = stored_max.maxThrust;
        ship.maxPitch = stored_max.maxPitch;
        ship.maxRoll = stored_max.maxRoll;
        ship.maxYaw = stored_max.maxYaw;
    }
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_SetMaxs = function _escortdeck_SetMaxs( ship, w ) {
    var old_f = this.$EscortDeckMassFactor;
    if( !ship || !ship.addCollisionException  //need Oolite v1.81
        || !w.$EscortDeckMassEffect ) return;
    var f = w.$EscortDeck_MassFactor(ship, w);
//    var m = w.$EscortDeckMaxs; //original maximums of player ship
    ship.thrust *= f / old_f;
    ship.maxPitch *= f / old_f;
    ship.maxRoll *= f / old_f;
    ship.maxYaw *= f / old_f;
    this.$EscortDeckMassFactor = f;
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_SetPads = function _escortdeck_SetPads(owner, w) {
    var carrier = false;
    if (w.$EscortDeckCWS && owner.dataKey.indexOf("carrier") > -1) carrier = true;
    var p = w.$EscortDeckLPos;
    var v = w.$EscortDeckLVec;
    var xl = "escortdeck";
    var m, s = [], si, x, y, z, maxmass;
    if (owner && owner.isValid) {
        if (owner.equipmentStatus("EQ_ESCORTDECKXL") === "EQUIPMENT_OK") {
            w.$EscortDeckXL = true;
        } else w.$EscortDeckXL = false;
        if (carrier) {
            xl = null; //use the carrier's hull as deck
            if (w.$EscortDeckXL) { //XL deck on carrier
                p = w.$EscortDeckCXPos;
                v = w.$EscortDeckCXVec;
                si = w.$EscortDeckCXSize;
            } else { //normal deck on carrier
                p = w.$EscortDeckCPos;
                v = w.$EscortDeckCVec;
                si = w.$EscortDeckCSize;
            }
            for (var i = 0; i < p.length; i++) {
                var siz = si[i];
                if( siz ) {
                    x = siz[0];
                    y = siz[1];
                    z = siz[2];
                    maxmass = siz[3];
                } else x = y = z = maxmass = 0;
                s[i] = [x, y, z, maxmass];
                if (i == 0) m = [x, y, z, maxmass];
                else {
                    m[0] = Math.max(x, m[0]);
                    m[1] = Math.max(y, m[1]);
                    m[2] = Math.max(z, m[2]);
                    m[3] = Math.max(maxmass, m[3]);
                }
            }
            s.push(m); //the last item contain the maximums of size values
        } else if (w.$EscortDeckXL) { //XL deck on non-carrier
            xl += "xl";
            p = w.$EscortDeckXLPos;
            v = w.$EscortDeckXLVec;
            x = w.$EscortDeckPad.xl[0];
            y = w.$EscortDeckPad.xl[1];
            z = w.$EscortDeckPad.xl[2];
            maxmass = w.$EscortDeckPad.xl[3]; //60t
            for (var i = 0; i <= p.length; i++) { // <= due to all equal
                s[i] = [x, y, z, maxmass]; //so the maximums are the same
            }
        } else { //normal deck on non-carrier
            if (owner.mass >= w.$EscortDeckWMass) {
                xl += "w";
                p = w.$EscortDeckWPos;
                v = w.$EscortDeckWVec;
            }
            if (owner.mass >= w.$EscortDeckWMass) { //wide pad
                x = w.$EscortDeckPad.wide[0];
                y = w.$EscortDeckPad.wide[1];
                z = w.$EscortDeckPad.wide[2];
                maxmass = w.$EscortDeckPad.wide[3];
            } else { //normal escort pad (non-wide)
                x = w.$EscortDeckPad.escort[0];
                y = w.$EscortDeckPad.escort[1];
                z = w.$EscortDeckPad.escort[2];
                maxmass = w.$EscortDeckPad.escort[3];
            }
            maxmass = Math.min(owner.mass * 0.4,  maxmass); //max. 40% of the owner's mass
//            maxmass = Math.min(owner.mass * 0.5,  maxmass); //max. 50% of the owner's mass
            maxmass = Math.min(w.$EscortDeckLargeMass,  maxmass); //max. 130t
            for ( var i = 0; i <= p.length; i++) {
                if (w.$EscortDeck_isMiniPad(i, owner, w))
                     s[i] = [w.$EscortDeckPad.mini[0],
                         w.$EscortDeckPad.mini[1],
                        w.$EscortDeckPad.mini[2],
                        w.$EscortDeckPad.mini[3]];
                else s[i] = [x, y, z, maxmass];
            }
        }
    }
    var len = p.length;
    if (owner && owner.isValid && !w.$EscortDeck_isLarge(owner, w))
        len = 4; //without adder pads
    w.$EscortDeckPadPos = []; //landing pad positions
    w.$EscortDeckPadSize = []; //landing pad sizes
    w.$EscortDeckPadVec = []; //vectors from landing pad to ship center
    for (var i = 0; i < len; i++) { //fill up from the proper array
        w.$EscortDeckPadPos[i] = p[i];
        w.$EscortDeckPadSize[i] = s[i];
        w.$EscortDeckPadVec[i] = v[i];
    }
    w.$EscortDeckPadSize[len] = s[s.length-1]; //the last item contain the maximums of size values
    log(w.name, "Pads Size:"+JSON.stringify(w.$EscortDeckPadSize)+", wide:"+JSON.stringify(w.$EscortDeckPad.wide));
    return(xl);
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_Show = function _escortdeck_Show(owner) { //create the deck and escorts
    var that = _escortdeck_Show;
    var w = (that.w = that.w || worldScripts.escortdeck);
    var xl = "notcarrier"; //carriers set xl to null
    if ((owner.equipmentStatus("EQ_ESCORTDECK") === "EQUIPMENT_OK" 
        || owner.equipmentStatus("EQ_ESCORTDECKXL") === "EQUIPMENT_OK")
        && owner.mass >= w.$EscortDeckMinMass) {
        xl = w.$EscortDeck_SetPads(owner, w); //return null for Carriers
        if (!w.$EscortDeckV && xl) {
            var r = system.addVisualEffect(xl, [0,0,0]);
            if (r) w.$EscortDeckV = r; //position and orientation will be set in FCB
        }
        
        //make the z offset of deck position from the center of the owner ship
        var zv = owner.weaponPositionAft;//to be able to fire backward if the deck is solid
        if (!zv.z) zv = zv[0]; //fix for the change of weaponPositions in Oolite 1.83.
        zv.x = 0;
        var hl = owner.boundingBox.z * 0.5 + 30;
        if (zv.z < hl) zv.z = -hl; //correct z position for carriers
        //if there is an aft Sniper Gun on the ship then move the deck nearer
        var sg = w.$EscortDeckSGWS;
        if (sg) {
            var sub = sg._checkAftSubEnt(owner, "snipergun", sg);
            if (sub >= 0) {
                var e = owner.exhausts;
                if (e && e[0] && e[0].position) {
                    if (w.$debug) log(w.name, "zvz:"+zv.z+" ez:"+(e[0].position.z-3));
                    zv.z = e[0].position.z - 3; //3m after engine
                } else { //no engine on this ship, like Nyoka
                    var subh = sg._checkAftSubEnt(owner, "snipergun-heavy", sg);
                    if (subh >= 0) { //Heavy Sniper Gun, move about 50m
                        var g = owner.subEntities[subh];
                        var subz = g.position.z - g.boundingBox.z;
                        if (subz < zv.z) zv.z += g.boundingBox.z - 10;
                    } else { //normal Sniper Gun, move about 20m
                        var g = owner.subEntities[sub];
                        var subz = g.position.z - g.boundingBox.z;
                        if (subz < zv.z) zv.z += g.boundingBox.z - 10;
                    }
                    if (w.$debug) log(w.name, "subz:"+subz+" zvz:"+zv.z);
                }
            }
        }
 
        if (zv.y > 0) zv.y = 0; //do not raise over the center line
        if (owner == player.ship) w.$EscortDeckZV = zv;
        else { //NPC deck
            if (!owner.script) owner.script = "oolite-default-ship-script.js"; //for sure
            owner.script.$EscortDeckZV = zv;
        }
        //spawn ships based on $EscortDeckShipData
        var b = system.shipsWithPrimaryRole("buoy-witchpoint"); //avoid instant collision after hyperjump
        for (var i = 0; i < w.$EscortDeckPadPos.length; i++) { //fill up ShipPos array
            var s = w.$EscortDeck_SpawnShip(i, w, owner);
            if (s && s.isValid) {
                if (b && b[0] && b[0].addCollisionException) //from v1.81
                    b[0].addCollisionException(s);
                var r = w.$EscortDeck_PutShip(true, s, i, owner);
                if (w.$debug) log(w.name, (i+1)+". spawn "+s+" r:"+r); //debug
            }
        }
    }
    w.$EscortDeck_MFD(w); //update MFD
    if ((w.$EscortDeckV || !xl) && !isValidFrameCallback(w.$EscortDeckFCB))
        w.$EscortDeckFCB = addFrameCallback(w.$EscortDeck_FCB);
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_SpawnOne = function _escortdeck_SpawnOne(role, w, isShip) {
    var s = system.addShips(role, 1, [10000000,0,0], 5000);
    // set script as soon as possible, hopefully before any OXP's shipSpawned event handler set any property in th ship's script
    if (isShip && s && s[0]) s[0].setScript("escortdeck_escort.js");
    return( s );
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_SpawnShip = function _escortdeck_SpawnShip(pad, w, owner) {
    var s = null;
    var d = w.$EscortDeckShipData[pad];
    if (d && d[1]) {
        if (w.$EscortDeckCWS && d[14] && d[14].usable > 0
            && w.$EscortDeckPlayableDataKeys.indexOf(d[1]) === -1) {
            var n = d[1]+"-player"; //try the player variant for Carriers
            if (w.$EscortDeckPlayableDataKeys.indexOf(n) > -1) d[1] = n;
            else {
                var e = d[1].indexOf("-trader"); //try without suffix
                if (e == d[1].length-(e.length)) {
                    n = d[1].substr(0, e);
                    if (w.$EscortDeckPlayableDataKeys.indexOf(n) > -1)
                        d[1] = n;
                    else {
                        n += "-player"; //try replace -trader to -player
                        if (w.$EscortDeckPlayableDataKeys.indexOf(n) > -1)
                            d[1] = n;
                    }
                } else {
                    var e = d[1].indexOf("-pirate"); //try without suffix
                    if (e == d[1].length-(e.length)) {
                        n = d[1].substr(0, e);
                        if (w.$EscortDeckPlayableDataKeys.indexOf(n) > -1)
                            d[1] = n;
                        else {
                            n += "-player"; //try replace to -player
                            if(w.$EscortDeckPlayableDataKeys.indexOf(n) > -1) d[1] = n;
                        }
                    }
                }
            }
        }
        s = w.$EscortDeck_SpawnOne("["+d[1]+"]", w, true);
        if (s && s[0]) {
            if (s.script) s.script.$ShipVersionIgnore = true;
            if (w.$debug) log(w.name, "Spawn escort pad:"+pad+" d:"+d+" s:"+s);
            s = w.$EscortDeck_RestoreShipData(s[0], d, w, owner);
            //setup restore in FCB also, need after ShipVersion changes in shipSpawned
            w.$EscortDeckSpawnedShips.push(s);
        }
    }
    return(s);
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_TooLarge = function _escortdeck_TooLarge(ship, owner, w) { //size or mass is too large to this deck?
    return(w.$EscortDeck_TooLarge2(ship, owner, w, -1));
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_TooLarge2 = function _escortdeck_TooLarge2(ship, owner, w, pad) { //use saved data if no ship
    if( !ship || !ship.isValid ) {
        if( pad < 0 ) return("invalid escort ship");
        var d = w.$EscortDeckShipData;
        if( !d ) return("missing escort ship data");
        d = d[pad];
        return(w.$EscortDeck_TooLarge2b(owner, w, pad, d ));
    } else { //ship is present
        var shipmass = ship.mass;
        if( ship.scriptInfo && ship.scriptInfo.hardarmour > 1 )
            shipmass = shipmass / 2; //remove extra density from HardShips and Granite ships
        var b = ship.boundingBox;
        return(w.$EscortDeck_TooLarge3(owner, w, shipmass, b.x, b.y, b.z, pad ));
    }
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_TooLarge2b = function _escortdeck_TooLarge2b(owner, w, pad, d) { //use saved data if no ship
    if( !d || !d[14] || !d[14].mass ) return("invalid escort ship data");
    var shipmass = d[14].mass;
    if( d[1].indexOf("hard") == 0 ) //if this is a HardShip or Granite ship
        shipmass = shipmass / 2; //remove the extra density
    return(w.$EscortDeck_TooLarge3(owner, w, shipmass, d[14].x, d[14].y, d[14].z, pad ));
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_TooLarge3 = function _escortdeck_TooLarge3(owner, w, shipmass, bx, by, bz, pad) { //check given data
    if (!owner || !owner.isValid)  return("invalid owner ship");
    if (!(shipmass > 0)) return("missing escort ship mass");
    if (!bx || !(bx>0) || !by || !(by>0) || !bz || !(bz>0))
        return("invalid escort ship size");
    var e, l, x = 0, y = 0, z = 0, maxmass = 0;
    var s = w.$EscortDeckPadSize;
    if (s && s[0]) {
        l = s.length-1;
        if (pad < 0 || pad > l) e = s[l]; //the last line contain the maximums
        else e = s[pad];
        if (e && e[0]) { 
            x = e[0];
            y = e[1];
            z = e[2];
            maxmass = e[3];
        }
    }
    var r = null;
    if (shipmass > maxmass) r = "too heavy ("+Math.ceil(shipmass/1000)+"t > "+Math.floor(maxmass/1000)+"t)";
    else if (bx > x) r = "too wide ("+Math.ceil(bx)+"m > "+x+"m)";
    else if (by > y) r = "too tall ("+Math.ceil(by)+"m > "+y+"m)";
    else if (bz > z) r = "too long ("+Math.ceil(bz)+"m > "+z+"m)";
    if (w.$debug) {
        var p = pad+1;
        if (p == 0) p = "any";
        if (r) var l = r;
        else var l = "["+Math.ceil(bx*100)/100+","+Math.ceil(by*100)/100+","+
            Math.ceil(bz*100)/100+","+Math.ceil(shipmass)+"] fit into ["+Math.floor(x*100)/100+","+
            Math.floor(y*100)/100+","+Math.floor(z*100)/100+","+Math.floor(maxmass)+"]";
//        log(w.name, "Pad "+p+": "+l); //debug
    }
    return(r); //if null then not too large
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_VClear = function _escortdeck_VClear(w) { //remove the models of deck and escorts
    var p = player.ship;
    if( isValidFrameCallback( w.$EscortDeckFCB ) )
        removeFrameCallback( w.$EscortDeckFCB );
    if( w.$EscortDeckV ) w.$EscortDeckV.remove();
    w.$EscortDeckV = null;
    w.$EscortDeckSelectedPad = -1;
    if( w.$EscortDeckShip ) {
        for(var i = 0; i < w.$EscortDeckPadPos.length; i++ ) {
            var s = w.$EscortDeckShip[i];
            if( s && s.isValid ) {
                var lost = false;
                if( !w.$EscortDeckShipPos[i] ) { //not in deck
                    var rnd = ( 5 * Math.random() + 1 ) * p.scannerRange;
                    var d = s.position.distanceTo(p.position) - p.scannerRange;
                    if( d > rnd ) lost = true; //leaved farther than its luck
                }
                if( lost ) player.consoleMessage(s.name+" is left behind from pad "+i, 10);
                else {
                    w.$EscortDeckShipData[i] = w.$EscortDeck_MakeShipData(s);
                    s.remove(true);
                }
                if(w.$debug) log(w.name, (i+1)+". "+w.$EscortDeckShipData[i]); //debug
            }
            w.$EscortDeckShip[i] = null;
            w.$EscortDeckShipPos[i] = null;
        }
    } else {
        w.$EscortDeckShip = [];
        w.$EscortDeckShipPos = [];
    }
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_TugDrone_Processing = function _escortdeck_TugDrone_Processing(ship) {
    var that = _escortdeck_TugDrone_Processing;
    var t = (that.t = that.t || worldScripts.towbar);
    var wse = (that.wse = that.wse || worldScripts.escortdeck);
    log(wse.name, "_escortdeck_TugDrone_Processing_escortdeck: Tug Drone towed ship "+ship.displayName);
    if (ship && ship.isValid && ship.script && ship.script.$EscortDeckUsable > 0) {
        log(wse.name, "Tug Drone towed ship "+ship.displayName+" is usable, storing in Hyperspace Hangar");
        wse.$EscortDeck_HHStoreShip(ship);
        player.consoleMessage("Usable ship "+ship.displayName+" stored into Hyperspace Hangar", 10);
        player.commsMessage("Usable ship "+ship.displayName+" stored into Hyperspace Hangar", 10);
        ship.remove(true);
    } else {
        log(wse.name, "Tug Drone towed ship "+ship.displayName+" not usable");
        t.$Towbar_TugDrone_ProcessingOrig(ship);
    }
}
//-----------------------------------------------------------------------------------------------//
this.$printDeckLoad = function _escortdeck_printDeckLoad() {
    var w = worldScripts.escortdeck;
    var _sArray = w.$EscortDeckShip;
    var _sDataArray = w.$EscortDeckShipData;
    var i;
    i = w.$EscortDeckPadPos.length;
    log(w.name, "Decks has "+i+" pads");
    while (i--) {
        if (_sArray[i])
            log(w.name, "Pad "+i+": "+_sArray[i].shipUniqueName+" ("+_sArray[i].dataKey+")");
        else
            log(w.name, "Pad "+i+": "+_sDataArray[i]);
    }
}
//-----------------------------------------------------------------------------------------------//
this.$EscortDeck_RefuelEscort = function _escortdeck_RefuelEscort(d) {
    if (!d) return;
    if (d[9] >= 7) return;
    var f = 7 - d[9];
    var v = f * 3; // 3 CR/LY (gotta charge extra for the service :-)
    if (player.credits < v) {
        log(this.name, "Insufficient funds to refuel escort "+d[0][3]);
        player.consoleMessage("Insufficient funds to refuel escort "+d[0][3]);
    } else {
        player.credits -= v;
        d[9] = 7;
        log(this.name, "Refuelled escort "+d[0][3]+": "+ f.toFixed(1) +"LY for "+ formatCredits(v, true, true));
        player.consoleMessage("Refuelled escort "+d[0][3]+": "+ f.toFixed(1) +"LY for "+ formatCredits(v, true, true));
    }
}
 |