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

Expansion Escort Deck

Content

Manifest

from Expansion Manager's OXP list from Expansion Manifest
Description Build up your escort fleet and hold on an external deck attached to your ship. Salvage damaged ships for cash. Build up your escort fleet and hold on an external deck attached to your ship. Salvage damaged ships for cash.
Identifier oolite.oxp.Norby.EscortDeck oolite.oxp.Norby.EscortDeck
Title Escort Deck Escort Deck
Category Activities Activities
Author Norby Norby
Version 1.12 1.12
Tags
Required Oolite Version
Maximum Oolite Version
Required Expansions
Optional Expansions
  • oolite.oxp.Norby.Carriers:0.9
  • oolite.oxp.Norby.EscortPack:1.1
  • oolite.oxp.Norby.Towbar:0.98
  • oolite.oxp.Svengali.Library:1.7.1
  • oolite.oxp.Norby.Carriers:0.9
  • oolite.oxp.Norby.EscortPack:1.1
  • oolite.oxp.Norby.Towbar:0.98
  • oolite.oxp.Svengali.Library:1.7.1
  • Conflict Expansions
    Information URL http://wiki.alioth.net/index.php/EscortDecks n/a
    Download URL https://wiki.alioth.net/img_auth.php/9/92/EscortDeck-1.12.oxz n/a
    License CC BY-NC-SA 4.0 CC BY-NC-SA 4.0
    File Size n/a
    Upload date 1671037018

    Documentation

    Also read http://wiki.alioth.net/index.php/Escort%20Deck

    Readme

    Escort Deck
    
    
    Build up your escort fleet and hold on an external deck attached to your ship.
    
    First you must buy Escort Deck equipment then you will see an attached platform in your aft view.
    
    
    Ship rules
    
    A deck provide 4 landing pads for escort ships with max. 130t mass, 70m width, 29m height and 95m length: Adder, Asp, Cobra Mark I, Constrictor, Fer-de-Lance, Gecko, Mamba, Moray, Sidewinder and Viper.
    
    If your ship is over 130t mass like Cobra Mark III then can hold another two small ships like Adders (max. 30t, 15m height, 39m width and 65m length) on the sides of the deck.
    
    The mass of your escort must be less than 40% of your ship's mass. For example an Asp (75t) can hold 4 small escorts below 30t: Adder, Gecko or Sidewinder. You should use a ship over 40t mass to be able to use Adders (16t) as escorts. Ships below 30t mass can not get Escort Deck.
    
    Beware of replacing your large ship to a smaller one, all of your escorts will be salvaged which isn't fit into the smaller limits.
    
    
    Wide deck
    
    Ships over 260t like Python receive wider deck to hold escorts up to 95m width like Krait or Tiger Ray (http://aegidian.org/bb/viewtopic.php?f=2&t=16926&hilit=Tiger+Ray&start=45#p229820).
    
    
    Purchasing escorts
    
    You can buy escorts in the ship outfitting (F3) screen. These are not available in the local shipyard but ordered from a remote owner so your time will forwarding a lot if you buy one, but in this way you can buy advanced ships in the low tech worlds also.
    
     Name	Cost	Energy	Recharg	Speed	Pitch	Mass t	Eqipment
     Adder	80000	85	2	240	2	16	Pulse laser, Injectors, Shield booster
     Asp	400000	350	4	400	1	75	Beam Lasers forward and aft, ECM, Injectors, Shield booster
     Gecko	100000	250	4	300	1.5	25	Beam Laser, ECM
     Sidew.	76000	240	2	370	1.6	26	Beam Laser
     Viper	600000	180	4	320	1.8	34	Military Laser, ECM, Injectors, Shield booster and enhancer
    
    There are very good pirate escort ships but these are not purchaseable in main stations: you must force to eject the pilot or find in the outfitting list in alternative stations.
    
    There are rumors about ace sniper pilots in pirate stations with better ships than an Asp, but these surely costs more than 1MCr if you want to hire them into your escort fleet.
    
    
    Derelicts
    
    You can pick up derelict ships: put your target lock on, go nearer than 300m and with less than 100m/s speed difference. If you can not target it (due to the target has jamming device) then you can collide very slowly to trigger the pickup.
    
    If an "usable" message shown in your console when target a derelict ship (Combat MFD show it also) then you can use this ship as your escort, else transfer it to a dock on your deck and you will get some money for the salvage.
    
    If you have Towbar OXP then you will get reputation also for the delivered salvage.
    
    
    Heavy escorts
    
    There are heavy escorts around 120t mass, these are multipurpose ships modified for escort jobs which mean removed cargo capacity and some bonuses (please install Escort Pack OXP to get these).
    
    You need a ship with 325t mass to be able to use all escorts up to the maximal 130t, so an Anaconda or special (OXP) Pythons can hold heavy escorts but a Cobra Mark III (215t) or a Boa (182t) can't.
    
    
    Usage
    
    You can prime Escort Deck with Shift+N then activate with "n" key to launch or call back your escorts. The mode key "b" select a single escort for launch or callback. You can not launch escorts over normal speeds to avoid collisions.
    
    You can prevent the launch of any ship if you select it by the mode key then activate once only: the first press will change the status to "LOCKED". The second activate will remove the lock and launch it, but you can call back instantly with a third keypress if you want to stay on the deck without the lock.
    
    There is an auto launch if you enter into red alert and auto callback when changed to green.
    
    There is a Multi-Function Display of Escort Deck, you can turn it on using the ":" and ";" keys. This show the list of current escorts, a  "->" mark before the selected one, name, energy, fuel (if this escort has working Injectors), distance, bearing (^> mean up left) and activity:
    
     ATTACK: Attack a hostile ship (your target first)
     DECK: Landed on the deck
     FLEE: Flee from a missile or recover some energy (from 40% to 80%)
     FOLLOW: Follow your ship at 2km range
     GOTO: Go to a hostile ship using Injectors if available until arrive within Beam Laser range (15km)
     INJECT: Attack using Injectors until distance is over 10km
     LANDING: Landing approach, will not do anything else until landed
     LOCKED: Stay on the deck and will not launch with the other ships
     SALVAGE: Unusable ship, will be salvaged in the next dock for a few thousand credits
     SNIPER: Sniper attack, need a forward laser over 15km range, like Military Laser
    
    You can pick up a new escort even if no more free landing pads listed on your MFD if you select a pad and maintain a target lock on the new ship when try to land or an escort is not in your deck. In these cases will replace the old one what you will find a bit farther behind your ship.
    
    The released escort will follow you (if is not derelict) until you stay within the same system and don't take a rest (reload the game), but will not defend you just itself. So you can pick up a derelict ship by replacing one of your escorts, transfer it into a station for payment, then leave the station and relock your old escort to the deck.
    
    If you dock or enter into a wormhole while some of your escorts are not in your deck then those that is within your scanner range will be docked also. Others can be lost, especially if travelled far from you.
    
    If you want to exchange the positions of two escorts on your deck then do these in order:
     1. Select an escort by mode key (b) if Escort Deck is primed with Shift+N and all escorts landed.
     2. Launch it with the activate key (n) and wait a few seconds to go out of landing distance.
     3. Press activate (n) again to order it back to landing and turn your ship to keep it in your screen.
     4. Press mode (b) until you select the second escort to flag the wanted landing position.
     5. Put your target lock to the launched ship: press "r" and aim well with your crosshairs.
     6. Wait for landing when the exchange will be done.
    
    The MFD can show the ship on Towbar in the last line, maked as T: . You can release the towed ship by selecting and activating it in this MFD. If no ship on your Towbar then show the maximal mass which your ship can tow (1.6 times of the mass of your ship).
    
    Your escorts will be refuelled when you buy fuel for your ship.
    
    
    Cost: 2000.0 Cr.
    Techlevel: 1
    
    
    
    Escort Deck XL
    
    If your ship is over 400t like an Anaconda then this longer deck can hold 8 escorts behind you, but the mass limit is lower: max. 60t, so you can't use Asp and heavy escort ships.
    
    If you have Towbar then you can't pick up any ship there due to the long XL deck occupy the same space behind you. The simple escort deck is shorter and Towbar compatible so if you plan to salvage larger ships than small escorts then you should choose the normal deck.
    
    Warning: your ship can be "uber" if you use 8 escorts, so if you want limit yoursef then stick with the smaller deck and avoid the XL variant.
    
    Carriers can hold 10 escorts on the internal XL deck (the "neck") and the mass limit is 130t as in the normal deck. Moreover the first two pads (on the top) can hold wider and heavier ships up to 378t (40% of Carriers' mass) and 130x35x95m including Cobra Mark III, Krait and other large ships also. This is even more "uber" but intentionally: a Carrier should be able to hold stronger fleet than others.
    
    
    Cost: 2000.0 Cr.
    Techlevel: 1
    
    
    
    Interface
    
    In F4 screen you can view, rename and salvage your current escorts. Moreover if Hyperspace Hangar is installed then you can buy esorts from the main shipyard (F3F3) using the following steps:
    
    1. Buy a small ship from a shipyard which fit into your deck and save your main ship into the Hyperspace Hangar.
    2. Equip it for combat as you wish, just avoid player-only tools like Railgun.
    3. Save the escort into the hangar by switching into your main ship.
    4. Go to the Escort Deck interface, select an empty deck and get the escort from the hangar.
    
    The hangar is usable as a temporary storage to rearrange your escorts as well as fix damaged equipments or buy new ones, just store your main ship into the hangar:
    
    1. Select a ship in the EscortDeck Interface.
    2. Put into Hyperspace Hangar.
    3. Go into the interface of Hyperspace Hangar and switch into the stored escort.
    4. Equip or fix it.
    5. Switch back into your main ship in the hangar interface.
    6. Go to the EscortDeck interface, select an empty deck and get your escort from the hangar.
    
    If you acquired an OXP escort ship which is not designed for player then you can not switch into.
    
    
    
    Ship mass table
    
    Adder   	16
    Moray    	51
    Cobra Mk I	52
    Fer-de-Lance	53
    Asp     	75
    Cobra Mk III	215
    Boa     	182
    Boa2    	216
    Python  	267
    Anaconda	438
    
    NPC:
    Gecko   	25
    Sidewinder	26
    Constrictor	28
    Viper   	34
    Mamba   	36
    Krait   	94
    
    
    
    Escort Deck types
    
    There are more appearance of decks depending on the mass of your ship. 
    
    * Below 30t: You can not use any decks.
    * 30t-130t: The smallest deck without side pads.
    * 130t-260t: Normal deck with side pads for two additional small escorts.
    * 260t-: Wider deck which can hold ships up to 95m width (like Krait).
    * 400t-: You can select either the wider deck or XL deck.
    * Carriers can hold the most powerful escort fleet, using the big hull as a deck.
    
    
    For more information see the regarding discussuion: http://aegidian.org/bb/viewtopic.php?f=4&t=17009
    
    
    Instructions:
    
    Do not unzip the .oxz file, just move into the AddOns folder of your Oolite installation.
    
    
    License:
    This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike License version 4.0.
    If you are re-using any piece of this OXP, please let me know by sending an e-mail to norbylite@gmail.com.
    
    The 3D models of decks are derived from Leviathan Platform in Aquatics OXP, thanks to Thargoid and P.A. Groove.
    
    Escort locked sound is equal with Towbar locked which is based on bgs-m_fx_shipyard1.ogg in BGS OXP.
    
    
    Changelog:
     2022.11.13. v1.12 Updates escorts' AIs and scripts to avoid closing too fast at landing.
                       Increases TL for equipments and escort ships.
                       Adds configuration for launching/calling back when going in/out of red alert.
                       Adds configuration for base usable derelict probability.
                       Configuration via Svengali's Library 'Config for AddOns'.
                       Takes NPC_equipmentDamage OXP's equipment damage into account for usable derelecit probability 
                       calculation (reduces base probability of being usable).
                       Makes Towbar not salvage usable derelicts if Hyperspace Hangar is installed.
                       Optimizes masss factor (degradation of ship maneuvarability by ship's total mass) updates. 
                       Tests for a funcitonal escortdeck in the player ship in event handlers.
                       Updates shipAtackedOther event handler to handle Laser Reductor presence and be compatible with use 
                       as a ship event handler for the escorts.
                       Puts a random fatcor into awarding equipment to usable derelicts, and those equipment couldd be damaged.
                       Uses ShipStorageHelper OXP for ship serialization if installed.
                       Sets event handlers in the escorts' ship scripts to use Laser Reductor.
                       Fixes bug in scrolling along the pads in the MFD.
                       Makes Towbar's Tug Drone store usable derelicts in the Hyperspace Hangar.
                       Performance updates to FCB.
                       Adds status of escorts' shields to MFD.
                       Adds menu entry to refuel escort to EscortDeck Station Interface.
                       Sets current galaxy number and system ID to the shipdata array when storing an escort in Hyperspace Hangar.
     2016.07.17. v1.11 Updated for Oolite 1.84 where player can not be the leader of a group.
     2016.06.25. v1.9  Fixed long message bug at salvage without Towbar, thanks to Strato1.
                       Too heavy message is not repeated too often anymore.
                       Deck is drifting away after player died.
                       Improved landing code based on the similar part in Fighters OXP.
     2016.03.13. v1.8  Interface menu display the names of ships in deck.
                       Fixed value in "you paid" message in costly non-main stations.
                       Aft Sniper Gun will not move the deck too far anymore, needed by Asp SG and Python SG.
                       Escort Deck can hold the long Asp SG and Fer-de-Lance SG, Wide Deck can hold Krait SG also.
                       Carrier top deck can hold Cobra Mark III SG, main deck can hold Krait SG.
                       Typo fix in shipdata, thanks to phkb.
     2015.10.14. v1.7  Interface functions: rename, salvage, put into Hyperspace Hangar and get back.
     2015.10.04. v1.6  Escort Deck Interface added into the F4 screen.
                       Deck positions can be locked to avoid launch of some ships.
                       Deck cost raised to 2000cr as suggested by Vincentz.
                       Escorts start with heat shield for sunskimming, requested by QCS.
                       Chance to find an usable derelict escort is reduced, salvage increased.
                       MFD show the ship on Towbar in the last line, maked as T: .
                       You can release the towed ship by selecting and activating it in MFD.
                       Show the mass of attached salvage ships on MFD.
                       Fixed for the change of weaponPositions to array in Oolite 1.83.
                       Escortship personality is restored from Oolite 1.81.
     2015.04.14. v1.5  MFD show max. mass and sizes of empty decks depending on player ship.
                       Further increased distances on deck before Oolite 1.81.
                       Fixed maximum mass check of escorts, must below 40% of owner ship's mass.
                       Hard and Granite escorts counted with original mass (without double density).
                       Fixed equipment restore on escorts if depends on another equipment.
     2015.04.11. v1.4  Collision fixes, needed before Oolite 1.81.
                       Granite ships on deck do not prevent hyperjump anymore.
                       From 1.81 slower steering and acceleration in proportion with the mass on deck.
     2015.04.06. v1.3  Increased distance around player without Injectors to 5km, thanks to QCS.
                       Workaround against escort terminated by Witchpoint Beacon.
     2015.03.20. v1.2  Refined escort landing sequence to prevent collisions, thanks to QCS.
                       Added a collisionException between the Witchpoint Beacon and escorts.
                       Fixed rollback price of unsuccessful escort purchase in Rock Hermits.
     2015.01.20. v1.1  No escort launch at injector speeds nor autolaunch at maximum speed.
                       Fixed escort equipments after load game.
                       All escorts got a missile pylon to workaround a bug with Carriers' sit into feature.
                       Escort view positions added.
     2015.01.11. v1.0  First release.
    

    Equipment

    Name Visible Cost [deci-credits] Tech-Level
    HUD no 0 100+
    Escort Deck yes 20000 5+
    Escort Deck XL yes 20000 5+
    Adder Escort yes 800000 5+
    Asp Escort yes 4000000 8+
    Gecko Escort yes 1000000 7+
    Sidewinder Escort yes 760000 7+
    Viper Escort yes 6000000 9+

    Ships

    Name
    Adder Escort
    Asp Escort
    Gecko Escort
    Sidewinder Escort
    Viper Escort

    Models

    This expansion declares no models.

    Scripts

    Path
    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));
        }
    }
    
    
    
    Scripts/escortdeck_condition.js
    "use strict";
    this.name        = "escortdeck_condition";
    this.author      = "Norby";
    this.copyright   = "2014 Norbert Nagy";
    this.licence     = "CC BY-NC-SA 4.0";
    this.description = "Escort deck equipment can not fit into small ships";
    this.version     = "1.0";
    
    this.allowAwardEquipment = function(eqKey, ship, context)
    {
    //bugfix for eqs with conditions scripts: need double replaceShip() to work!
    //player.replaceShip(shipname, pers); player.replaceShip(shipname, pers);
    //call twice! the first always got false for allow without run this script.
    //Proof: if you uncomment the following line, only the 2nd call will write into the log.
    //log("notforsmall", eqKey+" "+ship.name+" "+ship.mass+" "+context );
    
    	var w = worldScripts.escortdeck;
    	if( context == "scripted" //needed if player launched in a mini escort from a Carrier
    		|| ship.mass >= w.$EscortDeckMinMass ) return true;
    	else return false;
    }
    
    Scripts/escortdeck_condition_galcop.js
    "use strict";
    this.name        = "escortdeck_condition_galcop";
    this.author      = "Norby";
    this.copyright   = "2014 Norbert Nagy";
    this.licence     = "CC BY-NC-SA 4.0";
    this.description = "Vipers can fit into large ships and available in main stations only";
    this.version     = "1.0";
    
    this.allowAwardEquipment = function(eqKey, ship, context)
    {
    //bugfix for eqs with conditions scripts: need double replaceShip() to work!
    //player.replaceShip(shipname, pers); player.replaceShip(shipname, pers);
    //call twice! the first always got false for allow without run this script.
    //Proof: if you uncomment the following line, only the 2nd call will write into the log.
    //log("notforsmall", eqKey+" "+ship.name+" "+ship.mass+" "+context );
    
    	var w = worldScripts.escortdeck;
    	if( w.$EscortDeck_isLarge(ship, w)
    		&& ( ship.equipmentStatus("EQ_ESCORTDECK") == "EQUIPMENT_OK"
    		|| ship.equipmentStatus("EQ_ESCORTDECKXL") == "EQUIPMENT_OK" )
    		&& w.$EscortDeck_isFreePadFor(eqKey, ship, context, w)
    		&& player.ship && player.ship.docked && system.mainStation
    		&& player.ship.dockedStation == system.mainStation )
    		return true;
    	else return false;
    }
    
    Scripts/escortdeck_condition_large.js
    "use strict";
    this.name        = "escortdeck_condition_large";
    this.author      = "Norby";
    this.copyright   = "2014 Norbert Nagy";
    this.licence     = "CC BY-NC-SA 4.0";
    this.description = "Heavy escorts can fit into large ships only and need a deck";
    this.version     = "1.0";
    
    this.allowAwardEquipment = function(eqKey, ship, context)
    {
    //bugfix for eqs with conditions scripts: need double replaceShip() to work!
    //player.replaceShip(shipname, pers); player.replaceShip(shipname, pers);
    //call twice! the first always got false for allow without run this script.
    //Proof: if you uncomment the following line, only the 2nd call will write into the log.
    //log("notforsmall", eqKey+" "+ship.name+" "+ship.mass+" "+context );
    
    	var w = worldScripts.escortdeck;
    	if( w.$EscortDeck_isLarge(ship, w)
    		&& w.$EscortDeck_isFreePadFor(eqKey, ship, context, w)
    		&& ( ship.equipmentStatus("EQ_ESCORTDECK") == "EQUIPMENT_OK"
    		|| ship.equipmentStatus("EQ_ESCORTDECKXL") == "EQUIPMENT_OK" ) )
    		return true;
    	else return false;
    }
    
    Scripts/escortdeck_condition_largepirate.js
    "use strict";
    this.name        = "escortdeck_condition_largepirate";
    this.author      = "Norby";
    this.copyright   = "2014 Norbert Nagy";
    this.licence     = "CC BY-NC-SA 4.0";
    this.description = "Pirate ships can fit into large ships and available outside main stations";
    this.version     = "1.0";
    
    this.allowAwardEquipment = function(eqKey, ship, context)
    {
    //bugfix for eqs with conditions scripts: need double replaceShip() to work!
    //player.replaceShip(shipname, pers); player.replaceShip(shipname, pers);
    //call twice! the first always got false for allow without run this script.
    //Proof: if you uncomment the following line, only the 2nd call will write into the log.
    //log("notforsmall", eqKey+" "+ship.name+" "+ship.mass+" "+context );
    
    	var w = worldScripts.escortdeck;
    	if( w.$EscortDeck_isLarge(ship, w)
    		&& ( ship.equipmentStatus("EQ_ESCORTDECK") == "EQUIPMENT_OK"
    		|| ship.equipmentStatus("EQ_ESCORTDECKXL") == "EQUIPMENT_OK" )
    		&& w.$EscortDeck_isFreePadFor(eqKey, ship, context, w)
    		&& player.ship && player.ship.docked && system.mainStation
    		&& player.ship.dockedStation != system.mainStation )
    		return true;
    	else return false;
    }
    
    Scripts/escortdeck_condition_mini.js
    "use strict";
    this.name        = "escortdeck_condition_mini";
    this.author      = "Norby";
    this.copyright   = "2014 Norbert Nagy";
    this.licence     = "CC BY-NC-SA 4.0";
    this.description = "This escort can fit into mini landing pads";
    this.version     = "1.0";
    
    this.allowAwardEquipment = function(eqKey, ship, context)
    {
    //bugfix for eqs with conditions scripts: need double replaceShip() to work!
    //player.replaceShip(shipname, pers); player.replaceShip(shipname, pers);
    //call twice! the first always got false for allow without run this script.
    //Proof: if you uncomment the following line, only the 2nd call will write into the log.
    //log("notforsmall", eqKey+" "+ship.name+" "+ship.mass+" "+context );
    
    	var w = worldScripts.escortdeck;
    	if( ship.mass >= w.$EscortDeckMinMass
    		&& w.$EscortDeck_isFreePadFor(eqKey, ship, context, w, true) //the last true allow mini pads
    		&& ( ship.equipmentStatus("EQ_ESCORTDECK") == "EQUIPMENT_OK"
    		|| ship.equipmentStatus("EQ_ESCORTDECKXL") == "EQUIPMENT_OK" ) )
    		return true;
    	else return false;
    }
    
    Scripts/escortdeck_condition_minigalcop.js
    "use strict";
    this.name        = "escortdeck_condition_minigalcop";
    this.author      = "Norby";
    this.copyright   = "2014 Norbert Nagy";
    this.licence     = "CC BY-NC-SA 4.0";
    this.description = "This escort can fit into mini landing pads";
    
    this.allowAwardEquipment = function(eqKey, ship, context)
    {
    //bugfix for eqs with conditions scripts: need double replaceShip() to work!
    //player.replaceShip(shipname, pers); player.replaceShip(shipname, pers);
    //call twice! the first always got false for allow without run this script.
    //Proof: if you uncomment the following line, only the 2nd call will write into the log.
    //log("notforsmall", eqKey+" "+ship.name+" "+ship.mass+" "+context );
    
    	var w = worldScripts.escortdeck;
    	if( ship.mass >= w.$EscortDeckMinMass
    		&& w.$EscortDeck_isFreePadFor(eqKey, ship, context, w, true) //the last true allow mini pads
    		&& ( ship.equipmentStatus("EQ_ESCORTDECK") == "EQUIPMENT_OK"
    		|| ship.equipmentStatus("EQ_ESCORTDECKXL") == "EQUIPMENT_OK" )
    		&& player.ship && player.ship.docked && system.mainStation
    		&& player.ship.dockedStation == system.mainStation )
    		return true;
    	else return false;
    }
    
    Scripts/escortdeck_condition_minipirate.js
    "use strict";
    this.name        = "escortdeck_condition_minipirate";
    this.author      = "Norby";
    this.copyright   = "2014 Norbert Nagy";
    this.licence     = "CC BY-NC-SA 4.0";
    this.description = "This escort can fit into mini landing pads";
    
    this.allowAwardEquipment = function(eqKey, ship, context)
    {
    //bugfix for eqs with conditions scripts: need double replaceShip() to work!
    //player.replaceShip(shipname, pers); player.replaceShip(shipname, pers);
    //call twice! the first always got false for allow without run this script.
    //Proof: if you uncomment the following line, only the 2nd call will write into the log.
    //log("notforsmall", eqKey+" "+ship.name+" "+ship.mass+" "+context );
    
    	var w = worldScripts.escortdeck;
    	if( ship.mass >= w.$EscortDeckMinMass
    		&& w.$EscortDeck_isFreePadFor(eqKey, ship, context, w, true) //the last true allow mini pads
    		&& ( ship.equipmentStatus("EQ_ESCORTDECK") == "EQUIPMENT_OK"
    		|| ship.equipmentStatus("EQ_ESCORTDECKXL") == "EQUIPMENT_OK" )
    		&& player.ship && player.ship.docked && system.mainStation
    		&& player.ship.dockedStation != system.mainStation )
    		return true;
    	else return false;
    }
    
    Scripts/escortdeck_condition_small.js
    "use strict";
    this.name        = "escortdeck_condition_small";
    this.author      = "Norby";
    this.copyright   = "2014 Norbert Nagy";
    this.licence     = "CC BY-NC-SA 4.0";
    this.description = "Escorts can not fit into small ships and need a deck";
    this.version     = "1.0";
    
    this.allowAwardEquipment = function(eqKey, ship, context)
    {
    //bugfix for eqs with conditions scripts: need double replaceShip() to work!
    //player.replaceShip(shipname, pers); player.replaceShip(shipname, pers);
    //call twice! the first always got false for allow without run this script.
    //Proof: if you uncomment the following line, only the 2nd call will write into the log.
    //log("notforsmall", eqKey+" "+ship.name+" "+ship.mass+" "+context );
    
    	var w = worldScripts.escortdeck;
    	if( ship.mass >= w.$EscortDeckMinMass
    		&& w.$EscortDeck_isFreePadFor(eqKey, ship, context, w)
    		&& ( ship.equipmentStatus("EQ_ESCORTDECK") == "EQUIPMENT_OK"
    		|| ship.equipmentStatus("EQ_ESCORTDECKXL") == "EQUIPMENT_OK" ) )
    		return true;
    	else return false;
    }
    
    Scripts/escortdeck_condition_smallpirate.js
    "use strict";
    this.name        = "escortdeck_condition_smallpirate";
    this.author      = "Norby";
    this.copyright   = "2014 Norbert Nagy";
    this.licence     = "CC BY-NC-SA 4.0";
    this.description = "Geckos available outside main stations only";
    this.version     = "1.0";
    
    this.allowAwardEquipment = function(eqKey, ship, context)
    {
    //bugfix for eqs with conditions scripts: need double replaceShip() to work!
    //player.replaceShip(shipname, pers); player.replaceShip(shipname, pers);
    //call twice! the first always got false for allow without run this script.
    //Proof: if you uncomment the following line, only the 2nd call will write into the log.
    //log("notforsmall", eqKey+" "+ship.name+" "+ship.mass+" "+context );
    
    	var w = worldScripts.escortdeck;
    	if( ship.mass >= w.$EscortDeckMinMass
    		&& ( ship.equipmentStatus("EQ_ESCORTDECK") == "EQUIPMENT_OK"
    		|| ship.equipmentStatus("EQ_ESCORTDECKXL") == "EQUIPMENT_OK" )
    		&& w.$EscortDeck_isFreePadFor(eqKey, ship, context, w)
    		&& player.ship && player.ship.docked && system.mainStation
    		&& player.ship.dockedStation != system.mainStation )
    		return true;
    	else return false;
    }
    
    Scripts/escortdeck_escort.js
    "use strict";
    this.name        = "escortdeck_escort";
    this.author      = "Norby";
    this.copyright   = "2014 Norbert Nagy";
    this.licence     = "CC BY-NC-SA 4.0";
    this.description = "AI support functions for escorts";
    
    
    this.$debug = true;
    this._landing = function() {this._landing2(this.ship);}
    
    this._landing2 = function(ship) {
        var w = worldScripts.escortdeck;
        var p = player.ship;
        if( !p || !p.isValid ) return;
        ship.target = p;
        var dpos = p.position.add(p.velocity.multiply(2)); //target ahead the player when move
        ship.savedCoordinates = dpos; // must use setDestinationToTarget also
        ship.desiredRange = 0;
        var cd = p.collisionRadius + ship.collisionRadius + 50; //contact distance + a few for the deck
        var d = ship.position.distanceTo(p.position);
        if( d < w.$EscortDeckLandingDist ) { //enough near?
            var landnow = true;
            if( p.addCollisionException ) { //from Oolite v1.81
                p.addCollisionException(ship);
                if( d > cd ) {
                    landnow = false; //not so near
                    //apply "tractor beam" to pull this fighter to the deck
                    var v = p.position.add(p.heading.multiply(-p.collisionRadius)).subtract(ship.position);
                    //ship.velocity = v.direction().multiply(200).add(v.multiply(0.3)).add(p.velocity);
                    ship.velocity = v.direction().multiply(75).add(v.multiply(0.3)).add(p.velocity);
                }
            }
            if(landnow) {
                var pad = w.$EscortDeckShip.indexOf(ship);
                if( pad > -1 && !w.$EscortDeck_PutShip(false, ship, pad, p, w, true, true) )
                    ship.AIState = "DECK"; //landed
                else ship.AIState = "FOLLOW"; //no room or replaced with another ship
    //            ship.reportAIMessages = false;
                return;
            }
        }
        //do not attack hostiles during landing
        if( d <= cd ) ship.reactToAIMessage("PLAYER_NEXT");
        else if( d < w.$EscortDeckLandingDist ) ship.reactToAIMessage("PLAYER_NEAR");
        else {
            if (ship.fuel >= 0.5) {
                if( ship.injectorSpeedFactor > 0 )
                    ship.desiredSpeed = ship.injectorSpeedFactor * ship.maxSpeed;
                else ship.desiredSpeed = 7 * ship.maxSpeed; //for Oolite 1.80
            } else ship.desiredSpeed = ship.maxSpeed;
            ship.reactToAIMessage("PLAYER_FAR");
        }
    //    ship.performFlyToRangeFromDestination();
    }
    
    this._locatePlayer = function() {this._locatePlayer2(this.ship);}
    
    this._locatePlayer2 = function(ship) {
        var p = player.ship;
        if( !p || !p.isValid ) return;
        var sc = ship.script;
        ship.target = p;
        ship.savedCoordinates = p.position;
        var d = ship.position.distanceTo(p.position);
        if (player.alertCondition === 3 && sc._inEscortFleet2(ship)  && d < 25600) {
            //if not in fleet then follow but not defend the player, just himself
            if (this.$debug) log("escortdeck_escortdeck", "_locatePlayer: "+ship.displayName+" target:"+ p.displayName+" distance:"+d.toFixed(0)+", GOTO");
            ship.AIState = "GOTO"; //attack hostiles
        } else {
            if( d < 1500 ) {
                if (this.$debug) log("escortdeck_escortdeck", "_locatePlayer: "+ship.displayName+" target:"+ p.displayName+" distance:"+d.toFixed(0)+", PLAYER_NEXT_1");
                ship.reactToAIMessage("PLAYER_NEXT_1");
            } else if ( d < 5000 ) {
                if (this.$debug) log("escortdeck_escortdeck", "_locatePlayer: "+ship.displayName+" target:"+ p.displayName+" distance:"+d.toFixed(0)+", PLAYER_NEAR_1_5");
                ship.reactToAIMessage("PLAYER_NEAR_1_5");
            } else if (d <= 15000) {
                if (this.$debug) log("escortdeck_escortdeck", "_locatePlayer: "+ship.displayName+" target:"+ p.displayName+" distance:"+d.toFixed(0)+", PLAYER_FAR_5_15");
                ship.reactToAIMessage("PLAYER_FAR_5_15");
            } else if (d <= 25000) {
                if (this.$debug) log("escortdeck_escortdeck", "_locatePlayer: "+ship.displayName+" target:"+ p.displayName+" distance:"+d.toFixed(0)+", PLAYER_FAR_15_25");
                ship.reactToAIMessage("PLAYER_FAR_15_25");
            } else {
                if (this.$debug) log("escortdeck_escortdeck", "_locatePlayer: "+ship.displayName+" target:"+ p.displayName+" distance:"+d.toFixed(0)+", PLAYER_FAR_25_");
                ship.reactToAIMessage("PLAYER_FAR_25_");
            }
        }
    }
    
    this._combatCheck = function() {this._combatCheck2(this.ship);}
    
    this._combatCheck2 = function(ship) {
        var p = player.ship;
        if( !p || !p.isValid ) return;
        var sc = ship.script;
    
        if(ship.target && ship.target.isDerelict)
        {
            ship.target = null;
            ship.reactToAIMessage("TARGET_EJECTED");
        }
        
        var t = sc._playerTarget2(ship);
        if( !ship.target || !ship.target.isValid
            || t && t.isValid && ship.target != t ) {
            ship.AIState = "GOTO"; //find new target or player set new target
            return;
        }
        
        if( ship.position.distanceTo(p.position) > 24000 ) {
            // if escort is far from the player, break off combat
            if( ship.target && ship.target != sc._findPlayerNearestHostile2(ship) ) {
                ship.target = null;
                ship.AIState = "GOTO"; //change target to the player's nearest hostile
            }
        } else {
            var e = ship.energy;
            var m = ship.maxEnergy;
            ship.savedCoordinates = ship.target.position;
            if( e < m * 0.4 ) //cautious: flee if energy is below 40%
                ship.AIState="FLEE"; //avoid firing ECM
            else if( sc._Inject2(ship.target,ship) ) { //inject?
                if( ship.AIState == "INJECT" )
                    ship.reactToAIMessage("ENEMY_FIRE");
                else ship.AIState = "INJECT"; //switch to inject
            } else {
                if( ship.AIState == "ATTACK" )
                    ship.reactToAIMessage("ENEMY_FIRE");
                else ship.AIState = "ATTACK"; //no more inject
            }
        }
    }
    
    this._findPlayerHostiles = function(caller) {
        if (this.$debug) log(this.name, this.ship.displayName+" _findPlayerHostiles called from "+caller);
        this._findPlayerHostiles2(this.ship);
    }
    
    this._findPlayerHostiles2 = function(ship) {
        var p = player.ship;
        if( !p || !p.isValid ) return;
        var sc = ship.script;
    
        if( !sc._inEscortFleet2(ship) ) { //follow but not defend the player, just himself
            ship.target = null;
            if (this.$debug) log("escortdeck_escortdeck", ship.displayName+" - findPlayerHostiles: not acting as escort");
            ship.reactToAIMessage("NO_HOSTILE_FOUND");
        }
        if (this.$debug) log("escortdeck_escortdeck", ship.displayName+" - findPlayerHostiles: player's target: "+(p.target ? p.target.displayName+" at "+ship.position.distanceTo(p.target).toFixed(1) : "None"));
    
        if (ship.target && ship.target.isValid && sc._isHostileToPlayer(ship.target)) {
            if (this.$debug) log("escortdeck_escortdeck", ship.displayName+" - findPlayerHostiles: keeping the current target: "+ship.target.displayName+", at "+ship.position.distanceTo(ship.target).toFixed(1));
            sc._foundPlayerHostile2( ship.target, ship ); //keep the current target
            return;
        }
    
        var hostile = sc._findPlayerNearestHostile2(ship);
        if (hostile) {
            if (this.$debug) log("escortdeck_escortdeck", ship.displayName+" - findPlayerHostiles: targeting hostile closest to player: "+hostile.displayName+" at "+ship.position.distanceTo(hostile));
            sc._foundPlayerHostile2(hostile, ship);
        } else {
            var t = sc._playerTarget2(ship); //follow target changes of player
            if (this.$debug) log("escortdeck_escortdeck", ship.displayName+" - findPlayerHostiles: :_playerTarget2 returned "+t);
            if (t && t.isValid) {
                if (this.$debug) log("escortdeck_escortdeck", ship.displayName+" - findPlayerHostiles: targeting player's target: "+t.displayName);
                sc._foundPlayerHostile2(t, ship);
                return;
            }
    
            if (this.$debug) log("escortdeck_escortdeck", ship.displayName+" - findPlayerHostiles: No hostiles found");
            ship.target = p;
            ship.reactToAIMessage("NO_HOSTILE_FOUND");
        }
    }
    
    this._findPlayerNearestHostile = function() {this._findPlayerNearestHostile2(this.ship);}
    
    this._findPlayerNearestHostile2 = function(ship) {
        var sc = ship.script;
        var pship = player.ship;  
        var hostile = null;
        var distance, distance_tmp;
        var targets = pship.checkScanner(true);//much faster than filteredEntities
        if (targets && targets.length > 0) {
            var i = targets.length;
            while (i--) {
                var t = targets[i];
                if( t && t.isValid && sc._isHostileToPlayer( t ) ) {
                    distance_tmp = t.position.distanceTo(pship);
                    if (!distance || (distance && distance > distance_tmp)) {
                        distance = distance_tmp;
                        hostile = t;
                    }
                }
            }
        }
        return(hostile);
    }
    
    this._fireCheck = function() {this._fireCheck2(this.ship);}
    
    this._fireCheck2 = function(ship) {
        var t = ship.target;
        if( !t || !t.isValid || t.isPlayer || (t.script && ( t.script.$EscortDeckUsable > 0
            || t.script.$CarriersShipStorageID > -1 ) ) ) {
            if (this.$debug) log(this.name, ship.displayName+" _fireCheck, FRIENDLY_FIRE, target: "+t.displayName+", escort:"+t.script.$EscortDeckUsable+", carrierEscort:"+t.script.$CarriersShipStorageID);
            ship.target = null;
            ship.reactToAIMessage("FRIENDLY_FIRE");
        } else {
            if (this.$debug) log(this.name, ship.displayName, " energy "+ship.energy+", _fireCheck, ENEMY_FIRE, target: "+t.displayName+" at "+ship.position.distanceTo(t));
            ship.reactToAIMessage("ENEMY_FIRE");
        }
    }
    
    this._fleeCheck = function() {this._fleeCheck2(this.ship);}
    
    this._fleeCheck2 = function(ship) {
        var e = ship.energy;
        var m = ship.maxEnergy;
        if( (!ship.target || !ship.target.isMissile) && e > m * 0.8 ) {
            //not a missile run and recovered 80% energy
            ship.AIState = "GOTO";
        }
    }
    
    this._foundPlayerHostile = function( hostile ) {this._foundPlayerHostile2(hostile, this.ship);}
    
    this._foundPlayerHostile2 = function( hostile, ship ) {
        var sc = ship.script;
    
        if( !hostile || !hostile.isValid) {
            ship.AIState = "FOLLOW"; //find new target
            return;
        }
        
        ship.target = hostile;
        ship.savedCoordinates = hostile.position;
        var sniper = false;
        if( sc._isSniper2(ship) ) {
            if (this.$debug) log("escortdeck_escortdeck", ship.displayName+" - foundPlayerHostile: "+hostile.displayName+" at "+ship.position.distanceTo(hostile)+", SNIPING");
            ship.reactToAIMessage("SNIPING");
        } else if(sc._Inject2(hostile, ship)) {
            if (this.$debug) log("escortdeck_escortdeck", ship.displayName+" - foundPlayerHostile: "+hostile.displayName+" at "+ship.position.distanceTo(hostile)+", HOSTILE_FAR");
            ship.reactToAIMessage("HOSTILE_FAR"); //inject
        } else {
            if (this.$debug) log("escortdeck_escortdeck", ship.displayName+" - foundPlayerHostile: "+hostile.displayName+" at "+ship.position.distanceTo(hostile)+", HOSTILE_FOUND");
            ship.reactToAIMessage("HOSTILE_FOUND"); //attack
        }
    }
    
    this._inEscortFleet = function() {this._inEscortFleet2(this.ship);}
    
    this._inEscortFleet2 = function(ship) { //if not then follow but not defend the player, just himself
        var w = worldScripts.escortdeck;
        if( w.$EscortDeckShip.indexOf(ship) > -1 ) return true;
        return false;
    }
    
    this._isHostileToPlayer = function(entity) {
        return (entity.isThargoid || (entity.isShip && !entity.isPlayer && entity.target
            && !entity.isDerelict && ( entity.target == player.ship && entity.hasHostileTarget
                || entity.script && entity.script.$EscortDeck_Target )
            && ( !entity.script || !entity.script.$CarriersShipStorageID //is not owned by player
                || entity.script.$CarriersShipStorageID == -1
                || !( entity.script.$EscortDeckUsable > 0 ))));
    }
    
    this._isSniper = function() {this._isSniper2(this.ship);}
    
    this._isSniper2 = function(ship) {
        var l = ship.forwardWeapon.equipmentKey;
        if( l === "EQ_WEAPON_MILITARY_LASER" ) return true;
        else if( l ) {  //Oolite 1.81 can define custom lasers
            var weapon_info = ship.forwardWeapon.weaponInfo;
            if (weapon_info && weapon_info.range > 15000 ) return true;
        }
        return false;
    }
    
    this._Inject = function(target) {this._Inject2(target, this.ship);}
    
    this._Inject2 = function(target, ship) { //can use injectors and farther than 10km?
        if(ship && target && target.isValid && ship.fuel > 0
            && ship.equipmentStatus("EQ_FUEL_INJECTION") === "EQUIPMENT_OK"
            && ship.position.distanceTo(target) > 10000 ) {
            //more conditions: not enogh good accuracy, faster target or short range laser?
            if ( ship.accuracy < 7.5 || target.speed > ship.maxspeed )
                return true;
            var l = ship.forwardWeapon.equipmentKey;
            if( l === "EQ_WEAPON_PULSE_LASER" ) return true;
            else if( l ) {  //Oolite 1.81 can define custom lasers
                var weapon_info = ship.forwardWeapon.weaponInfo;
                if (weapon_info && weapon_info.range < 15000 ) return true;
            }
        }
        return false;
    }
    
    this._playerTarget = function() {this._playerTarget2(this.ship);}
    
    this._playerTarget2 = function(ship) {
        var t = player.ship.target;
        if( t && t.isValid && !t.isDerelict && ( !t.script || !( t.script.$EscortDeck_Usable > 0 ) )
            && ( t.bounty > 2 || t.script && t.script.$EscortDeck_Target ) && //nonclean or player fired it
            t.scanClass !== "CLASS_CARGO" // avoid escape capsules
    //        && ship.position.distanceTo(t.position) < ship.scannerRange )
          )
            return(t);
        return(null);
    }
    
    
    //ship script events
    this.shipCollided = function(otherShip) {
        var w = worldScripts.escortdeck;
        if( otherShip == player.ship && this.ship.energy < 50
            && !this.ship.addCollisionException //before Oolite 1.81
            && w.$EscortDeckShip.indexOf(this.ship) > -1 )
            this.ship.energy = 50; //prevent kill owned escorts by accident
    }
    
    this.shipTargetDestroyed = function(target) {
        if(this.ship == player.ship) return; //exit if worldScript, do in ship script only
        if(target.primaryRole == "constrictor" && missionVariables.conhunt && missionVariables.conhunt == "STAGE_1") {
            // just in case an escort kills the constrictor, let's not break the mission for the player...
            missionVariables.conhunt = "CONSTRICTOR_DESTROYED";
        }
        
        if(target.isRock || target.isBoulder || target.isCargo || target.isDerelict || target.isWeapon)
            return;
        
    //    player.score += 1; - score should remain personal
        player.credits += target.bounty;
        if( this.$EscortDeckUsable > 0 || this.$CarriersShipStorageID > -1 )
            player.consoleMessage(this.ship.displayName+" killed "
                + target.name + " : " + target.bounty + "₢ awarded.", 5);
        log(this.name, this.ship.displayName+" killed " + target.name + " : " + target.bounty +"₢");
    }
    
    this.shipDied = function(whom,why) {
        if(this.ship == player.ship) return; //exit if worldScript, do in ship script only
        var w = worldScripts.escortdeck;
    
        //increase steering and acceleration in proportion with the smaller mass on deck
        w.$EscortDeck_SetMaxs( player.ship, w );
    
        var pad = w.$EscortDeckShip.indexOf(this.ship);
        if( pad > -1 ) w.$EscortDeckShipData[pad] = null; //prevent recreation in dock
        
        if( whom && whom.isValid && (this.$EscortDeckUsable > 0
            || this.$CarriersShipStorageID > -1 )) {
            player.commsMessage(this.ship.displayName+" terminated.", 10);
        }
        log(this.name, this.ship.displayName+" terminated by "+whom+" "+why);
    }
    
    Scripts/escortdeck_xlcondition.js
    "use strict";
    this.name        = "escortdeck_xlcondition";
    this.author      = "Norby";
    this.copyright   = "2014 Norbert Nagy";
    this.licence     = "CC BY-NC-SA 4.0";
    this.description = "XL deck equipment can not fit into ships under 400t";
    this.version     = "1.0";
    
    this.allowAwardEquipment = function(eqKey, ship, context)
    {
    	var w = worldScripts.escortdeck;
    	if( context == "scripted" //needed if player launched in an escort from a Carrier
    		|| ship.mass >= w.$EscortDeckXLMass ) return true;
    	else return false;
    }