Back to Index Page generated: May 8, 2024, 6:16:03 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.8 1.3.8
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/b/b3/ShipRespray_1.3.8.oxz n/a
    License CC-BY-NC-SA 4.0 CC-BY-NC-SA 4.0
    File Size n/a
    Upload date 1700192370

    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.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
    phkb_gecko_10_player
    phkb_gecko_11_player
    phkb_gecko_12_player
    phkb_gecko_13_player
    phkb_gecko_14_player
    phkb_gecko_15_player
    phkb_gecko_16_player
    phkb_gecko_17_player
    phkb_gecko_18_player
    phkb_gecko_19_player
    phkb_gecko_1_player
    phkb_gecko_20_player
    phkb_gecko_21_player
    phkb_gecko_22_player
    phkb_gecko_23_player
    phkb_gecko_24_player
    phkb_gecko_25_player
    phkb_gecko_26_player
    phkb_gecko_27_player
    phkb_gecko_2_player
    phkb_gecko_3_player
    phkb_gecko_4_player
    phkb_gecko_5_player
    phkb_gecko_6_player
    phkb_gecko_7_player
    phkb_gecko_8_player
    phkb_gecko_9_player
    phkb_krait_10_player
    phkb_krait_11_player
    phkb_krait_12_player
    phkb_krait_13_player
    phkb_krait_14_player
    phkb_krait_15_player
    phkb_krait_16_player
    phkb_krait_17_player
    phkb_krait_18_player
    phkb_krait_19_player
    phkb_krait_1_player
    phkb_krait_20_player
    phkb_krait_21_player
    phkb_krait_22_player
    phkb_krait_23_player
    phkb_krait_24_player
    phkb_krait_25_player
    phkb_krait_26_player
    phkb_krait_27_player
    phkb_krait_2_player
    phkb_krait_3_player
    phkb_krait_4_player
    phkb_krait_5_player
    phkb_krait_6_player
    phkb_krait_7_player
    phkb_krait_8_player
    phkb_krait_9_player
    phkb_mamba_10_player
    phkb_mamba_11_player
    phkb_mamba_12_player
    phkb_mamba_13_player
    phkb_mamba_14_player
    phkb_mamba_15_player
    phkb_mamba_16_player
    phkb_mamba_17_player
    phkb_mamba_18_player
    phkb_mamba_19_player
    phkb_mamba_1_player
    phkb_mamba_20_player
    phkb_mamba_21_player
    phkb_mamba_22_player
    phkb_mamba_23_player
    phkb_mamba_24_player
    phkb_mamba_25_player
    phkb_mamba_26_player
    phkb_mamba_27_player
    phkb_mamba_2_player
    phkb_mamba_3_player
    phkb_mamba_4_player
    phkb_mamba_5_player
    phkb_mamba_6_player
    phkb_mamba_7_player
    phkb_mamba_8_player
    phkb_mamba_9_player
    phkb_mamba_escort_10_player
    phkb_mamba_escort_11_player
    phkb_mamba_escort_12_player
    phkb_mamba_escort_13_player
    phkb_mamba_escort_14_player
    phkb_mamba_escort_1_player
    phkb_mamba_escort_2_player
    phkb_mamba_escort_3_player
    phkb_mamba_escort_4_player
    phkb_mamba_escort_5_player
    phkb_mamba_escort_6_player
    phkb_mamba_escort_7_player
    phkb_mamba_escort_8_player
    phkb_mamba_escort_9_player
    phkb_sidewinder_10_player
    phkb_sidewinder_11_player
    phkb_sidewinder_12_player
    phkb_sidewinder_13_player
    phkb_sidewinder_14_player
    phkb_sidewinder_15_player
    phkb_sidewinder_16_player
    phkb_sidewinder_17_player
    phkb_sidewinder_18_player
    phkb_sidewinder_19_player
    phkb_sidewinder_1_player
    phkb_sidewinder_20_player
    phkb_sidewinder_21_player
    phkb_sidewinder_22_player
    phkb_sidewinder_23_player
    phkb_sidewinder_24_player
    phkb_sidewinder_25_player
    phkb_sidewinder_26_player
    phkb_sidewinder_27_player
    phkb_sidewinder_28_player
    phkb_sidewinder_29_player
    phkb_sidewinder_2_player
    phkb_sidewinder_30_player
    phkb_sidewinder_31_player
    phkb_sidewinder_32_player
    phkb_sidewinder_33_player
    phkb_sidewinder_34_player
    phkb_sidewinder_35_player
    phkb_sidewinder_36_player
    phkb_sidewinder_37_player
    phkb_sidewinder_38_player
    phkb_sidewinder_39_player
    phkb_sidewinder_3_player
    phkb_sidewinder_40_player
    phkb_sidewinder_41_player
    phkb_sidewinder_42_player
    phkb_sidewinder_43_player
    phkb_sidewinder_44_player
    phkb_sidewinder_45_player
    phkb_sidewinder_4_player
    phkb_sidewinder_5_player
    phkb_sidewinder_6_player
    phkb_sidewinder_7_player
    phkb_sidewinder_8_player
    phkb_sidewinder_9_player
    phkb_sidewinder_escort_10_player
    phkb_sidewinder_escort_11_player
    phkb_sidewinder_escort_12_player
    phkb_sidewinder_escort_13_player
    phkb_sidewinder_escort_14_player
    phkb_sidewinder_escort_15_player
    phkb_sidewinder_escort_16_player
    phkb_sidewinder_escort_17_player
    phkb_sidewinder_escort_18_player
    phkb_sidewinder_escort_1_player
    phkb_sidewinder_escort_2_player
    phkb_sidewinder_escort_3_player
    phkb_sidewinder_escort_4_player
    phkb_sidewinder_escort_5_player
    phkb_sidewinder_escort_6_player
    phkb_sidewinder_escort_7_player
    phkb_sidewinder_escort_8_player
    phkb_sidewinder_escort_9_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.licence     = "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.licence     = "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) {
    
    	log(this.name, "got here");
    	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;
    		}
    	}
    }