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

Expansion Contracts on Bulletin Board

Content

Warnings

  1. http://wiki.alioth.net/index.php/Contracts%20on%20Bulletin%20Board -> 404 Not Found
  2. Low hanging fuit: Information URL exists...

Manifest

from Expansion Manager's OXP list from Expansion Manifest
Description Moves all cargo, passenger and parcel contracts onto the Bulletin Board Moves all cargo, passenger and parcel contracts onto the Bulletin Board
Identifier oolite.oxp.phkb.ContractsOnBB oolite.oxp.phkb.ContractsOnBB
Title Contracts on Bulletin Board Contracts on Bulletin Board
Category Miscellaneous Miscellaneous
Author phkb phkb
Version 1.11 1.11
Tags
Required Oolite Version
Maximum Oolite Version
Required Expansions
  • oolite.oxp.phkb.BulletinBoardSystem:1.0
  • oolite.oxp.phkb.BulletinBoardSystem:1.0
  • Optional Expansions
    Conflict Expansions
    Information URL https://wiki.alioth.net/index.php/Bulletin_Board_Contracts n/a
    Download URL https://wiki.alioth.net/img_auth.php/b/b1/ContractsOnBB.oxz n/a
    License CC-BY-NC-SA 4.0 CC-BY-NC-SA 4.0
    File Size n/a
    Upload date 1709981698

    Documentation

    readme.txt

    Contracts On BB
    By Nick Rogers
    
    Overview
    ========
    This OXP moves all contracts (cargo, passenger and parcel) onto the Bulletin Board system, and removes the 3 F4 interface entries. There are no functional changes to the contracts themselves - simply the means of viewing and accepting them, unifying the user experience and reducing the number of items on the F4 interface screen.
    
    The list of active cargo, passenger and parcel contracts will appear in their normal position on the F5F5 Manifest screen. The details of each contract can also be seen on the bulletin board. Once the contract is fulfilled, the mission will be automatically removed from the Bulletin Board.
    
    If the "Smugglers - The Galactic Underworld" OXP is installed, all smuggling contracts will also be moved onto the Bulletin Board.
    
    Other Mission OXP's
    ===================
    The following OXP's can have their missions moved onto the Bulletin Board by using the Library OXP, finding the "Contracts On BB" section, and then turning on the appropriate flags. By default, all the flags are turned off. Turning one of the flags on without having the actual OXP installed will not do anything.
    
    If you change the flag to true at a station where any of the contracts from the OXP's below are already on offer, you may need to launch and re-dock before the missions are added to the Bulletin Board.
    
    Escort Contracts
    ----------------
    When adding Escort Contracts to the Bulletin Board, these missions do not have an expiry date, and no ability to manually terminate the contract. This follows the procedure of the OXP, which assumes that once a mission is accepted it is either completed successfully, or failed.
    
    In either case, once the mission is completed, the mission will be automatically removed from the Bulletin Board.
    
    Contract status will still be displayed in the "Missions" section of the F5F5 Manifest screen. The F4 interface screen will be removed.
    
    Rescue Stations
    ---------------
    Adding Rescue Station missions to the Bulletin Board does change the mission framework a little. In it's original form, players are presented with an overview of a mission, with some unspecific details (eg "Pay: Moderate"). The player can then choose to attend the mission briefing, where the actual details are displayed (eg destination, pay). At this screen, players are given two options, to either accept or reject the mission. If the player rejects this mission, it is completely removed - there is no chance to change your mind and go back into the mission and accept it.
    
    When accessed through the Bulletin Board, the first step of showing a generalised summary is removed. When a mission is displayed, you will be seeing the actual details of the mission (eg destination, pay, etc). You can also view this mission multiple times without accepting it.
    
    The details of each mission has also been adjusted slightly for better alignment with the style of display on the Bulletin Board.
    
    Mission status will still be displayed in the "Missions" section of the F5F5 Manifest screen. The F4 interface screen will be removed. Once the mission is completed, the mission will be automatically removed from the Bulletin Board.
    
    Random Hits
    -----------
    Adding Random Hits missions to the Bulletin Board makes some minor changes to the sequence of screens displayed. In it's original form, players are shown an email-like screen when they accept a contract. This screen has been removed, and, if the email system is installed, an actual email will be generated.
    
    Contracts can still be terminated, and accessing the Bulletin Board on a Space Bar will still incur a small charge.
    
    Contract status will still be displayed in the "Missions" section of the F5F5 Manifest screen. The F4 interface screen will be removed. Once the mission is completed, the mission will be automatically removed from the Bulletin Board.
    
    In-System Taxi
    --------------
    In-System Taxi contracts are similar to passenger missions, except that there is no destination system. When converted to use the Bulletin Board, the destination will be the current system. The taxi contract can be accepted from the Bulletin Board, and then will be viewable in the "Missions" section of the F5F5 Manifest screen, and will be removed automatically when the passenger is delivered. Taxi contracts accepted while in flight will not be displayed on the Bulletin Board at all.
    
    Mining Contracts
    ----------------
    Mining Contracts are only available from Rock Hermits, and work in the same way as the original pack. The Bulletin Board will now always be available at Rock Hermits, even if there are no contracts available. A new menu item in the Bulletin Board, "Wait for new mining contracts", will perform the same "Wait" function as expected.
    
    An additional benefit from having the mining contracts on the Bulletin Board is that, once you have accepted one of them, you can dock at any station and hand in cargo, rather than being limited to only docking at Rock Hermits. You will still need to dock at a Rock Hermit in order to complete the contract.
    
    Mining contracts will be listed on the F5F5 Manifest screen, under the "Bulletin Board Contracts" section, rather than in the general "Missions" section.
    
    Taxi Galactica
    --------------
    Taxi Galactica contracts are only available from Taxi Stations located in Corporate State systems. All TG missions translate to standard passenger missions, and will be available from the BB upon docking.
    
    Contract status will still be displayed in the "Missions" section of the F5F5 Manifest screen (as per standard passenger contracts). Once the mission is completed, the mission will be automatically removed from the Bulletin Board.
    
    License
    =======
    This work is licensed under the Creative Commons Attribution-Noncommercial-Share Alike 4.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/
    
    Life preserver image made by "Icon Works" http://www.flaticon.com/authors/icon-works from http://www.flaticon.com, licensed by CC 3.0 BY http://creativecommons.org/licenses/by/3.0/
    Target image made by "Freepik" http://www.freepik.com from http://www.flaticon.com, licensed by CC 3.0 BY http://creativecommons.org/licenses/by/3.0/
    Shield image made by "Freepik" http://www.freepik.com from http://www.flaticon.com, licensed by CC 3.0 BY http://creativecommons.org/licenses/by/3.0/
    Rocket image from http://simpleicon.com/rocket.html
    Shovel/Pick icon made by "Made by Made" https://www.flaticon.com/authors/made-by-made from https://www.flaticon.com/, licensed by http://creativecommons.org/licenses/by/3.0/
    
    Version History
    ===============
    1.11
    - Added further protection for OXP cargo type removal.
    
    1.10
    - Added protection for when OXP cargo types are removed when the related OXP is uninstalled.
    
    1.9
    - Fixed issue that was leaving orphaned contracts in the list and potentially causing script failures.
    
    1.8
    - Improved logic for removing missions before adding new ones.
    - Small text corrections.
    
    1.7
    - Improved logic for adding missions to prevent duplicate ID's being selected.
    - Fixed bug that was preventing passenger contracts from being re-indexed correctly.
    - Code cleanup.
    
    1.6
    - Fixed issue with Taxi Galctica contract conversion flag not being restored correctly from a save game.
    - Fixed issue with the link to the Bounty System for failed Mining Contracts that generate bounties.
    
    1.5
    - Fixed issue with Escort Contracts showing as being available when they don't actually exist any more.
    
    1.4
    - Fixed issue with Escort Contracts, where failing the contract by jumping to the wrong system would not remove the Bulletin Board item.
    - Added contracts from "Taxi Galactica" OXP (v2.0) onto the Bulletin Board.
    - Updates to handle new features in Bulletin Board v1.4
    - Code refactoring.
    
    1.3 
    - Better handling of situation where Smugglers OXP is not installed.
    
    1.2
    - More adjustments for accepting of RH contracts.
    
    1.1
    - Adjusted code for accepting Random Hits contracts to improve reliability.
    - Code refactoring.
    
    1.0
    - Added contracts from the "Mining Contracts" OXP onto the Bulletin Board.
    - Fixed issue where the flag for converting In-System Taxi contracts was not being saved correctly in the save game.
    - Turned on decimal place display on all credit value output.
    
    0.12
    - Fixed bug with passenger contracts causing a JavaScript error.
    - Fixed bug with there being no main station in interstellar space.
    
    0.11
    - Fixed buggy process for checking when Smuggling contracts are completed.
    
    0.10
    - More robust logic for connecting with Smugglers Black Market.
    - Added logic to remove passenger contracts after purchasing a new ship.
    
    0.9
    - Attempt to fix issue where using an escape pod leads to indexing errors.
    - Added contracts from the "In-System Taxi" OXP onto the Bulletin Board.
    
    0.8
    - Added checks to ensure no mission is added to the BB with an expiry date in the past.
    
    0.7
    - Corrected Xenon Redux UI image filenames.
    - Corrected Xenon Redux UI world script reference.
    - Code refactoring.
    
    0.6
    - Added the "Shuffle BB" flag to the Library options.
    - Turned of the "Shuffle BB" flag by default.
    - Fixed incompatibility with v0.20 of Bulletin Board system.
    
    0.5
    - Removed selected asteroid keys from being used as background models for Rescue Station missions.
    - Fixed improper casing in details of cargo and parcel contracts.
    
    0.4
    - Default settings for the various OXP inclusions now set correctly to "false".
    
    0.3
    - Added contracts from the "Escort Contracts" OXP onto the Bulletin Board.
    - Added missions from the "Rescue Stations" OXP onto the Bulletin Board.
    - Added contracts from the "Random Hits" OXP onto the Bulletin Board.
    - Contract conversion for core contracts will now only occur when at a main station.
    - Contract conversion for smuggling contracts will now only occur at stations with a black market.
    
    0.2
    - Fixed problem with calling Smuggling contract validation without checking that the Smuggling OXP is installed.
    
    0.1
    - Initial release.
    

    Equipment

    This expansion declares no equipment. This may be related to warnings.

    Ships

    This expansion declares no ships. This may be related to warnings.

    Models

    This expansion declares no models. This may be related to warnings.

    Scripts

    Path
    Scripts/contracts_on_bb.js
    "use strict";
    this.name = "ContractsOnBB";
    this.author = "phkb";
    this.copyright = "2017 phkb";
    this.description = "Converts the cargo, parcel and passenger contracts to be delivered through the BB interface";
    this.licence = "CC BY-NC-SA 4.0";
    
    this._disabled = false; // flag to quickly disable the OXP
    this._turnOffF4Entries = true; // flag to indicate all F4 interface entries should be removed for applicable contract OXP's
    this._bbShuffle = false; // flag to turn on/off BB list shuffle post conversion process
    this._smuggling = false; // flag to indicate smuggling contracts will be converted
    this._repopulate = true; // flag used to note when the first repopulate process is being run
    this._performConversions = true; // flag used to determine if contracts need to be converted after docking
    this._switchDefault = false; // convenience switch to make it simple to turn on or off contract conversion by default
    
    /* 
        Escort contracts are offered to the player via a menu, and then player is then left to launch themselves.
        This makes these contracts the easiest to convert, as we're essentially just changing one menu system for another.
    */
    this._convertEscortContracts = this._switchDefault; // flag to turn on/off escort contract conversions
    this._doEscortContractsConversion = false; // flag to indicate that the conversion process is going/about to be run
    // used by the escort contracts conversion process
    this._govTypes = ["an anarchy", "a feudal state", "a multi-government system", "a dictatorship", "a communist system", "a confederacy", "a democracy", "a corporate state"];
    this._govRisk = ["very high", "high", "moderate", "moderate", "low", "low", "very low", "very low"];
    
    /* 
        RRS missions are presented to the player in two steps: first a very simple overview, followed by a more detailed briefing.
        Once the player has viewed the more detailed briefing, that mission can only be accepted or discarded completely.
        Mission variables are only setup when the detailed briefing is displayed, including random destinations or risk factors.
        Re-run initialisation phase mean new values will be established. But the BB wants the more detailed information
        the initialisation phase provides, and well ahead of when the player may or may not choose to accept the mission. 
        This means we need to store the first set of values, and override all variables once the mission is accepted.
    
        We are also using customised versions of the mission briefing details for RRS missions. This was purely for aesthetic reasons:
        the deadlines and payment amounts are shown on the BB item details, so I removed them from the text. It was too complicated
        to try and do some sort of automatic text replacement.
    
        The upshot is, should the RRS OXP change in any way internally (eg adding new mission variables, changing variable meanings), 
        this OXP will break.
    */
    this._convertRRSContracts = this._switchDefault; // flag to turn on/off rescue station mission conversions
    this._doRRSContractsConversion = false; // flag to indicate that the conversion process is going/about to be run
    this._rrsMissionList = []; // holds list of mission scenarios currently being offered
    this._rrsMissionStorage = []; // storage point for all mission scenario mission variables
    this._rrsMissionCurrent = {}; // storage of the current mission variables, in case a mission is already active
    // lists of the mission variables in use by the different RRS scenarios
    this._rrsMissionVariables = {
        "Rescue Scenario 1": ["rescuestation_danger1", "rescuestation_danger2", "rescuestation_leader_home", "rescuestation_derelict_home", "rescuestation_startsystem", "rescuestation_destsystem", "rescuestation_destsystem_name", "rescuestation_destsystem_type", "rescuestation_shiprole", "rescuestation_shiprole2", "rescuestation_missionwingsize", "rescuestation_wingname", "rescuestation_reward", "rescuestation_livefire"],
        "Rescue Scenario 1a": ["rescuestation_danger1", "rescuestation_danger2", "rescuestation_leader_home", "rescuestation_derelict_home", "rescuestation_startsystem", "rescuestation_destsystem", "rescuestation_destsystem_name", "rescuestation_destsystem_type", "rescuestation_shiprole", "rescuestation_shiprole2", "rescuestation_missionwingsize", "rescuestation_wingname", "rescuestation_reward", "rescuestation_livefire"],
        "Rescue Scenario 1b": ["rescuestation_danger1", "rescuestation_danger2", "rescuestation_leader_home", "rescuestation_derelict_home", "rescuestation_startsystem", "rescuestation_destsystem", "rescuestation_destsystem_name", "rescuestation_destsystem_type", "rescuestation_shiprole", "rescuestation_shiprole2", "rescuestation_missionwingsize", "rescuestation_wingname", "rescuestation_reward", "rescuestation_livefire"],
        "Rescue Scenario 2": ["rescuestation_reward", "rescuestation_system"],
        "Rescue Scenario 2a": ["rescuestation_reward", "rescuestation_system", "rescuestation_deadline"],
        "Rescue Scenario 2b": ["rescuestation_reward", "rescuestation_system", "rescuestation_deadline"],
        "Rescue Scenario 3": ["rescuestation_startsystem", "rescuestation_startsystem_name", "rescuestation_destsystem", "rescuestation_destsystem_name", "rescuestation_destsystem_desc", "rescuestation_deadline", "rescuestation_deadline_text", "rescuestation_reward", "rescuestation_danger1", "rescuestation_danger2"],
        "Rescue Scenario 3a": ["rescuestation_startsystem", "rescuestation_startsystem_name", "rescuestation_destsystem", "rescuestation_destsystem_name", "rescuestation_destsystem_desc", "rescuestation_deadline", "rescuestation_deadline_text", "rescuestation_reward", "rescuestation_danger1", "rescuestation_danger2"],
        "Rescue Scenario 4": ["rescuestation_system", "rescuestation_system_name", "rescuestation_destsystem", "rescuestation_destsystem_name", "rescuestation_destsystem_desc", "rescuestation_deadline", "rescuestation_deadline_text", "rescuestation_pilot", "rescuestation_pilotdesc", "rescuestation_reward"],
        "Rescue Scenario 4a": ["rescuestation_system", "rescuestation_system_name", "rescuestation_destsystem", "rescuestation_destsystem_name", "rescuestation_destsystem_desc", "rescuestation_deadline", "rescuestation_deadline_text", "rescuestation_pilot", "rescuestation_pilotdesc", "rescuestation_reward", "rescuestation_danger1", "rescuestation_danger2"],
        "Rescue Scenario 4b": ["rescuestation_system", "rescuestation_system_name", "rescuestation_destsystem", "rescuestation_destsystem_name", "rescuestation_deadline", "rescuestation_pilot", "rescuestation_pilotdesc", "rescuestation_agent", "rescuestation_agentdesc", "rescuestation_reward"],
        "Rescue Scenario 5": ["rescuestation_reward", "rescuestation_system", "rescuestation_shipname"],
        "Rescue Scenario 5a": ["rescuestation_reward", "rescuestation_system", "rescuestation_shipname"],
        "Rescue Scenario 6": ["rescuestation_system", "rescuestation_destsystem", "rescuestation_destsystem_name", "rescuestation_destsystem_desc", "rescuestation_deadline", "rescuestation_deadline_text", "rescuestation_reward", "rescuestation_danger"],
        "Rescue Scenario 6a": ["rescuestation_system", "rescuestation_destsystem", "rescuestation_destsystem_name", "rescuestation_destsystem_desc", "rescuestation_destsystem2", "rescuestation_destsystem2_name", "rescuestation_destsystem2_desc", "rescuestation_deadline", "rescuestation_deadline_text", "rescuestation_reward", "rescuestation_danger", "rescuestation_disease"]
    };
    // lists the model to use on the background of the mission description for all the different RRS scenarios
    this._rrsMissionShipRole = {
        "Rescue Scenario 1": "shipModel",
        "Rescue Scenario 1a": "shipModel",
        "Rescue Scenario 1b": "shipModel",
        "Rescue Scenario 2": "rescue_station",
        "Rescue Scenario 2a": "rescue_station",
        "Rescue Scenario 2b": "EQ_RRS_SOLAR_MINE",
        "Rescue Scenario 3": "asteroidModel",
        "Rescue Scenario 3a": "asteroidModel",
        "Rescue Scenario 4": "rescue_station",
        "Rescue Scenario 4a": "rescue_station",
        "Rescue Scenario 4b": "rescue_station",
        "Rescue Scenario 5": "rescue_station",
        "Rescue Scenario 5a": "rescue_station",
        "Rescue Scenario 6": "rescue_station",
        "Rescue Scenario 6a": "rescue_station"
    };
    this._rrsAsteroidKeyList = [];
    
    /* 
        RH stores mission data in missionVariables, like RRS does. The variables are swapped in and out with 
        the "readHitpage" routine. But because the "pages" array has all the variable data, so we don't need to store it ourselves,
        as we do for RRS missions.
    
        Because of some idiosyncrasies with expandMissionText, the mission variable replacement doesn't occur when it should. 
        Thus, we have created a routine to look for and replace all mission variable reference items in the text with the 
        actual mission variable content.
    
        Again, because of the way we're doing things, should RH change any of it's mission variables, or change the variable's meanings,
        this OXP will break.
    */
    this._convertRHContracts = this._switchDefault; // flag to turn on/off random hits mission conversions
    this._doRHContractsConversion = false; // flag to indicate that the conversion process is going/about to be run
    // lists all the mission variables in use by RH
    this._rhMissionVariables = [
        "random_hits_mark_system",
        "random_hits_mark_direction",
        "random_hits_mark_gender",
        "random_hits_mark_first_name",
        "random_hits_mark_nick_name",
        "random_hits_mark_second_name",
        "random_hits_mark_race_part1",
        "random_hits_mark_race_part2",
        "random_hits_mark_ship_description",
        "random_hits_mark_ship",
        "random_hits_mark_ship_name",
        "random_hits_mark_ship_ad_name",
        "random_hits_mark_fee",
        "random_hits_mark_ship_personality",
        "random_hits_assassination_board_poster_title",
        "random_hits_assassination_board_job_name",
        "random_hits_assassination_board_poster_surname",
        "random_hits_assassination_board_poster_name",
        "random_hits_assassination_board_poster_system",
        "random_hits_assassination_board_subject",
        "random_hits_assassination_board_address1",
        "random_hits_assassination_board_address2",
        "random_hits_assassination_board_part1",
        "random_hits_assassination_board_part2",
        "random_hits_assassination_board_part3",
        "random_hits_assassination_board_part4",
        "random_hits_assassination_board_part5",
        "random_hits_assassination_board_part6",
        "random_hits_assassination_board_part7",
        "random_hits_showship"
    ];
    
    /* In-System Taxi */
    this._convertTaxiContracts = this._switchDefault;
    this._doTaxiContractsConversion = false;
    
    /*
        Bug in In-System Taxi: if you receive a comms-based notification of a client, then do a fast dock, launch, accept the contracts, then fast dock again, 
        the contract ends up with an expiry time in the past.
    */
    
    /* mining contracts */
    /* 
        note: because of the way mining contracts is structured, we can't reuse its internal functions for mission processing
        so much of the code that exists in mining contracts has been rewritten here, with the BB system's methodologies in mind
    */
    this._convertMiningContracts = this._switchDefault;
    this._doMiningContractsConversion = false;
    
    /* taxi galactica */
    this._convertTaxiGalacticaContracts = this._switchDefault;
    this._doTaxiGalacticaContractsConversion = false;
    
    // configuration settings for use in Lib_Config
    this._COBBConfig = {
        Name: this.name,
        Alias: "Contracts On BB",
        Display: "Conversion Options",
        Alive: "_COBBConfig",
        Notify: "$changeSettings",
        Bool: {
            B0: {
                Name: "_convertEscortContracts",
                Def: false,
                Desc: "Escort contracts"
            },
            B1: {
                Name: "_convertRRSContracts",
                Def: false,
                Desc: "Rescue Stations"
            },
            B2: {
                Name: "_convertRHContracts",
                Def: false,
                Desc: "Random Hits"
            },
            B3: {
                Name: "_convertTaxiContracts",
                Def: false,
                Desc: "In-System Taxi"
            },
            B4: {
                Name: "_convertMiningContracts",
                Def: false,
                Desc: "Mining contracts"
            },
            B5: {
                Name: "_convertTaxiGalacticaContracts",
                Def: false,
                Desc: "Taxi Galactica contracts"
            },
            B6: {
                Name: "_turnOffF4Entries",
                Def: true,
                Desc: "Remove F4 items"
            },
            Info: "0 - Converts Escort contracts\n1 - Converts Rescue Station missions.\n2 - Converts Random Hits missions.\n3 - Converts In-System Taxi contracts.\n4 - Converts Mining contracts.\n5 - Convert Taxi Galactica contracts.\n6 - Removes any F4 interface entries."
        },
    };
    this._trueValues = ["yes", "1", 1, "true", true];
    
    this._waitTimer = null;
    
    //-------------------------------------------------------------------------------------------------------------
    this.startUp = function () {
        if (this._disabled === true) {
            delete this.startUpComplete;
            delete this.systemWillPopulate;
            delete this.systemWillRepopulate;
            delete this.missionScreenOpportunity;
            delete this.guiScreenWillChange;
            delete this.shipDockedWithStation;
            delete this.shipWillDockWithStation;
            delete this.shipWillEnterWitchspace;
            delete this.playerCompletedContract;
            delete this.playerWillSaveGame;
            delete this.startUp;
            return;
        }
    
        var mc = worldScripts.miningcontracts;
        if (mc) {
            // hide this function for the moment
            mc.$cobb_ovr_startUpComplete = mc.startUpComplete;
        }
    
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.startUpComplete = function () {
        var count = 0;
        var bb = worldScripts.BulletinBoardSystem;
    
        if (worldScripts.BountySystem_Core) {
            var bs = worldScripts.BountySystem_Core;
            bs["contract obligations"] = {
                description: "Reneging on contract obligations with the Mining Association.",
                severity: "2"
            };
        }
    
        if (missionVariables.ContractsOnBB_ShuffleBB) this._bbShuffle = (this._trueValues.indexOf(missionVariables.ContractsOnBB_ShuffleBB) >= 0 ? true : false);
    
        if (missionVariables.ContractsOnBB_EscortContracts) this._convertEscortContracts = (this._trueValues.indexOf(missionVariables.ContractsOnBB_EscortContracts) >= 0 ? true : false);
        if (!worldScripts["Escort_Contracts"]) this._convertEscortContracts = false;
    
        if (missionVariables.ContractsOnBB_RRSContracts) this._convertRRSContracts = (this._trueValues.indexOf(missionVariables.ContractsOnBB_RRSContracts) >= 0 ? true : false);
        if (!worldScripts["Rescue Stations"]) {
            this._convertRRSContracts = false;
        } else {
            this.$rrsGetAsteroidKeyList();
            if (this._convertRRSContracts) {
                // monkey patch our required changes into RSS
                // this allows us to easily monitor when missions are completed
                var rrs = worldScripts["Rescue Stations"];
                rrs.$cobb_hold_missionSuccess = rrs.missionSuccess;
                rrs.missionSuccess = this.$cobb_missionSuccess;
                rrs.$cobb_hold_failMission = rrs.failMission;
                rrs.failMission = this.$cobb_failMission;
            }
        }
    
        if (missionVariables.ContractsOnBB_RHContracts) this._convertRHContracts = (this._trueValues.indexOf(missionVariables.ContractsOnBB_RHContracts) >= 0 ? true : false);
        if (!worldScripts["Random_Hits"]) this._convertRHContracts = false;
    
        if (missionVariables.ContractsOnBB_TaxiContracts) this._convertTaxiContracts = (this._trueValues.indexOf(missionVariables.ContractsOnBB_TaxiContracts) >= 0 ? true : false);
        if (!worldScripts["in-system_taxi"]) this._convertTaxiContracts = false;
    
        if (missionVariables.ContractsOnBB_MiningContracts) this._convertMiningContracts = (this._trueValues.indexOf(missionVariables.ContractsOnBB_MiningContracts) >= 0 ? true : false);
        if (!worldScripts["miningcontracts"]) this._convertMiningContracts = false;
    
        if (missionVariables.ContractsOnBB_TaxiGalacticaContracts) this._convertTaxiGalacticaContracts = (this._trueValues.indexOf(missionVariables.ContractsOnBB_TaxiGalacticaContracts) >= 0 ? true : false);
        if (!worldScripts["taxi_galactica_main"] || worldScripts["taxi_galactica_main"].version.indexOf("1.") >= 0) this._convertTaxiGalacticaContracts = false;
    
        if (missionVariables.ContractsOnBB_RRSMissionList) {
            this._rrsMissionList = JSON.parse(missionVariables.ContractsOnBB_RRSMissionList);
            delete missionVariables.ContractsOnBB_RRSMissionList;
        }
        if (missionVariables.ContractsOnBB_RRSMissionStorage) {
            this._rrsMissionStorage = JSON.parse(missionVariables.ContractsOnBB_RRSMissionStorage);
            delete missionVariables.ContractsOnBB_RRSMissionStorage;
        }
    
        // register our settings, if Lib_Config is present
        if (worldScripts.Lib_Config) worldScripts.Lib_Config._registerSet(this._COBBConfig);
    
        // cargo contracts
        // we're monkey patching the validateContracts function so we can do some link re-referencing of our own
        worldScripts["oolite-contracts-cargo"]._validateContracts = this.$bbovr_cargo_validateContracts;
        count = worldScripts["oolite-contracts-cargo"].$contracts.length;
        // are there any contracts available at the moment?
        if (count > 0) {
            // see if there are any unaccepted missions at ID 31000
            var itm = bb.$getItem(31000);
            // if there isn't, run the conversion process
            if (itm == null || this.$countContracts(31000) != count) this.$convertContracts("cargo");
        }
    
        // passenger contracts
        // we're monkey patching the validateContracts function so we can do some link re-referencing of our own
        worldScripts["oolite-contracts-passengers"]._validateContracts = this.$bbovr_passengers_validateContracts;
        count = worldScripts["oolite-contracts-passengers"].$passengers.length;
        if (count > 0) {
            // see if there are any unaccepted missions @ ID 32000
            var itm = bb.$getItem(32000);
            // if there isn't, run the conversion process
            if (itm == null || this.$countContracts(32000) != count) this.$convertContracts("passengers");
        }
    
        // parcel contracts
        // we're monkey patching the validateContracts function so we can do some link re-referencing of our own
        worldScripts["oolite-contracts-parcels"]._validateContracts = this.$bbovr_parcels_validateContracts;
        count = worldScripts["oolite-contracts-parcels"].$parcels.length;
        if (count > 0) {
            // see if there are any unaccepted missions @ ID 33000
            var itm = bb.$getItem(33000);
            // if there isn't, run the conversion process
            if (itm == null || this.$countContracts(33000) != count) this.$convertContracts("parcels");
        }
    
        // register our validateContracts script to be run just before the mission list is displayed
        bb.$registerBBEvent(this.name, "$validateContracts", "preListDisplay");
        // register our bar bill script to be run whenever the BB is exited
        bb.$registerBBEvent(this.name, "$rhBarBill", "close");
        bb.$registerBBEvent(this.name, "$rhBarBill", "exit");
        bb.$registerBBEvent(this.name, "$rhBarBillPostLaunch", "launchExit");
    
        // in-system taxi
        // we're monkey patching the "$acceptOffer" function to make it easier to determine when the player accepts a contract during flight
        if (this._convertTaxiContracts) {
            var taxi = worldScripts["in-system_taxi"];
            taxi.$ccob_acceptOffer = taxi.$acceptOffer;
            taxi.$acceptOffer = this.$acceptTaxiOffer_inFlight;
        }
    
        // mining contracts
        bb.$registerBBEvent(this.name, "$miningContractOpen", "preItemDisplay");
        bb.$registerBBEvent(this.name, "$miningContractListOpen", "preListDisplay");
        var mc = worldScripts.miningcontracts;
        if (this._convertMiningContracts) {
            this.$checkForHermit(player.ship.dockedStation);
            mc.$cobb_ovr_shipExitedWitchspace = mc.shipExitedWitchspace;
            mc.shipExitedWitchspace = this.$mc_shipExitedWitchspace;
        } else {
            if (mc) mc.$cobb_ovr_startUpComplete();
        }
    
        var tg = worldScripts.taxi_galactica_main;
        if (this._convertTaxiGalacticaContracts) {
            tg.$cobb_ovr_missionScreenOpportunity = tg.missionScreenOpportunity;
            delete tg.missionScreenOpportunity;
        }
    
        // do a quick check to make sure we don't have any orphaned smuggling contracts
        this.$checkSmugglingContracts();
        // un-accepted escort contracts are not saved between sessions - they're available when you dock,
        // but if you save and reload, they disappear. 
        // so do a quick check to make sure we don't have any orphaned escort contracts, where the source data
        // has been cleared away by a reload
        this.$checkEscortContacts();
    
        this.systemWillRepopulate();
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.systemWillPopulate = function () {
        // add our station key to the main station, so the contracts are only offered there
        if (system.mainStation) {
            worldScripts.BulletinBoardSystem.$addStationKey(this.name, system.mainStation, "mainStation");
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.systemWillRepopulate = function () {
        if (this._repopulate === true) {
            this._repopulate = false;
            var taxi = [];
            if (worldScripts["in-system_taxi"]) {
                taxi = worldScripts["in-system_taxi"].$listOfStations();
            }
            var sbm = worldScripts.Smugglers_BlackMarket;
            var bb = worldScripts.BulletinBoardSystem;
            for (var i = 0; i < system.stations.length; i++) {
                var stn = system.stations[i];
                if (sbm && sbm.$doesStationHaveBlackMarket) {
                    // add the black market station keys
                    if (sbm.$doesStationHaveBlackMarket(stn) === true) bb.$addStationKey(this.name, stn, "blackMarket");
                }
                // add rrs station keys
                if (stn.hasRole("rescue_station")) bb.$addStationKey(this.name, stn, "rescueStation");
                // add rh station keys
                if (stn.name === "A Seedy Space Bar") bb.$addStationKey(this.name, stn, "randomHits");
                // add tax station keys
                if (taxi.indexOf(stn) >= 0) bb.$addStationKey(this.name, stn, "taxi");
                // add rock hermit station keys
                if (Ship.roleIsInCategory(stn.primaryRole, "oolite-rockhermits") === true) bb.$addStationKey(this.name, stn, "rockHermit");
                // add taxi galactica station keys
                if (stn.hasRole("taxi_station")) bb.$addStationKey(this.name, stn, "taxiGalactica");
            }
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.shipWillEnterWitchspace = function () {
        this._repopulate = true;
        if (this._convertMiningContracts) {
            // remove any mining contracts from the bb
            // they'll be deleted from the mining contracts data via its own script - this just saves confusion
            var curr = this.$countContracts(39000);
            if (curr > 0) {
                var bb = worldScripts.BulletinBoardSystem;
                for (var i = 0; i < curr; i++) {
                    bb.$removeBBMission(39000 + i);
                }
            }
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.guiScreenWillChange = function (to, from) {
        // if we're able to show the interfaces screen while on the main station...
        if (to === "GUI_SCREEN_INTERFACES" && player.ship.dockedStation && this._turnOffF4Entries) {
            // remove the interfaces from the station
            if (system.mainStation) {
                system.mainStation.setInterface("oolite-contracts-cargo", null);
                system.mainStation.setInterface("oolite-contracts-passengers", null);
                system.mainStation.setInterface("oolite-contracts-parcels", null);
            }
            if (this._convertEscortContracts) player.ship.dockedStation.setInterface("escort_f4contracts", null);
            if (this._convertRRSContracts) player.ship.dockedStation.setInterface("rescue_station", null);
            if (this._convertRHContracts) player.ship.dockedStation.setInterface("RandomHits", null);
            if (this._convertTaxiContracts) player.ship.dockedStation.setInterface("in-system_taxi", null);
            if (this._convertMiningContracts) player.ship.dockedStation.setInterface("MiningContracts-bm", null);
            if (this._convertTaxiGalacticaContracts) player.ship.dockedStation.setInterface("taxi_galactica_main", null);
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.missionScreenOpportunity = function () {
        if (this._performConversions === true) {
            this._performConversions = false;
    
            var do_interface = false;
            var bb = worldScripts.BulletinBoardSystem;
            // do any contract conversion for EC, RRS and RH here
            // these flags will be turned on during shipDockedWithStation
            // ec
            if (this._doEscortContractsConversion === true) {
                this._doEscortContractsConversion = false;
                if (worldScripts["Escort_Contracts"].ec_targetsystems) {
                    var count = worldScripts["Escort_Contracts"].ec_targetsystems.length;
                    if (count > 0) {
                        // see if there are any unaccepted missions @ ID 35000
                        var itm = bb.$getItem(35000);
                        // if there isn't, run the conversion process
                        if (itm == null || this.$countContracts(35000) != count) {
                            this.$convertContracts("escort");
                            do_interface = true;
                        }
                    }
                }
            }
            // rrs
            if (this._doRRSContractsConversion === true) {
                this._doRRSContractsConversion = false;
                var rrs = worldScripts["Rescue Stations"];
                this._rrsMissionList.length = 0;
                if (rrs.availablescenarios && rrs.availablescenarios.length > 0) {
                    this._rrsMissionList = rrs.availablescenarios.concat();
                }
                if (rrs.availablescenarios_save && rrs.availablescenarios_save.length > 0) {
                    this._rrsMissionList = rrs.availablescenarios_save.concat();
                }
                if (this._rrsMissionList.length > 0) {
                    this._rrsMissionStorage = new Array(this._rrsMissionList.length);
                    // see if there are any unaccepted missions @ ID 36000
                    var itm = bb.$getItem(36000);
                    // if there isn't, run the conversion process
                    if (itm == null || this.$countContracts(36000) != this._rrsMissionList.length) {
                        this.$convertContracts("rescue");
                        do_interface = true;
                    }
                }
            }
            // rh
            if (this._doRHContractsConversion === true) {
                this._doRHContractsConversion = false;
                var rh = worldScripts["Random_Hits"];
                if (rh.pages && missionVariables.random_hits_status !== "RUNNING") {
                    var count = rh.pages.length;
                    if (count > 0) {
                        // see if there are any unaccepted missions @ ID 37000
                        var itm = bb.$getItem(37000);
                        // if there isn't, run the conversion process
                        if (itm == null || this.$countContracts(37000) != count) {
                            this.$convertContracts("random_hits");
                            do_interface = true;
                        }
                    }
                }
            }
            // in-system taxi
            if (this._doTaxiContractsConversion === true) {
                this._doTaxiContractsConversion = false;
                var taxi = worldScripts["in-system_taxi"];
                if (taxi.$stationOffers.length > 0) {
                    this.$convertContracts("taxi");
                    do_interface = true;
                }
            }
            // mining contracts
            if (this._doMiningContractsConversion === true) {
                this._doMiningContractsConversion = false;
                var mining = worldScripts.miningcontracts;
                var count = mining.MC_contracts.length;
                if (count > 0) {
                    // see if there are any unaccepted missions @ ID 39000
                    var itm = bb.$getItem(39000);
                    // if there isn't, run the conversion process
                    if (itm == null || this.$countContracts(39000) != count) {
                        this.$convertContracts("mining");
                        do_interface = true;
                    }
                }
            }
            // taxi galaxtica
            if (this._doTaxiGalacticaContractsConversion === true) {
                this._doTaxiGalacticaContractsConversion = false;
                var tg = worldScripts.taxi_galactica_main;
                // see if there are any unaccepted missions @ ID 40000
                var itm = bb.$getItem(40000);
                // if there isn't, run the conversion process
                if (itm == null || this.$countContracts(40000) != 3) {
                    this.$convertContracts("taxi_galactica");
                    do_interface = true;
                }
            }
            // force an update to the interface entry
            if (do_interface) bb.$initInterface(player.ship.dockedStation);
        }
    
        // due to the way taxi galactica is structured, we need to recreate some of the functionality from missionScreenOpportunity
        if (this._convertTaxiGalacticaContracts === true && worldScripts.taxi_galactica_main) {
            var tg = worldScripts.taxi_galactica_main;
            // Check if Player is currently docked at the main station:
            if (player.ship.dockedStation.isMainStation) {
                // Check if Player is doing a contract and is at the contract mission destination:
                if (missionVariables.taxistatus === "ON_THE_JOB" && system.ID === missionVariables.taxi_dest) {
                    // Change variable to reflect no longer doing contract:
                    missionVariables.taxistatus = "NOT_ON_JOB";
                    // Increase variable to reflect another contract completed:
                    missionVariables.taxijobstotal += 1;
                    // Refuel Player ship:
                    player.ship.fuel = 7.0;
                    // Check if Player was doing special mission #1:
                    if (missionVariables.taxi_diff === 3) {
                        // Change variable to reflect special mission #1 complete:
                        missionVariables.taxi_specmiss_1 = "MISSION_COMPLETE";
                        // Check if Snoopers OXP is installed and if so insert news story about events of special mission #1:
                        if (worldScripts.snoopers) worldScripts.snoopers.insertNews({
                            ID: this.name,
                            Agency: 1,
                            Message: expandDescription("[taxi_snoopers_specmiss_1]")
                        });
                        if (worldScripts.GNN) worldScripts.GNN._insertNews({
                            ID: this.name,
                            Agency: 1,
                            Message: expandDescription("[taxi_snoopers_specmiss_1]")
                        });
                    }
                }
            }
            // Check if Player is current docked at a Taxi Station:
            if (player.ship.dockedStation.hasRole("taxi_station")) {
                if (missionVariables.taxistat === "ACCEPTED") {
                    // Display contract accepted screen:
                    /*mission.runScreen(
                        {
                            title: "Taxi Galactica Contract Accepted", 
                            screenID: "taxi_galactica_accepted",
                            messageKey: "mission_taxi_accepted", 
                            model: "taxi_adder", 
                            choicesKey: "mission_taxi_on_job_choices"
                        });*/
                    missionVariables.taxistat = "ON_THE_JOB_1";
                    return;
                }
                if (missionVariables.taxistat === "REVISIT") {
                    // Check if Player is currently doing a contract and if so prompt Player that they need to complete contract:
                    if (missionVariables.taxistatus !== "ON_THE_JOB") {
                        // Check if Player has completed 5 or more contracts, has not yet completed special mission #1 and has a free passenger berth:
                        if (missionVariables.taxijobstotal >= 5 && missionVariables.taxi_specmiss_1 !== "MISSION_COMPLETE" && player.ship.passengerCount < player.ship.passengerCapacity) {
                            // Generate name for ambassador for special mission #1 briefing:
                            missionVariables.taxi_specmis_1_ambassador = randomName() + " " + randomName();
                            // Generate special mission #1 destination:
                            missionVariables.taxi_specmis1_dest = tg._generateTaxiDest(20);
                            missionVariables.taxi_specmis1_dest_name = System.systemNameForID(missionVariables.taxi_specmis1_dest);
                            // Get special mission #1 destination government type and assign name to variable: 
                            missionVariables.taxi_specmis1_dest_gov = System.infoForSystem(galaxyNumber, missionVariables.taxi_specmis1_dest).government;
                            if (missionVariables.taxi_specmis1_dest_gov === 0) missionVariables.taxi_specmis1_dest_gov_name = "Anarchy";
                            if (missionVariables.taxi_specmis1_dest_gov === 1) missionVariables.taxi_specmis1_dest_gov_name = "Feudal";
                            if (missionVariables.taxi_specmis1_dest_gov === 2) missionVariables.taxi_specmis1_dest_gov_name = "Multi-Governmental";
                            if (missionVariables.taxi_specmis1_dest_gov === 3) missionVariables.taxi_specmis1_dest_gov_name = "Dictatorship";
                            if (missionVariables.taxi_specmis1_dest_gov === 4) missionVariables.taxi_specmis1_dest_gov_name = "Communist";
                            if (missionVariables.taxi_specmis1_dest_gov === 5) missionVariables.taxi_specmis1_dest_gov_name = "Confederacy";
                            if (missionVariables.taxi_specmis1_dest_gov === 6) missionVariables.taxi_specmis1_dest_gov_name = "Democracy";
                            if (missionVariables.taxi_specmis1_dest_gov === 7) missionVariables.taxi_specmis1_dest_gov_name = "Corporate State";
                            // Generate time limit for special mission #1:
                            missionVariables.taxi_specmis1_time = Math.floor(Math.random() * (20 - 10 + 1) + 10);
                            // Determine special mission #1 destination distance and assign to variable:
                            missionVariables.taxi_specmis1_dist = Math.round(system.info.distanceToSystem(System.infoForSystem(galaxyNumber, missionVariables.taxi_specmis1_dest)) * 5) / 5;
                            // Display special mission #1 briefing screen:
                            mission.runScreen({
                                    title: "Taxi Galactica Contract Offer",
                                    screenID: "taxi_galactica_intro",
                                    messageKey: "mission_taxi_spec_mis_1_intro",
                                    model: "taxi_adder",
                                    modelPersonality: tg._modelPersonality,
                                    choicesKey: "mission_taxi_spec_mis_1_intro_choices"
                                },
                                this.$tg_choiceEvaluation);
    
                            missionVariables.taxistat = "SPEC_MIS_01_1";
                            return;
                        } else {
                            // Display welcome screen for Taxi Station and generate new contract destinations:
                            mission.runScreen({
                                    title: "Welcome to the Taxi Galactica Station",
                                    screenID: "taxi_galactica_mission_intro",
                                    messageKey: "mission_taxi_intro",
                                    model: "taxi_adder",
                                    modelPersonality: tg._modelPersonality,
                                    choicesKey: "mission_taxi_intro_choices"
                                },
                                this.$tg_choiceEvaluation);
                            tg._generateTaxiJobs();
                            this.$convertContracts("taxi_galactica");
                            worldScripts.BulletinBoardSystem.$initInterface(player.ship.dockedStation);
                            missionVariables.taxistat = "REVISIT_1";
                            return;
                        }
                    }
                }
            }
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.shipWillDockWithStation = function (station) {
        if (station.isMainStation) {
            this.$convertContracts("cargo");
            this.$convertContracts("passengers");
            this.$convertContracts("parcels");
        }
        if (this._smuggling) {
            if (worldScripts.Smugglers_BlackMarket.$doesStationHaveBlackMarket(station) === true) this.$convertContracts("smuggling");
        }
        var found = false;
        var bb = worldScripts.BulletinBoardSystem;
        // are any escort contracts going to be completed when we dock?
        if (this._convertEscortContracts) {
            var ecList = this.$getActiveContractsByType("escort");
            var ec = worldScripts["Escort_Contracts"];
            if (ecList.length > 0 && !ec.ec_currentcontract) {
                // there should only ever be one, which should be index 0
                bb.$removeBBMission(ecList[0].ID);
            }
            if ((ec.ec_currentcontract && ec.ec_currentcontract !== "success") || (ec.ec_currentcontract === "success" && system.info.systemID === ec.ec_targetsystem && player.ship.dockedStation.isMainStation)) {
                // find the contract in the BB to remove it
                // there should only ever be one, which should be index 0
                bb.$removeBBMission(ecList[0].ID);
            }
        }
        if (this._convertRHContracts) {
            // are any rh contracts going to be completed when we dock
            var rh = worldScripts["Random_Hits"];
            if (missionVariables.random_hits_status && rh.hitMissionEndings.indexOf(missionVariables.random_hits_status) > -1) {
                var rhList = this.$getActiveContractsByType("randomhits");
                if (rhList.length > 0) {
                    // find the contract in the BB to remove it
                    // there should only ever be one, which should be index 0
                    bb.$removeBBMission(rhList[0].ID);
                }
            }
        }
        if (this._convertMiningContracts) this.$checkForHermit(station);
    
        if (found === true) bb.$initInterface(player.ship.dockedStation);
        // turn on the flag that will do OXP contract conversions after the player docks
        this._performConversions = true;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.shipDockedWithStation = function (station) {
        var bb = worldScripts.BulletinBoardSystem;
    
        if (this._convertEscortContracts && station.isMainStation) this._doEscortContractsConversion = true;
        if (this._convertRRSContracts && station.hasRole("rescue_station")) this._doRRSContractsConversion = true;
        if (this._convertRHContracts && station.name === "A Seedy Space Bar") this._doRHContractsConversion = true;
    
        // first, reset the overlay back to the default
        bb.$resetOverlayDefault();
        // set the default overlay to use on the main BB list
        // rh board
        if (this._convertRHContracts && player.ship.dockedStation.name === "A Seedy Space Bar") bb.$setOverlayDefault({
            name: "cobb_rh.png",
            height: 546
        });
        // rrs board
        if (this._convertRRSContracts && player.ship.dockedStation.hasRole("rescue_station")) bb.$setOverlayDefault({
            name: "cobb_rrs.png",
            height: 546
        });
    
        // look for a taxi contract being completed and remove the BB item
        var taxi = worldScripts["in-system_taxi"];
        if (taxi && this._convertTaxiContracts) {
            if (taxi.$fireMissionScreen >= 2) {
                // note for screen 6, the passenger is converted into a normal passenger so the game can handle the processing
                // rather than trying to keep the bb item until the final delivery, I'm removing it here as with all other
                // outcomes. The passenger details will be on the F5F5 screen, just not on the BB
                //
                var tList = this.$getActiveContractsByType("taxi");
                if (tList.length > 0) {
                    // find the taxi contract and delete it.
                    // there should only ever be one, which should be index 0
                    bb.$removeBBMission(tList[0].ID);
                }
            }
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.shipLaunchedFromStation = function (station) {
        if (this._convertMiningContracts) {
            var bb = worldScripts.BulletinBoardSystem;
            // turn off the always visible flag
            if (Ship.roleIsInCategory(station.primaryRole, "oolite-rockhermits") === true) {
                bb._alwaysShowBB = false;
                // remove main menu items
                bb.$removeMainMenuItem(this.name, "$miningContract_wait");
                bb.$removeMainMenuItem(this.name, "$miningContract_repayDebt");
            }
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.playerCompletedContract = function (type, result, fee, contract) {
        switch (type) {
            case "cargo":
            case "passenger":
            case "parcel":
                // find and remove the contract from the BB
                var bb = worldScripts.BulletinBoardSystem;
                for (var i = 0; i < bb._data.length; i++) {
                    var itm = bb._data[i];
                    if (itm != null && itm.accepted === true) {
                        // is this item the one we're dealing with?
                        if (itm.destination === contract.destination && itm.payment === contract.fee && itm.source === contract.start) {
                            bb.$removeBBMission(itm.ID);
                            break;
                        }
                    }
                }
                break;
        }
    
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.playerBoughtNewShip = function (ship, price) {
        var bb = worldScripts.BulletinBoardSystem;
        var d = bb._data;
        var found = false;
        for (var i = d.length - 1; i >= 0; i--) {
            if (d[i].accepted === true && d[i].worldScript === this.name && d[i].initiateCallback === "$acceptPassengerContract") {
                bb.$removeBBMission(d[i].ID);
                found = true;
            }
        }
        if (found === true) bb.$initInterface(player.ship.dockedStation);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.playerWillSaveGame = function () {
        delete missionVariables.ContractsOnBB_EscortContracts;
        if (worldScripts["Escort_Contracts"]) missionVariables.ContractsOnBB_EscortContracts = this._convertEscortContracts;
        delete missionVariables.ContractsOnBB_RRSContracts;
        if (worldScripts["Rescue Stations"]) {
            missionVariables.ContractsOnBB_RRSContracts = this._convertRRSContracts;
            if (this._rrsMissionList && this._rrsMissionList.length > 0) {
                missionVariables.ContractsOnBB_RRSMissionList = JSON.stringify(this._rrsMissionList);
                missionVariables.ContractsOnBB_RRSMissionStorage = JSON.stringify(this._rrsMissionStorage);
            }
        }
        delete missionVariables.ContractsOnBB_RHContracts;
        if (worldScripts["Random_Hits"]) missionVariables.ContractsOnBB_RHContracts = this._convertRHContracts;
        delete missionVariables.ContractsOnBB_TaxiContracts;
        if (worldScripts["in-system_taxi"]) missionVariables.ContractsOnBB_TaxiContracts = this._convertTaxiContracts;
        delete missionVariables.ContractsOnBB_MiningContracts;
        if (worldScripts["miningcontracts"]) missionVariables.ContractsOnBB_MiningContracts = this._convertMiningContracts;
        delete missionVariables.ContractsOnBB_TaxiGalacticaContracts;
        if (worldScripts["taxi_galactica_main"]) missionVariables.ContractsOnBB_TaxiGalacticaContracts = this._convertTaxiGalacticaContracts;
        missionVariables.ContractsOnBB_ShuffleBB = this._bbShuffle;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$changeSettings = function () {
        var rrs = worldScripts["Rescue Stations"];
        if (rrs) {
            if (this._convertRRSContracts) {
                // monkey patch rrs with our new versions
                rrs.$cobb_hold_missionSuccess = rrs.missionSuccess;
                rrs.missionSuccess = this.$cobb_missionSuccess;
                rrs.$cobb_hold_failMission = rrs.failMission;
                rrs.failMission = this.$cobb_failMission;
            } else {
                // remove any monkey patches from rrs
                if (rrs.$cobb_hold_missionSuccess) rrs.missionSuccess = rrs.$cobb_hold_missionSuccess;
                if (rrs.$cobb_hold_failMission) rrs.failMission = rrs.$cobb_hold_failMission;
                delete rrs.$cobb_hold_missionSuccess;
                delete rrs.$cobb_hold_failMission;
            }
        }
        var tg = worldScripts.taxi_galactica_main;
        if (tg) {
            if (this._convertTaxiGalacticaContracts) {
                // monkey patch tg with our new versions
                tg.$cobb_ovr_missionScreenOpportunity = tg.missionScreenOpportunity;
                delete tg.missionScreenOpportunity;
            } else {
                // remove any monkey patches from tg
                tg.missionScreenOpportunity = tg.$cobb_ovr_missionScreenOpportunity;
                delete tg.$cobb_ovr_missionScreenOpportunity;
            }
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$convertContracts = function (contractType) {
        var bb = worldScripts.BulletinBoardSystem;
        var xui = false;
        var ovr1 = "";
        var ovr2 = "";
        var ovr3 = "";
        if (worldScripts.XenonReduxUI) {
            xui = true;
            ovr1 = {
                name: "xrui-cargo.png",
                height: 546
            };
            ovr2 = {
                name: "xrui-boardingpass.png",
                height: 546
            };
            ovr3 = {
                name: "xrui-briefcase.png",
                height: 546
            };
        }
        if (worldScripts.XenonUI) {
            xui = true;
            ovr1 = {
                name: "xui-cargo.png",
                height: 546
            };
            ovr2 = {
                name: "xui-boardingpass.png",
                height: 546
            };
            ovr3 = {
                name: "xui-briefcase.png",
                height: 546
            };
        }
    
        var curr = 0;
    
        /* Essentially, we're making a copy of all the contracts and assigning them an in in the 31000+ range.
        The ID assigned, minus the base amount, should match the index value of the contract. 
        Once a contract is accepted, the ID of the equivalent BB item is reset back to the normal range (0 - 30000), 
        excluding it from the rest of this code.
        When a mission is completed, the BB item is linked to the contract based on start system, destination system, and the payment amount.
        */
    
        switch (contractType) {
            case "cargo":
                // cargo contracts
                var cc = worldScripts["oolite-contracts-cargo"];
                // remove anything already there
                curr = this.$countContracts(31000);
                if (curr > 0) this.$clearMissionSet(curr, 31000);
                curr = 0;
                for (var i = 0; i < cc.$contracts.length; i++) {
                    if (system.mainStation.market[cc.$contracts[i].commodity] == undefined) continue
                    // make sure we only add missions that haven't passed their deadline
                    if (cc.$contracts[i].deadline > clock.adjustedSeconds) {
                        // make sure the id is clear
                        if (bb.$getIndex(31000 + curr) != -1) {
                            for (var j = 1; j < 100; j++) {
                                if (bb.$getIndex(31000 + curr + j) === -1) {
                                    curr = curr + j;
                                    break;
                                }
                            }
                        }
                        bb.$addBBMission({
                            ID: (31000 + curr), // cargo contracts are from 31000
                            source: system.ID,
                            destination: cc.$contracts[i].destination,
                            stationKey: "mainStation",
                            description: expandDescription("[contract-cargo-title]") + " " + cc._descriptionForGoods(cc.$contracts[i]).toLowerCase(),
                            details: expandDescription("[contract-cargo-description]", {
                                cargo: cc._descriptionForGoods(cc.$contracts[i]).toLowerCase(),
                                destination: System.systemNameForID(cc.$contracts[i].destination)
                            }),
                            payment: cc.$contracts[i].payment,
                            deposit: cc.$contracts[i].deposit,
                            allowTerminate: false,
                            completionType: "IMMEDIATE",
                            stopTimeAtComplete: true,
                            allowPartialComplete: false,
                            expiry: cc.$contracts[i].deadline,
                            disablePercentDisplay: true,
                            noEmails: true,
                            markerShape: "NONE", // marker control will be handled by contracts system
                            overlay: (xui === true ? ovr1 : ""),
                            playAcceptedSound: false,
                            remoteDepositProcess: true,
                            initiateCallback: "$acceptCargoContract",
                            availableCallback: "$cargoContractAvailable",
                            worldScript: this.name,
                        });
                        curr += 1;
                    }
                }
                break;
            case "passengers":
                // passenger contracts
                var pc = worldScripts["oolite-contracts-passengers"];
                // remove anything already there
                curr = this.$countContracts(32000);
                if (curr > 0) this.$clearMissionSet(curr, 32000);
                curr = 0;
                for (var i = 0; i < pc.$passengers.length; i++) {
                    // make sure we only add missions that haven't passed their deadline
                    if (pc.$passengers[i].deadline > clock.adjustedSeconds) {
                        var cust = [];
                        if (pc.$passengers[i].advance > 0) cust.push({
                            heading: expandDescription("[contract-advance]"),
                            value: formatCredits(pc.$passengers[i].advance, true, true)
                        });
                        cust.push({
                            heading: expandDescription("[contract-clientname]"),
                            value: pc.$passengers[i].name + ", a " + pc.$passengers[i].species
                        });
                        // make sure the id is clear
                        if (bb.$getIndex(32000 + curr) != -1) {
                            for (var j = 1; j < 100; j++) {
                                if (bb.$getIndex(32000 + curr + j) === -1) {
                                    curr = curr + j;
                                    break;
                                }
                            }
                        }
                        bb.$addBBMission({
                            ID: (32000 + curr), // passenger contracts are from 32000
                            source: system.ID,
                            destination: pc.$passengers[i].destination,
                            stationKey: "mainStation",
                            description: expandDescription("[contract-passenger-title]"),
                            details: expandDescription("[contract-passenger-description]", {
                                client: pc.$passengers[i].name,
                                destination: System.systemNameForID(pc.$passengers[i].destination)
                            }),
                            payment: pc.$passengers[i].payment,
                            allowTerminate: false,
                            completionType: "IMMEDIATE",
                            stopTimeAtComplete: true,
                            allowPartialComplete: false,
                            expiry: pc.$passengers[i].deadline,
                            disablePercentDisplay: true,
                            noEmails: true,
                            markerShape: "NONE", // marker control will be handled by contracts system
                            overlay: (xui === true ? ovr2 : ""),
                            playAcceptedSound: false,
                            customDisplayItems: cust,
                            initiateCallback: "$acceptPassengerContract",
                            availableCallback: "$passengerContractAvailable",
                            worldScript: this.name,
                        });
                        curr += 1;
                    }
                }
                break;
            case "parcels":
                // parcel contracts
                var bc = worldScripts["oolite-contracts-parcels"];
                // remove anything already there
                curr = this.$countContracts(33000);
                if (curr > 0) this.$clearMissionSet(curr, 33000);
                curr = 0;
                for (var i = 0; i < bc.$parcels.length; i++) {
                    // make sure we only add missions that haven't passed their deadline
                    if (bc.$parcels[i].deadline > clock.adjustedSeconds) {
                        // make sure the id is clear
                        if (bb.$getIndex(33000 + curr) != -1) {
                            for (var j = 1; j < 100; j++) {
                                if (bb.$getIndex(33000 + curr + j) === -1) {
                                    curr = curr + j;
                                    break;
                                }
                            }
                        }
                        var cust = [];
                        cust.push({
                            heading: expandDescription("[contract-clientname]"),
                            value: bc.$parcels[i].sender
                        });
    
                        bb.$addBBMission({
                            ID: (33000 + curr), // passenger contracts are from 33000
                            source: system.ID,
                            destination: bc.$parcels[i].destination,
                            stationKey: "mainStation",
                            description: expandDescription("[contract-parcel-title]"),
                            details: expandDescription("[contract-parcel-description]", {
                                parcel: bc.$parcels[i].description.toLowerCase(),
                                destination: System.systemNameForID(bc.$parcels[i].destination)
                            }),
                            payment: bc.$parcels[i].payment,
                            allowTerminate: false,
                            completionType: "IMMEDIATE",
                            stopTimeAtComplete: true,
                            allowPartialComplete: false,
                            expiry: bc.$parcels[i].deadline,
                            disablePercentDisplay: true,
                            noEmails: true,
                            markerShape: "NONE", // marker control will be handled by contracts system
                            overlay: (xui === true ? ovr3 : ""),
                            playAcceptedSound: false,
                            customDisplayItems: cust,
                            initiateCallback: "$acceptParcelContract",
                            availableCallback: "$parcelContractAvailable",
                            worldScript: this.name
                        });
                        curr += 1;
                    }
                }
                break;
            case "smuggling":
                if (this._smuggling) {
                    // smuggling contracts
                    var sc = worldScripts.Smugglers_Contracts;
                    // remove anything already there
                    curr = this.$countContracts(34000);
                    if (curr > 0) this.$clearMissionSet(curr, 34000);
                    curr = 0;
                    for (var i = 0; i < sc._contracts.length; i++) {
                        // make sure we only add missions that haven't passed their deadline
                        if (sc._contracts[i].deadline > clock.adjustedSeconds) {
                            // make sure the id is clear
                            if (bb.$getIndex(34000 + curr) != -1) {
                                for (var j = 1; j < 100; j++) {
                                    if (bb.$getIndex(34000 + curr + j) === -1) {
                                        curr = curr + j;
                                        break;
                                    }
                                }
                            }
                            bb.$addBBMission({
                                ID: (34000 + curr), // smuggling contracts are from 34000
                                source: system.ID,
                                destination: sc._contracts[i].destination,
                                stationKey: "blackMarket",
                                description: expandDescription("[contract-smuggling-title]") + " " + sc.$descriptionForGoods(sc._contracts[i]),
                                details: expandDescription("[contract-smuggling-description]", {
                                    cargo: sc.$descriptionForGoods(sc._contracts[i]),
                                    destination: System.systemNameForID(sc._contracts[i].destination)
                                }),
                                payment: sc._contracts[i].payment,
                                deposit: sc._contracts[i].deposit,
                                allowTerminate: false,
                                completionType: "IMMEDIATE",
                                stopTimeAtComplete: true,
                                allowPartialComplete: false,
                                expiry: sc._contracts[i].deadline,
                                disablePercentDisplay: true,
                                noEmails: true,
                                markerShape: "NONE", // marker control will be handled by smuggling contract system
                                overlay: {
                                    name: "stgu-skull.png",
                                    height: 546
                                },
                                playAcceptedSound: false,
                                remoteDepositProcess: true,
                                initiateCallback: "$acceptSmugglingContract",
                                availableCallback: "$smugglingContractAvailable",
                                worldScript: this.name,
                            });
                            curr += 1;
                        }
                    }
                    // force an update here
                    if (player.ship.docked) bb.$initInterface(player.ship.dockedStation);
                }
                break;
            case "escort":
                if (this._convertEscortContracts) {
                    var ec = worldScripts["Escort_Contracts"];
                    // remove anything already there
                    curr = this.$countContracts(35000);
                    if (curr > 0) this.$clearMissionSet(curr, 35000);
                    curr = 0;
                    for (var i = 0; i < ec.ec_targetsystems.length; i++) {
                        // make sure the id is clear;
                        if (bb.$getIndex(35000 + curr) != -1) {
                            for (var j = 1; j < 100; j++) {
                                if (bb.$getIndex(35000 + curr + j) === -1) {
                                    curr = curr + j;
                                    break;
                                }
                            }
                        }
                        var cust = [];
                        cust.push({
                            heading: expandDescription("[contract-escort-bonus]"),
                            value: formatCredits(ec.ec_killsbonuses[i], true, true) + " per kill"
                        });
    
                        var text = expandDescription("[ec_mission_details]", {
                            mothername: ec.ec_mothernames[i],
                            targetsystemname: ec.ec_targetsystemsnames[i],
                            dbcontractactualprice: formatCredits(ec.ec_contractactualprices[i], true, true),
                            dbkillsbonus: formatCredits(ec.ec_killsbonuses[i], true, true),
                            targetsystemgovernmentname: this._govTypes[ec.ec_targetsystemsgovernment[i]],
                            contractdifficultydesc: this._govRisk[ec.ec_targetsystemsgovernment[i]]
                        });
                        bb.$addBBMission({
                            ID: (35000 + curr), // escort contracts are from 35000
                            source: system.ID,
                            destination: ec.ec_targetsystems[i],
                            stationKey: "mainStation",
                            description: expandDescription("[contract-escort-title]"),
                            details: text,
                            payment: ec.ec_contractactualprices[i],
                            deposit: 0,
                            allowTerminate: false,
                            completionType: "IMMEDIATE",
                            stopTimeAtComplete: true,
                            allowPartialComplete: false,
                            expiry: -1,
                            disablePercentDisplay: true,
                            noEmails: false, // escort contracts don't have emails built in, so use the BB's system
                            markerShape: "NONE", // marker control will be handled by escort contracts
                            overlay: {
                                name: "cobb_ec.png",
                                height: 546
                            },
                            remoteDepositProcess: false,
                            customDisplayItems: cust,
                            initiateCallback: "$acceptEscortContract",
                            availableCallback: "$escortContractAvailable",
                            worldScript: this.name,
                            data: "escort"
                        });
                        curr += 1;
                    }
                }
                break;
            case "rescue":
                if (this._convertRRSContracts) {
                    var rrs = worldScripts["Rescue Stations"];
                    // remove anything already there
                    curr = this.$countContracts(36000);
                    if (curr > 0) this.$clearMissionSet(curr, 36000);
                    curr = 0;
                    // if we're currently running an RRS mission, then store the current variables before we start
                    if (rrs.missionActive()) this.$rrsStoreCurrentVariables();
                    for (var i = 0; i < this._rrsMissionList.length; i++) {
                        var cust = [];
                        cust.push({
                            heading: "Client name:",
                            value: "RRS Operations Manager"
                        });
                        var scen = "Rescue Scenario " + this._rrsMissionList[i];
                        if (!worldScripts[scen] && this._rrsMissionList[i].length > 1) scen = "Rescue Scenario " + this._rrsMissionList[i].substring(0, 1);
    
                        // load the mission variables for this scenario, either from RRS or from our saved data
                        if (this._rrsMissionStorage[scen]) {
                            this.$rrsReloadMissionVariables(scen, i);
                        } else {
                            worldScripts[scen].initMissionVariables();
                            this.$rrsSaveMissionVariables(scen, i);
                        }
                        // get the model for the mission type, but convert it into a shipKey so that the same model is shown every time.
                        // although secondary screens, displayed by RRS, might still show a different one. At least the BB will be consistent.
                        var mdl = this.$rrsGetKeyForShipRole(this.$rrsGetShipRoleForScenario(scen));
                        // make sure we only add missions that haven't passed their deadline
                        if (missionVariables.rescuestation_deadline > clock.adjustedSeconds) {
                            // make sure the id is clear
                            if (bb.$getIndex(36000 + curr) != -1) {
                                for (var j = 1; j < 100; j++) {
                                    if (bb.$getIndex(36000 + curr + j) === -1) {
                                        curr = curr + j;
                                        break;
                                    }
                                }
                            }
                            // add the mission to the bb
                            bb.$addBBMission({
                                ID: (36000 + curr), // rrs contracts are from 36000
                                source: system.ID,
                                destination: (missionVariables.rescuestation_destsystem ? missionVariables.rescuestation_destsystem : missionVariables.rescuestation_system),
                                stationKey: "rescueStation",
                                description: this.$rrsExtractMissionTitle(this._rrsMissionList[i]),
                                details: this.$rrsExtractMissionDetails(this._rrsMissionList[i]),
                                payment: missionVariables.rescuestation_reward,
                                deposit: 0,
                                allowTerminate: false,
                                completionType: "IMMEDIATE",
                                stopTimeAtComplete: true,
                                allowPartialComplete: false,
                                expiry: (missionVariables.rescuestation_deadline ? missionVariables.rescuestation_deadline : -1),
                                disablePercentDisplay: true,
                                noEmails: false,
                                markerShape: "NONE", // marker control will be handled by RRS
                                model: "[" + mdl + "]",
                                modelPersonality: Math.floor(Math.random() * 32768),
                                overlay: {
                                    name: "cobb_rrs.png",
                                    height: 546
                                },
                                remoteDepositProcess: false,
                                customDisplayItems: cust,
                                initiateCallback: "$acceptRRSContract",
                                availableCallback: "$rrsContractAvailable",
                                worldScript: this.name,
                                data: "rescue"
                            });
                            curr += 1;
                        }
                        this.$rrsResetMissionVariables(scen);
                    }
                    // put everything back how we found it
                    if (rrs.missionActive()) this.$rrsReturnCurrentVariables();
                }
                break;
            case "random_hits":
                if (this._convertRHContracts) {
                    var rh = worldScripts["Random_Hits"];
                    // remove anything already there
                    curr = this.$countContracts(37000);
                    if (curr > 0) this.$clearMissionSet(curr, 37000);
                    var difficultyText = ["Easy", "Challenging", "Hard"];
                    var page = 1;
                    curr = 0;
                    if (rh.pages && rh.pages.length > 0 && rh.pages[0] && rh.pages[0].hasOwnProperty("mark_system")) {
                        for (var i = 0; i < rh.pages.length; i++) {
                            var cust = [];
                            rh.readHitpage(i + 1);
                            //log(this.name, "rh " + missionVariables.random_hits_mark_system);
                            cust.push({
                                heading: "Client name:",
                                value: missionVariables.random_hits_assassination_board_poster_title + " " + missionVariables.random_hits_assassination_board_poster_name + " " + missionVariables.random_hits_assassination_board_poster_surname
                            });
                            cust.push({
                                heading: "Difficulty:",
                                value: difficultyText[page - 1]
                            })
                            cust.push({
                                heading: "Termination fee:",
                                value: formatCredits(missionVariables.random_hits_mark_fee / 10, true, true)
                            });
                            // make sure the id is clear
                            if (bb.$getIndex(37000 + curr) != -1) {
                                for (var j = 1; j < 100; j++) {
                                    if (bb.$getIndex(37000 + curr + j) === -1) {
                                        curr = curr + j;
                                        break;
                                    }
                                }
                            }
                            bb.$addBBMission({
                                ID: (37000 + curr), // rh contracts are from 37000
                                source: system.ID,
                                destination: (missionVariables.random_hits_mark_system ? System.systemIDForName(missionVariables.random_hits_mark_system) : system.ID),
                                stationKey: "randomHits",
                                description: this.$rhExtractMissionTitle(missionVariables.random_hits_assassination_board_subject),
                                details: this.$rhExtractMissionDetails(this.$rhReplaceMissionVariables(expandMissionText("level_" + page + "_mark_advert"))) + this.$rhDifficultyText(page),
                                payment: missionVariables.random_hits_mark_fee,
                                deposit: 0,
                                allowTerminate: true,
                                completionType: "IMMEDIATE",
                                stopTimeAtComplete: true,
                                allowPartialComplete: false,
                                expiry: -1,
                                disablePercentDisplay: true,
                                noEmails: false,
                                markerShape: "NONE", // marker control will be handled by RH
                                remoteDepositProcess: false,
                                customDisplayItems: cust,
                                model: "[" + missionVariables.random_hits_mark_ship + "]",
                                modelPersonality: missionVariables.random_hits_mark_ship_personality,
                                initiateCallback: "$acceptRHContract",
                                terminateCallback: "$terminateRHContract",
                                availableCallback: "$rhContractAvailable",
                                worldScript: this.name,
                                data: "randomhits"
                            });
                            curr += 1;
                            page += 1;
                            page = ((page - 1) % 3) + 1;
                        }
                    }
                }
                break;
            case "taxi":
                if (this._convertTaxiContracts) {
                    var taxi = worldScripts["in-system_taxi"];
                    // remove anything already there
                    curr = this.$countContracts(38000);
                    if (curr > 0) this.$clearMissionSet(curr, 38000);
                    curr = 0;
                    for (var i = 0; i < taxi.$stationOffers.length; i++) {
                        if (taxi.$stationOffers[i][0] < 2) {
                            var cust = [];
                            cust.push({
                                heading: expandDescription("[contract-clientname]"),
                                value: taxi.$stationOffers[i][5]
                            });
                            var text = "Taxi Galactica contract to transfer " + taxi.$stationOffers[i][5] + " from the " +
                                taxi.$stationOffers[i][1].displayName + " to the " + taxi.$stationOffers[i][2].displayName + ".\n\n" +
                                "Tips may be awarded for quick transfers.";
                            // make sure the id is clear
                            if (bb.$getIndex(38000 + curr) != -1) {
                                for (var j = 1; j < 100; j++) {
                                    if (bb.$getIndex(38000 + curr + j) === -1) {
                                        curr = curr + j;
                                        break;
                                    }
                                }
                            }
                            bb.$addBBMission({
                                ID: (38000 + curr), // escort contracts are from 35000
                                source: system.ID,
                                destination: system.ID,
                                stationKey: "taxi",
                                description: "Taxi Contract",
                                details: text,
                                payment: taxi.$stationOffers[i][4],
                                deposit: 0,
                                allowTerminate: false,
                                completionType: "IMMEDIATE",
                                stopTimeAtComplete: true,
                                allowPartialComplete: false,
                                expiry: taxi.$stationOffers[i][3],
                                disablePercentDisplay: true,
                                noEmails: false, // escort contracts don't have emails built in, so use the BB's system
                                markerShape: "NONE", // marker control will be handled by escort contracts
                                overlay: {
                                    name: "cobb_taxi.png",
                                    height: 546
                                },
                                remoteDepositProcess: false,
                                customDisplayItems: cust,
                                initiateCallback: "$acceptTaxiContract",
                                availableCallback: "$taxiContractAvailable",
                                worldScript: this.name,
                                data: "taxi"
                            });
                            curr += 1;
                        }
                    }
    
                }
                break;
            case "mining":
                if (this._convertMiningContracts) {
                    var mc = worldScripts.miningcontracts;
                    // remove anything already there
                    curr = this.$countContracts(39000);
                    if (curr > 0) this.$clearMissionSet(curr, 39000);
                    curr = 0;
                    for (var i = 0; i < mc.MC_contracts.length; i++) {
                        var contract = mc.MC_contracts[i];
                        var cust = [];
                        cust.push({
                            heading: "Client name:",
                            value: contract.company
                        });
                        cust.push({
                            heading: "Forfeit penalty:",
                            value: formatCredits(contract.penalty, true, true) + " per ton of shortage"
                        });
    
                        var med = "Mining Association of " + mc.MC_system;
                        var custMenu = [];
                        custMenu.push({
                            text: "Dispatch current cargo load",
                            worldScript: this.name,
                            callback: "$partMiningContract_process",
                            condition: "$partMiningContract_condition",
                            autoRemove: false
                        });
    
                        var text = expandDescription("[contract-mining-details]", {
                            amount: (contract.signed === 0 ? contract.amount : contract.gross),
                            cargo: displayNameForCommodity(contract.cargo).toLowerCase(),
                            mediator: med
                        });
    
                        // make sure the id is clear
                        if (bb.$getIndex(39000 + curr) != -1) {
                            for (var j = 1; j < 100; j++) {
                                if (bb.$getIndex(39000 + curr + j) === -1) {
                                    curr = curr + j;
                                    break;
                                }
                            }
                        }
    
                        bb.$addBBMission({
                            ID: (39000 + curr), // mining contracts are from 39000
                            source: system.ID,
                            destination: 256, // ignore the destination
                            stationKey: "rockHermit",
                            description: expandDescription("[contract-mining-title]", {
                                amount: (contract.signed === 0 ? contract.amount : contract.gross),
                                cargo: displayNameForCommodity(contract.cargo).toLowerCase()
                            }),
                            details: text,
                            payment: contract.reward,
                            deposit: 0,
                            allowTerminate: true,
                            completionType: "AT_SOURCE",
                            stopTimeAtComplete: false,
                            allowPartialComplete: false,
                            expiry: contract.deadline,
                            disablePercentDisplay: false,
                            percentComplete: (contract.signed === 0 ? 0.0 : (contract.gross - contract.amount) / contract.gross),
                            accepted: (contract.signed === 1 ? true : false),
                            noEmails: true, // because of the way we're re-adding these contracts all the time, we can't use the built in emailer
                            markerShape: "NONE", // mining contracts can only be completed in current system, so don't add any
                            overlay: {
                                name: "cobb_mining.png",
                                height: 546
                            },
                            remoteDepositProcess: false,
                            customMenuItems: custMenu,
                            customDisplayItems: cust,
                            initiateCallback: "$acceptMiningContract",
                            completedCallback: "$completedMiningContract",
                            confirmCompleteCallback: "$confirmCompleteMiningContract",
                            terminateCallback: "$terminateMiningContract",
                            manifestCallback: "$miningManifest",
                            worldScript: this.name,
                            postStatusMessages: [{
                                    status: "completed",
                                    return: "list",
                                    overlay: {
                                        name: "cobb_mining.png",
                                        height: 546
                                    },
                                    text: expandDescription("[contract-mining-completed]", {
                                        mediator: med,
                                        amount: formatCredits(contract.reward, true, true)
                                    })
                                },
                                {
                                    status: "terminated",
                                    return: "list",
                                    overlay: {
                                        name: "cobb_mining.png",
                                        height: 546
                                    },
                                    text: expandDescription("[contract-mining-terminated-1]", {
                                        mediator: med,
                                        amount: 0
                                    })
                                }
                            ],
                            data: "mining"
                        });
                        curr += 1;
                    }
                }
                break;
            case "taxi_galactica":
                var tg = worldScripts.taxi_galactica_main;
                // remove anything already there
                curr = this.$countContracts(40000);
                if (curr > 0) this.$clearMissionSet(curr, 40000);
                curr = 0;
                for (var i = 1; i <= 3; i++) {
                    var cust = [];
                    var s = System.infoForSystem(galaxyNumber, (Math.random() > 0.5 ? missionVariables["taxi_job_dest_" + i] : system.ID));
                    cust.push({
                        heading: expandDescription("[contract-clientname]"),
                        value: missionVariables["taxi_job_name_" + i] + ", a " + s.inhabitant
                    }, {
                        heading: "Difficulty",
                        value: missionVariables["taxi_job_diff_name_" + i]
                    });
                    // make sure the id is clear
                    if (bb.$getIndex(40000 + curr) != -1) {
                        for (var j = 1; j < 20; j++) {
                            if (bb.$getIndex(40000 + curr + j) === -1) {
                                curr = curr + j;
                                break;
                            }
                        }
                    }
                    bb.$addBBMission({
                        ID: (40000 + curr), // taxi galactica contracts are from 40000
                        source: system.ID,
                        destination: missionVariables["taxi_job_dest_" + i],
                        stationKey: "taxiGalactica",
                        description: expandDescription("[taxi-galactica-passenger-title]"),
                        details: expandDescription("[taxi-galactica-passenger-description]", {
                            client: missionVariables["taxi_job_name_" + i],
                            destination: System.systemNameForID(missionVariables["taxi_job_dest_" + i])
                        }),
                        payment: missionVariables["taxi_job_pay_" + i],
                        allowTerminate: false,
                        completionType: "IMMEDIATE",
                        stopTimeAtComplete: true,
                        allowPartialComplete: false,
                        expiry: (clock.seconds + missionVariables["taxi_job_time_" + i] * 24 * 3600),
                        disablePercentDisplay: true,
                        noEmails: true,
                        markerShape: "NONE", // marker control will be handled by contracts system
                        overlay: {
                            name: "cobb_taxi.png",
                            height: 546
                        },
                        customDisplayItems: cust,
                        initiateCallback: "$tg_acceptContract",
                        availableCallback: "$taxiGalacticaContractAvailable",
                        worldScript: this.name,
                        data: "taxi_galactica"
                    });
                    curr += 1;
                }
                break;
        }
    
        if (this._bbShuffle) bb.$shuffleBBList();
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // accept contract functions
    //-------------------------------------------------------------------------------------------------------------
    this.$acceptCargoContract = function (missID) {
        var idx = parseInt(missID);
        var cc = worldScripts["oolite-contracts-cargo"];
        cc.$contractIndex = idx - 31000;
        cc._acceptContract();
        // reindex the bb item, because we don't need to know about the details anymore
        var bb = worldScripts.BulletinBoardSystem;
        var item = bb.$getItem(missID);
        item.ID = bb.$nextID();
        bb._selectedItem = item.ID
    
        this.$reindexContracts(31000, idx - 31000, false);
    
        if (this._turnOffF4Entries) system.mainStation.setInterface("oolite-contracts-cargo", null);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$acceptPassengerContract = function (missID) {
        var idx = parseInt(missID);
        var pc = worldScripts["oolite-contracts-passengers"];
        pc.$contractIndex = idx - 32000;
        pc._acceptContract();
        // reindex the bb item, because we don't need to know about the details anymore
        var bb = worldScripts.BulletinBoardSystem;
        var item = bb.$getItem(missID);
        item.ID = bb.$nextID();
        bb._selectedItem = item.ID
    
        this.$reindexContracts(32000, idx - 32000, false);
    
        if (this._turnOffF4Entries) system.mainStation.setInterface("oolite-contracts-passengers", null);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$acceptParcelContract = function (missID) {
        var idx = parseInt(missID);
        var bc = worldScripts["oolite-contracts-parcels"];
        bc.$contractIndex = idx - 33000;
        bc._acceptContract();
        // reindex the bb item, because we don't need to know about the details anymore
        var bb = worldScripts.BulletinBoardSystem;
        var item = bb.$getItem(missID);
        item.ID = bb.$nextID();
        bb._selectedItem = item.ID
    
        this.$reindexContracts(33000, idx - 33000, false);
    
        if (this._turnOffF4Entries) system.mainStation.setInterface("oolite-contracts-parcels", null);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$acceptSmugglingContract = function (missID) {
        var idx = parseInt(missID);
        var sc = worldScripts.Smugglers_Contracts;
        sc._contractIndex = idx - 34000;
        sc.$acceptContract();
        // reindex the bb item, because we don't need to know about the details anymore
        var bb = worldScripts.BulletinBoardSystem;
        var item = bb.$getItem(missID);
        item.ID = bb.$nextID();
        bb._selectedItem = item.ID
    
        this.$reindexContracts(34000, idx - 34000, false);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$acceptEscortContract = function (missID) {
        var idx = parseInt(missID) - 35000;
        var ec = worldScripts["Escort_Contracts"];
        ec.ec_contractno = idx;
        ec.ec_updatemissionvariables();
        ec.ec_currentcontract = true;
        ec.ec_contractexpiretime = clock.minutes + 180;
        ec.ec_missionkills = 0;
        ec.ec_payment = 0;
        ec.ec_mothername = missionVariables.ec_mothername;
        ec.ec_targetsystem = missionVariables.ec_targetsystem;
        ec.ec_contractactualprice = missionVariables.ec_dbcontractactualprice;
        ec.ec_killsbonus = missionVariables.ec_dbkillsbonus;
        mission.setInstructionsKey("Contract_Details", "Escort_Contracts");
        ec.ec_cleanupmissionVariables();
    
        // theoretically we don't need to do this because you can only accept one escort mission at a time
        // but to be consistent...
    
        // reindex the bb item, because we don't need to know about the details anymore
        var bb = worldScripts.BulletinBoardSystem;
        var item = bb.$getItem(missID);
        item.ID = bb.$nextID();
        bb._selectedItem = item.ID
    
        this.$reindexContracts(35000, idx, false);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$acceptRRSContract = function (missID) {
        var bb = worldScripts.BulletinBoardSystem;
        // RRS missions have their own post-acceptance screen, but we need to exit the BB to see it
        bb._displayType = -1;
    
        // reset mission variables back to the details for the mission
        var idx = parseInt(missID) - 36000;
        var rrsScenario = this._rrsMissionList[idx];
        // run the initMissionVariables routine, but we'll be overwriting all the values anyway.
        worldScripts["Rescue Stations"].initMissionVariables(rrsScenario);
        // force the stage into 2, to ensure the correct mission screen is displayed during the "missionOfferDecision" routine.
        missionVariables.rescuestation_stage = 2;
    
        // now that the mission is accepted, plug in the variables we stored
        var list = this._rrsMissionVariables["Rescue Scenario " + rrsScenario];
        for (var i = 0; i < list.length; i++) {
            missionVariables[list[i]] = this._rrsMissionStorage[idx][list[i]];
        }
    
        // remove the mission from our holding arrays
        this._rrsMissionList.splice(idx, 1);
        this._rrsMissionStorage.splice(idx, 1);
    
        // check for, and run, any post-mission acceptance routine 
        // this may result in the player being forcably launched
        if (worldScripts["Rescue Scenario " + rrsScenario].missionOfferDecision) worldScripts["Rescue Scenario " + rrsScenario].missionOfferDecision("yes");
    
        // theoretically we don't need to do this because you can only accept one RRS mission at a time
        // but to be consistent...
    
        // reindex the bb item, because we don't need to know about the details anymore
        var item = bb.$getItem(missID);
        item.ID = bb.$nextID();
        bb._selectedItem = item.ID
    
        this.$reindexContracts(36000, idx, false);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$acceptRHContract = function (missID) {
        // we're reproducing the mission acceptance code here so we can also replace the email display
        //var idx = parseInt(missID) - 37000;
        var idx = -1;
        var bb = worldScripts.BulletinBoardSystem;
        var item = bb.$getItem(missID);
    
        var rh = worldScripts["Random_Hits"];
        // find the linked RH item
        for (var i = 0; i < rh.pages.length; i++) {
            rh.readHitpage(i + 1);
            if (item.destinationName === missionVariables.random_hits_mark_system && parseInt(item.payment) === parseInt(missionVariables.random_hits_mark_fee)) {
                idx = i;
                break;
            }
        }
        if (idx === -1) {
            log(this.name, "!!ERROR: Unable to link BB item to original Random Hits contract");
            // un-accept the contract so we don't get confused.
            item.accepted = false;
            bb.$initInterface(player.ship.dockedStation);
            return;
        }
        rh.setStoreVariables(idx + 1);
        rh.$markSystem(missionVariables.random_hits_planetnumber);
    
        // send email
        if (worldScripts.EmailSystem) {
            var email = worldScripts.EmailSystem;
            var text = this.$rhReplaceMissionVariables(expandDescription("Thank you [assassination_board_refused2] [mission_random_hits_playertitle] [commander_name], " +
                "you [assassination_board_accepted1] [assassination_board_accepted2] [assassination_board_accepted3] [assassination_board_accepted4] " +
                "[assassination_board_accepted5] this [mission_random_hits_assassination_board_job_name]. " +
                "[assassination_board_accepted6] [assassination_board_refused5] [assassination_board_accepted7]! " +
                "[mission_random_hits_assassination_board_part5] mission_random_hits_mark_fee Credits [assassination_board_accepted7a] " +
                "upon docking with any GalCop Station or Seedy Space Bar following the [assassination_board_accepted8] of [assassination_board_refused5b]. " +
                "This [assassination_board_accepted9] will be in addition to any bounty paid by GalCop. [assassination_board_accepted10]\n\n" +
                "You have accepted this [mission_random_hits_assassination_board_job_name]. [assassination_board_accepted11]. If you leave Galaxy " +
                "[mission_random_hits_show_galaxy] you will be deemed to have abandoned the [mission_random_hits_assassination_board_job_name]."));
    
            email.$createEmail({
                sender: this.$rhReplaceMissionVariables(expandDescription("mission_random_hits_assassination_board_poster_title mission_random_hits_assassination_board_poster_name")),
                subject: this.$rhReplaceMissionVariables(expandDescription("[assassination_board_refused1] mission_random_hits_mark_first_name mission_random_hits_mark_nick_name [mission_random_hits_mark_second_name]")),
                date: global.clock.seconds,
                message: text
            });
        }
    
        //random shipnames sometimes makes very long names that don't fit in one missionline.
        //if that happens we'll use plain ship name instead.
        var instructions = expandMissionText("random_hits_shortdescription");
        if (defaultFont.measureString(instructions) > 32) {
            missionVariables.random_hits_mark_ship_short_name = Ship.shipDataForKey(missionVariables.random_hits_mark_ship)["name"];
            instructions = expandMissionText("random_hits_shorterdescription");
        }
        mission.setInstructions(instructions, "Random_Hits");
        delete missionVariables.random_hits_mark_ship_short_name;
    
        rh.setupMarkPosition(); // sets mark position and various fees.
        missionVariables.random_hits_status = "RUNNING";
    
        // theoretically we don't need to do this because you can only accept one RH mission at a time
        // but to be consistent...
    
        // reindex the bb item, because we don't need to know about the details anymore
        item.ID = bb.$nextID();
        bb._selectedItem = item.ID
    
        // we don't need to reindex this time - RH keeps 9 items in the array even if a contract has been accepted and then terminated
        //this.$reindexContracts(37000, idx, false);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$acceptTaxiContract = function (missID) {
        var idx = parseInt(missID) - 38000;
        var taxi = worldScripts["in-system_taxi"];
        taxi.$removeOfferTimer(); //in case there is an offerTimer running from in-flight offers.
        taxi.$currentOffer = taxi.$stationOffers[idx];
        taxi.$currentOffer[0] = 2;
    
        // reindex the bb item, because we don't need to know about the details anymore
        var bb = worldScripts.BulletinBoardSystem;
        var item = bb.$getItem(missID);
        item.ID = bb.$nextID();
        bb._selectedItem = item.ID
    
        this.$reindexContracts(38000, idx, false);
    
        taxi.$deleteMissionInstructions();
        player.ship.addPassenger(taxi.$currentOffer[5] + " (going to " + taxi.$currentOffer[2].displayName + ")", system.ID, system.ID, taxi.$currentOffer[3], 0);
        taxi.$currentOffer[0] = 3;
        taxi.$currentOffer[7] = taxi.$currentOffer[3] - clock.seconds; //premium is based on basetime. basetime is defined when pickin up passenger.
        taxi.$fireMissionScreen = 0;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$acceptMiningContract = function (missID) {
        var idx = missID - 39000;
        var mc = worldScripts.miningcontracts;
        var contract = mc.MC_contracts[idx];
        contract.signed = 1;
        contract.gross = contract.amount;
        player.consoleMessage("Contract signed");
        if (worldScripts.EmailSystem) {
            var bb = worldScripts.BulletinBoardSystem;
            var itm = bb.$getItem(missID);
            itm.manifestText = "Dispatch " + contract.amount + "t × " + displayNameForCommodity(contract.cargo).toLowerCase() + " for " + contract.company;
            itm.noEmails = false;
            bb.$sendEmail(player.ship.dockedStation, "accepted", missID);
            itm.noEmails = true;
        }
        this.$convertContracts("mining");
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$tg_acceptContract = function (missID) {
        var idx = (missID - 40000) + 1; // should be 1, 2 or 3
        var bb = worldScripts.BulletinBoardSystem;
        var item = bb.$getItem(missID);
    
        // Store passenger contract #1 details into variables and add passenger to ship & manifest:
        missionVariables.taxi_passenger = missionVariables["taxi_job_name_" + idx];
        missionVariables.taxi_dest = missionVariables["taxi_job_dest_" + idx];
        missionVariables.taxi_dest_name = missionVariables["taxi_job_dest_name_" + idx];
        missionVariables.taxi_diff = missionVariables["taxi_job_diff_" + idx];
        missionVariables.taxi_pay = missionVariables["taxi_job_pay_" + idx];
        missionVariables.taxi_time = missionVariables["taxi_job_time_" + idx];
        missionVariables.taxistatus = "ON_THE_JOB";
        var name = missionVariables.taxi_passenger;
        var curloc = system.ID;
        var dest = missionVariables.taxi_dest;
        var time = missionVariables.taxi_time;
        var pay = missionVariables.taxi_pay;
        player.ship.addPassenger(name, curloc, dest, clock.seconds + time * 24 * 3600, pay);
        missionVariables.taxistat = "ACCEPTED";
    
        // reindex the bb item, because we don't need to know about the details anymore
        item.ID = bb.$nextID();
        bb._selectedItem = item.ID
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$acceptTaxiOffer_inFlight = function () {
        // run the internal part first
        if (this.$cobb_acceptOffer) this.$cobb_acceptOffer;
    
        // now add the BB item to match
        var bb = worldScripts.BulletinBoardSystem;
        var text = "Taxi Galactica contract to transfer " + this.$currentOffer[5] + " from the " +
            this.$currentOffer[1].displayName + " to the " + this.$currentOffer[2].displayName + ".\n\n" +
            "Tips may be awarded for quick transfers.";
        bb.$addBBMission({
            source: system.ID,
            destination: system.ID,
            stationKey: "taxi",
            description: "Taxi Contract",
            details: text,
            payment: this.$currentOffer[4],
            deposit: 0,
            allowTerminate: false,
            completionType: "IMMEDIATE",
            stopTimeAtComplete: true,
            allowPartialComplete: false,
            expiry: this.$currentOffer[3],
            disablePercentDisplay: true,
            noEmails: false,
            markerShape: "NONE",
            overlay: {
                name: "cobb_taxi.png",
                height: 546
            },
            remoteDepositProcess: false,
            initiateCallback: "$acceptTaxiContract",
            availableCallback: "$taxiContractAvailable",
            worldScript: this.name,
            data: "taxi"
        });
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$terminateRHContract = function (missID) {
        var rh = worldScripts["Random_Hits"];
        rh.showScreen = "cancelMission";
        rh.missionOffers();
        rh.showScreen = "";
        worldScripts.BulletinBoardSystem._displayType = -1;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$completedMiningContract = function (missID) {
        var idx = missID - 39000;
        var mc = worldScripts.miningcontracts;
        var contract = mc.MC_contracts[idx];
        var reward = contract.reward - contract.amount * contract.penalty;
        var repayment = Math.min(reward, mc.MC_debt);
        reward -= repayment;
        mc.MC_debt -= repayment;
        if (reward > 0) {
            player.consoleMessage(formatCredits(reward, true, true) + " received");
        } else {
            player.consoleMessage(formatCredits(Math.abs(reward), true, true) + " deducted");
        }
        player.credits += reward;
        if (worldScripts.EmailSystem) {
            var bb = worldScripts.BulletinBoardSystem;
            var itm = bb.$getItem(missID);
            var med = "Mining Association of " + mc.MC_system;
            if (contract.amount === 0) {
                // success result
                itm.originalManifestText = "Dispatch " + contract.gross + "t × " + displayNameForCommodity(contract.cargo).toLowerCase() + " for " + contract.company + (repayment === 0 ? "" : "\n\nNote: A debt repayment amount of " + formatCredits(repayment, true, true) + " was deducted from the original payment amount.");
                // move this to the event callback
                itm.noEmails = false;
                bb.$sendEmail(player.ship.dockedStation, "success", missID, reward);
                itm.noEmails = true;
            } else {
                // terminated/failed result
                itm.originalManifestText = "Dispatch " + contract.gross + "t × " + displayNameForCommodity(contract.cargo).toLowerCase() + " for " + contract.company;
                itm.noEmails = false;
                if (contract.deadline > clock.adjustedSeconds) {
                    // not expired, so must be a manual termination
                    bb.$sendEmail(player.ship.dockedStation, "terminated", missID, -reward);
                } else {
                    // ran out of time
                    bb.$sendEmail(player.ship.dockedStation, "fail", missID, -reward);
                }
                itm.noEmails = true;
            }
        }
        mc.MC_contracts.splice(idx, 1);
        this.$convertContracts("mining");
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$confirmCompleteMiningContract = function (missID) {
        var idx = missID - 39000;
        var mc = worldScripts.miningcontracts;
        var contract = mc.MC_contracts[idx];
        if (contract.signed === 2) return "Contract terminated";
        var amount = manifest[contract.cargo];
        if (amount < contract.amount) return "Insufficient " + displayNameForCommodity(contract.cargo).toLowerCase() + " in cargo hold";
        return "";
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$terminateMiningContract = function (missID) {
        player.consoleMessage("Contract terminated");
        this.$completedMiningContract(missID);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // manifest functions
    
    // There doesn't appear to be any way to override the F5F5 manifest entries for cargo, passenger or parcel contracts. 
    // So we'll leave them to do their thing. These next 3 functions would add entries to the "Bulletin Board" section of the F5F5 screen.
    
    //-------------------------------------------------------------------------------------------------------------
    this.$cargoManifest = function (missID) {
        var bb = worldScripts.BulletinBoardSystem;
        var itm = bb.$getItem(missID);
        var desc = "";
        for (var i = 0; i < p.contracts.length; i++) {
            if (parseInt(itm.destination) === parseInt(p.contracts[i].destination) && Math.round(itm.payment, 1) === Math.round((p.contracts[i].fee + p.contracts[i].premium), 1) && parseint(itm.source) === parseInt(p.contracts[i].start)) {
                var unit = "tons";
                if (system.mainStation.market[p.contracts[i].commodity]["quantity_unit"] == "1") {
                    unit = "kilograms";
                } else if (system.mainStation.market[p.contracts[i].commodity]["quantity_unit"] == "2") {
                    unit = "grams";
                }
                desc = "Deliver " + p.contracts[i].quantity + unit + " × " + displayNameForCommodity(p.contracts[i].commodity) + " to " + System.systemNameForID(p.contracts[i].destination) + " in " + bb.$getTimeRemaining(itm.expiry) + ".";
                break;
            }
        }
        bb.$updateBBManifestText(missID, desc);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$passengerManifest = function (missID) {
        var p = player.ship;
        var bb = worldScripts.BulletinBoardSystem;
        var itm = bb.$getItem(missID);
        var desc = "";
        for (var i = 0; i < p.passengers.length; i++) {
            if (parseInt(itm.destination) === parseInt(p.passengers[i].destination) && Math.round(itm.payment, 1) === Math.round((p.passengers[i].fee + p.contracts[i].premium), 2) && parseInt(itm.source) === parseInt(p.passengers[i].start)) {
                desc = "Transport " + p.passengers[i].name + " to " + System.systemNameForID(p.passengers[i].destination) + " in " + bb.$getTimeRemaining(itm.expiry) + ".";
                break;
            }
        }
        bb.$updateBBManifestText(missID, desc);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$parcelManifest = function (missID) {
        var p = player.ship;
        var bb = worldScripts.BulletinBoardSystem;
        var itm = bb.$getItem(missID);
        var desc = "";
        for (var i = 0; i < p.parcels.length; i++) {
            if (parseInt(itm.destination) === parseInt(p.parcels[i].destination) && Math.round(itm.payment, 1) === Math.round(p.parcels[i].fee, 1) && parseInt(itm.source) === parseInt(p.parcels[i].start)) {
                desc = "Deliver " + p.parcels[i].name + " to " + System.systemNameForID(p.parcels[i].destination) + " in " + bb.$getTimeRemaining(itm.expiry) + ".";
                break;
            }
        }
        bb.$updateBBManifestText(missID, desc);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$smugglingManifest = function (missID) {
        var bb = worldScripts.BulletinBoardSystem;
        var sc = worldScripts.Smugglers_Contracts;
        var itm = bb.$getItem(missID);
        var desc = "";
        for (var i = 0; i < sc._smugglingContracts.length; i++) {
            if (parseInt(itm.destination) === parseInt(sc._smugglingContracts[i].destination) && Math.round(itm.payment, 1) === Math.round((sc._smugglingContracts[i].fee + sc._smugglingContracts[i].premium), 1) && parseint(itm.source) === parseInt(sc._smugglingContracts[i].start)) {
                var unit = "tons";
                if (system.mainStation.market[sc._smugglingContracts[i].commodity]["quantity_unit"] == "1") {
                    unit = "kilograms";
                } else if (system.mainStation.market[sc._smugglingContracts[i].commodity]["quantity_unit"] == "2") {
                    unit = "grams";
                }
                desc = "Deliver " + sc._smugglingContracts[i].quantity + unit + " " + displayNameForCommodity(sc._smugglingContracts[i].commodity) + " to " + System.systemNameForID(sc._smugglingContracts[i].destination) + " in " + bb.$getTimeRemaining(itm.expiry) + ".";
                break;
            }
        }
        bb.$updateBBManifestText(missID, desc);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$taxiManifest = function (missID) {
        var bb = worldScripts.BulletinBoardSystem;
        var item = bb.$getItem(missID);
        var taxi = worldScripts["in-system_taxi"];
        bb.$updateBBManifestText(
            missID,
            "Deliver " + taxi.$currentOffer[5] + " from " + taxi.$currentOffer[1].displayName + " to " + taxi.$currentOffer[2].displayName + " in " + Math.floor((taxi.$currentOffer[3] - clock.seconds) / 60) + " minutes."
        );
        // status text doesn't need expiry time, as it's shown elsewhere on the BB item
        bb.$updateBBStatusText(
            missID,
            "Deliver " + taxi.$currentOffer[5] + " from " + taxi.$currentOffer[1].displayName + " to " + taxi.$currentOffer[2].displayName + "."
        );
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$miningManifest = function (missID) {
        var bb = worldScripts.BulletinBoardSystem;
        var itm = bb.$getItem(missID);
        var idx = missID - 39000;
        var mc = worldScripts.miningcontracts;
        var contract = mc.MC_contracts[idx];
        var desc = "Dispatch " + contract.amount + "t " + displayNameForCommodity(contract.cargo).toLowerCase() + " for " + contract.company + " in " + bb.$getTimeRemaining(itm.expiry) + ".";
        var sts = (contract.gross - contract.amount) + "t of " + contract.gross + "t " + displayNameForCommodity(contract.cargo).toLowerCase() + " " + ((contract.gross - contract.amount) === 1 ? "has" : "have") + " been dispatched";
        bb.$updateBBManifestText(missID, desc);
        bb.$updateBBStatusText(missID, sts)
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$getActiveContractsByType = function (type) {
        var list = [];
        var bb = worldScripts.BulletinBoardSystem;
    
        // find the contract in the BB to remove it
        for (var i = 0; i < bb._data.length; i++) {
            if (bb._data[i].accepted === true && bb._data[i].worldScript === this.name && bb._data[i].data === type) {
                list.push(bb._data[i]);
            }
        }
        return list;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // availability functions
    //-------------------------------------------------------------------------------------------------------------
    // perform checks to see if the cargo contract can be accepted by the player
    this.$cargoContractAvailable = function (missID) {
        var idx = parseInt(missID) - 31000;
        var cc = worldScripts["oolite-contracts-cargo"];
        if (!system.mainStation.market[cc.$contracts[idx].commodity]) return "cargo type missing";
        if (!cc._hasSpaceFor(cc.$contracts[idx])) return "insufficient cargo space";
        return "";
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // perform checks to see if the passenger contract can be accepted by the player
    this.$passengerContractAvailable = function (missID) {
        var idx = parseInt(missID) - 32000;
        var pc = worldScripts["oolite-contracts-passengers"];
        // temp variable to simplify code
        var passenger = pc.$passengers[idx];
        if (passenger) {
            var playerrep = worldScripts["oolite-contracts-helpers"]._playerSkill(player.passengerReputationPrecise);
            // if the player has a spare cabin
            if (player.ship.passengerCapacity <= player.ship.passengerCount) {
                return expandMissionText("oolite-contracts-passengers-command-unavailable").replace("(", "").replace(")", "");
            } else if (playerrep < passenger.skill) {
                var utype = "both";
                if (player.passengerReputationPrecise * 10 >= passenger.skill) {
                    utype = "kills";
                } else if (Math.sqrt(player.score) >= passenger.skill) {
                    utype = "rep";
                }
                return expandMissionText("oolite-contracts-passengers-command-unavailable-" + utype).replace("(", "").replace(")", "");
            }
        }
        return "";
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // perform checks to see if the parcel contract can be accepted by the player
    this.$parcelContractAvailable = function (missID) {
        var idx = parseInt(missID) - 33000;
        var bc = worldScripts["oolite-contracts-parcels"];
        // temp variable to simplify code
        var parcel = bc.$parcels[idx];
        var playerrep = worldScripts["oolite-contracts-helpers"]._playerSkill(player.parcelReputationPrecise);
        if (parcel.skill > playerrep) {
            var utype = "both";
            if (player.parcelReputationPrecise * 10 >= parcel.skill) {
                utype = "kills";
            } else if (Math.sqrt(player.score) >= parcel.skill) {
                utype = "rep";
            }
            return expandMissionText("oolite-contracts-parcels-command-unavailable-" + utype).replace("(", "").replace(")", "");
        }
        return "";
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // perform checks to see if the smuggling contract can be accepted by the player
    this.$smugglingContractAvailable = function (missID) {
        var idx = parseInt(missID) - 34000;
        var sc = worldScripts.Smugglers_Contracts;
        if (!sc.$hasSpaceFor(sc._contracts[idx])) return "insufficient cargo space";
        return "";
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // perform checks to see if the escort contract can be accepted by the player
    this.$escortContractAvailable = function (missID) {
        if (worldScripts["Escort_Contracts"].ec_currentcontract) {
            // basically, once a contract is accepted, all other contracts become unavailable
            return "unable to access multiple escort contracts";
        }
        return "";
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // perform checks to see if the rrs mission can be accepted by the player
    this.$rrsContractAvailable = function (missID) {
        if (worldScripts["Rescue Stations"].missionActive()) {
            return "unable to access multiple RRS missions";
        }
        return "";
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$rhContractAvailable = function (missID) {
        if (missionVariables.random_hits_status === "RUNNING") return "unable to access multiple contracts";
        var idx = parseInt(missID) - 37000;
        var lvl = Math.floor(idx / 3) + 1;
        if ((lvl === 1 && player.score < 32) || (lvl === 2 && player.score < 128) || (lvl === 3 && player.score < 512))
            return "insufficient combat experience";
        return "";
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$taxiContractAvailable = function (missID) {
        var taxi = worldScripts["in-system_taxi"];
        var idx = missID - 38000;
        if (player.ship.passengerCapacity <= player.ship.passengerCount) {
            return expandMissionText("oolite-contracts-passengers-command-unavailable").replace("(", "").replace(")", "");
        }
        if (taxi.$currentOffer && worldScripts["in-system_taxi"].$currentOffer[0] >= 2) {
            return "unable to access multiple taxi contracts";
        }
        var rightStartingPoint = false;
        if (taxi.$stationOffers[idx][1].isPlanet) { //is it a planetary starting point?
            if (worldScripts.PlanetFall.lastPlanet == taxi.$stationOffers[idx][1] && (player.ship.dockedStation.hasRole("planetFall_surface")))
                rightStartingPoint = true;
        } else if (player.ship.dockedStation == taxi.$stationOffers[idx][1])
            rightStartingPoint = true;
        if (rightStartingPoint === false) return "incorrect starting point";
    
        return "";
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // perform checks to see if the taxi contract can be accepted by the player
    this.$taxiGalacticaContractAvailable = function (missID) {
        if (missionVariables.taxistatus == "ON_THE_JOB") {
            return "unable to access multiple Taxi Galactica contracts";
        }
        if (player.ship.passengerCapacity <= player.ship.passengerCount) {
            return expandMissionText("oolite-contracts-passengers-command-unavailable").replace("(", "").replace(")", "");
        }
        return "";
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // the various contract scripts regularly do checks to validate contracts, making sure they are still acceptable.
    // any found to be out of date are removed. 
    this.$validateContracts = function () {
        worldScripts["oolite-contracts-cargo"]._validateContracts();
        worldScripts["oolite-contracts-passengers"]._validateContracts();
        worldScripts["oolite-contracts-parcels"]._validateContracts();
        if (this._smuggling) worldScripts.Smugglers_Contracts.$validateSmugglingContracts();
        // taxi contracts recycle regularly, so we need to refresh the data each time we open the BB
        if (this._convertTaxiContracts) {
            var taxi = worldScripts["in-system_taxi"];
            //var itm = worldScripts.BulletinBoardSystem.$getItem(38000);
            taxi.$clearOldOffers();
            // redo the conversion
            this.$convertContracts("taxi");
        }
        if (this._convertMiningContracts) {
            worldScripts.miningcontracts.MC_generateContracts();
            this.$convertContracts("mining");
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // we're overriding the base function "validateContracts" as any change to the index could spell disaster for mission links
    this.$bbovr_cargo_validateContracts = function () {
        var c = this.$contracts.length - 1;
        var removed = false;
        // iterate downwards so we can safely remove as we go
        for (var i = c; i >= 0; i--) {
            // if the time remaining is less than 1/3 of the estimated
            // delivery time, even in the best case it's probably not
            // going to get there.
            if (this.$helper._timeRemainingSeconds(this.$contracts[i]) < this.$helper._timeEstimateSeconds(this.$contracts[i]) / 3) {
                worldScripts.ContractsOnBB.$reindexContracts(31000, i, true);
                // remove it
                this.$contracts.splice(i, 1);
                removed = true;
            }
        }
        /*if (removed) {
            // update the interface description if we removed any
            this._updateMainStationInterfacesList();
        }*/
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$bbovr_passengers_validateContracts = function () {
        var c = this.$passengers.length - 1;
        var removed = false;
        // iterate downwards so we can safely remove as we go
        for (var i = c; i >= 0; i--) {
            // if the time remaining is less than 1/3 of the estimated
            // delivery time, even in the best case it's probably not
            // going to get there.
            if (this.$helper._timeRemainingSeconds(this.$passengers[i]) < this.$helper._timeEstimateSeconds(this.$passengers[i]) / 3) {
                worldScripts.ContractsOnBB.$reindexContracts(32000, i, true);
                // remove it
                this.$passengers.splice(i, 1);
                removed = true;
            }
        }
        /*if (removed) {
            // update the interface description if we removed any
            this._updateMainStationInterfacesList();
        }*/
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$bbovr_parcels_validateContracts = function () {
        var c = this.$parcels.length - 1;
        var removed = false;
        // iterate downwards so we can safely remove as we go
        for (var i = c; i >= 0; i--) {
            // if the time remaining is less than 1/3 of the estimated
            // delivery time, even in the best case it's probably not
            // going to get there.
            if (this.$helper._timeRemainingSeconds(this.$parcels[i]) < this.$helper._timeEstimateSeconds(this.$parcels[i]) / 3) {
                worldScripts.ContractsOnBB.$reindexContracts(33000, i, true);
                // remove it
                this.$parcels.splice(i, 1);
                removed = true;
            }
        }
        /*if (removed) {
            // update the interface description if we removed any
            this._updateMainStationInterfacesList();
        }*/
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // counts the number of BB contracts at a particular base ref
    this.$countContracts = function (baseRef) {
        var done = false;
        var chk = baseRef;
        var count = 0;
        var bb = worldScripts.BulletinBoardSystem;
        do {
            if (bb.$getItem(chk) == null && bb.$getItem(chk + 1) == null) {
                done = true;
            } else {
                count += 1;
                chk += 1;
            }
        } while (done === false);
        return count;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // reindexes contracts (type based on baseref), from a particular point in the array
    this.$reindexContracts = function (baseRef, point, bbRemove) {
        var bb = worldScripts.BulletinBoardSystem;
        var idx = baseRef + point;
        if (bbRemove == true || bbRemove == null) bb.$removeBBMission(idx);
        var chk = idx + 1;
        var done = false;
        do {
            var item = bb.$getItem(chk);
            if (item != null) {
                // reindex the bb item
                item.ID = chk - 1;
                chk += 1;
            } else {
                done = true;
            }
        } while (done === false);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // smuggling contracts specific functions
    //-------------------------------------------------------------------------------------------------------------
    // removes a smuggling contract from the BB
    this.$removeSmugglingContract = function (idx) {
        // find and remove the contract from the BB
        var bb = worldScripts.BulletinBoardSystem;
        var sc = worldScripts.Smugglers_Contracts;
        for (var i = 0; i < bb._data.length; i++) {
            var itm = bb._data[i];
            if (itm != null && itm.accepted === true) {
                // is this item the one we're dealing with?
                if (itm.destination === sc._smugglingContracts[idx].destination && itm.payment === sc._smugglingContracts[idx].fee && itm.source === sc._smugglingContracts[idx].start) {
                    bb.$removeBBMission(itm.ID);
                    break;
                }
            }
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // clean up any orphaned smuggling contracts
    this.$checkSmugglingContracts = function () {
        var bb = worldScripts.BulletinBoardSystem;
        var sc = worldScripts.Smugglers_Contracts;
        if (!sc) return;
        for (var i = bb._data.length - 1; i >= 0; i--) {
            var itm = bb._data[i];
            // is this a smuggling contract
            if (itm.stationKey === "blackMarket" && itm.availableCallback === "$smugglingContractAvailable") {
                var found = false;
                // see if this contract is still active
                for (var j = 0; j < sc._smugglingContracts.length; j++) {
                    // is this item the one we're dealing with?
                    if (itm.destination === sc._smugglingContracts[j].destination && itm.payment === sc._smugglingContracts[j].fee && itm.source === sc._smugglingContracts[j].start) {
                        found = true;
                        break;
                    }
                }
                // if it doesn't exist we need to remove the BB item as well
                if (found === false) {
                    bb.$removeBBMission(itm.ID);
                    //bb._data.splice(i, 1);
                }
            }
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // called by smugglers contracts, to ensure the contracts have been restored before we try to convert them
    this.$convertSmugglingContracts = function () {
        var count = 0;
        // smuggling contracts
        if (worldScripts.Smugglers_Contracts) {
            this._smuggling = true;
            count = worldScripts.Smugglers_Contracts._contracts.length;
            if (count > 0) {
                // see if there are any unaccepted missions @ ID 34000
                var itm = worldScripts.BulletinBoardSystem.$getItem(34000);
                // if there isn't, run the conversion process
                if (itm == null || this.$countContracts(34000) != count) this.$convertContracts("smuggling");
            }
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // Escort Contracts specific functions
    //-------------------------------------------------------------------------------------------------------------
    this.$checkEscortContacts = function () {
        var bb = worldScripts.BulletinBoardSystem;
        var ec = worldScripts.Escort_Contracts;
        if (!ec) return;
        for (var i = bb._data.length - 1; i >= 0; i--) {
            var itm = bb._data[i];
            if (itm.accepted === false && itm.data && itm.data === "escort") {
                // is this item still active?
                if (!ec.ec_targetsystems) {
                    //log(this.bame, "no ec data - removing bb item id " + itm.ID);
                    bb.$removeBBMission(itm.ID);
                } else {
                    var found = false;
                    for (var j = 0; j < ec.ec_targetsystems.length - 1; j++) {
                        if (ec.ec_targetsystems[j] === itm.destination && itm.payment === ec.ec_contractactualprices[j]) found = true;
                    }
                    if (found === false) {
                        //log(this.bame, "no ec data match - removing bb item id " + itm.ID);
                        bb.$removeBBMission(itm.ID);
                    }
                }
            }
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // Rescue Station specific functions
    //-------------------------------------------------------------------------------------------------------------
    this.$rrsExtractMissionTitle = function (ref) {
        var text = expandMissionText("rescue_scenario_" + ref + "_synopsis");
        var title = text.substring(0, text.indexOf("\n"));
        return title;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$rrsExtractMissionDetails = function (ref) {
        var text = expandMissionText("cobb_rescue_scenario_" + ref + "_mission_available");
        if (!text && ref.length > 1) text = expandMissionText("cobb_rescue_scenario_" + ref.substring(0, 1) + "_mission_available");
        if (!text) text = "<<Error: mission details for scenario " + ref + " not found>>";
        return text;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$rrsStoreCurrentVariables = function () {
        var sc = "Rescue Scenario " + missionVariables.rescuestation_scenario;
        var list = this._rrsMissionVariables[sc];
        this._rrsMissionCurrent = {};
        for (var i = 0; i < list.length; i++) {
            this._rrsMissionCurrent[list[i]] = missionVariables[list[i]];
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$rrsReturnCurrentVariables = function () {
        var sc = "Rescue Scenario " + missionVariables.rescuestation_scenario;
        var list = this._rrsMissionVariables[sc];
        this._rrsMissionCurrent = {};
        for (var i = 0; i < list.length; i++) {
            missionVariables[list[i]] = this._rrsMissionCurrent[list[i]];
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$rrsReloadMissionVariables = function (scen, idx) {
        var list = this._rrsMissionVariables[scen];
        for (var i = 0; i < list.length; i++) {
            missionVariables[list[i]] = this._rrsMissionStorage[idx][list[i]];
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$rrsSaveMissionVariables = function (scen, idx) {
        var list = this._rrsMissionVariables[scen];
        this._rrsMissionStorage[idx] = {};
        for (var i = 0; i < list.length; i++) {
            // look for any expansion items in the mission variable content
            if (typeof missionVariables[list[i]] === "string" && missionVariables[list[i]].indexOf("[") >= 0) {
                missionVariables[list[i]] = expandDescription(missionVariables[list[i]]);
            }
            this._rrsMissionStorage[idx][list[i]] = missionVariables[list[i]];
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$rrsResetMissionVariables = function (scen) {
        var list = this._rrsMissionVariables[scen];
        for (var i = 0; i < list.length; i++) {
            delete missionVariables[list[i]];
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$rrsGetShipRoleForScenario = function (scen) {
        var role = this._rrsMissionShipRole[scen];
        if (role === "shipModel") role = missionVariables.rescuestation_shiprole;
        return role;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$rrsGetKeyForShipRole = function (shiprole) {
        var key = "";
        if (shiprole === "asteroidModel") {
            key = this._rrsAsteroidKeyList[Math.floor(Math.random() * this._rrsAsteroidKeyList.length)];
        } else {
            var dataKeys = Ship.keysForRole(shiprole);
            var index = Math.floor(Math.random() * dataKeys.length);
            key = dataKeys[index];
        }
        return key;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // replacement for the standard RRS mission success routine. Allows us to hook in.
    this.$cobb_missionSuccess = function () {
        worldScripts.ContractsOnBB.$rrsCompleteMission();
        var sckey = missionVariables.rescuestation_scenario;
        if (!this.missionlog[sckey + "_complete"]) {
            this.missionlog[sckey + "_complete"] = 1;
        } else {
            this.missionlog[sckey + "_complete"]++;
        }
        this.syncMissionLog();
        worldScripts["Rescue Scenario " + missionVariables.rescuestation_scenario].missionSuccess();
        this.cleanupMission();
        this.fillMissionBoard();
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // replacement for the standard RRS mission failue routine. Allows us to hook in.
    this.$cobb_failMission = function () {
        worldScripts.ContractsOnBB.$rrsCompleteMission();
        var sckey = missionVariables.rescuestation_scenario;
        if (!this.missionlog[sckey + "_failed"]) {
            this.missionlog[sckey + "_failed"] = 1;
        } else {
            this.missionlog[sckey + "_failed"]++;
        }
        this.syncMissionLog();
        worldScripts["Rescue Scenario " + missionVariables.rescuestation_scenario].failMission();
        this.cleanupMission(); // all for now
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$rrsCompleteMission = function () {
        // find the contract in the BB to remove it. There should only be one accepted mission
        var bb = worldScripts.BulletinBoardSystem;
        var rrsList = this.$getActiveContractsByType("rescue");
        if (rrsList.length > 0) {
            var found = false;
            bb.$removeBBMission(rrsList[0].ID);
            /*for (var i = 0; i < bb._data.length; i++) {
                if (bb._data[i].data === "rescue" && bb._data[i].accepted === true) {
                    found = true;
                    bb._data.splice(i, 1);
                    break;
                }
            }*/
            //if (found === true) 
            bb.$initInterface(player.ship.dockedStation);
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$rrsGetAsteroidKeyList = function () {
        // these asteroid models don't work on a mission screen, so exclude them
        var exclude = ["Casteroid1", "Casteroid2", "Casteroid3", "Casteroid4", "Casteroid5", "Casteroid6", "Casteroid7",
            "Casteroid8", "Casteroid9", "Casteroid10", "Casteroid11", "Casteroid12"
        ];
        var keys = Ship.keysForRole("asteroid");
        this._rrsAsteroidKeyList.length = 0;
        for (var i = 0; i < keys.length; i++) {
            // and exclude any of the yah billboards as well
            if (keys[i].indexOf("billboard") === -1 && exclude.indexOf(keys[i]) === -1) this._rrsAsteroidKeyList.push(keys[i]);
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // Random Hits specific functions
    //-------------------------------------------------------------------------------------------------------------
    this.$rhReplaceMissionVariables = function (text) {
        if (text == null) return "";
        for (var i = 0; i < this._rhMissionVariables.length; i++) {
            if (text.indexOf(this._rhMissionVariables[i]) >= 0) {
                do {
                    text = text.replace("mission_" + this._rhMissionVariables[i], missionVariables[this._rhMissionVariables[i]]);
                } while (text.indexOf(this._rhMissionVariables[i]) >= 0);
            }
        }
        return text;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$rhExtractMissionDetails = function (text) {
        var point1 = text.indexOf("\n\n") + 2;
        var point2 = text.indexOf("REPLIES TO");
        return text.substring(point1, point2);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$rhExtractMissionTitle = function (text) {
        return text.substring(0, text.length - 1);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$rhDifficultyText = function (diff) {
        var text = "";
        switch (diff) {
            case 1:
                text = expandDescription("This is [random_hits_makeoffer3_start_levelone] [mission_random_hits_assassination_board_job_name]. [assassination_board_easykill]");
                break;
            case 2:
                text = expandDescription("This is [random_hits_makeoffer3_start_leveltwo] [mission_random_hits_assassination_board_job_name]. [assassination_board_mediumkill]");
                break;
            case 3:
                text = expandDescription("This is [random_hits_makeoffer3_start_levelthree] [mission_random_hits_assassination_board_job_name]. [assassination_board_hardkill]");
                break;
        }
        return text;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$rhBarBill = function () {
        if (this._convertRHContracts && player.ship.dockedStation.name === "A Seedy Space Bar" && player.credits > 0) {
            var rh = worldScripts["Random_Hits"];
            if (rh.random_hits_billdue > player.credits) rh.random_hits_billdue = player.credits;
            rh.showScreen = "barBill";
            rh.missionOffers();
            rh.showScreen = "";
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$rhBarBillPostLaunch = function (station) {
        if (this._convertRHContracts && station.name === "A Seedy Space Bar" && player.credits > 0) {
            var rh = worldScripts["Random_Hits"];
            if (rh.random_hits_billdue > player.credits) rh.random_hits_billdue = player.credits;
            var text = expandMissionText("cobb_random_hits_barbill_postlaunch", {
                barbill: formatCredits(parseFloat(rh.random_hits_billdue), true, true)
            });
            player.credits -= rh.random_hits_billdue;
            station.commsMessage(text, player.ship);
        }
    
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // mining contract specific functions
    //-------------------------------------------------------------------------------------------------------------
    this.$partMiningContract_process = function (missID) {
        var bb = worldScripts.BulletinBoardSystem;
        var itm = bb.$getItem(missID);
        var idx = missID - 39000;
        var mc = worldScripts.miningcontracts;
        var contract = mc.MC_contracts[idx];
        var amount = Math.min(contract.amount, manifest[contract.cargo]);
        player.consoleMessage(amount + "t of " + displayNameForCommodity(contract.cargo).toLowerCase() + " dispatched");
        contract.amount -= amount;
        manifest[contract.cargo] -= amount;
        mc.MC_subscr = Math.max(mc.MC_subscr, 900 * Math.floor(clock.seconds / 900)) + amount * 900;
        this.$convertContracts("mining");
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$partMiningContract_condition = function (missID) {
        var idx = missID - 39000;
        var mc = worldScripts.miningcontracts;
        var contract = mc.MC_contracts[idx];
        var amount = manifest[contract.cargo];
        if (contract.signed === 2) return "Contract terminated";
        if (contract.amount === 0) return "Contract can be completed";
        if (amount === 0) return "No " + displayNameForCommodity(contract.cargo).toLowerCase() + " in cargo hold";
        return "";
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$miningContract_wait = function () {
        var mc = worldScripts.miningcontracts;
        if (player.credits < mc.MC_waitPrice) {
            player.consoleMessage("Insufficient credits");
            return;
        }
        player.consoleMessage(formatCredits(mc.MC_waitPrice, true, true) + " deducted");
        player.credits -= mc.MC_waitPrice;
        mc.MC_waitForContract();
        this.$convertContracts("mining");
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$miningContract_repayDebt = function () {
        var mc = worldScripts.miningcontracts;
        if (mc.MC_debt > player.credits) {
            player.consoleMessage("Insufficient credits");
        } else {
            player.consoleMessage(formatCredits(mc.MC_debt, true, true) + " deducted");
            player.credits -= mc.MC_debt;
            mc.MC_debt = 0;
            worldScripts.BulletinBoardSystem.$removeMainMenuItem(this.name, "$miningContract_repayDebt");
        }
        mc.MC_generateContracts();
        this.$convertContracts("mining");
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$miningContractOpen = function (missID) {
        if (this._convertMiningContracts) {
            if (missID >= 39000) {
                var bb = worldScripts.BulletinBoardSystem;
                var itm = bb.$getItem(missID);
                var idx = missID - 39000;
                var mc = worldScripts.miningcontracts;
                var contract = mc.MC_contracts[idx];
                if (manifest[contract.cargo] > 0 && contract.amount > 0) {
                    itm.customMenuItems[0].text = "Dispatch current cargo load (" + Math.min(contract.amount, manifest[contract.cargo]) + "t × " + displayNameForCommodity(contract.cargo).toLowerCase() + ")";
                } else {
                    itm.customMenuItems[0].text = "Dispatch current cargo load";
                }
    
                // keep these details up to date whenever a mining contract is opened
                var med = "Mining Association of " + mc.MC_system;
                var reward = contract.reward - contract.amount * contract.penalty;
                var repayment = Math.min(reward, mc.MC_debt);
                reward -= repayment;
                var balance = contract.reward - contract.amount * contract.penalty;
                itm.postStatusMessages[0].text = expandDescription("[contract-mining-completed]", {
                    mediator: med,
                    amount: formatCredits(reward, true, true)
                });
    
                if (balance < 0) {
                    itm.postStatusMessages[1].text = expandDescription("[contract-mining-terminated-1]", {
                        mediator: med,
                        amount: formatCredits(-balance, true, true)
                    });
                } else {
                    itm.postStatusMessages[1].text = expandDescription("[contract-mining-terminated-2]", {
                            mediator: med,
                            amount: formatCredits(balance, true, true)
                        }) +
                        ((mc.MC_debt - repayment) > 0 ? expandDescription("[contract-mining-debt]", {
                            mediator: med,
                            amount: formatCredits(Math.min(reward, (mc.MC_debt - repayment)), true, true)
                        }) : "");
                }
            }
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$miningContractListOpen = function () {
        if (this._convertMiningContracts) {
            if (Ship.roleIsInCategory(player.ship.dockedStation.primaryRole, "oolite-rockhermits") === true) {
                var bb = worldScripts.BulletinBoardSystem;
                var mc = worldScripts.miningcontracts;
                if (mc.MC_debt > 0) {
                    bb.$removeMainMenuItem(this.name, "$miningContract_repayDebt");
                    bb.$addMainMenuItem({
                        text: "Repay debt (" + formatCredits(mc.MC_debt, true, true) + ")",
                        worldScript: this.name,
                        menuCallback: "$miningContract_repayDebt"
                    });
                } else {
                    bb.$removeMainMenuItem(this.name, "$miningContract_repayDebt");
                }
            }
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$checkForHermit = function (station) {
        // turn on the always visible flag at hermits
        if (Ship.roleIsInCategory(station.primaryRole, "oolite-rockhermits") === true) {
            var bb = worldScripts.BulletinBoardSystem;
            var mc = worldScripts.miningcontracts;
            bb._alwaysShowBB = true;
            bb.$removeMainMenuItem(this.name, "$miningContract_wait");
            bb.$addMainMenuItem({
                text: "Wait for new mining contracts (" + formatCredits(mc.MC_waitPrice, true, true) + ")",
                worldScript: this.name,
                menuCallback: "$miningContract_wait"
            });
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // small adjustment to the shipExitedWitchspace routine, to eliminate some unneccessary functions
    this.$mc_shipExitedWitchspace = function () {
        this.MC_countAsteroids();
        var number = this.MC_contracts.length;
        var total = this.MC_debt;
    
        for (var i = 0; i < number; i++) {
            var contract = this.MC_contracts[i];
            if (contract.signed) total += contract.amount * contract.penalty - contract.reward;
        }
    
        this.MC_contracts.length = 0;
        this.MC_debt = 0;
        this.MC_clock = 0;
    
        if (total > 0) {
            player.ship.setBounty(player.bounty + 50, "contract obligations");
            var targets = system.shipsWithPrimaryRole("buoy-witchpoint");
            if (targets.length > 0) {
                targets[0].commsMessage("The Mining Association of " + this.MC_system + " has put a bounty of " + formatCredits(50, true, true) + " on your head.", player.ship);
            }
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // taxi galactica specific functions
    //-------------------------------------------------------------------------------------------------------------
    this.$tg_choiceEvaluation = function (choice) {
        // Mission text screen choices are below:
        // Welcome screen choices:
        if (missionVariables.taxistat === "REVISIT_1") {
            if (choice === "1BOARD") {
                worldScripts.BulletinBoardSystem.$showBB();
                return;
            } else if (choice === "2EXPLORE") {
                // Return to the manifest screen:
                missionVariables.taxistat = "EXPLORE";
            }
        }
        // Special Mission #1 screen choices:
        if (missionVariables.taxistat === "SPEC_MIS_01_1") {
            if (choice === "1ACCEPT") {
                this.$tg_acceptSpecialMission();
            }
        }
        return;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$tg_acceptSpecialMission = function () {
        // Store special mission #1 details into variables and add passenger to ship & manifest:
        missionVariables.taxi_passenger = missionVariables.taxi_specmis_1_ambassador;
        missionVariables.taxi_dest = missionVariables.taxi_specmis1_dest;
        missionVariables.taxi_dest_name = missionVariables.taxi_specmis1_dest_name;
        missionVariables.taxi_diff = 3;
        missionVariables.taxi_pay = 10000;
        missionVariables.taxi_time = missionVariables.taxi_specmis1_time;
        missionVariables.taxistatus = "ON_THE_JOB";
        var name = missionVariables.taxi_passenger;
        var curloc = system.ID;
        var dest = missionVariables.taxi_dest;
        var time = missionVariables.taxi_time;
        var pay = missionVariables.taxi_pay;
        player.ship.addPassenger(name, curloc, dest, clock.seconds + time * 24 * 3600, pay);
        missionVariables.taxistat = "ACCEPTED";
    
        // add this mission to the bulletin board
        var bb = worldScripts.BulletinBoardSystem;
        bb.$addBBMission({
            source: system.ID,
            destination: missionVariables.taxi_specmis1_dest,
            stationKey: "mainStation",
            description: expandDescription("[taxi-galactica-passenger-title]"),
            details: expandDescription("[taxi-galactica-passenger-description]", {
                client: missionVariables.taxi_specmis_1_ambassador,
                destination: System.systemNameForID(missionVariables.taxi_specmis1_dest)
            }),
            payment: 10000,
            accepted: true,
            allowTerminate: false,
            completionType: "IMMEDIATE",
            stopTimeAtComplete: true,
            allowPartialComplete: false,
            expiry: (clock.seconds + missionVariables.taxi_specmis1_time * 24 * 3600),
            disablePercentDisplay: true,
            noEmails: true,
            markerShape: "NONE", // marker control will be handled by contracts system
            overlay: {
                name: "cobb_taxi.png",
                height: 546
            },
            customDisplayItems: cust,
            initiateCallback: "$tg_acceptContract",
            availableCallback: "$taxiGalacticaContractAvailable",
            worldScript: this.name,
        });
    
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$clearMissionSet = function $clearMissionSet(curr, range) {
        var bb = worldScripts.BulletinBoardSystem;
        var chk = 0;
        var found = 0;
        do {
            if (bb.$getIndex(range + chk) >= 0) {
                bb.$removeBBMission(range + chk);
                found += 1;
            }
            // failsafe in case we don't find one
            if (chk >= (range + 100)) found = curr + 1;
            chk += 1;
        } while (found < curr);
    }