Back to Index Page generated: May 8, 2024, 6:16:03 AM

Expansion Repair Bots

Content

Manifest

from Expansion Manager's OXP list from Expansion Manifest
Description Pylon-stored and triggered nanobots, can repair damaged ships systems. Also now available a new repair system, which gives full damage control for any ship not already featuring it. Pylon-stored and triggered nanobots, can repair damaged ships systems. Also now available a new repair system, which gives full damage control for any ship not already featuring it.
Identifier oolite.oxp.Thargoid.RepairBots oolite.oxp.Thargoid.RepairBots
Title Repair Bots Repair Bots
Category Equipment Equipment
Author Thargoid Thargoid
Version 2.15 2.15
Tags equipment equipment
Required Oolite Version
Maximum Oolite Version
Required Expansions
Optional Expansions
Conflict Expansions
Information URL http://wiki.alioth.net/index.php/Repair_Bots_OXP n/a
Download URL https://wiki.alioth.net/img_auth.php/d/d9/RepairBots_2.15.oxz n/a
License CC BY-NC-SA 3.0 CC BY-NC-SA 3.0
File Size n/a
Upload date 1701741971

Documentation

Also read http://wiki.alioth.net/index.php/Repair%20Bots

Readme.txt

RepairBots OXP
--------------
by Thargoid

The Ships Systems Department of the Aquarian Shipbuilding Corporation would like to present to you their latest products, the repair nanobots. These useful little things come in a pylon-mounted cannister, which when released (with the cannister selected, activate them by pressing "T" and then release by pressing "M") scan the player's ship for broken equipment.

They can fix almost any broken item, although their chances decrease as the complexity of the equipment increases. For the higher tech items they may not succeed in their task, although a second attempt (via another cannister) may work.

If there is nothing that the 'bots can fix, then the cannister will simply be wasted.


Breaking News:

Just announced by the Corporation is their latest product - the Self-Repair System. Declassified documents recently released details of a heavily wounded Caduceus bioship arriving at Corporate HQ for repairs, its own damage control systems having been destroyed. In the course of the treatment it was discovered that the system had evolved from the assimilation and adaptation of the Corporations own nanobots by the craft's ancestors. The craft was successfully treated and returned to full health, and in return shared its knowledge of using the 'bots for adaptive self-healing and regeneration.

There are two versions of the system: the normal version with enough 'bots for 10 repair attempts, the light version for 5 repair attempts. Supplies can be refilled once around half are used. If the system empties then no further repairs (except for itself) can be performed until it is refilled. A note of how many attempts are left is available on the ships manifest (F5) screen.

The system also takes cargo space in the ships hold, to store the nanobot charges: the normal system takes 2 TCs and the light takes one TC. Both consume energy when repairing equipment, 1 energy unit per second, double that when self-repairing, and will shutdown when there is a single energy bank left, resuming operation once energy levels come back to at least 2 energy banks.

Requires v1.79 or later of Oolite. It will not run on older versions.


Notes of OXP developers
-----------------------

OXPs can control how Repair Bots repairs their equipment through the equipment's script_info. To do that, add to the equipment difinition in equipment.plist:

        script_info = { thargoidRepairBotChance = X; };

here X is the probability of a successfull repair, a number between 0 (never repaired) and 1 (always repaired).

Other OXPs can also temporarily inhibit Repair Bots from repairing an equipment through the use of the disabling/enabling equipment functions described bellow.

worldScripts["Repair System"].$disableEquipment(eqKey, who, why)
    eqkey: string, equipment key
    who: string, identifies the caller
    why: string, describes the reason why the equipment is disabled

    Sets the equipment as DAMAGED and inserts it in the disabled equipment list (equipment in th disabled list is not repaired)

worldScripts["Repair System"].$enableEquipment(eqKey, who)
    eqkey: string, equipment key
    who: string, identifies the caller
    
    Removes the caller from the disablers of the equipment and if there is no disabler left, sets the equipmenty status to OK 

License
-------

iThis OXP is released under the Creative Commons Attribution - Non-Commercial - Share Alike 3.0 license. 
(https://creativecommons.org/licenses/by-nc-sa/3.0/)

Version history
---------------

05/09/2008 - Version 0.10, beta test release.
25/09/2008 - Version 0.99, pre-release version with some OXP equipment support (more to follow). Inclusion of chance of failure for more complex equipment (above equipment tech level 8) and lowering of price to 3000 credits.
10/11/2008 - Version 1.00, script update for v1.72 compatability.
02/09/2009 - Version 1.10, ditto for v1.73.
17/05/2010 - Version 2.00, update for v1.74 (only) - now uses script to scan players equipment, so can fix anything not explicitly in the exclusion list. Also new addition of the repair system
13/02/2011 - Version 2.01, removal of upper limit, to allow running with 1.75
02/04/2011 - Version 2.02, superb new pylon icon for the repair bot cannister, by Eldon.
02/11/2011 - Version 2.03, script speed hack courtesy of Capt. Murphy.
03/11/2011 - Version 2.04, further optimisation by Eric Walch.
19/03/2012 - Version 2.05, corrected error in script - thanks to Cmdr Cheyd.
09/05/2012 - Version 2.06, made repair system require 2t of cargo space ready for v1.77 implementation. Also will only work for 10 repairs before needing a recharge.
03/06/2012 - Version 2.07, removing minor glitch so remaining charges only show on F5 manifest screen when the Repair System is actually installed - thanks to JazHaz for the heads-up.
23/07/2012 - Version 2.08, small bugfix to cope better with selling of ships and persistence of mission variables.
07/08/2012 - Version 2.09, temporary removal of requirement for 2t cargo due to bug in 1.76.1 (rollback of v2.06 in part)
xx/xx/2012 - Version 2.10, requirement now for 1.77. Restored the 2t space requirement. Also added an 0.5s delay to activation on damage, to allow other scripts to run first (OXP equipment self-repair and also realistic damage OXP).
24/08/2020 - Version 2.11, introduced Repair System Light, with 5 charges taking 1 TC of cargo space, and Recharge 5-Pack; introduced energy cost while repairing, with minimum of one energy bank to work. (Dybal)
13/09/2020 - Version 2.12, checks for repair controller existence before starting the repair system.

04/10/2020 - Version 2.13
* Energy thresholds are now proportional to the ship's maxEnergy and not absolute: repair stops when energry drops to 20% and resumes when it reaches 40%.
* Self-repair, i.e., repairing the Repair System Controller, consumes 2 charges.
* New functions for other OXPs tell Repair Systems that an equipment is disabled, i.e., it's damaged but should not be repaired. This allows OXPs to inhibit Repair Systems actions on a given equipment during a period of time, while the probabilities in the scriptInfo are "hard-coded", i.e., can't be changed by scripts.

17/08/2022 - Version 2.14
* Changes the to remove the "Thargoid clauses" after Thargoid allowed modification when retired from maintaining his OXps (http://aegidian.org/bb/viewtopic.php?f=4&t=17085).
* Fixes typo that prevented resuming operation after a low energy period (thanks to cag and UK_Eliter!).

05/12/2023 - Version 2.15
* Fixed extraneous value in equipment.plist in the definition for EQ_REPAIRBOTS_REMOVER.
* Added escape character to % in equipment.plist.
* Spelling corrections.

Acknowledgements
----------------
With thanks to ClymAngus and Commander Wyvern for the inspiration for the self-repair system, from their recycling of the repair bots code to form the DCN node in the Caduceus family of ships.

And to DredgerMan and Cim about the issue with 1.76.1 and required cargo space.

Equipment

Name Visible Cost [deci-credits] Tech-Level
Self-Repair System yes 120000 11+
Self-Repair System Light yes 100000 12+
Repair Nanobots yes 30000 9+
Self-Repair System Recharge, 10-Pack yes 10000 9+
Self-Repair System Recharge, 5-Pack yes 6000 9+
Sell Self-Repair System yes 1000 1+

Ships

Name
Repair Nanobots

Models

This expansion declares no models.

Scripts

Path
Scripts/repairBots_Repair.js
this.name        = "Repair Nanobots";
this.author      = "Thargoid";
this.copyright   = "CC BY-NC-SA 3.0";
this.description = "Scan through a list of possible broken equipment, see what the player has, and then fix one of them";
this.version     = "2.11";
"use strict";

//------------------------------------------------------------------------------------------//
this.$repairSystems = function() {
    var _fname = "repairSystems";
    this.$playerDamagedList = [];
    var _ship = player.ship;
    var _equipment = _ship.equipment;
    var _listCounter = 0 ; // reset the counter
    var _eqInfo;

    for(_listCounter = 0; _listCounter < _equipment.length; _listCounter++) {
        if (_ship.equipmentStatus(equipment[_listCounter].equipmentKey) !== "EQUIPMENT_DAMAGED")
            continue; 
        _eqInfo = _equipment[_listCounter];
        log(this.name, _fname+": Equipment damaged:"+_eqInfo.equipmentKey+", RepairBotChance:"+_eqInfo.scriptInfo.thargoidRepairBotChance);
        if (_eqInfo.scriptInfo.thargoidRepairBotChance === undefined 
                || isNaN(_eqInfo.scriptInfo.thargoidRepairBotChance)
                || (!isNaN(_eqInfo.scriptInfo.thargoidRepairBotChance) && _eqInfo.scriptInfo.thargoidRepairBotChance > 0) ) { 
            // if it's broke and fixable, add it to the list.
            log(this.name, _fname+": Putting damaged equipment in repair list:"+_eqInfo.equipmentKey);
            this.$playerDamagedList.push(eqInfo.equipmentKey); 
        }
    }
 
    if (this.$playerDamagedList.length === 0) {
        log(this.name, _fname+": Nothing damaged that the repair bots can fix.");
        player.consoleMessage("Nothing damaged that the repair bots can fix.", 5);
        return;
    } else {
        var damagedEquipment = Math.floor(Math.random() * this.$playerDamagedList.length); // pick a random element from the list...
        _fixedItem = this.$playerDamagedList[damagedEquipment]; // ...define the item...
        _eqInfo = EquipmentInfo.infoForKey(_fixedItem);
        _fixedName = _eqInfo.name; // define it's screen name
        let _fixedTech = _eqInfo.effectiveTechLevel // tech level of the item
        log(this.name, _fname+": Choose "+_fixedItem+" to repair from list: "+this.$playerDamagedList);
        log(this.name, _fname+": Chosen equipment "+_fixedItem+" RepairBotChance: "+_eqInfo.scriptInfo.thargoidRepairBotChance+", fixedTech: "+_fixedTech+", Name: "+_fixedName);

        switch (true) {
            case (_eqInfo.scriptInfo.thargoidRepairBotChance !== undefined && !isNaN(_eqInfo.scriptInfo.thargoidRepairBotChance)): {
                let _fixChance = _eqInfo.scriptInfo.thargoidRepairBotChance;
                break;
            }
            case (_fixedTech < 9): { 
                let _fixChance = 1; 
                break; 
            }
            case (_fixedTech > 8 && _fixedTech < 17): {
                let _fixChance = 1 - ((_fixedTech - 8)/10); 
                break;
            }
            case (_fixedTech === 99): {
                let _fixChance = 0.1; 
                break;
            }
            default: {
                let _fixChance = 0.2; 
                break;
            }
        }

        log(this.name, _fname+": fixChance: "+_fixChance);
        if(Math.random() < _fixChance)
            this.$fixItem(_eqInfo); 
        else { 
            log(this.name, _fname+": "+_fixedName + " repair attempt has failed.");
            player.consoleMessage(_fixedName + " repair attempt has failed.", 5); 
        }
    }
}

//------------------------------------------------------------------------------------------//
this.$fixItem = function(eqInfo) {
    var _fname = "fixItem";
    var _ship = player.ship;
    var _eqKey = eqInfo.equipmentKey;

    _ship.setEquipmentStatus(_eqKey,"EQUIPMENT_OK"); // and actually fix the thing!
    log(this.name, _fname+": "+eqInfo.name + " fixed by the repair bots.");
    player.consoleMessage(eqInfo.name + " fixed by the repair bots.", 5)
    switch (_eqKey) { // specific OXP equipment which need rebooting after fixing, or have other issues.
        case "EQ_FRAME_FUEL_COLLECTOR": {
            if (worldScripts["Fuel Collector"]) 
                worldScripts["Fuel Collector"].shipLaunchedFromStation(); // restart the timers in it's world script
            break;
        }
        case "EQ_FRAME_BOUNTY_SCANNER": {
            if (worldScripts["Bounty Scanner"])  
                worldScripts["Bounty Scanner"].shipLaunchedFromStation(); // restart the timers in it's world script
            break;
        }
        case "EQ_EEU": {
            if (worldScripts["Emergency Energy Unit"])
                worldScripts["Emergency Energy Unit"].shipLaunchedFromStation(); // restart the timers in it's world script
            break;
        }
        case "EQ_ROCKHERMIT_SCANNER": { 
            if (worldScripts["rockHermit_Locator"]) {
                if (worldScripts["rockHermit_Locator"].version === "1.2.3")
                    worldScripts["rockHermit_Locator"].shipLaunchedFromStation() // use the inbuild scripting of the OXP to restart it.
                else {
                    let _beacons = system.shipsWithPrimaryRole("rockbeacof"); // list the inactive beacons (no lights or "R" on compass) 
                    if (_beacons.length > 0) { 
                        for (let i=0; i < _beacons.length;i++) { 
                            if (oolite.compareVersion('1.73') <= 0) 
                                _beacons[i].position = _beacons[i].position.multiply(100); //throw the rockbeacof into space... 
                            else 
                                _beacons[i].setPosition(_beacons[i].position.multiply(100)); //throw the rockbeacof into space... 
                            _beacons[i].explode(); //...and blow it up. 
                        } 
                    } 
                    worldScripts["rockHermit_Locator"].buoy = "rockbeacon" 
                    worldScripts["rockHermit_Locator"].addBuoys() // no inactive beacons, so use original code to add active beacons. 
                }
            }
            break; 
        } 
    }
}
Scripts/repairBots_System.js
"use strict";
this.name        = "Repair System";
this.author      = "Thargoid";
this.copyright   = "CC BY-NC-SA 3.0";
this.description = "Damage Control system based on repair bots";
this.version     = "2.14";

this.$repairControllers = [ "EQ_REPAIRBOTS_CONTROLLER",         // 10 charges version
                            "EQ_REPAIRBOTS_CONTROLLER_LIGHT"    // 5 charges version
                          ];
this.$repairRecharges = [ "EQ_REPAIRBOTS_RECHARGE_10",
                          "EQ_REPAIRBOTS_RECHARGE_5"
                        ];
this.$repairControllerCapacity = {
                                    EQ_REPAIRBOTS_CONTROLLER: 10,
                                    EQ_REPAIRBOTS_CONTROLLER_LIGHT: 5
                                 };
this.$repairRechargesCapacity = {
                                    EQ_REPAIRBOTS_RECHARGE_10: 10,
                                    EQ_REPAIRBOTS_RECHARGE_5: 5
                                };
this.$energyCostPerSecond = 1;  // cost of (energy unit)/s while Repair System is active repairing things
                                // self-repair (repairing the REpair System itself) costs double
this.$energyLowThreshold = 0.2;  // Self Repair System shutdown if energy bellow this (fraction of maxEnergy)
this.$energyResumeThreshold = 0.4; // Self-Repair System resumes if energy above this (fraction of max energy)
this.$selfRepairsSystemStatus; // ['Stand-by','Active','Self-repair', 'Shutdown']
this.$controllerVersion;    // The version of Self-Repair System Controller installed in the player ship
this.$delayArray;           // list of damaged equipment to be repaired
this.$repairControllerRepaired = false; // to discern true repair controller additions from repairs
this.$playerDamagedList = [];
this.$disabledEquipment = {}; // dictionary of equipments disabled, i.e., appear as damaged but should not be repaired
this.$accumEnergyCost = 0;
this.$energyCostCounter = 0;
this.$debug = false;
this.$logging = true;

/****************************************************************************************************/
// Event Handlers 
//--------------------------------------------------------------------------------------------------//
this.startUp = function _repsys_startUp() { 
    var ship = player.ship;
    var repairControllers = this.$repairControllers;
    var i;

    this.$controllerVersion = null;
    this.$delayArray = [];
    i = repairControllers.length;
    while (i--) {
        if (ship.equipmentStatus(repairControllers[i]) === "EQUIPMENT_OK" ||
            ship.equipmentStatus(repairControllers[i]) === "EQUIPMENT_DAMAGED") {
            this.$controllerVersion = repairControllers[i];
            if (!missionVariables.repairCounter) 
                missionVariables.repairCounter = this.$repairControllerCapacity[repairControllers[i]];
            break;
        }
    }
    if (this.$controllerVersion)
        if (this.$logging) log(this.name, ship.displayName+" has Repair System Controller "+this.$controllerVersion+" with "+missionVariables.repairCounter+" charges remaining"); 
}

//--------------------------------------------------------------------------------------------------//
this.playerBoughtEquipment = function _repsys_playerBoughtEquipment(equipment) {
    var _player = player;
    var repairRecharges = this.$repairRecharges;
    var i;

    // controllers will be dealt with in equipmentAdded
    
    i = repairRecharges.indexOf(equipment);
    if (i >= 0) {
        // player bought a Sefl-Repair System recharge
        var recharge = this.$repairRechargesCapacity[repairRecharges[i]];
        missionVariables.repairCounter += recharge; 
        log(this.name, "Recharging Self-Repair System with "+recharge+" charges");
        _player.ship.removeEquipment(equipment);
        this.$limitCharges();
    } else if (equipment === "EQ_REPAIRBOTS_REMOVER") {
            var refund = this.$calculateRepairSystemRefund();
            var msg = "Refunding "+formatCredits(refund, false, true)+" for "+EquipmentInfo.infoForKey(this.$controllerVersion).name;
            log(this.name, msg);
            _player.consoleMessage(msg, 5);
            _player.credits += refund;
            this.$removeRepairController();
            _player.ship.removeEquipment(equipment);
    }
}

//--------------------------------------------------------------------------------------------------//
this.equipmentAdded = function _repsys_equipmentAdded(equipment) {
    // deals only wjith the controllers, which can be added by script
    var repairControllers = this.$repairControllers;
    var i = repairControllers.indexOf(equipment);

    if (i >= 0) {
        if (this.$repairControllerRepaired)
            // the controller was repaired
            this.$repairControllerRepaired = false;
        else {
            // player bought a Self-Repair System
            this.$controllerVersion = repairControllers[i];
            missionVariables.repairCounter = this.$repairControllerCapacity[repairControllers[i]];
            log(this.name, player.ship.displayName+"was awarded Self-Repair System with "+missionVariables.repairCounter+" charges");
        }
    }
}

//--------------------------------------------------------------------------------------------------//
this.playerBoughtNewShip = function _repsys_playerBoughtNewShip(ship) {
    var ship = player.ship;
    var repairControllers = this.$repairControllers;
    var i;

    this.$controllerVersion = null;    
    i = repairControllers.length;
    while (i--) {
        if (ship.equipmentStatus(repairControllers[i]) === "EQUIPMENT_OK" ||
            ship.equipmentStatus(repairControllers[i]) === "EQUIPMENT_DAMAGED") {
            missionVariables.repairCounter = this.$repairControllerCapacity[repairControllers[i]];
            this.$controllerVersion = repairControllers[i];
            if (this.$logging) log(this.name, ship.displayName+" has Repair System Controller "+this.$controllerVersion+" with "+missionVariables.repairCounter+" charges remaining"); 
            break;
        }
    }

    if (this.$controllerVersion == null) 
        this.$removeRepairController();
}
 
//--------------------------------------------------------------------------------------------------//
this.guiScreenWillChange = function _repsys_guiScreenWillChange(to, from) {
    if (this.$controllerVersion == null) return;
    if (to === "GUI_SCREEN_MANIFEST") {
        switch (this.$getRepairSystemEqStatus()) {
            case "EQUIPMENT_OK": {
                if (missionVariables.repairCounter > 0)
                    this.repairDisplay = "Remaining repair system charges - " + missionVariables.repairCounter;
                else
                    this.repairDisplay = "Repair system offline - recharge required";
                mission.setInstructions(this.repairDisplay); 
                break;
            }
        
            case "EQUIPMENT_DAMAGED": {
                mission.setInstructions("Repair system offline - damaged");
                break;
            }            
            
            case "EQUIPMENT_UNAVAILABLE":
            case "EQUIPMENT_UNKNOWN": {
                this.$removeRepairController();
                break;
            }
        }
    }    
}    

//--------------------------------------------------------------------------------------------------//
this.equipmentDamaged = function _repsys_equipmentDamaged(equipment) {
    if (this.$controllerVersion == null) return;
    if (player.ship.docked) return;

    if (equipment == this.$controllerVersion) this.$repairControllerDamaged = true;

    if (this.$disabledEquipment && equipment in this.$disabledEquipment && this.$disabledEquipment[equipment].length > 0) {
        if (this.$debug) log(this.name, "Damaged "+equipment+" is on the disabled list, ignoring");
        return;
    }

    var eqInfo = EquipmentInfo.infoForKey(equipment);
    if (eqInfo.scriptInfo.thargoidRepairBotChance &&
        !isNaN(eqInfo.scriptInfo.thargoidRepairBotChance) &&
        eqInfo.scriptInfo.thargoidRepairBotChance == 0)
        return;

    if (this.$logging) log(this.name, "Equipment "+equipment+" damaged, putting in list for eventual repair");
    if (this.$delayArray.indexOf(equipment) === -1)
        this.$delayArray.push(equipment);
    if (!this.$equipmentDelay)  
        this.$equipmentDelay = new Timer(this, this.$checkEquipment, 0.5, 5); 
    else 
        this.$equipmentDelay.start();
}

//--------------------------------------------------------------------------------------------------//
this.equipmentDestroyed  = function _repsys_equipmentDestroyed(equipment) {
    if (equipment == this.$controllerVersion) {
        log(this.name, "Self-Repair System Controller destroyed"); 
        this.$removeRepairController();
    } else {
        var eqInfo = EquipmentInfo.infoForKey(equipment);
        log(this.name, "Equipment "+eqInfo.name+" ("+equipment+") destroyed");
    }
}    

//--------------------------------------------------------------------------------------------------//
this.shipWillDockWithStation = this.shipDied = function _repsys_shipWillDockWithStation() {
    this.$stopTimers();
    this.$selfRepairsSystemStatus = 'Shutdown';
}

//--------------------------------------------------------------------------------------------------//
this.shipWillLaunchFromStation = function _repsys_shipWillLaunchFromStation() {
    this.$startRepairSystem("Launch");
}



/****************************************************************************************************/
// Internal Functions 

//--------------------------------------------------------------------------------------------------//
this.$checkEquipment = function _repsys_checkEquipment() {
    var logging = this.$logging;
    var debug = this.$debug;

    if(!this.$delayArray || this.$delayArray.length === 0) return;

    // get one equipment from the damaged list
    var equipment = this.$delayArray.pop();
    if(player.ship.equipmentStatus(equipment) !== "EQUIPMENT_DAMAGED") { 
        if (logging) log(this.name, "Equipment "+EquipmentInfo.infoForKey(equipment).name+" ("+equipment+") has already been repaired");
        return; 
    }
    
    if (equipment == this.$controllerVersion) {
        // Self-Repair Controller damaged
        if (this.$damageControlTimer && this.$damageControlTimer.isRunning) {
            this.$damageControlTimer.stop();
            delete this.$damageControlTimer;
        }
        var repairTime = 60 + Math.ceil(Math.random() * 120);
        if (logging) log(this.name, "Repair system entering self-repair mode, repair time "+repairTime);
        player.consoleMessage("Repair system entering self-repair mode", 6);
        this.$selfRepairsSystemStatus = 'Self-repair';
        this.$selfRepairTimer = new Timer(this, this.$selfRepair, repairTime);
        if (debug) log(this.name, "Starting timer for selfRepair, repair time "+repairTime);
        this.$startEnergyConsumption();
    } else {
        // other equipment than the Self-repair System Controller was damaged
        if ((missionVariables.repairCounter && missionVariables.repairCounter > 0) && 
               (!this.$damageControlTimer || !this.$damageControlTimer.isRunning)) {
            // Self-Repair System in standby, activating
            var repairTime = 120 + Math.ceil(Math.random() * 180);
            if (logging) log(this.name, "Repair system activated, repair time "+repairTime);
            player.consoleMessage("Repair system activated", 6);
            this.$selfRepairsSystemStatus = 'Active';
            this.$damageControlTimer = new Timer(this, this.$repairSystems, repairTime);
            if (debug)log(this.name, "Starting timer for repairSystems");
            this.$startEnergyConsumption();
        }
    }
}

//--------------------------------------------------------------------------------------------------//
this.$selfRepair = function _repsys_selfRepair() {
    var _player = player;
    var logging = this.$logging;
    var debug = this.$debug;

    if (this.$controllerVersion == null ) return;
    
    if(Math.random() < 0.75) {
        // 75% chance of self-repair
        missionVariables.repairCounter -= 2;
        if (missionVariables.repairCounter < 0) missionVariables.repairCounter = 0;
        if (logging) log(this.name, "Repair System self-repair complete, deducted charges, "+missionVariables.repairCounter+" remain");
        this.$repairControllerRepaired = true;
        _player.ship.setEquipmentStatus(this.$controllerVersion,"EQUIPMENT_OK");
        this.$checkSystems();
        if ((missionVariables.repairCounter && missionVariables.repairCounter > 0) && 
            this.$playerDamagedList.length > 0) {
            // there are damaged equipments, activate system
            _player.consoleMessage("Repair system online and activated", 6);
            var repairTime = 120 + Math.ceil(Math.random() * 180);
            if (logging) log(this.name, "Repair System online and activated, repair time "+repairTime);
            this.$selfRepairsSystemStatus = 'Active';
            this.$damageControlTimer = new Timer(this, this.$repairSystems, repairTime);
            if (debug) log(this.name, "Starting timer for repairSystems, repair time "+repairTime);
            this.$startEnergyConsumption();
        } else {
            this.$stopEnergyConsumption();
            if (missionVariables.repairCounter && missionVariables.repairCounter > 0) { 
                // no damaged equipment
                if (logging) log(this.name, "Repair system online and in standby mode");
                _player.consoleMessage("Repair system online and in standby mode", 6); 
                this.$selfRepairsSystemStatus = 'Standby';
            } else {
                if (logging) log(this.name, "Repair system offline - repair charges exhausted, recharge required");
                _player.consoleMessage("Repair system offline - repair charges exhausted, recharge required", 5);
                mission.setInstructions("Repair system offline - recharge required");
                this.$selfRepairsSystemStatus = 'Shutdown';
            }
        }
     } else {
        if (_player.ship.equipmentStatus(this.$controllerVersion) === "EQUIPMENT_DAMAGED") {
            // Self-repair System Controller still damaged (just in case it was repaired in the meantime)
            var repairTime = 60 + Math.ceil(Math.random() * 120);
            if (logging) log(this.name,"Repair system offline - self-repair continues");
            player.consoleMessage("Repair system offline - self-repair continues", 6);
            this.$selfRepairsSystemStatus = 'Self-repair';
            this.$selfRepairTimer = new Timer(this, this.$selfRepair, repairTime);
            if (debug) log(this.name, "Starting timer for selfRepair, repair time: "+repairTime);
            this.$startEnergyConsumption();
        }
    }
}

//--------------------------------------------------------------------------------------------------//
this.$checkSystems = function _repsys_checkSystems() {
    var ship = player.ship;
    var equipment = ship.equipment;
    var logging = this.$logging;
    var debug = this.$debug;
    var eqInfo, eq, i;

    this.$playerDamagedList.length = 0;
    i = equipment.length;
    while (i--) {
        eqInfo = equipment[i];
        eq = eqInfo.equipmentKey;    
        if (ship.equipmentStatus(eq) === "EQUIPMENT_DAMAGED") {
            if (eqInfo.scriptInfo.thargoidRepairBotChance &&
                !isNaN(eqInfo.scriptInfo.thargoidRepairBotChance) &&
                eqInfo.scriptInfo.thargoidRepairBotChance == 0)
                // equipment scriptInfo says not to repair the equipment
                continue;
            if (this.$disabledEquipment && eq in this.$disabledEquipment && this.$disabledEquipment[eq].length > 0)
                // equipment in disabled equipment "list", so it's not really broken
                continue;
            this.$playerDamagedList.push(eq) // if it's broke and fixable, add it to the list.
        }
    }
    if (debug) {
        log(this.name, "Damaged equipment list: '"+this.$playerDamagedList+"'");
        log(this.name, "Disabled equipment: "+JSON.stringify(this.$disabledEquipment));
    }
}

//--------------------------------------------------------------------------------------------------//
this.$energyConsumption = function _repsys_energyConsumption() {
    var ship = player.ship;
    var logging = this.$logging;
    var debug = this.$debug;
    var eqStatus;
    var energyCost;

    if (!this.$controllerVersion) return;
    eqStatus = ship.equipmentStatus(this.$controllerVersion);
    if (eqStatus === "EQUIPMENT_OK")
        energyCost = this.$energyCostPerSecond / 2;
    else if (eqStatus === "EQUIPMENT_DAMAGED")
        energyCost = this.$energyCostPerSecond;
    if (isNaN(energyCost)) {
        // defense againts invulnerable ship bug
        log(this.name, "energyCost is NaN: "+energyCost+", $energyCostPerSecond:"+this.$energyCostPerSecond);
        return;
    }
    ship.energy -= energyCost;

    this.$energyCostCounter++;
    this.$accumEnergyCost += energyCost;
    if (this.$energyCostCounter % 60 === 0) { // 30s
        if (debug) log(this.name, "Energy cost in the last 30s: "+this.$accumEnergyCost.toFixed(1));
        this.$energyCostCounter  = 0;
        this.$accumEnergyCost = 0;
    }

    if (ship.energy <= this.$energyLowThreshold * ship.maxEnergy) {
        if (logging) log(this.name, "Low energy threshold reached, shutting down Self-Repair System");
        player.consoleMessage("Low energy threshold reached, shutting down Self-Repair System", 5);
        this.$selfRepairsSystemStatus = 'Shutdown';
        this.$stopTimers();

        if (!this.$energyResumeTimer)
            this.$energyResumeTimer = new Timer(this, this.$verifyEnergyResume, 0.5, 0.5);
        else if (!this.$energyResumeTimer.isRunning)
            this.$energyResumeTimer.start();
    }
}

//--------------------------------------------------------------------------------------------------//
this.$verifyEnergyResume = function _repsys_verifyEnergyResume() {
    var ship = player.ship;

    if (this.$debug) log(this.name, ship.displayName+" energy:"+ship.energy.toFixed(0)+", activation threshold:"+this.$energyResumeThreshold);
    if (ship.energy >= this.$energyResumeThreshold * ship.maxEnergy) {
        this.$stopTimers();
        this.$startRepairSystem("Resume");
    }
}

//--------------------------------------------------------------------------------------------------//
this.$startRepairSystem = function _repsys_startRepairSystem(context) {
    var _player = player;
    var logging = this.$logging;
    var debug = this.$debug;

    if (!this.$controllerVersion) return;
    var eqStatus = this.$getRepairSystemEqStatus();
    if (this.$controllerVersion && logging) log(this.name, context+": Repair System equipment status: "+eqStatus);
    if (eqStatus === "EQUIPMENT_OK") {
        this.$checkSystems();
        if (missionVariables.repairCounter && missionVariables.repairCounter > 0 && this.$playerDamagedList.length > 0) {
            // there is equipment damaged and the Self-Repair System has charges
            var repairTime = 120 + Math.ceil(Math.random() * 180);
            if (logging) log(this.name, context+": Repair system activated");
            _player.consoleMessage("Repair system activated", 6);
            this.$selfRepairsSystemStatus = 'Active';
            this.$damageControlTimer = new Timer(this, this.$repairSystems, repairTime);    
            if (debug) log(this.name, "starting timer for repairSystems, repair time: "+repairTime);
            this.$startEnergyConsumption();
        } else if (missionVariables.repairCounter && missionVariables.repairCounter > 0) {
            // Self-repair System has charges
            if (logging) log(this.name, context+": Repair System Online, no equipment damaged, entering Standby mode");
            _player.consoleMessage("Repair System Online, no equipment damaged, entering Standby mode", 6);
            this.$selfRepairsSystemStatus = 'Standby';
        } else {
            // Self-Repair System charges exausted
            if (logging) log(this.name, context+": Repair charges exhausted ("+missionVariables.repairCounter+"), shutting down");
            _player.consoleMessage("Repair System exhausted - recharge required", 6); 
            this.$selfRepairsSystemStatus = 'Shutdown';
            this.$stopTimers();
        }
    } else {
        var repairTime = 60 + Math.ceil(Math.random() * 120);
        if (logging) log(this.name, context+": Repair System entering self-repair mode, repair time "+repairTime);
        player.consoleMessage("Repair System entering self-repair mode", 6);
        this.$selfRepairsSystemStatus = 'Self-repair';
        this.$selfRepairTimer = new Timer(this, this.$selfRepair, repairTime);    
        if (debug) log(this.name, "starting timer for selfRepair, repairt time: "+repairTime);
        this.$startEnergyConsumption();
    }
}

//--------------------------------------------------------------------------------------------------//
this.$repairSystems = function _repsys_repairSystems() {
    var _player = player;
    var logging = this.$logging;
    var debug = this.$debug;
    var fixedItem, fixedName, fixedTech, fixChance;

    this.$checkSystems();
    if (this.$playerDamagedList.length === 0) {
        if (logging) {
            log(this.name, "Available ship repair completed ("+missionVariables.repairCounter+" charges remain)");
            log(this.name, "Repair system entering standby mode");
        }
        _player.consoleMessage("Available ship repair completed - Repair system entering standby mode", 5);
        this.$selfRepairsSystemStatus = 'Standby';
        this.$stopEnergyConsumption();
        return;
    } else {
        var damagedEquipment = Math.floor(Math.random() * this.$playerDamagedList.length); // pick a random element from the list...
        fixedItem = this.$playerDamagedList[damagedEquipment]; // ...define the item...
        fixedName = EquipmentInfo.infoForKey(fixedItem).name; // define it's screen name
        fixedTech = EquipmentInfo.infoForKey(fixedItem).effectiveTechLevel // tech level of the item

        if (_player.ship.equipmentStatus(fixedItem) !== "EQUIPMENT_DAMAGED") {
            if (debug) log(this.name, "Equipment random-chosen for repair ("+fixedItem+") is already repaired, picking another");
            this.$repairSystems();
            return;
        } 
         
        var eqInfo = EquipmentInfo.infoForKey(fixedItem);
        switch(true) {
            case (eqInfo.scriptInfo.thargoidRepairBotChance !== undefined && !isNaN(eqInfo.scriptInfo.thargoidRepairBotChance)): {
                fixChance = parseFloat(eqInfo.scriptInfo.thargoidRepairBotChance);
                break;
            }
            case (fixedTech < 9): {
                fixChance = 1; 
                break;
            }
            case ((fixedTech > 8) && (fixedTech < 17)): {
                fixChance = 1 - ((fixedTech - 8)/10); 
                break;
            }
            case (fixedTech == 99): {
                fixChance = 0.1; 
                break;
            }
            default: {
                fixChance = 0.2; 
                break;
            }
        }    
        var rnd = Math.random();
        if (debug) log(this.name, "Probability of fixing "+fixedName+" is "+fixChance.toFixed(3)+", random: "+rnd.toFixed(3));    
        if(rnd < fixChance)
            this.$fixItem(eqInfo);
        else {
            if (missionVariables.repairCounter && missionVariables.repairCounter > 0) {
                var repairTime = 120 + Math.ceil(Math.random() * 180);
                _player.consoleMessage(fixedName + ": repair attempt failed - work continuing.", 5);
                if (logging) log(this.name, "Repair attempt of "+fixedItem+" failed, trying again in "+repairTime);
                this.$selfRepairsSystemStatus = 'Active';
                this.$damageControlTimer = new Timer(this, this.$repairSystems, repairTime);
                if (debug) log(this.name, "starting timer for repairSystems, repair time: "+repairTime);
                this.$startEnergyConsumption();
            } else {
                if (logging) log(this.name, "Repair charges exhausted ("+missionVariables.repairCounter+") while trying to repair "+fixedItem+", damaged equipment:"+this.$playerDamagedList);
                _player.consoleMessage("Repair system exhausted - recharge required", 6); 
                this.$selfRepairsSystemStatus = 'Shutdown';
                this.$stopEnergyConsumption();
            }
        }
    }
}

//--------------------------------------------------------------------------------------------------//
this.$fixItem = function _repsys_fixItem(eqInfo) {
    var _player = player;
    var eq = eqInfo.equipmentKey;
    var logging = this.$logging;
    var debug = this.$debug;

    if (missionVariables.repairCounter > 0) {
        _player.ship.setEquipmentStatus(eq,"EQUIPMENT_OK"); // and actually fix the thing!
        missionVariables.repairCounter--;
        if (logging) log(this.name, eqInfo.name+" online and operational, deducted one charge ("+missionVariables.repairCounter+" remain)");
        _player.consoleMessage(eqInfo.name + " online and operational", 5);
        switch (eq) {
            // specific OXP equipment which need rebooting after fixing, or have other issues.
            case "EQ_FRAME_FUEL_COLLECTOR": {
                if (worldScripts["Fuel Collector"])
                    worldScripts["Fuel Collector"].shipLaunchedFromStation(); // restart the timers in it's world script
                break;
            }
            case "EQ_FRAME_BOUNTY_SCANNER": {
                if (worldScripts["Bounty Scanner"])
                    worldScripts["Bounty Scanner"].shipLaunchedFromStation(); // restart the timers in it's world script
                break;
            }
            case "EQ_EEU": {
                if (worldScripts["Emergency Energy Unit"])
                    worldScripts["Emergency Energy Unit"].shipLaunchedFromStation(); // restart the timers in it's world script
                break;
            }
            case "EQ_ROCKHERMIT_SCANNER": { 
                if (worldScripts["rockHermit_Locator"]) {
                    if(worldScripts["rockHermit_Locator"].version === "1.2.3")
                        worldScripts["rockHermit_Locator"].shipLaunchedFromStation() // use the inbuild scripting of the OXP to restart it.
                    else {
                        this.$beacons = system.shipsWithPrimaryRole("rockbeacof"); // list the inactive beacons (no lights or "R" on compass) 
                        if (this.$beacons.length > 0) { 
                            for (let i=0; i<this.$beacons.length;i++) { 
                                if (oolite.compareVersion('1.73') <= 0) 
                                    this.$beacons[i].position = this.$beacons[i].position.multiply(100); //throw the rockbeacof into space... 
                                else 
                                    this.$beacons[i].setPosition(this.$beacons[i].position.multiply(100)); //throw the rockbeacof into space... 
                                this.$beacons[i].explode(); //...and blow it up. 
                            } 
                        } 
                        worldScripts["rockHermit_Locator"].buoy = "rockbeacon" 
                        worldScripts["rockHermit_Locator"].addBuoys() // no inactive beacons, so use original code to add active beacons. 
                    }
                } 
                break; 
            }
        }
        if (this.$playerDamagedList.length > 1) {
            if (missionVariables.repairCounter && missionVariables.repairCounter > 0) {
                var repairTime = 120 + Math.ceil(Math.random() * 180);
                if (logging) log(this.name, "Further damage detected - repairs continuing, repair time "+repairTime);
                _player.consoleMessage("Further damage detected - repairs continuing", 6);
                this.$selfRepairsSystemStatus = 'Active';
                this.$damageControlTimer = new Timer(this, this.$repairSystems, repairTime);
                if (debug) log(this.name, "starting timer for repairSystems, repair time: "+repairTime);
                this.$startEnergyConsumption();    
            } else  { 
                if (logging) log(this.name, "Repair system exhausted - recharge required"); 
                _player.consoleMessage("Repair system exhausted - recharge required", 6); 
                this.$selfRepairsSystemStatus = 'Shutdown';
                this.$stopEnergyConsumption();
            }
        } else {
            if (logging) log(this.name, "No further damage, Repair system entering standby mode");
            _player.consoleMessage("No further damage, Repair system entering standby mode", 6); 
            this.$selfRepairsSystemStatus = 'Standby';
            this.$stopEnergyConsumption();
        }
    }
}

//--------------------------------------------------------------------------------------------------//
this.$startEnergyConsumption = function _repsys_startEnergyConsumption() {
    if (!this.$energyConsumptionTimer)
        this.$energyConsumptionTimer = new Timer(this, this.$energyConsumption, 0.5, 0.5);
    else if (!this.$energyConsumptionTimer.isRunning)
        this.$energyConsumptionTimer.start();
}

//--------------------------------------------------------------------------------------------------//
this.$stopEnergyConsumption = function _repsys_stopEnergyConsumption() {
    if (this.$energyConsumptionTimer && this.$energyConsumptionTimer.isRunning) {
        this.$energyConsumptionTimer.stop();
        delete this.$energyConsumptionTimer;
        if (this.$debug) log(this.name, "Energy cost in the last "+(this.$energyCostCounter/2).toFixed(1)+"s: "+this.$accumEnergyCost.toFixed(1));
        this.$energyCostCounter  = 0;
        this.$accumEnergyCost = 0;
    }
}

//--------------------------------------------------------------------------------------------------//
this.$stopTimers = function _repsys_stopTimers() { 
    if(this.$damageControlTimer && this.$damageControlTimer.isRunning) {
        this.$damageControlTimer.stop();
        delete this.$damageControlTimer;
    }
    if (this.$selfRepairTimer && this.$selfRepairTimer.isRunning) {
        if (this.$controllerVersion) player.ship.setEquipmentStatus(this.$controllerVersion,"EQUIPMENT_OK");
        this.$selfRepairTimer.stop();
        delete this.$selfRepairTimer;
    }    
    if (this.$equipmentDelay && this.$equipmentDelay.isRunning) {
        this.$equipmentDelay.stop();
        delete this.$equipmentDelay;
    }
    if (this.$energyConsumptionTimer && this.$energyConsumptionTimer.isRunning) {
        this.$energyConsumptionTimer.stop();
        delete this.$energyConsumptionTimer;
    }
    if (this.$energyResumeTimer && this.$energyResumeTimer.isRunning) {
        this.$energyResumeTimer.stop();
        delete this.$energyResumeTimer;
    }
}

//--------------------------------------------------------------------------------------------------//
this.$getRepairSystemEqStatus = function _repsys_getRepairSystemEqStatus() {
    if (this.$controllerVersion == null) return null;
    return (player.ship.equipmentStatus(this.$controllerVersion))
}

//--------------------------------------------------------------------------------------------------//
this.$limitCharges = function _repsys_limitCharges() {
    var limit = (this.$controllerVersion ? this.$repairControllerCapacity[this.$controllerVersion] : null);
    if (limit == null) {
        if (missionVariables.repairCounter) delete missionVariables.repairCounter;
    } else if (missionVariables.repairCounter > limit)
        missionVariables.repairCounter = limit;
}

//--------------------------------------------------------------------------------------------------//
this.$removeRepairController = function _repsys_removeRepairController() {
    if (this.$controllerVersion) player.ship.removeEquipment(this.$controllerVersion);
    this.$controllerVersion = null;
    if (missionVariables.repairCounter) delete missionVariables.repairCounter;
    this.$stopTimers(); 
    mission.setInstructions(null);
}

//--------------------------------------------------------------------------------------------------//
this.$calculateRepairSystemRefund = function _repsys_calculateRepairSystemRefund() {
    var ship = player.ship;
    var eq = this.$controllerVersion;
    var eqInfo = EquipmentInfo.infoForKey(eq);
    var refund;

    if (ship.equipmentStatus(eq) === "EQUIPMENT_OK") {
        refund = eqInfo.price * 0.80 / 10;
        log(this.name, "Refund for "+eqInfo.name+" ("+eq+") calculated at "+refund);
        return refund;
    }
    return 0;
}

//-----------------------------------------------------------------------------------------------//
this.$disableEquipment = function _repsys_disableEquipment(eqKey, who, why) {
    var that = _repsys_disableEquipment;
    var ws = worldScripts["Repair System"];
    var validStatus = (that.validStatus = that.validStatus || ["EQUIPMENT_OK", "EQUIPMENT_DAMAGED"]);
    var pship = player.ship;
    var eqStatus = pship.equipmentStatus(eqKey);
    var logging = ws.$logging;
    var debug = ws.$debug;

    if (validStatus.indexOf(eqStatus) === -1) {
        log(this.name, "Error: "+eqKey+" has invalid status "+eqStatus+" for disabling, ignoring");
        return false;
    }
    if (!ws.$disabledEquipment) ws.$disabledEquipment = {};
    if (!(eqKey in ws.$disabledEquipment)) ws.$disabledEquipment[eqKey] = [];
    var disabledReasons = ws.$disabledEquipment[eqKey];
    var i = disabledReasons.length;
    var found = false;
    while (i--)
        if (disabledReasons[i].who === who) {
            found = true;
            disabledReasons[i].why = why;
            break;
        }
    if (!found) ws.$disabledEquipment[eqKey].push({who: who, why: why});
    if (eqStatus !== "EQUIPMENT_DAMAGED")
        pship.setEquipmentStatus(eqKey, "EQUIPMENT_DAMAGED");
    var ok = (pship.equipmentStatus(eqKey) === "EQUIPMENT_DAMAGED");
    if (logging) log(ws.name, eqKey+" has been disabled by "+who+" because "+why+", success: "+ok);
    return ok;
}

//-----------------------------------------------------------------------------------------------//
this.$enableEquipment = function _repsys_enableEquipment(eqKey, who) {
    var that = _repsys_enableEquipment;
    var ws = worldScripts["Repair System"];
    var validStatus = (that.validStatus = that.validStatus || ["EQUIPMENT_OK", "EQUIPMENT_DAMAGED"]);
    var pship = player.ship;
    var eqStatus = pship.equipmentStatus(eqKey);
    var logging = ws.$logging;
    var debug = ws.$debug;
    var error;

    if (debug) log(ws.name, who+" asked for "+eqKey+" to be enabled");
    if (validStatus.indexOf(eqStatus) === -1)
        error = eqKey+" has invalid status "+eqStatus+" for enabling, ignoring";
    else if (!ws.$disabledEquipment)
        error = "Can't find a disabled equipment list";
    else if (!(eqKey in ws.$disabledEquipment))
        error = eqKey+" is not in the disabled equipment list";
    else {
        var disabledReasons = ws.$disabledEquipment[eqKey];
        var i = disabledReasons.length;
        var why = null;
        while (i--)
            if (disabledReasons[i].who === who) {
                why = disabledReasons[i].why;
                disabledReasons.splice(i, 1);
            }
        if (!why)
            error = eqKey+" was not disabled by "+who;
        else {
            if (disabledReasons.length > 0) {
                if (logging) log(ws.name, who+" was removed from the disabled list of "+eqKey+", but there are "+disabledReasons.length+" more, so it stays disabled");
                return false;
            } else {
                pship.setEquipmentStatus(eqKey, "EQUIPMENT_OK");
                var ok = (pship.equipmentStatus(eqKey) === "EQUIPMENT_OK");
                delete ws.$disabledEquipment[eqKey];
                if (logging) log(ws.name, eqKey+" was enabled, success: "+ok);
                return ok;
            }
        } 
    }

    log(ws.name, "Error: "+error);
    return error;
}

Scripts/repair_system_conditions.js
"use strict";
this.name           = "repair_system_conditions";
this.author         = "Dybal"
this.copyright      = "2020 Dybal";
this.license        = "CC BY-NC-SA 4.0";
this.description    = "Repair System Equipment Conditions";
this.version        = "2.11";

this.allowAwardEquipment = function(equipment, ship, context) {
    var _controller = worldScripts["Repair System"].$controllerVersion;
    var _charges_left = missionVariables.repairCounter;
    var ret = false;    

    if (equipment === "EQ_REPAIRBOTS_RECHARGE_10") {
        if (_charges_left <= 5)    
            ret = true;
    } else if (equipment === "EQ_REPAIRBOTS_RECHARGE_5") {
        if (_controller === "EQ_REPAIRBOTS_CONTROLLER_LIGHT" &&  _charges_left <= 2 ||
            _controller === "EQ_REPAIRBOTS_CONTROLLER" &&  _charges_left <= 7)    
            ret = true;
    }
    return ret;
}