| Scripts/ShieldCycler.js | "use strict";
this.name           = "Shield Cycler";
this.author         = "Lone_Wolf";
this.copyright      = "(C) 2011-2012, 2014-2015,2025 Lone_Wolf.";
this.licence        = "CC-BY-SA 4.0";
this.description    = "Keeps shield energy aligned with a specific configuration";
this.version        = "2.1.1";
/* Main script for Shield Cycler
 * + functions used in other SC scripts
 * + functions used by/for other oxps
*/
/* All settings that need saving are stored in missionVariables.sc_settings using JSON
   for ease of use & performance the SC scripts internally use the object this._sc_settings / worldScripts["Shield Cycler"]._sc_settings
     
    name                        Type   Comment
    $sc_settings                obj   
        threshold               int    percentage, can be 0-100
        start_configuration     int    number , 0 = disabled, 1 = aft 2 = fwd 3 = equal
        current_cycle_position  int    number, position in the cycle (Equal, Fwd, Equal, Aft, Disabled), first position being 0
        current_configuration   int    number , 0 = disabled, 1 = aft 2 = fwd 3 = equal
        cycler_mask             int    bitmask (size = 4 ) used to keep track which settings are available for cycling
                                       bit order (MSB first) : equal, forward, aft , disabled
                                       bit = 1 : available, 0 not available
        functional              int    percentage, 100 is fully functional, 20 is minimum 
        version                 int    number, 0=none 1 = basic , 2 = standard, 3 =advanced 
        manual_version          int    number, 0=none 1 = basic , 2 = standard, 3 =advanced 
};
  NOTE 1: missionvariables are loaded at startup, then converted into this._sc_setting object .
          during game the missionvariables are NOT kept uptodate.
          they are updated only when player saves game.
  NOTE 2: the settings object cane change from version to version.
          other OXPs that need to interact with Shield Cycler
  equipment items
    EQ_SC_SHIELD_CYCLER_INTERNAL          - present if shield cycler is installed, for backward compatibility
    EQ_SC_SHIELD_CYCLER_BASIC
    EQ_SC_SHIELD_CYCLER_STANDARD
    EQ_SC_SHIELD_CYCLER_ADVANCED
    EQ_SC_MANUAL_CONFIGURATOR_INTERNAL    - present if a manual configurator is installed, for backward compatibility
    EQ_SC_MANUAL_CONFIGURATOR_BASIC
    EQ_SC_MANUAL_CONFIGURATOR_STANDARD
    EQ_SC_MANUAL_CONFIGURATOR_ADVANCED
    EQ_SC_SELL_SHIELD_CYCLER
    EQ_SC_SELL_MANUAL
    EQ_SC_SC_REPAIR
    
    NOTE: EQ_SC_SHIELD_CYCLER_* and EQ_SC_MANUAL_CONFIGURATOR_* will be present in the savefile but NOT visible on the F5 equipment screen
*/
this.$debug = true;
this.$logging = true;
this.$compatibility = true;
// RO stuff, i haven't been able to figure out how to make these read-only, so the values here can be changed by code, but that's not supposed to happen
this.$sc_const = {
    sc_list: ["None","EQ_SC_SHIELD_CYCLER_BASIC","EQ_SC_SHIELD_CYCLER_STANDARD","EQ_SC_SHIELD_CYCLER_ADVANCED"],
    manual_list: ["None","EQ_SC_MANUAL_CONFIGURATOR_BASIC","EQ_SC_MANUAL_CONFIGURATOR_STANDARD","EQ_SC_MANUAL_CONFIGURATOR_ADVANCED"],
    sc_names: ["None", "Basic", "Standard", "Advanced"],
    manual_names: ["None", "Basic", "Standard", "Advanced"],
    modes: { disabled:0, aft:1, forward:2, both:3, count:4 },
    cycle: [ 3, 2, 3, 1, 0 ],
    mode_names: [ "Disabled", "Aft", "Forward", "Equal"],
};
// WORM stuff
this.$sc_worm = {
    sc_cost: [],
    manual_cost: []
};
  
// R/W stuff
this.$sc_settings = {
        threshold: 100,
        current_cycle_position: 0,
        start_configuration: this.$sc_const.cycle[0],
        current_configuration: this.$sc_const.cycle[0],
        cycler_mask: 0x00f,
        functional: 100,
        version: 0,
        manual_version: 0,
};
this.$sc_disabled = false;
// for backwards compatibility, it's not used anymore
// (some OXP might be acessing it)
// it will reflect changes to this.$sc_settings
this._sc_settings = {
        sc : this.$sc_settings,
        general : {
            disabled : this.$sc_settings.current_configuration === 0,
            sc_disabled : this.$sc_settings.current_configuration === 0,
            savegame_version : 0
        }
};
this._sc_const = this.$sc_const;
//
// Properties for internal use
//
this.$sc_cycler_loss = [
    // power loss for no cycler, basic, standard, advanced (one sbu-array for each)
    // each sub-array holds energy cost in % of energy transfer
    // indexes on the sub-arrays based on <energy transfer> / <energy bank size>
        [ 0, 0, 0, 0, 0, 0 ], // no cycler present, so no power loss
        [ 0.0600, 0.0840, 0.1176, 0.1646, 0.2305, 0.3227 ], // basic cycler
        [ 0.0450, 0.0585, 0.0760, 0.0989, 0.1285, 0.1671 ], // standard cycler
        [ 0.0300, 0.0360, 0.0432, 0.0518, 0.0622, 0.0746 ], // advanced cycler
];
// powerloss multiplier for manual configurators
// none, basic, standard, advanced
this.$sc_manual_reduction = [ 1, 1, 0.50, 0.20];
    
this._sc_error = 0;
    /* used for error tracking
    * 0 = no error
    * 1 = savegame to old
    * 2 = savegame to new
    */
this._sc_display_status_title =  "Shield Cycler Status";
// for Lib_Config 
this._scConfig =  {
        Name:           "Shield Cycler",
        Display:        "Shield Cycler Config",
        Alive:          "_scConfig",
        Bool:   {
                    B0: {
                            Name:   "$sc_disabled",
                            Desc:   "disable SC OXP.",
                            Def:    false, 
                            Hide:   false,
                            Notify: "_scChange_disabled",
                        },
                    Info:   "enable/disable Shield Cycler oxp functionality"
                },
        SInt:   {
                    S0: {
                            Name:   "$sc_settings.threshold",
                            Desc:   "threshold %",
                            Def:100, Min:0, Max:100, 
                            Hide:   false,
                            Notify: "_scChange_threshold",
                        },
                    S1: {
                            Name:   "$sc_settings.start_configuration",
                            Desc:   "launch setting",
                            Def:3, Min:0, Max:3, 
                            Hide:   false,
                            Notify: "_scChange_start_configuration",
                        },
                    Info:   "Threshold % determines when adjusting is done. 100=always, 0=never.\nStart configuration is the configuration that will be set upon launch (3=equal, 2=fwd, 1=aft, 0=disabled)."
                },
        EInt:   {
                    E0: {
                            Name:   "$sc_settings.cycler_mask",
                            Desc:   ["Disabled","Aft","Forward","Equal"],
                            Def:0x001, Min:0x000, Max:0x00f, 
                            Hide:   false,
                            Notify: "_scChange_cycler_mask",
                        },
                    Info:   "enable/disable the availability of the settings for manual cycling. Note: if you disable all, Shield Cycler oxp will enable the disabled setting."
                }
}
//
//  Event Handlers
//
//--------------------------------------------------------------------------------------------------//
this.startUp = function _sc_startUp() {
    var wsc = worldScripts["Shield Cycler"];
    var ship = player.ship;
    var i, list;
    this.ship = ship;
    if (missionVariables.sc_settings) { 
        let ret = wsc._parseSavedSettings(missionVariables.sc_settings);
        if (ret) 
            log(this.name, "missionVariables sc_settings has invalid JSON string: "+missionVariables.sc_settings);
        delete missionVariables.sc_settings;
    } else {
        wsc._resetSettings(wsc.$sc_settings);
        wsc.$sc_settings.version = 0;
        wsc.$sc_settings.manual_version = 0;
    }
    // set Lib_Config settings visibility
    wsc._setLibConfigVisibility();
    // calculate price values
    list = wsc.$sc_const.sc_list;
    i = list.length;
    while (i--) 
        if (i === 0)
            wsc.$sc_worm.sc_cost[i] = 0;
        else
            wsc.$sc_worm.sc_cost[i] = EquipmentInfo.infoForKey(list[i]).price;
    list = wsc.$sc_const.manual_list;
    i = list.length;
    while (i--) 
        if (i === 0)
            wsc.$sc_worm.manual_cost[i] = 0;
        else
            wsc.$sc_worm.manual_cost[i] = EquipmentInfo.infoForKey(list[i]).price;
    if (this.$logging) log(wsc.name, "Shield Cycler completed settings: "+JSON.stringify(wsc.$sc_settings));
}
//--------------------------------------------------------------------------------------------------//
this.startUpComplete = function _sc_startUpComplete() {
    var wsc = worldScripts["Shield Cycler"];
    wsc._sc_update_status();
    if (worldScripts.Lib_Config) {
        var ret = worldScripts.Lib_Config._registerSet(wsc._scConfig);
        if (this.$debug) log(wsc.name, "Lib_Config regsterSet returned "+ret);
    }
}
//--------------------------------------------------------------------------------------------------//
this.playerWillSaveGame = function _sc_playerWillSaveGame() {
    missionVariables.sc_settings = JSON.stringify(worldScripts["Shield Cycler"]._sc_settings);
};
//--------------------------------------------------------------------------------------------------//
this.playerBoughtNewShip = this.playerReplacedShip = function _sc_playerReplacedShip(ship) {// player ship only
    var wsc = worldScripts["Shield Cycler"];
    this.ship = player.ship;
    // sell shieldcycler equipment if still present and reset settings 
    if (this.$sc_settings.version !== 0) {
        log(this.name, "Player's got a new ship, removing Shield Cycler equipments");
        if (this.$sc_settings.manual_version !== 0) 
            wsc._sc_remove_manual();
        wsc._sc_remove_shield_cycler();
        wsc._sc_update_status();
    }
}
//--------------------------------------------------------------------------------------------------//
this.equipmentDamaged = this.equipmentDestroyed = function _sc_equipmentDamaged(eqKey) {// NPC-ready
    // can be called from NPCs ship.script if 'this' is made to be the ship's script
    var wsc = worldScripts["Shield Cycler"];
 
    if (eqKey === wsc.$sc_const.sc_list[this.$sc_settings.version] || eqKey === wsc.$sc_const.manual_list[this.$sc_settings.manual_version]) {
        player.commsMessage("Shield Cycler devices taking damage, functionality is reduced",6);
        this.ship.setEquipmentStatus(EquipmentInfo.infoForKey(eqKey), "EQUIPMENT_OK");
        if (this.$sc_settings.functional >= 50 )
            this.$sc_settings.functional -= 10;
        else
            this.$sc_settings.functional = 40;
        if (this.$logging) log(wsc.name, "Shield Cycler devices taking damage, functionality is reduced to"+this.$sc_settings.functional+"%");
    }
}
//--------------------------------------------------------------------------------------------------//
this.playerBoughtEquipment = function _sc_playerBoughtEquipment(eqKey) {// player ship only
    var wsc = worldScripts["Shield Cycler"];
    if (wsc.$sc_const.sc_list.indexOf(eqKey) >= 0) {
        // SC device proper (EQ_SHIELD_CYCLER_BASIC, EQ_SHIELD_CYCLER_STANDARD, EQ_SHIELD_CYCLER_ADVANCED)
        wsc._sc_equipment_setup(eqKey);
    } else if (wsc.$sc_const.manual_list.indexOf(eqKey) >= 0) {
        // SC Manual Configurator (EQ_SC_MANUAL_CONFIGURATOR_BASIC, EQ_SC_MANUAL_CONFIGURATOR_STANDARD, EQ_SC_MANUAL_CONFIGURATOR_ADVANCED)
        wsc._sc_equipment_setup(eqKey);
    } else if (eqKey === "EQ_SC_SELL_SHIELD_CYCLER") {
        player.credits += wsc._sc_remove_shield_cycler();
        wsc._sc_update_status();
    } else if (eqKey === "EQ_SC_SELL_MANUAL_CONFIGURATOR") {
        player.credits += wsc._sc_remove_manual();
        wsc._sc_update_status();
    } else if (eqKey === "EQ_SC_SC_REPAIR") {
        wsc.$sc_settings.functional = 100;
        this.ship.removeEquipment(equipmentKey);
        wsc._sc_update_status();
    }
}
//--------------------------------------------------------------------------------------------------//
this.shipLaunchedFromStation = function _sc_shipLaunchedFromStation(station) {// NPC-ready
    var wsc = worldScripts["Shield Cycler"];
    var sc_eqs = wsc._sc_get_sc_versions(null);
    if (wsc.$sc_settings.version !== sc_eqs.version) {
        log(this.name, this.ship.displayName+" settings ("+wsc.$sc_settings.version+"/"+wsc.$sc_settings.manual_version+") don't match installed equipments ("+sc_eqs.version+"/"+sc_eqs.manual_version+"), adjusting settings");
        wsc.$sc_settings.version = sc_eqs.version;    
        wsc.$sc_settings.manual_version = sc_eqs.manual_version;
    }
    if (wsc.$sc_disabled) return;
    // check if player has a Shield Cycler
    if (this.$sc_settings.version === 0) return;
    if (this.$sc_settings.current_configuration) 
        // don't change it if it's 0:Disabled
        this.$sc_settings.current_configuration = this.$sc_settings.start_configuration;
    this.$sc_settings.current_cycle_position = wsc.$sc_const.cycle.indexOf(this.$sc_settings.start_configuration);
    wsc._sc_sc_adjust.call(this, true, "Shield Cycler");
}
//--------------------------------------------------------------------------------------------------//
this.shipTakingDamage = function _sc_shipTakingDamage(amount, fromEntity, damageType) {// NPC-ready
    var wsc = worldScripts["Shield Cycler"];
    if (wsc.$sc_disabled) return;
    if (this.$sc_settings.version === 0) return;
    if (this.$logging) log(wsc.name, this.ship.displayName+" taking "+amount.toFixed(1)+" damage of type "+damageType+" from "+fromEntity.displayName);
    wsc._sc_sc_adjust.call(this, false, "Shield Cycler");
}
//--------------------------------------------------------------------------------------------------//
this.shipLaunchedEscapePod = function _sc_shipLaunchedEscapePod(escapepod, passengers) {// player ship only
    var wsc = worldScripts["Shield Cycler"];
    // reset devices / vars when pilot ejects
    if (this.$sc_settings.version === 0) return;
    if (this.$sc_settings.manual_version !== 0) { 
        wsc._sc_remove_manual();
    }
    wsc._sc_remove_shield_cycler();
}
//
// Internal Functions
//
 
//--------------------------------------------------------------------------------------------------//
this._parseSavedSettings = function _sc_parseSavedSettings(saved) {// NPC-ready
    // can be called to configure NPCs if 'this' is made to be the ship's script
    // parse a settings JSON string and initialize the OXP settings object
    // deals with JSON strings in legacy settings format
    var that = _sc_parseSavedSettings;
    var wsc = worldScripts["Shield Cycler"];
    var unchanged_fields = (that.unchanged_fields = that.unchanged_fields || 
                            ["threshold","start_configuration","current_configuration","cycler_mask", "functional","version", "manual_version"]);
    var tmp;
    if (saved && saved.length > 0) {
        try { 
            tmp = JSON.parse(saved);
        } catch (e) {
            log(wsc.name, "JSON exception parsing settings: "+e);
            return 3;
        }
    } else 
        if (wsc.$debug) log(wsc.name, "empty settings JSON string");
    // create settings object in NPC ship's script
    if (!this.$sc_settings) this.$sc_settings = {};
    if (!tmp) {
        // empty settings, assume v2.x
        wsc._resetSettings(this.$sc_settings);
        // "equipments rule"
        wsc._reconcileSettings.call(this, false);
    } else if ("sc" in tmp && !("current_cycle_position" in tmp.sc)) {
        // settings from Shield Cycler v1.x 
        // copy unchanged fields
        if (this.$debug) log(wsc.name, this.ship.displayName+": restoring from v1.x settings");
        wsc._resetSettings(this.$sc_settings);
        var i = unchanged_fields.length;
        while (i--) if (tmp.sc[unchanged_fields[i]]) this.$sc_settings[unchanged_fields[i]] = tmp.sc[unchanged_fields[i]];
        // set changed fields
        this.$sc_settings.current_cycle_position = wsc.$sc_const.cycle.indexOf(wsc.$sc_settings.current_configuration);
        // will not have the actual equipments installed, so "settings rule!"
        wsc._reconcileSettings.call(this, true);
    } else if ("current_cycle_position" in tmp) {
        // settings from Shield Cycler v2.x
        if (this.$debug) log(wsc.name, this.ship.displayName+": restoring from v2.x settings");
        this.$sc_settings = tmp;
        if (typeof this.$sc_settings.version === "string") {
            // deprecated development version of settings
            this.$sc_settings.version =  wsc.$sc_const.sc_list.indexOf(this.$sc_settings.version);
            this.$sc_settings.manual_version =  wsc.$sc_const.sc_list.indexOf(this.$sc_settings.manual_version);
        }
        // already migrated to new version, so "equipments rule!"
        wsc._reconcileSettings.call(this, false);
    } else if ("sc" in tmp && ("current_cycle_position" in tmp.sc)) {
        // v2.x compatibility object
        if (this.$debug) log(wsc.name, this.ship.displayName+": restoring from v2.x compatibility settings");
        this.$sc_settings = tmp.sc;
        if (typeof this.$sc_settings.version === "string") {
            // deprecated development version of settings
            this.$sc_settings.version =  wsc.$sc_const.sc_list.indexOf(this.$sc_settings.version);
            this.$sc_settings.manual_version =  wsc.$sc_const.sc_list.indexOf(this.$sc_settings.manual_version);
        }
        // already migrated to new version, so "equipments rule!"
        wsc._reconcileSettings.call(this, false);
    } else {
        if (this.$debug) log(wsc.name, this.ship.displayName+": no setting to restore, setting None");
        this.$sc_settings.version = this.$sc_settings.manual_version = 0;
        wsc._resetSettings(this.$sc_settings);
        wsc._reconcileSettings.call(this, false);
    }
    if (this.ship == player.ship)
        // copy reference to legacy settings object
        wsc._sc_settings.sc = wsc.$sc_settings;
    return 0;
}
//--------------------------------------------------------------------------------------------------//
this._reconcileSettings = function _sc_reconcileSettings(settings_rule) {// NPC-ready
    // can be called to configure NPCs if 'this' is made to be the ship's script
    var wsc = worldScripts["Shield Cycler"];
    var ship = this.ship;
    var list, i;
    if (settings_rule) {
        // Truth is what's in the settings (legacy of v1.x)
        // remove installed SC equipments if not the one in settings
        if (this.$debug) log(wsc.name, this.ship.displayName+": reconciling settings with 'settings rule' (for legacy settings) :"+JSON.stringify(this.$sc_settings));
        list = wsc.$sc_const.sc_list;
        i = list.length;
        while (i--) 
            if (i > 0 && i !== this.$sc_settings.version && ship.equipmentStatus(list[i]) === "EQUIPMENT_OK") {
                if (this.$logging) log(wsc.name, ship.displayName+" "+list[i]+" is not in Shield Cycler settings, removing");
                ship.removeEquipment(list[i]);
            }
        list = wsc.$sc_const.manual_list;
        i = list.length;
        while (i--) 
            if (i > 0 && i !== this.$sc_settings.manual_version && ship.equipmentStatus(list[i]) === "EQUIPMENT_OK") {
                if (this.$logging) log(wsc.name, ship.displayName+" "+list[i]+" is not in Shield Cycler settings, removing");
                ship.removeEquipment(list[i]);
            }
        // award the correct ones
        if (this.$sc_settings.version !== 0) ship.awardEquipment(wsc.$sc_const.sc_list[this.$sc_settings.version]);
        if (this.$sc_settings.manual_version !== 0) ship.awardEquipment(wsc.$sc_const.manual_list[this.$sc_settings.manual_version]);
        if (this.$logging) log(wsc.name, this.ship.displayName+": awarded "+wsc.$sc_const.sc_list[this.$sc_settings.version]+" and "+wsc.$sc_const.manual_list[this.$sc_settings.manual_version]);
    } else {
        // Truth is whatever is installed in the ship (v2.x)
        log(wsc.name, this.ship.displayName+": reconciling settings with 'equipments rule' (for v2.x SC)");
        list = wsc.$sc_const.sc_list;
        i = list.length;
        while (i--) 
            if (i > 0 && ship.equipmentStatus(list[i]) === "EQUIPMENT_OK") {
                if (this.$logging) log(wsc.name, this.ship.displayName+": found "+list[i]+", setting it up");
                wsc._sc_equipment_setup.call(this, list[i]);
                break;
            }
        list = wsc.$sc_const.manual_list;
        i = list.length;
        while (i--) 
            if (i > 0 && ship.equipmentStatus(list[i]) === "EQUIPMENT_OK") {
                if (this.$logging) log(wsc.name, this.ship.displayName+": found "+list[i]+", setting it up");
                wsc._sc_equipment_setup.call(this, list[i]);
                break;
            }
        log(wsc.name, this.ship.displayName+": Shield Cycler settings: "+JSON.stringify(this.$sc_settings));
    }
    if (wsc.$compatibility) {
        // awards the deprecated internal equipments for compatibility reasons 
        // (so when saved the ship would still work with v1.x)
        if (this.$sc_settings.version !== 0 && ship.equipmentStatus("EQ_SC_SHIELD_CYCLER_INTERNAL") !== "EQUIPMENT_OK") 
            ship.awardEquipment("EQ_SC_SHIELD_CYCLER_INTERNAL");
        if (this.$sc_settings.manual_version !== 0 && ship.equipmentStatus("EQ_SC_MANUAL_CONFIGURATOR_INTERNAL") !== "EQUIPMENT_OK") 
            ship.awardEquipment("EQ_SC_MANUAL_CONFIGURATOR_INTERNAL");
    }
}
//--------------------------------------------------------------------------------------------------//
this._setLibConfigVisibility = function _sc_setLibConfigVisibility() {// player ship only
    var wsc = worldScripts["Shield Cycler"];
    if (this.ship != player.ship) return;
    wsc._changeConfiguration(1, (this.$sc_settings.version < 2)); 
    wsc._changeConfiguration(2, (this.$sc_settings.version < 3)); 
    wsc._changeConfiguration(3, (this.$sc_settings.manual_version < 3));      
}
//--------------------------------------------------------------------------------------------------//
this._changeConfiguration = function _sc_changeConfiguration(setting, hide) {// player ship only
    // sets/resets visibility of configure options in LibConfig
    var wsc = worldScripts["Shield Cycler"];
    if (this.ship != player.ship) return;
    switch (setting) {
        case 1:
            wsc._scConfig.SInt.S0.Hide = hide;
            break;
        case 2:
            wsc._scConfig.SInt.S1.Hide = hide;
            break;
        case 3:
            wsc._scConfig.EInt.E0.Hide = hide;
            break;
        default:
            break;
    }
}
  
//--------------------------------------------------------------------------------------------------//
this._resetSettings = function _sc_resetSettings(settings) {// NPC-ready
    var wsc = worldScripts["Shield Cycler"];
    settings.threshold = 100;
    settings.current_cycle_position = 0;
    settings.start_configuration = wsc.$sc_const.cycle[0];
    settings.current_configuration = wsc.$sc_const.cycle[0];
    settings.cycler_mask = 0x00f;
    settings.functional = 100;
}
//==================================================================================================//
// 
// Functions for use by other OXPs
//
//--------------------------------------------------------------------------------------------------//
this._sc_get_sc_versions = function _sc_get_sc_versions(versions) {// NPC-ready
    // can be called for NPCs ship.script if 'this' is made to be the ship's script
    // versions == null means get data from installed equipments
    // versions = { sc:<integer>, manual:<integer> } means get the data for those sc equipments
    var wsc = worldScripts["Shield Cycler"];
    var ret;
    var ship = this.ship;
    var sc = {  
                sc_eqKey: null,
                version: 0,
                version_name: "None",
                manual_eqKey: null,
                manual_version: 0,
                manual_version_name: "None" 
            };
    function verify_equipment_list(list) {
        var i;
        i = list.length;
        while (i--)
            if (i > 0 && ship.equipmentStatus(list[i]) === "EQUIPMENT_OK") {
                 return {eqKey: list[i],  version: i};
            } 
        return {eqKey: "None",  version: 0};
    }
    
    if (!versions) {
        // data from installed equipments
        ret = verify_equipment_list(wsc.$sc_const.sc_list);
        sc.sc_eqKey = (ret.eqKey === "None" ? null : ret.eqKey);
        sc.version = ret.version;
        sc.version_name = wsc.$sc_const.sc_names[ret.version];
        ret = verify_equipment_list(wsc.$sc_const.manual_list);
        sc.manual_eqKey = (ret.eqKey === "None" ? null : ret.eqKey);
        sc.manual_version = ret.version;
        sc.manual_version_name = wsc.$sc_const.manual_names[ret.manual_version];
    } else {
        // data from specified equipments
        if (versions.sc < wsc.$sc_const.sc_list.length) {
            sc.sc_eqKey = wsc.$sc_const.sc_list[versions.sc];
            sc.version = versions.sc;
            sc.version_name = wsc.$sc_const.sc_names[sc.version];
        }
        if (versions.manual < wsc.$sc_const.manual_list.length) {
            sc.manual_eqKey = wsc.$sc_const.manual_list[versions.manual];
            sc.manual_version = versions.manual;
            sc.manual_version_name = wsc.$sc_const.manual_names[sc.manual_version];
        }
    }
    return  sc;
}
//--------------------------------------------------------------------------------------------------//
this._sc_award_equipment = function _sc_award_equipment(eqKey) { //NPC-only
    // can be called from NPCs ship.script if 'this' is made to be the ship's script
    var wsc = worldScripts["Shield Cycler"];
    if (this.ship == player.ship) return false;
    var ret;
    var i = wsc.$sc_const.sc_list.indexOf(eqKey);
    if (i >= 0) {
        ret = this.ship.awardEquipment(eqKey);
        if (ret) 
            wsc._sc_equipment_setup.call(this, eqKey);
        if (wsc.$debug) log(wsc.name, this.ship.displayName+" awarding "+eqKey+", success:"+ret);
        return ret;
    } else {
        i = wsc.$sc_const.manual_list.indexOf(eqKey);
        if (i >= 0) {
            ret = this.ship.awardEquipment(eqKey);
            if (ret) 
                wsc._sc_equipment_setup.call(this, eqKey);
            if (wsc.$debug) log(wsc.name, this.ship.displayName+" awarding "+eqKey+", success:"+ret);
            return ret;
        }
    }    
    return false;
}
//--------------------------------------------------------------------------------------------------//
this._sc_equipment_setup = function _sc_equipment_setup(eqKey) { //NPC-ready
    // can be called from NPCs ship.script if 'this' is made to be the ship's script
    // sets the equipment up, but the equipment must already have been awarded to the ship
    var wsc = worldScripts["Shield Cycler"];
    var npc = (this.ship != player.ship);
    if (this.ship.equipmentStatus(eqKey) !== "EQUIPMENT_OK") return;
    var index = wsc.$sc_const.sc_list.indexOf(eqKey);
    if (index >= 0) {
        // SC device proper (EQ_SHIELD_CYCLER_BASIC, EQ_SHIELD_CYCLER_STANDARD, EQ_SHIELD_CYCLER_ADVANCED)
        if (!this.$sc_settings) {
            // NPC setup!
            this.$sc_settings = {};
            wsc._resetSettings(this.$sc_settings);
            this.$sc_settings.version = 0;
            this.$sc_settings.manual_version = 0;
            if (wsc.$compatibility &&  ship.equipmentStatus("EQ_SC_SHIELD_CYCLER_INTERNAL") !== "EQUIPMENT_OK")
                ship.awardEquipment("EQ_SC_SHIELD_CYCLER_INTERNAL");
        }
        this.$sc_settings.version = index;
        wsc._resetSettings(this.$sc_settings);
        if (!npc) wsc._sc_update_status.call(this);
    } else {
        index = wsc.$sc_const.manual_list.indexOf(eqKey);
        if (index >= 0) {
            // SC Manual Configurator (EQ_SC_MANUAL_CONFIGURATOR_BASIC, EQ_SC_MANUAL_CONFIGURATOR_STANDARD, EQ_SC_MANUAL_CONFIGURATOR_ADVANCED)
            this.$sc_settings.manual_version = index;
            if (wsc.$compatibility &&  ship.equipmentStatus("EQ_SC_MANUAL_CONFIGURATOR_INTERNAL") !== "EQUIPMENT_OK")
                ship.awardEquipment("EQ_SC_MANUAL_CONFIGURATOR_INTERNAL");
            if (!npc) wsc._sc_update_status();
        }
    }
}
//--------------------------------------------------------------------------------------------------//
this._sc_remove_manual = function _sc_remove_manual() {// NPC-ready
    // can be called to configure NPCs if 'this' is made to be the ship's script
    // removes manual configurator devices only
    // returns refund value in credits
    var wsc = worldScripts["Shield Cycler"];
    if (this.$sc_settings.manual_version === 0) return 0;
    var refund = wsc.$sc_worm.manual_cost[this.$sc_settings.manual_version];
    refund = refund * this.$sc_settings.functional * 0.01;
    this.ship.removeEquipment(wsc.$sc_const.manual_list[this.$sc_settings.manual_version]);
    this.$sc_settings.manual_version = 0;
    this.$sc_settings.cycler_mask = 0x00f;
    // hide configuration for advanced manual configurator
    if (this.ship == player.ship) wsc._changeConfiguration(3, true); 
    refund = parseInt(refund * 0.1 * 0.6);
    return refund;
}  
//--------------------------------------------------------------------------------------------------//
this._sc_remove_shield_cycler = function _sc_remove_shield_cycler() {// NPC-ready
    // can be called to configure NPCs if 'this' is made to be the ship's script
    // removes all shield cycler devices
    // returns refund value in credits
    var wsc = worldScripts["Shield Cycler"];
    if (this.$sc_settings.version === 0) return 0;
    var refund = 0;
    // if manual configurator devices are present, remove them first
    if (this.$sc_settings.manual_version !== 0)
        refund = wsc._sc_remove_manual.call(this);
    refund += wsc.$sc_worm.sc_cost[this.$sc_settings.version];
    if (this.ship == player.ship)
        wsc._changeConfiguration(this.$sc_settings.version-1, true);
    refund *= this.$sc_settings.functional * 0.01;
    this.ship.removeEquipment(wsc.$sc_const.sc_list[this.$sc_settings.version]);
    this.$sc_settings.version = 0;
    wsc._resetSettings(this.$sc_settings);
    refund = parseInt(refund * 0.1 * 0.6);
    return refund;
}
  
//--------------------------------------------------------------------------------------------------//
this._sc_update_status = function _sc_update_status() {// player ship only
    var wsc = worldScripts["Shield Cycler"];
    var sc_display_status = [];
    var ship = this.ship;
    if (ship != player.ship) return;
    sc_display_status.push(wsc._sc_display_status_title);
    mission.setInstructions(null, "SC Core"); // for old savefiles
    if (this.$sc_settings.version === 0 ||
        ship.equipmentStatus(wsc.$sc_const.sc_list[this.$sc_settings.version]) !== "EQUIPMENT_OK") {
        if (this.$logging) log(wsc.name, "Player ship has no ShieldCycler, cleaning message in Manifest Screen: "+sc_display_status);
        mission.setInstructions(null, "Shield Cycler");
        return; 
    };
    sc_display_status.push("SC version: " + wsc.$sc_const.sc_names[this.$sc_settings.version]);
    sc_display_status.push("Manual Configurator: " + wsc.$sc_const.manual_names[this.$sc_settings.manual_version]);
    sc_display_status.push(" " + this.$sc_settings.functional + "%% functional");
    if (this.$logging) log(wsc.name, "Player ship has ShieldCycler, setting up  message in Manifest Screen: "+sc_display_status);
    mission.setInstructions(sc_display_status, "Shield Cycler");
}
//--------------------------------------------------------------------------------------------------//
this._sc_sc_adjust = function _sc_sc_adjust(init, caller) {// NPC-ready
    // can be called from NPCs ship.script if 'this' is made to be the ship's script
    // adjusts shields based on Shield Cycler settings stored in 'this'
    // uses Shield Capacitors if present and charged
    // THIS UPDATES THE SHIP'S SHIELD CHARGE AND DEDUCTS ENERGY COST
    var wsc =  worldScripts["Shield Cycler"];
    var ship = this.ship;
    if (wsc == null || this.$sc_settings.version === 0 || 
        ship.equipmentStatus(wsc.$sc_const.sc_list[this.$sc_settings.version]) !== "EQUIPMENT_OK") 
        return;
    // setup things for shield cycling
    var adjust_input = {
        caller: (caller ? caller : "Unknown"),
        setting: this.$sc_settings.current_configuration,
        functional: this.$sc_settings.functional,
        fwd: ship.forwardShield,
        fwd_threshold: (ship.maxForwardShield * this.$sc_settings.threshold * 0.01),
        aft: ship.aftShield,
        aft_threshold: (ship.maxAftShield * this.$sc_settings.threshold * 0.01),
        version: this.$sc_settings.version,
        manual_version: this.$sc_settings.manual_version,
        init: init,
        max_energy: ship.maxEnergy,
        ship: ship
    };
    var _old_energy = ship.energy;
    var adjust_output = {power: 0, fwd:0, aft:0};
    adjust_output = wsc._sc_adjust(adjust_input);
    if (adjust_input.init)
        player.commsMessage("Cycler configuration : " + wsc.$sc_const.mode_names[adjust_input.setting]);
    if (this.$logging || ship == player.ship.target) log(wsc.name, ship.displayName+": adjusting shields from (F/A) "+adjust_input.fwd.toFixed(0)+"/"+adjust_input.aft.toFixed(0)+" to "+adjust_output.fwd.toFixed(0)+"/"+adjust_output.aft.toFixed(0)+", energy:"+_old_energy.toFixed(1)+"->"+ship.energy.toFixed(1))
    return;
}
//--------------------------------------------------------------------------------------------------//
this._sc_adjust = function _sc_adjust(adjust) {// NPC ready
    // can be called for NPCs withouth having to mess with 'this'
    // adjusts shields based on Shield Cycler settings stored in 'adjust' parameter
    // uses Shield Capacitors if present and charged
    // THIS UPDATES THE SHIP'S SHIELD CHARGE AND DEDUCTS ENERGY COST IF adjust.ship IS DEFINED
    
    /* adjust is an object set by the calling function
      properties of the object adjust :
    caller         string  identifier of calling oxp
    setting        int     0 = disabled, 1 = aft, 2 = forward, 3 = equal
    functional     %       indicates how much damage shieldcycler device has, 100 = no damage
    fwd            int     current forward shield strength
    fwd_threshold  int     see code
    aft            int     aft shield strength
    aft_threshold  int     see code
    version        int     number 0:None,1:Basic,2:Standard,3:advanced determining which version of automatic shield cycler is present
    manual_version string   number 0:None,1:Basic,2:Standard,3:Advanced determining which version of manual configurator is present
    init           bool    true : calc powerloss for switching to another setting
                           false : calc powerloss for cycling
    max_energy     int     needed to calculate powerloss for init = true
    ship           object  needed to alter the shield charge
    
      returns an object with these properties :
    power: amount of energy used for cycling or -1 in case of error
    fwd: new forward shield value
    aft: new aft shield value
    
    NOTE1 : this function is intended to be usable for both player & npc ships
    NOTE2: this function UPDATES SHIP'S SHIELD PROPERTIES
    */
    
    // set result at power 0, fwd & aft at input values. This corresponds with no changes needed.
    // initialise some things
    var wsc = worldScripts["Shield Cycler"];
    var wsec = worldScripts["shieldequalizercapacitors"];
    var result = { power:0,fwd:adjust.fwd,aft:adjust.aft};
    var ac = {fwd:adjust.fwd,aft:adjust.aft};
    var sc_mode = wsc.$sc_const.mode_names[adjust.setting];
    var sc_cycler_loss = wsc.$sc_cycler_loss[adjust.version];
    var ship = adjust.ship;
    var npc = (ship != player.ship);
    var logging = (npc ? (ship.script.$logging || ship == player.ship.target) : this.$logging);
    var debug = (npc ? ship.script.$debug : this.$debug);
    var energy_transfer = -1;
    var desired_transfer = 0;
    var shCap, apply_capacitor_fwd, apply_capacitor_aft, energy_range;
    var seen = [];
    var strip_cycling_obj = function(k, v) { // for use with JSON.stringify in log messages
        if (v != null && typeof v == "object") {
            if (seen.indexOf(v) >= 0) return;
            seen.push(v);
        }
        return v;
    }
    if (ship && debug) log(wsc.name, ship.displayName+" - adjust_input: "+JSON.stringify(adjust, strip_cycling_obj)+", energy:"+ship.energy.toFixed(1));
    // add test for SEC presnce to avoid breaking SC functionality completely
    if (wsec) {
        if (ship) {
            // caller was updated to use Shield Cycler v1.13
            if (npc)
                shCap = ship.script;
            else
                shCap = wsec;
            apply_capacitor_fwd = (shCap.$forwardshieldcapacitor ? shCap.$forwardshieldcapacitor.bind(shCap) : null);
            apply_capacitor_aft = (shCap.$aftshieldcapacitor ? shCap.$aftshieldcapacitor.bind(shCap) : null);
        }
        if (shCap && apply_capacitor_fwd) {
            // ship has Shield Capacitors... use them!
            apply_capacitor_fwd();
            apply_capacitor_aft();
            result.fwd = ac.fwd = ship.forwardShield;
            result.aft = ac.aft = ship.aftShield;
        }
    }
    
    if (wsc.$sc_disabled) return result;
    switch (sc_mode) {
        case "Equal":
            if (ac.aft !== ac.fwd)
                if ( (ac.fwd < adjust.fwd_threshold) || (ac.aft < adjust.aft_threshold) ) {
                    desired_transfer = 0.5 * Math.abs(ac.fwd - ac.aft);
                    energy_transfer = desired_transfer * adjust.functional * 0.01 ;
                    result.fwd = (ac.fwd < ac.aft) ? ac.fwd + energy_transfer : ac.fwd - energy_transfer ;
                    result.aft = (ac.fwd < ac.aft) ? ac.aft - energy_transfer : ac.aft + energy_transfer ;
                }
            break;
        case "Forward":
            if (ac.fwd < adjust.fwd_threshold) { 
                desired_transfer  = (adjust.fwd_threshold - ac.fwd <= ac.aft) ? adjust.fwd_threshold - ac.fwd : ac.aft ;
                energy_transfer = desired_transfer * adjust.functional * 0.01 ;
                result.fwd = ac.fwd + energy_transfer;
                result.aft = ac.aft - energy_transfer;
            }
            break;
        case "Aft":
            if (ac.aft < adjust.aft_threshold) {
                desired_transfer  = (adjust.aft_threshold - ac.aft <= ac.fwd) ? adjust.aft_threshold - ac.aft : ac.fwd ;
                energy_transfer = desired_transfer * adjust.functional * 0.01 ;
                result.fwd = ac.fwd - energy_transfer;
                result.aft = ac.aft + energy_transfer;
            }
            break;
        case "Disabled":
            // no adjustments are done in disabled configuration, so no energy is transferred
            desired_transfer = 0;
            break;
        default:
            // should never happen unless caller has set an invalid mode
            desired_transfer = -1;
            result.power = -1;
            log(wsc.name,JSON.stringify(adjust, strip_cycling_obj));
            log(wsc.name,wsc.$sc_const.names[adjust.setting]);
            log(wsc.name,JSON.stringify(result));
            log(wsc.name,"error while adjusting shields");
            break;
    }
    
    // no transfer done, so no power loss
    if (desired_transfer == 0) { 
        result.power = 0; 
    } else if (desired_transfer < 0) return result; 
    else if (adjust.init)
        // configuration change costs 2 * (number of energy banks) energy units
        result.power = 2 * (Math.floor(adjust.max_energy / 64) + 1);
    else { 
        energy_range = Math.floor(energy_transfer / 64);
        if (energy_range >= sc_cycler_loss.length) energy_range = sc_cycler_loss.length - 1;
        result.power = energy_transfer * sc_cycler_loss[energy_range];
        result.power *= wsc.$sc_manual_reduction[adjust.manual_version];
    }
    if (debug) 
        log(wsc.name, (ship?ship.displayName:"")+"-> Mode:"+ sc_mode+" threshold:"+adjust.fwd_threshold.toFixed(1)+"/"+adjust.aft_threshold.toFixed(1)+", desired_transfer:"+desired_transfer.toFixed(1)+", transfer:"+energy_transfer.toFixed(1)+", in (after capacitors):"+ac.fwd.toFixed(1)+"/"+ac.aft.toFixed(1)+", out:"+result.fwd.toFixed(1)+"/"+result.aft.toFixed(1)+", r_power:"+result.power.toFixed(4)+"("+(100*result.power/energy_transfer).toFixed(1)+"%)"+(ship && ship.script && ship.script.$EscortDeckUsable ?", escortDeckUsable:"+ship.script.$EscortDeckUsable:""));
    else if (logging)
        log(wsc.name, (ship?ship.displayName:"")+": adjusting shields (after capacitors) from (F/A) "+ac.fwd.toFixed(0)+"/"+ac.aft.toFixed(0)+" to "+result.fwd.toFixed(0)+"/"+result.aft.toFixed(0)+" with "+result.power.toFixed(1)+" energy cost (thresholds "+adjust.fwd_threshold.toFixed(1)+"/"+adjust.aft_threshold.toFixed(1)+")");
   
    if (ship) { 
        ship.aftShield = result.aft;
        ship.forwardShield = result.fwd;
        if (isNaN(result.power))
            log("Shield Cycler", ship.displayName+": energy cost coul not be applied, invalid value: "+result.power+", energy trasnfer: "+energy_transfer.toFixed(3)+", energy range:"+energy_range+", % cost:"+sc_cycler_loss[energy_range]);
        else
            ship.energy -= result.power;
    }
    return result;
}
//--------------------------------------------------------------------------------------------------//
this._sc_stop = function _sc_stop() { // NPC-ready
    // can be called from NPCs ship.script if 'this' is made to be the ship's script
// returns:
//    -2 if device was stopped already
//    0 if stopping was succesfull
    var success = -3;
    if (!this.$sc_settings.current_configuration)
        return -2;
    this.$sc_settings.current_configuration  = 0;
    return 0;
}
//--------------------------------------------------------------------------------------------------//
this._sc_start = function _sc_start(device) {// NPC-ready
    // can be called from NPCs ship.script if 'this' is made to be the ship's script
// returns:
//    -2 if device was started already
//    0 if starting was succesfull
    var wsc = worldScripts["Shield Cycler"];
    var success = -3;
    if (this.$sc_settings.current_configuration)
        return -2;
    this.$sc_settings.current_configuration  =  wsc.$sc_const.cycle[this.$sc_settings.current_cycle_position];
    return 0;
}
//--------------------------------------------------------------------------------------------------//
this._sc_store_devices = function _sc_store_devices() {// NPC-ready
    // can be called for NPCs if 'this' is made to be the ship's script
    // Shield Cycler devices are not portable between ships and will stay that way.
    // This poses a problem for oxps (like Ship Storage Helper) that store ships for later retrieval
    // returns an object with 2 strings :
    // first holds SC values in JSON format
    // second (now deprecated) used to hold an encrypted version of first
    var wsc = worldScripts["Shield Cycler"];
    var sc_values = {
        json: "",
        enc: ""
    };
    if (this.$sc_settings)
        sc_values.json = JSON.stringify(this.$sc_settings);
    else if (this._sc_settings)
        sc_values.json = JSON.stringify(this._sc_settings);
    return sc_values;
}
  
//--------------------------------------------------------------------------------------------------//
this._sc_retrieve_devices = function _sc_retrieve_devices(sc_values) {// NPC-ready
    // can be called for NPCs if 'this' is made to be the ship's script
    // counterpart of wsc._sc_store_devices
    // input : an object created by wsc._sc_store_devices
    // returns a code value :
    // 0 : import sucessfull
    // 1 : input != an object
    // 2 : object has wrong structure
    // 3 : invalid json data
    // 4 : plain version doesn't match encrypted version
    // 5 : stored data to old
    // 6 : stored data to new
    var wsc = worldScripts["Shield Cycler"];
    var npc = (this.ship != player.ship);
    if (typeof sc_values !== "object") return 1;
    if (typeof sc_values.json != "string") return 2;
//    if (sc_values.json.length == 0) return 0;
    if (!npc && worldScripts.Lib_Config) worldScripts.Lib_Config._unregisterSet(wsc._scConfig);
    var ret = wsc._parseSavedSettings.call(this, sc_values.json);
    if (ret) {
        return ret;
    }
    if (!npc && worldScripts.Lib_Config) {
        wsc._setLibConfigVisibility();
        worldScripts.Lib_Config._registerSet(wsc._scConfig);
    }
    log(wsc.name,"imported stored ship data");
    if (this.ship == player.ship) 
        wsc._sc_update_status();
    return 0;
}
//--------------------------------------------------------------------------------------------------//
this._scChange_disabled = function _scChange_disabled() {
    var wsc = worldScripts["Shield Cycler"];
    wsc._sc_settings.general.disabled = wsc._sc_settings.general.sc_disabled = wsc.$sc_disabled;
}
//--------------------------------------------------------------------------------------------------//
this._scChange_threshold = function _scChange_threshold() {
    return;
}
//--------------------------------------------------------------------------------------------------//
this._scChange_start_configuration = function _scChange_start_configuration() {
    return;
}
//--------------------------------------------------------------------------------------------------//
this._scChange_cycler_mask = function _scChange_cycler_mask() {
    return;
}
 |