Back to Index Page generated: May 18, 2025, 8:34:31 AM

Expansion Ship Respray

Content

Manifest

from Expansion Manager's OXP list from Expansion Manifest
Description Resprays your ship using one of the installed OXP styles Resprays your ship using one of the installed OXP styles
Identifier oolite.oxp.phkb.ShipRespray oolite.oxp.phkb.ShipRespray
Title Ship Respray Ship Respray
Category Ambience Ambience
Author phkb phkb
Version 1.3.9 1.3.9
Tags
Required Oolite Version
Maximum Oolite Version
Required Expansions
  • oolite.oxp.CaptMurphy.ShipStorageHelper:0.24
  • oolite.oxp.CaptMurphy.ShipStorageHelper:0.24
  • Optional Expansions
    Conflict Expansions
    Information URL https://wiki.alioth.net/index.php/Ship_Respray_OXP n/a
    Download URL https://wiki.alioth.net/img_auth.php/e/e0/ShipRespray_1.3.9.oxz n/a
    License CC-BY-NC-SA 4.0 CC-BY-NC-SA 4.0
    File Size n/a
    Upload date 1747150297

    Documentation

    Also read http://wiki.alioth.net/index.php/Ship%20Respray

    readme.txt

    Ship Respray
    by Nick Rogers
    
    About this OXP
    ==============
    This OXP provides a way to quickly switch the style of your ship, using whatever OXP player templates you have installed of the same class type. That is, if you are flying a Cobra Mark III, and you have Z_Groovy's Variety Packs installed, you can quickly and easily give your ship a new paint job, without having to mess about in the save game file.
    
    There are a couple of variations for the Cobra Mark III, Cobra Mark I and the Python that come with Oolite by default, but not that many. For this OXP to do something really useful you need to have some extra ship OXP's installed. For instance:
    	http://wiki.alioth.net/index.php/Griff_Industries
    	http://wiki.alioth.net/index.php/Staer9%27s_Shipset
    	http://wiki.alioth.net/index.php/No_Shaders_alternate_or_extra_ships_and_accessories#Dertien.27s_Griff_repaint_Variety
    	http://wiki.alioth.net/index.php/Deepspace_Ships
    	gsagostinho's Texture Packs
    You can get a lot of additional shipsets from the download manager inside Oolite.
    	
    Once this OXP is installed a menu will appear on the ship outfitting screen, labelled "Ship Respray". When you select this, a new screen will appear, listing all the available resprays for your ship. Select one of these, and you can then see what the respray will look like on your ship. Some ship models use the "entityPersonality" to adjust the look of the ship. You can select a random personality by selecting "Change personality". 
    
    Once you have selected a model, and found a personality look-and-feel you like, to go ahead with the paint job select "Purchase this respray" from the menu. The amount of time it will take to do the respray is showing on the menu item, and can be anywhere from 24 to 60 hours, depending on the size of the vessel.
    
    This OXP uses the ShipStorageHelper to update the players ship to the selected model.
    
    This OXP is slightly different to the "Respray for Griffs" OXP by Capt Murphy, as this will work with OXP's other than Griffs, and in no-shader mode.
    
    3rd Party Interfaces
    ====================
    As mentioned, this OXP uses the ShipStorageHelper OXP to switch the player ship between different ship variants. Howveer, there are times when SSH doesn't know of some important configuration setting specific to your OXP. To overcome this scenario and allow an OXP to perform specialised operations before or after the player's ship is stored using the Ship Storage Helper, the following interfaces are provided:
    
    	var sr = worldScripts.ShipRespray;
    	sr.$addPreSprayCall("my_worldscript_name", "my_functionname");
    	sr.$addPostSprayCall("my_worldscript_name", "my_functionname");
    
    The "$addPreSprayCall" adds a worldScript/functionName combination to the list of functions that will be called prior to the respray.
    The "$addPostSprayCall" adds a worldScript/functionName combination to the list of functions that will be called after the respray.
     
    Licence
    =======
    This work is licensed under the Creative Commons Attribution-Noncommercial-Share Alike 4.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/
    
    Image from https://icons8.com/
    Icons made by http://www.freepik.com from https://www.flaticon.com licensed by Creative Commons BY 3.0 (http://creativecommons.org/licenses/by/3.0/)
    
    Version History
    ===============
    1.3.9
    - Removed player versions of "Factory Paint Jobs" Gecko, Krait, Mamba and Sidewinder texture packs.
    
    1.3.8
    - Added player versions of "Factory Paint Jobs" Gecko, Krait, Mamba and Sidewinder texture packs.
    
    1.3.7
    - Added missing ";" to shipdata-overrides.plist and descriptions.plist.
    
    1.3.6
    - Fixed issue with number of items on the page with "big GUI" enabled pushing heading text off page.
    - Normalised price of ZGroovy's Cobra Mark I and Cobra Mark III.
    
    1.3.5
    - Ensured that a respray of your ship doesn't result in a loss of memory of player's actions.
    - Added link to Home System OXP.
    
    1.3.4
    - Added better integration with New Lasers OXP (LMSS required).
    
    1.3.3
    - Added the ability to stop the ship model spinning, to make it easier to view a paint job.
    - Removed the shipyard.plist entry for the Python Blackdog.
    - Changed background overlay image.
    
    1.3.2
    - Removed the Python Blackdog from being an equivalent to the standard Python (it has a slightly different body shape).
    - Cleaned up the background overlay image.
    
    1.3.1
    - Turned off some debug messages.
    - Limited respraying services from being offered at rock hermits, as it doesn't seem likely they'd offer the service.
    
    1.3.0
    - Added routines to allow 3rd party OXP's to have functions called both pre and post a respray, in case specialised setup needs to be performed before or after changing ships.
    
    1.2.2
    - Updated check for "Allow Big GUI".
    - Fixed error when leaving the ship respray selection screen without making a selection.
    - Added a "job completed" message.
    - Changed "==" comparisons to "===" for performance improvements.
    - Fixed max_cargo size of oolite-cobra3-alternate-player, which is based on oolite_template_cobra3-alternate, which has a larger than standard max_cargo setting.
    - Fixed various settings for the additional player versions of the Cobra Mk1 and Python ships so they match the standard versions of those ships.
    - Added a shipdata override for some OXP ships to correct some bugs.
    
    1.2.1
    - Small text tweaks.
    - Updated screenID's to enable BGS background sounds.
    - Renamed background overlay image to prevent possibility of future duplication.
    - Toned down overlay image.
    
    1.2.0
    - Added overlay background image to initial interface screen.
    
    1.1.3
    - Added a missing semi-colon in the manifest file.
    - Added routine to use 1.83/4 code to check for big GUI HUD's.
    
    1.1.2
    - Fixed shipdata.plist entries for alternate Cobra Mk3's having reduced max speed and thrust.
    
    1.1.1
    - Added check for "allow_big_gui" HUD's.
    
    1.1.0
    - Added menu option to turn on/off view of ship data keys
    - Improvements to the auto selection of menu items.
    
    1.0.3 
    - Removed ship keys from screen interfaces.
    
    1.0.2
    - Added some extra shipdata.plist and shipyard.plist entries so the player can choose from the built-in variations of the Cobra Mk III, Cobra Mk I, and the Python.
    - Fixed a bug where the current page index was not being reset when opening the respray interface.
    
    1.0.1
    - For Oolite 1.82 and later, added an "installation_time" of 1 second to the equipment item, so opening the respray screen doesn't take any time.
    - For Oolite 1.80, changed the cost of the equipment item to zero, so only 10 minutes of time is taken up when opening the initial page.
    - Added a mass-based installation time calculation to the end of the process. So, respraying a Boa takes longer than respraying an Adder.
    - The "Purchase this respray" option now also includes the installation time.
    - Fixed a bug where doing a respray, buying a new ship and doing another respray could end up with a confused menu.
    - Code cleanup
    
    1.0.0
    - Initial release

    Equipment

    Name Visible Cost [deci-credits] Tech-Level
    Ship Respray no 1000 1+
    Ship Respray no 1000 1+

    Ships

    Name
    noshaders_z_groovy_cobra_Mk3_admiral-PLAYER
    noshaders_z_groovy_cobra_Mk3_alpha-PLAYER
    noshaders_z_groovy_cobra_Mk3_bravo-PLAYER
    noshaders_z_groovy_cobra_Mk3_charlie-PLAYER
    noshaders_z_groovy_cobra_Mk3_delta-PLAYER
    noshaders_z_groovy_cobra_Mk3_echo-PLAYER
    noshaders_z_groovy_cobra_Mk3_flame-PLAYER
    noshaders_z_groovy_cobra_Mk3_foxtrot-PLAYER
    noshaders_z_groovy_cobra_Mk3_golf-PLAYER
    noshaders_z_groovy_cobra_Mk3_hotel-PLAYER
    noshaders_z_groovy_cobra_Mk3_india-PLAYER
    noshaders_z_groovy_cobra_Mk3_juliet-PLAYER
    noshaders_z_groovy_cobra_Mk3_kilo-PLAYER
    noshaders_z_groovy_cobra_Mk3_lima-PLAYER
    noshaders_z_groovy_cobra_Mk3_mike-PLAYER
    noshaders_z_groovy_cobra_Mk3_november-PLAYER
    noshaders_z_groovy_cobra_Mk3_oscar-PLAYER
    noshaders_z_groovy_cobra_Mk3_papa-PLAYER
    noshaders_z_groovy_cobra_Mk3_quebec-PLAYER
    noshaders_z_groovy_cobra_Mk3_romeo-PLAYER
    noshaders_z_groovy_cobra_Mk3_sierra-PLAYER
    noshaders_z_groovy_cobra_Mk3_tango-PLAYER
    noshaders_z_groovy_cobra_Mk3_uniform-PLAYER
    noshaders_z_groovy_cobra_Mk3_victor-PLAYER
    noshaders_z_groovy_cobra_Mk3_whiskey-PLAYER
    noshaders_z_groovy_cobra_Mk3_xray-PLAYER
    noshaders_z_groovy_cobra_Mk3_yankee-PLAYER
    noshaders_z_groovy_cobra_Mk3_zulu-PLAYER
    oolite-cobra3-alternate-player
    oolite-cobra3-pirate-player
    oolite-cobramk1-alternate-player
    oolite-cobramk1-miner-player
    oolite-python-alternate-player

    Models

    This expansion declares no models.

    Scripts

    Path
    Scripts/phkb_gecko_conditions.js
    "use strict";
    this.name = "phkb_gecko_conditions_script";
    this.author = "phkb";
    this.copyright = "2021 phkb";
    this.license = "CC BY-NC-SA 4.0";
    
    this.allowOfferShip = function(shipKey) {
        // only offer certain styles/colours on a regular basis
        var keys = {
            0: [1,2,3,4,5,6,7,8,9],
            1: [10,11,12,13,14,15,16,17,18],
            2: [19,20,21,22,23,24,25,26,27]
        }
        var week = parseInt(clock.daysComponent / 7) % 4;  // will be 0-3
        var period = parseInt(clock.daysComponent / 30) % 3;  // will be 0-2
        var keylist = keys[period];
        var avail = [];
        switch (week) {
            case 0: 
                avail.push(keylist[8]);
                avail.push(keylist[5]);
                break;
            case 1:
                avail.push(keylist[7]);
                avail.push(keylist[4]);
                break;
            case 2:
                avail.push(keylist[6]);
                avail.push(keylist[3]);
                avail.push(keylist[1]);
                break;
            case 3:
                avail.push(keylist[2]);
                avail.push(keylist[0]);
                break;
        }
        for (var i = 0; i < avail.length; i++) {
            if ((shipKey + "E").indexOf("_" + avail[i] + "E") >= 0) return true;
        }
        return false;
    }
    Scripts/phkb_krait_conditions.js
    "use strict";
    this.name = "phkb_krait_conditions_script";
    this.author = "phkb";
    this.copyright = "2021 phkb";
    this.license = "CC BY-NC-SA 4.0";
    
    this.allowOfferShip = function(shipKey) {
        // only offer certain styles/colours on a regular basis
        var keys = {
            0: [1,2,3,4,5,6,7,8,9],
            1: [10,11,12,13,14,15,16,17,18],
            2: [19,20,21,22,23,24,25,26,27]
        }
        var week = parseInt(clock.daysComponent / 7) % 4;  // will be 0-3
        var period = parseInt(clock.daysComponent / 30) % 3;  // will be 0-2
        var keylist = keys[period];
        var avail = [];
        switch (week) {
            case 0: 
                avail.push(keylist[2]);
                avail.push(keylist[3]);
                break;
            case 1:
                avail.push(keylist[7]);
                avail.push(keylist[0]);
                break;
            case 2:
                avail.push(keylist[8]);
                avail.push(keylist[4]);
                avail.push(keylist[5]);
                break;
            case 3:
                avail.push(keylist[6]);
                avail.push(keylist[1]);
                break;
        }
        for (var i = 0; i < avail.length; i++) {
            if ((shipKey + "E").indexOf("_" + avail[i] + "E") >= 0) return true;
        }
        return false;
    }
    Scripts/phkb_mamba_conditions.js
    "use strict";
    this.name = "phkb_mamba_conditions_script";
    this.author = "phkb";
    this.copyright = "2021 phkb";
    this.license = "CC BY-NC-SA 4.0";
    
    this.allowOfferShip = function(shipKey) {
        // only offer certain styles/colours on a regular basis
        var keys = {
            0: [1,2,3,4,5,6,7,8,9],
            1: [10,11,12,13,14,15,16,17,18],
            2: [19,20,21,22,23,24,25,26,27]
        }
        var week = parseInt(clock.daysComponent / 7) % 4;  // will be 0-3
        var period = parseInt(clock.daysComponent / 30) % 3;  // will be 0-2
        var keylist = keys[period];
        var avail = [];
        switch (week) {
            case 0: 
                avail.push(keylist[4]);
                avail.push(keylist[1]);
                break;
            case 1:
                avail.push(keylist[5]);
                avail.push(keylist[2]);
                break;
            case 2:
                avail.push(keylist[6]);
                avail.push(keylist[0]);
                avail.push(keylist[7]);
                break;
            case 3:
                avail.push(keylist[3]);
                avail.push(keylist[8]);
                break;
        }
        for (var i = 0; i < avail.length; i++) {
            if ((shipKey + "E").indexOf("_" + avail[i] + "E") >= 0) return true;
        }
        return false;
    }
    Scripts/phkb_mamba_escort_conditions.js
    "use strict";
    this.name = "phkb_mamba_escort_conditions_script";
    this.author = "phkb";
    this.copyright = "2021 phkb";
    this.license = "CC BY-NC-SA 4.0";
    
    this.allowOfferShip = function(shipKey) {
        // only offer certain styles/colours on a regular basis
        var keys = {
            0: [1,2,3,4,5,6,7],
            1: [8,9,10,11,12,13,14]
        }
        var week = parseInt(clock.daysComponent / 7) % 4;  // will be 0-3
        var period = parseInt(clock.daysComponent / 30) % 2;  // will be 0-1
        var keylist = keys[period];
        var avail = [];
        switch (week) {
            case 0: 
                avail.push(keylist[0]);
                avail.push(keylist[1]);
                break;
            case 1:
                avail.push(keylist[2]);
                avail.push(keylist[3]);
                break;
            case 2:
                avail.push(keylist[4]);
                avail.push(keylist[5]);
                break;
            case 3:
                avail.push(keylist[6]);
                break;
        }
        for (var i = 0; i < avail.length; i++) {
            if ((shipKey + "E").indexOf("_" + avail[i] + "E") >= 0) return true;
        }
        return false;
    }
    Scripts/phkb_sidewinder_conditions.js
    "use strict";
    this.name = "phkb_sidewinder_conditions_script";
    this.author = "phkb";
    this.copyright = "2021 phkb";
    this.license = "CC BY-NC-SA 4.0";
    
    this.allowOfferShip = function(shipKey) {
        // only offer certain styles/colours on a regular basis
        var keys = {
            0: [ 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,0],
            1: [12,13,14,15,16,17,18,19,20,21,22,0],
            2: [23,24,25,26,27,28,29,30,31,32,33,0],
            3: [34,35,36,37,38,39,40,41,42,43,44,45],
        }
        var week = parseInt(clock.daysComponent / 7) % 4;  // will be 0-3
        var period = parseInt(clock.daysComponent / 30) % 4;  // will be 0-3
        var keylist = keys[period];
        var avail = [];
        switch (week) {
            case 0: 
                avail.push(keylist[2]);
                avail.push(keylist[3]);
                avail.push(keylist[9]);
                break;
            case 1:
                avail.push(keylist[8]);
                avail.push(keylist[5]);
                avail.push(keylist[10]);
                break;
            case 2:
                avail.push(keylist[1]);
                avail.push(keylist[7]);
                avail.push(keylist[0]);
                break;
            case 3:
                avail.push(keylist[4]);
                avail.push(keylist[6]);
                avail.push(keylist[11]);
                break;
        }
        for (var i = 0; i < avail.length; i++) {
            if ((shipKey + "E").indexOf("_" + avail[i] + "E") >= 0) return true;
        }
        return false;
    }
    Scripts/phkb_sidewinder_escort_conditions.js
    "use strict";
    this.name = "phkb_sidewinder_escort_conditions_script";
    this.author = "phkb";
    this.copyright = "2021 phkb";
    this.license = "CC BY-NC-SA 4.0";
    
    this.allowOfferShip = function(shipKey) {
        // only offer certain styles/colours on a regular basis
        var keys = {
            0: [1,2,3,4,5,6,7,8,9],
            1: [10,11,12,13,14,15,16,17,18]
        }
        var week = parseInt(clock.daysComponent / 7) % 4;  // will be 0-3
        var period = parseInt(clock.daysComponent / 30) % 2;  // will be 0-1
        var keylist = keys[period];
        var avail = [];
        switch (week) {
            case 0: 
                avail.push(keylist[4]);
                avail.push(keylist[7]);
                break;
            case 1:
                avail.push(keylist[2]);
                avail.push(keylist[0]);
                break;
            case 2:
                avail.push(keylist[6]);
                avail.push(keylist[1]);
                avail.push(keylist[5]);
                break;
            case 3:
                avail.push(keylist[3]);
                avail.push(keylist[8]);
                break;
        }
        for (var i = 0; i < avail.length; i++) {
            if ((shipKey + "E").indexOf("_" + avail[i] + "E") >= 0) return true;
        }
        return false;
    }
    Scripts/respray_conditions.js
    "use strict";
    this.name        = "ShipRespray_Conditions";
    this.author      = "phkb";
    this.copyright   = "2015 phkb";
    this.license     = "CC BY-NC-SA 4.0";
    
    this.allowAwardEquipment = function(equipment, ship, context) {
    
    	// the respray equipment can only happen via the ship outfitting, and the equipment item is removed straight away anyway, so only allow the purchase context
    	if (context != "purchase") return false;
    	if (equipment === "EQ_SHIP_RESPRAY_180" && oolite.compareVersion("1.80") < 0) return false;
    	if (equipment === "EQ_SHIP_RESPRAY" && oolite.compareVersion("1.80") >= 0) return false;
    	// because some ships only have 1 type, but multiple personalities, I've commented this restriction out
    	//if (worldScripts.ShipRespray._data.length <= 1) return false;
    
    	// it's actually unlikely rockhermits would offer repainting services...
    	if (player.ship.dockedStation.hasRole("rockhermit") === true) return false;
    	
    	// otherwise allowed
    	return true;
    }
    
    
    Scripts/shiprespray.js
    "use strict";
    this.name        = "ShipRespray";
    this.author      = "phkb";
    this.copyright   = "2015 phkb";
    this.description = "Allows user to select a respray of their ship using one of the installed OXP ship styles";
    this.license     = "CC BY-NC-SA 4.0";
    
    this._data = [];				// storage array of ship data keys
    this._selectedItem = "";		// currently selected item from the main list
    this._selectedIndex = 0;		// index of the currently selected item
    this._personality = 0;			// current entityPersonality setting
    this._displayType = 0;			// respray gui display mode 0 = main list, 1 = selected item
    this._lastchoice = null;		// last item chosen from the list
    this._curpage = 0;				// current page being shown
    this._pagesize = 16;			// max number of entries on a page
    this._lastsubchoice = "";		// last item chosen from the selected item display
    this._showDetails = false;		// when on, displays the datakey on the list and on the selected item page
    this._baseTime = 48;			// base time, using cobra mk3 as a reference
    this._preCalls = [];
    this._postCalls = [];
    this._spinning = true;
    
    //-------------------------------------------------------------------------------------------------------------
    this.$addPreSprayCall = function(ws, fn) {
    	this._preCalls.push({worldScript:ws, functionName:fn});
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$addPostSprayCall = function(ws, fn) {
    	this._postCalls.push({worldScript:ws, functionName:fn});
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.startUpComplete = function() {
    	this.$compileData();
    	// pick a random personality to start with
    	this._personality = this.$rand(32768) - 1;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.playerBoughtEquipment = function(equipment) {
    	var respray = false;
    	if (equipment === "EQ_SHIP_RESPRAY") {
    		player.ship.removeEquipment("EQ_SHIP_RESPRAY");
    		// for 1.82 and later, refund the credits at this point - if the player buys the respray it will be deducted later
    		player.credits += 100;
    		respray = true;
    	}
    	if (equipment === "EQ_SHIP_RESPRAY_180") {
    		player.ship.removeEquipment("EQ_SHIP_RESPRAY_180");
    		// for 1.80, the cost is already zero.
    		respray = true;
    	}
    	if (respray === true) {
    		// reset the menu before displaying
    		this._spinning = true;
    		this._displayType = 0;
    		this._curpage = 0;
    		this._selectedItem = "";
    		this._selectedIndex = 0;
    		this._lastchoice = null;
    		this._lastsubchoice = null;
    		this.$showPage();
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // get a list of all datakeys available for the players current ship class type (ie. "Cobra Mark III")
    this.$compileData = function() {
    
    	this._data = [];
    
    	// get a list of all ship keys that have a role of "player" (ie. these are playable ships, not NPC ships)
    	var shipKeys = Ship.keysForRole("player");
    	var freq = "";
    	// loop through the list
    	for (var i = 0; i < shipKeys.length; i++) {
    		// get the shipdata entry for this key
    		var shipdata = Ship.shipDataForKey(shipKeys[i]);
    		// does it match the player's current ship?
    		if (shipdata.name === player.ship.shipClassName && !this.$shipScriptInfoHasResprayOff(shipdata)) {
    			// add it to the array if it's not there already
    			if (this.$itemIsInArray(shipKeys[i], this._data) === false) {
    				this._data.push(shipKeys[i]);
    			}
    		}
    	}
    	// sort the array (basic alpha sort)
    	this._data.sort();
    
    }
    
    this.$shipScriptInfoHasResprayOff = function(shipdata) {
    	if (!shipdata.hasOwnProperty("scriptInfo")) return false;
    	if (!shipdata.scriptInfo.hasOwnProperty("respray")) return false;
    	if (!shipdata.scriptInfo.respray) return false;
    	return true;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.playerBoughtNewShip = function(ship, price) {
    	// recompile the list whenever the player buys a new ship
    	this.$compileData();
    }
    
    //=============================================================================================================
    // screen interfaces
    
    //-------------------------------------------------------------------------------------------------------------
    this.$showPage = function() {
    
    	var curChoices = {};
    
    	if (this.$isBigGuiActive() === true) {
    		this._pagesize = 22;
    	} else {
    		this._pagesize = 16;
    	}
    
    	if (this._displayType === 0) {
    
    		var min = (this._curpage * this._pagesize);
    		var max = this._curpage * this._pagesize + this._pagesize;
    		if (max >= this._data.length) max = this._data.length;
    
    		for (var i = min; i < max; i++) {
    			curChoices["01_ITEM_" + (i < 10 ? "0" : "") + i + "~" + this._data[i]] = {text:player.ship.shipClassName + " respray style " + (i + 1).toString() +
    			(this._showDetails === true ? " (" + this._data[i] + ")" : ""), color:"orangeColor", alignment:"LEFT"};
    		}
    
    		if (max - min < this._pagesize) {
    			for (var i = (this._pagesize - (max - min)); i > 0; i--) {
    				curChoices["02_SPACER_" + (i < 10 ? "0" : "") + i] = "";
    			}
    		}
    
    		if (this._showDetails === true) {
    			curChoices["94_HIDEKEYS"] = {text:"Hide data keys"};
    		} else {
    			curChoices["95_SHOWKEYS"] = {text:"Show data keys"};
    		}
    
    		if (this._curpage > 0) {
    			curChoices["96_PREV"] = {text:"Previous page", color:"yellowColor"};
    		} else {
    			curChoices["96_PREV"] = {text:"Previous page", color:"darkGrayColor", unselectable:true};
    		}
    		if (this._data.length > this._pagesize && this._curpage < (Math.ceil(this._data.length / this._pagesize) - 1)) {
    			curChoices["97_NEXT"] = {text:"Next page", color:"yellowColor"};
    		} else {
    			curChoices["97_NEXT"] = {text:"Next page", color:"darkGrayColor", unselectable:true};
    		}
    
    		curChoices["99_EXIT"] = {text:"Exit", color:"yellowColor"};
    
    		var def = "99_EXIT";
    
    		var opts = {
    			screenID: "oolite-shiprespray-main-map",
    			title: player.ship.shipClassName + " Respray",
    			allowInterrupt: true,
    			overlay: {name:"sr-paint.png", height:546},
    			exitScreen: "GUI_SCREEN_EQUIP_SHIP",
    			choices: curChoices,
    			initialChoicesKey: this._lastchoice?this._lastchoice:def,
    			message: "Select a respray style"
    		};
    	}
    
    	if (this._displayType === 1) {
    		var calc = Math.floor((player.ship.mass / 214737.6875) * this._baseTime);
    		if (calc < 24) calc = 24;
    		if (worldScripts.HomeSystem && worldScripts.HomeSystem.$isHomeSystem(system.ID) === true) {
    			calc /= 2;
    		}
    		var def = "98_CLOSE";
    
    		curChoices["02_SELECT"] = {text:"Purchase this respray (100 Cr, " + calc.toFixed(1) + " hours)", color:"yellowColor"};
    		curChoices["03_PERSONALITY"] = {text:"Change personality (" + this._personality + ")", color:"yellowColor"};
    		if (this._spinning === true) {
    			curChoices["04_STOP_SPIN"] = {text:"Stop ship spinning", color:"yellowColor"};
    		} else {
    			curChoices["05_START_SPIN"] = {text:"Start ship spinning", color:"yellowColor"};
    		}
    		curChoices["98_CLOSE"] = {text:"Close", color:"yellowColor"};
    		var opts = {
    			screenID: "oolite-shiprespray-item-map",
    			title: player.ship.shipClassName + " Respray",
    			model:"[" + this._selectedItem + "]",
    			spinModel:this._spinning,
    			modelPersonality: this._personality,
    			exitScreen: "GUI_SCREEN_EQUIP_SHIP",
    			allowInterrupt: true,
    			choices: curChoices,
    			initialChoicesKey: this._lastsubchoice?this._lastsubchoice:def,
    			message: player.ship.shipClassName + " respray style " + (this._selectedIndex + 1).toString() + (this._showDetails === true ? " (" + this._selectedItem + ")" : "")
    		};
    	}
    
    	mission.runScreen(opts, this.$selectScreenHandler, this);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$selectScreenHandler = function(choice) {
    
    	if (choice == null) return;
    		
    	if (choice.indexOf("~") >= 0) {
    		this._displayType = 1;
    		this._selectedItem = choice.substring(choice.indexOf("~") + 1);
    		this._selectedIndex = parseInt(choice.substring(8, 10));
    		this._lastchoice = choice;
    		this._lastsubchoice = null;
    	} else if (choice === "02_SELECT") {
    		// need code to recreate player ship, transferring all cargo and equipment
    		this.$resprayShip(this._selectedItem, this._personality);
    		choice = "99_EXIT";
    	} else if (choice === "04_STOP_SPIN") {
    		this._spinning = false;
    	} else if (choice === "05_START_SPIN") {
    		this._spinning = true;
    	} else if (choice === "03_PERSONALITY") {
    		this._personality = this.$rand(32768) - 1;
    		this._lastsubchoice = "03_PERSONALITY";
    	} else if (choice === "94_HIDEKEYS") {
    		this._showDetails = false;
    		this._lastchoice = "95_SHOWKEYS";
    	} else if (choice === "95_SHOWKEYS") {
    		this._showDetails = true;
    		this._lastchoice = "94_HIDEKEYS";
    	} else if (choice === "96_PREV") {
    		this._curpage -= 1;
    		if (this._curpage > 0) {
    			this._lastchoice = choice;
    		} else {
    			this._lastchoice = "97_NEXT";
    		}
    	} else if (choice === "97_NEXT") {
    		this._curpage += 1;
    		if (this._curpage < (Math.ceil(this._data.length / this._pagesize) - 1)) {
    			this._lastchoice = choice;
    		} else {
    			this._lastchoice = "96_PREV";
    		}
    	} else if (choice === "98_CLOSE") {
    		this._displayType = 0;
    	}
    
    	if (choice != "99_EXIT") {
    		this.$showPage();
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // do the respray, using ShipStorageHelper
    this.$resprayShip = function(dataKey, personality) {
    
    	var p = player.ship;
    	var playershipname = p.shipUniqueName;
    	var hud = p.hud;
    
    	if (this._preCalls.length > 0) {
    		for (var i = 0; i < this._preCalls.length; i++) {
    			var itm = this._preCalls[i];
    			worldScripts[itm.worldScript][itm.functionName]();
    		}
    	}
    
    	// first, store all equipment and cargo the player might have
    	var shipstr = worldScripts["Ship_Storage_Helper.js"].storeCurrentShip();
    
    	// change the datakey to the new one
    	var data = JSON.parse(shipstr);
    	data[1] = dataKey; // apply the data key to the ship
    	data[13] = personality; // apply the selected personality to the ship
    	shipstr = JSON.stringify(data);
    
    	var lmss = worldScripts.LMSS_Core;
    	if (lmss) lmss._switching = true;
    
    	// store the current list of player roles
    	var roles = [];
    	for (var i = 0; i < p.roleWeights.length; i++) {
    		roles.push(p.roleWeights[i]);
    	}
    
    	// restore the players ship
    	worldScripts["Ship_Storage_Helper.js"].restoreStoredShip(shipstr);
    
    	// restore the previous list of roles
    	for (var i = 0; i < p.roleWeights.length; i++) {
    		if (roles.length - 1 >= i) p.setPlayerRole(roles[i], i);
    	}
    
    	if (lmss) lmss._switching = false;
    	
    	// restore some things that the storage helper doesn't replace
    	p.shipUniqueName = playershipname;
    	p.hud = hud;
    
    	// charge the player
    	player.credits -= 100;
    
    	if (this._postCalls.length > 0) {
    		for (var i = 0; i < this._postCalls.length; i++) {
    			var itm = this._postCalls[i];
    			worldScripts[itm.worldScript][itm.functionName]();
    		}
    	}
    	
    	// send an email if the email system is installed
    	var w = worldScripts.EmailSystem;
    	if (w) {
    		var ga = worldScripts.GalCopAdminServices;
    		w.$createEmail({sender:expandDescription("[purchase-maintenance-sender]"),
    			subject:p.shipClassName + " Respray",
    			date:global.clock.seconds,
    			message:expandDescription("[respray_email]", {shipclass:p.shipClassName}),
    			expiryDays:ga._defaultExpiryDays}
    		);
    	}
    
    	// work out how much install time is required
    	// using cobra mass as the benchmark
    	var install = Math.floor((p.mass / 214737.6875) * this._baseTime);
    	if (install < 24) install = 24;
    	// only half the time if this is our home system
    	if (worldScripts.HomeSystem && worldScripts.HomeSystem.$isHomeSystem(system.ID) === true) {
    		install /= 2;
    	}
    
    	install = parseInt(install * 60 * 60);
    
    	mission.runScreen({
    		screenID:"oolite-shiprespray-complete-map",
    		title: player.ship.shipClassName + " Respray",
    		model:"[" + dataKey + "]",
    		modelPersonality: personality,
    		allowInterrupt: true,
    		message: "Your ship has been successfully resprayed.",
    		exitScreen: "GUI_SCREEN_EQUIP_SHIP",
    	});
    
    	global.clock.addSeconds(install);
    
    	// if the station dock control is installed, tell it to check for launched ships
    	if (worldScripts.StationDockControl) {
    		var w = worldScripts.StationDockControl;
    		if (w._disable === false) {
    			w.$checkForLaunchedShips();
    		}
    	}
    
    	this._displayType = 0;
    	this._lastchoice = null;
    }
    
    //=============================================================================================================
    // helper functions
    
    //-------------------------------------------------------------------------------------------------------------
    // return a random number between 1 and max
    this.$rand = function(max) {
    	return Math.floor((Math.random() * max) + 1)
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // checks if a given element is in the array
    this.$itemIsInArray = function(element, array) {
    	var found = false;
    	if (array != null && array.length > 0) {
    		for (var i = 0; i < array.length; i++) {
    			if (array[i] === element) found = true;
    		}
    	}
    	return found;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns true if a HUD with allowBigGUI is enabled, otherwise false
    this.$isBigGuiActive = function() {
    	if (oolite.compareVersion("1.83") <= 0) {
    		return player.ship.hudAllowsBigGui;
    	} else {
    		var bigGuiHUD = ["XenonHUD.plist", "coluber_hud_ch01-dock.plist"]; 	// until there is a property we can check, I'll be listing HUD's that have the allow_big_gui property set here
    		if (bigGuiHUD.indexOf(player.ship.hud) >= 0) {
    			return true;
    		} else {
    			return false;
    		}
    	}
    }