| Scripts/home_system.js | (
    function () {
        "use strict";
        this.name = "HomeSystem";
        this.author = "phkb";
        this.copyright = "2017 phkb";
        this.description = "Controls the output of home system messages to the player, as well as applying home system benefits to the player.";
        this.license = "CC BY-NC-SA 4.0";
        /*
            TODO:
                Areas to add text to:
                    - Traders? probably not
                Additional expenses:
                    - ongoing maintenance if visits per month is less than X?
                        slugging the player at random intervals seems cheap
                    - or reduce loyalty value if it doesn't increase steadily? (ie degrade reputation)
                    - sponsoring convoys heading to system by providing seed capital
                        force player to be involved or decline with a mission screen, or leave it as a mission via BB?
                        random/semi-random result?
                        could player be employed as an escort to ensure mission success
                    - 
                Special missions
                    Collect specialty from another system and bring it back?
                    Transport trade delegation to nearby system
                        Passenger cabin?
                Additional benefits:
                    bonus equipment
                    ?
        */
        this._maxHomeSystems = 3;
        this._homeSystems = [[], [], [], [], [], [], [], []];
        this._dockCounts = [{}, {}, {}, {}, {}, {}, {}, {}];
        this._investCounts = [{}, {}, {}, {}, {}, {}, {}, {}];
        this._messageNotification = {};
        this._messageTime = {};
        this._dockingStation = null;
        this._messageTimer = null;
        this._dockTimer = null;
        this._dockAvail = false;
        this._dockMessage = 0;
        this._systemCounted = false;
        this._playerRoles = [];
        this._purchasePrice = 0;
        this._simulator = false;
        this._stationTypes = ["galcop"]; // just for galcop stations, or for any non-pirate station?
        // this controls how many times the player must visit the system (and dock at one of the station types above) in order to get that benefit
        this._levels = {
            base: 0,
            police: 2,
            missions: 5,
            pirate: 6,
            dock_fee: 10,
            dock_priority: 15,
            clear_offender: 20,
            fuel: 25,
            trade: 30,
            equip_rebate: 40,
            ship_rebate: 50,
            contracts: 60,
            missions_2: 75,
            equip_rebate_2: 100,
            ship_rebate_2: 150
        };
        this._trueValues = ["yes", "1", 1, "true", true];
        //-------------------------------------------------------------------------------------------------------------
        this.startUpComplete = function () {
            if (worldScripts["oolite-system-data-config"] && worldScripts["CommpressedF7Layout"]) {
                worldScripts["oolite-system-data-config"].addChangeCallback(this.name, "$addInfoToSystemDataScreen");
                delete this.guiScreenWillChange;
                delete this.guiScreenChanged;
                delete this.infoSystemWillChange;
            }
            if (missionVariables.HomeSystems) this._homeSystems = JSON.parse(missionVariables.HomeSystems);
            if (missionVariables.HomeSystem_DockCounts) this._dockCounts = JSON.parse(missionVariables.HomeSystem_DockCounts);
            if (missionVariables.HomeSystem_InvestCounts) this._investCounts = JSON.parse(missionVariables.HomeSystem_InvestCounts);
            if (missionVariables.HomeSystem_Messages) this._messageNotification = JSON.parse(missionVariables.HomeSystem_Messages);
            if (missionVariables.HomeSystem_Counted) this._systemCounted = (this._trueValues.indexOf(missionVariables.HomeSystem_Counted) >= 0 ? true : false);
            if (missionVariables.HomeSystem_PlayerRoles) this._playerRoles = JSON.parse(missionVariables.HomeSystem_PlayerRoles);
            // testing
            //this._homeSystems[galaxyNumber].push(240);
            //this._dockCounts[galaxyNumber][240] = 20;
            //this._investCounts[galaxyNumber][240] = 4;
            //this._homeSystems[galaxyNumber].push(1);
            //this._homeSystems[galaxyNumber].push(2);
            //this._homeSystems[galaxyNumber].push(3);
            //player.ship.fuel = 0;
            //this._homeSystems[galaxyNumber].push(system.ID);
            //this._dockCounts[galaxyNumber][2] = 10;
            //this._dockCounts[galaxyNumber][3] = 20;
            //this._dockCounts[galaxyNumber][system.ID] = 100;
            //this._dockCounts[galaxyNumber] = {};
            //for (let i = 0; i < 256; i++) {
            //    this._dockCounts[galaxyNumber][i] = Math.floor(Math.random() * 1000 + 100);
            //}
            // make sure the current system gets logged if this is the first time we've been run
            this.shipDockedWithStation(player.ship.dockedStation);
            if (worldScripts.EmailSystem) {
                // stop the normal purchase emails from being sent for these items
                worldScripts.GalCopAdminServices._purchase_ignore_equip.push("EQ_HOMESYSTEM_CLEAN");
                worldScripts.GalCopAdminServices._purchase_ignore_equip.push("EQ_HOMESYSTEM_OFFENDER");
                worldScripts.GalCopAdminServices._purchase_ignore_equip.push("EQ_HOMESYSTEM_FUGITIVE");
                worldScripts.GalCopAdminServices._purchase_ignore_equip.push("EQ_HOMESYSTEM_REMOVE");
                worldScripts.GalCopAdminServices._purchase_ignore_equip.push("EQ_HOMESYSTEM_INVEST");
            }
            this.$checkForMarkedSystems();
            this.$addInvestmentLevelToGalCopReputations();
            this.$initInterface(player.ship.dockedStation);
        }
        //-------------------------------------------------------------------------------------------------------------
        this.playerWillSaveGame = function () {
            missionVariables.HomeSystems = JSON.stringify(this._homeSystems);
            missionVariables.HomeSystem_DockCounts = JSON.stringify(this._dockCounts);
            missionVariables.HomeSystem_InvestCounts = JSON.stringify(this._investCounts);
            missionVariables.HomeSystem_Messages = JSON.stringify(this._messageNotification);
            missionVariables.HomeSystem_Counted = this._systemCounted;
            missionVariables.HomeSystem_PlayerRoles = JSON.stringify(this._playerRoles);
        }
        //-------------------------------------------------------------------------------------------------------------
        this.playerBoughtEquipment = function (equipmentKey) {
            let equip = null;
            let stn = player.ship.dockedStation;
            if (equipmentKey === "EQ_HOMESYSTEM_CLEAN" || equipmentKey === "EQ_HOMESYSTEM_OFFENDER" || equipmentKey === "EQ_HOMESYSTEM_FUGITIVE") {
                player.ship.removeEquipment(equipmentKey);
                let tot = this.$homeSystemCount();
                if (tot >= this._maxHomeSystems) {
                    equip = EquipmentInfo.infoForKey(equipmentKey);
                    this._purchaseEquipKey = equipmentKey
                    stn = player.ship.dockedStation;
                    this._purchasePrice = ((parseInt(equip.price) / 10) * stn.equipmentPriceFactor).toFixed(1);
                    this.$askPlayerAboutRemoval();
                } else {
                    this.$setHomeSystem(equipmentKey);
                }
                return;
            }
            if (equipmentKey === "EQ_HOMESYSTEM_REMOVE") {
                this.$removeHomeSystem(system.ID);
                this.$sendGoodbyeEmail(system.ID);
                this.$swapRoles(false);
                player.ship.removeEquipment(equipmentKey);
                // send email
                return;
            }
            if (equipmentKey === "EQ_HOMESYSTEM_INVEST") {
                player.ship.removeEquipment(equipmentKey);
                this.$askPlayerAboutInvestment();
                return;
            }
            // equipment rebate?
            if (this.$isHomeSystem(system.ID) === true && this.$checkLevel(system.ID, "equip_rebate") === true) {
                if (this._stationTypes.indexOf(stn.allegiance) === -1) return;
                let pct = 0.05;
                if (this.$checkLevel(system.ID, "equip_rebate_2") === true) pct = 0.1;
                equip = EquipmentInfo.infoForKey(equipmentKey);
                if (parseInt(equip.price) <= 0) return;
                let cost = (parseInt(equip.price) / 10) * stn.equipmentPriceFactor;
                let refund = Math.floor(cost * pct);
                if (refund > 0) {
                    player.credits += refund;
                    player.consoleMessage(expandDescription("[homesystem_rebate]", { amount: formatCredits(refund, false, true) }));
                }
            }
        }
        //-------------------------------------------------------------------------------------------------------------
        this.playerBoughtNewShip = function (ship, price) {
            if (this.$isHomeSystem(system.ID) === true && this.$checkLevel(system.ID, "ship_rebate") === true) {
                let stn = player.ship.dockedStation;
                if (this._stationTypes.indexOf(stn.allegiance) === -1) return;
                let pct = 0.05;
                if (this.$checkLevel(system.ID, "ship_rebate_2") === true) pct = 0.1;
                let refund = Math.floor(price * pct);
                if (refund > 0) {
                    player.credits += refund;
                    player.consoleMessage(expandDescription("[homesystem_rebate]", { amount: formatCredits(refund, false, true) }));
                }
            }
        }
        //-------------------------------------------------------------------------------------------------------------
        this.shipWillEnterWitchspace = function () {
            // clear out any message info
            this._messageNotification = {};
            this._systemCounted = false;
            this.$stopTimers();
        }
        //-------------------------------------------------------------------------------------------------------------
        this.playerEnteredNewGalaxy = function () {
            if (this._homeSystems[galaxyNumber].length > 0) {
                let hs = this._homeSystems[galaxyNumber];
                for (let i = 0; i < hs.length; i++) {
                    mission.markSystem({ system: hs[i], name: this.name, markerShape: "MARKER_DIAMOND", markerColor: "purpleColor", markerScale: 2 });
                }
            }
        }
        //-------------------------------------------------------------------------------------------------------------
        this.shipExitedWitchspace = function () {
            if (!this._dockCounts[galaxyNumber][system.ID]) this._dockCounts[galaxyNumber][system.ID] = 0;
            if (this.$isHomeSystem(system.ID) === true && this.$checkLevel(system.ID, "contracts") === true && !system.isInterstellarSpace && !system.sun.hasGoneNova && system.mainStation) {
                // must be a regular system with a main station
                this.$addSpecialParcelContracts();
                worldScripts["oolite-contracts-parcels"]._updateMainStationInterfacesList();
                this.$addSpecialPassengerContracts();
                worldScripts["oolite-contracts-passengers"]._updateMainStationInterfacesList();
            }
            this.$startTimer();
            this.$swapRoles(this.$isHomeSystem(system.ID));
        }
        //-------------------------------------------------------------------------------------------------------------
        this.escapePodSequenceOver = function () {
            this._simulator = true;
        }
        //-------------------------------------------------------------------------------------------------------------
        this.shipWillDockWithStation = function (station) {
            if (this._simulator === true) return;
            this.$stopTimers();
            this._dockAvail = false;
            if (this.$isHomeSystem(system.ID) === true) {
                if (this.$checkLevel(system.ID, "dock_fee") === true) {
                    let df = worldScripts["Docking Fees"];
                    // turn off the docking fees for stations in home system
                    if (df) df._simulator = true;
                }
                if (this._stationTypes.indexOf(station.allegiance) >= 0 && player.ship.bounty < 50) {
                    let txt = expandDescription("[homesystem_dockingreport]"); // *** expansions required
                    if (this.$checkLevel(system.ID, "clear_offender") === true && player.ship.bounty > 0) {
                        txt += expandDescription("[homesystem_clearoffender]");
                        player.bounty = 0;
                    }
                    player.addMessageToArrivalReport(txt);
                }
            }
            this._dockingStation = null;
            // add the f4 interface
            this.$initInterface(station);
        }
        //-------------------------------------------------------------------------------------------------------------
        this.shipDockedWithStation = function (station) {
            this._tellPlayerAboutFuel = false;
            if (this._simulator === true) return;
            if (this._stationTypes.indexOf(station.allegiance) >= 0) {
                if (this._systemCounted === false) {
                    let oldLevel = this.$baseLevel(system.ID);
                    let current = 0;
                    if (!this._dockCounts[galaxyNumber]) this._dockCounts[galaxyNumber] = {};
                    if (this._dockCounts[galaxyNumber][system.ID]) current = parseInt(this._dockCounts[galaxyNumber][system.ID]);
                    current += 1;
                    this._dockCounts[galaxyNumber][system.ID] = current;
                    if (this.$isHomeSystem(system.ID) === true) this.$newBenefitEmail(system.ID, oldLevel, false);
                    this._systemCounted = true;
                }
                if (this.$isHomeSystem(system.ID) && this.$checkLevel(system.ID, "fuel") === true && player.ship.fuel < 7) {
                    player.ship.fuel = 7;
                    player.consoleMessage(expandDescription("[homesystem_refuelled]"));
                    let fe = worldScripts.FuelTweaks_FuelEconomy
                    if (fe && (fe.$isFuelAvailable(system.ID) === false || fe.$fuelRationsInUse(system.ID) === true)) {
                        this._tellPlayerAboutFuel = true;
                    }
                }
            }
            this.$initInterface(station);
        }
        //-------------------------------------------------------------------------------------------------------------
        this.missionScreenOpportunity = function () {
            if (this._tellPlayerAboutFuel === true) {
                this._tellPlayerAboutFuel = false;
                mission.runScreen({
                    screenID: "oolite-homesystem-fuel-map",
                    title: expandDescription("[homesystem_refuelled_title]"),
                    overlay: { name: "hs-home.png", height: 546 },
                    message: expandDescription("[homesystem_fuelmessage]"),
                    exitScreen: "GUI_SCREEN_INTERFACES"
                });
            }
        }
        //-------------------------------------------------------------------------------------------------------------
        this.shipWillLaunchFromStation = function (station) {
            this.$startTimer();
        }
        //-------------------------------------------------------------------------------------------------------------
        this.shipLaunchedFromStation = function (station) {
            if (this.$simulatorRunning()) { this._simulator = true; this.$stopTimers(); return; }
            this._simulator = false;
            if (this.$isHomeSystem(system.ID) === true) {
                if (this._stationTypes.indexOf(station.allegiance) >= 0 && player.ship.bounty < 50 && (!this._messageNotification[station.displayName + "_departure"] || this._messageNotification[station.displayName + "_departure"] === false)) {
                    let txt = expandDescription("[homesystem_departurecomms]");
                    station.commsMessage(txt, player.ship);
                    this._messageNotification[station.displayName + "_departure"] = true;
                }
            }
        }
        //-------------------------------------------------------------------------------------------------------------
        this.playerRequestedDockingClearance = function (message) {
            /*switch (message) {
                case "DOCKING_CLEARANCE_DENIED_TRAFFIC_OUTBOUND":
                case "DOCKING_CLEARANCE_DENIED_TRAFFIC_INBOUND":
                    if (this.$isHomeSystem(system.ID) === true && this.$checkLevel(system.ID, "dock_priority") === true) {
                        this._dockTimer = new Timer(this, this.$autoDock, 5, 5);
                    }
                case "DOCKING_CLEARANCE_GRANTED":
                case "DOCKING_CLEARANCE_NOT_REQUIRED":
                    this._dockAvail = true;
                    let stn = player.ship.target;
                    if (stn.isStation && this._dockingStation == null) this._dockingStation = stn;
                    break;
                case "DOCKING_CLEARANCE_DENIED_SHIP_FUGITIVE":
                case "DOCKING_CLEARANCE_CANCELLED":
                    this._dockingStation = null;
                    break;
            }*/
            // Rather than try to get linter to ignore the use of the switch statement above, i've re-written the routine
            // to hopefully make it easier to understand
            // if we've been told we have to wait for incoming or outgoing ships, then start a timer for an autodock
            if (message === "DOCKING_CLEARANCE_DENIED_TRAFFIC_OUTBOUND" ||
                message === "DOCKING_CLEARANCE_DENIED_TRAFFIC_INBOUND") {
                this._dockAvail = false;
                if (this.$isHomeSystem(system.ID) === true && this.$checkLevel(system.ID, "dock_priority") === true) {
                    this._dockTimer = new Timer(this, this.$autoDock, 5, 5);
                }
            }
            // what station is this?
            if (message === "DOCKING_CLEARANCE_DENIED_TRAFFIC_OUTBOUND" ||
                message === "DOCKING_CLEARANCE_DENIED_TRAFFIC_INBOUND" ||
                message === "DOCKING_CLEARANCE_GRANTED" ||
                message === "DOCKING_CLEARANCE_NOT_REQUIRED") {
                let stn = player.ship.target;
                if (stn.isStation && this._dockingStation == null) this._dockingStation = stn;
            }
            // clear out the stored station reference
            if (message === "DOCKING_CLEARANCE_DENIED_SHIP_FUGITIVE" ||
                message === "DOCKING_CLEARANCE_CANCELLED") {
                this._dockingStation = null;
            }
        }
        //-------------------------------------------------------------------------------------------------------------
        this.playerDockingClearanceGranted = function () {
            if (this.$isHomeSystem(system.ID) === true) {
                this._dockAvail = true;
                if (!this._messageNotification[this._dockingStation.displayName + "_docking"] || this._messageNotification[this._dockingStation.displayName + "_docking"] === false) {
                    let txt = expandDescription("[homesystem_dockcomms]");
                    this._dockingStation.commsMessage(txt, player.ship); // *** expansions required
                    this._messageNotification[this._dockingStation.displayName + "_docking"] = true;
                }
            }
        }
        //-------------------------------------------------------------------------------------------------------------
        this.playerBoughtCargo = function (commodity, units, price) {
            if (this.$isHomeSystem(system.ID) === true && this.$checkLevel(system.ID, "trade") === true && units > 0 && this._stationTypes.indexOf(player.ship.dockedStation.allegiance) >= 0) {
                let tot = 0;
                if (this._messageNotification["cargo_bought"]) tot = parseInt(this._messageNotification["cargo_bought"]);
                let start = tot;
                tot += units;
                this._messageNotification["cargo_bought"] = tot;
                let bonus = 0;
                if (tot >= 10 && start < 10) bonus = 5;
                if (tot >= 50 && start < 50) bonus = 10;
                if (tot >= 100 && start < 100) bonus = 20;
                if (tot >= 200 && start < 200) bonus = 50;
                if (tot >= 500 && start < 500) bonus = 100;
                if (bonus > 0) {
                    player.credits += bonus;
                    player.consoleMessage(expandDescription("[homesystem_tradebonus]", { amount: formatCredits(bonus, true, false) }));
                }
                // apply this to the sold setting, so the player doesn't get awarded twice
                //this._messageNotification["cargo_sold"] = tot;
            }
        }
        //-------------------------------------------------------------------------------------------------------------
        this.playerSoldCargo = function (commodity, units, price) {
            if (this.$isHomeSystem(system.ID) === true && this.$checkLevel(system.ID, "trade") === true && units > 0 && this._stationTypes.indexOf(player.ship.dockedStation.allegiance) >= 0) {
                let tot = 0;
                if (this._messageNotification["cargo_bought"]) tot = parseInt(this._messageNotification["cargo_bought"]);
                let start = tot;
                tot -= units;
                // mke sure the player can't just ossillate across one of the bonus points by buying and selling without moving
                if (tot < 500 && start >= 500) tot = 500;
                if (tot < 200 && start >= 200) tot = 200;
                if (tot < 100 && start >= 100) tot = 100;
                if (tot < 50 && start >= 50) tot = 50;
                if (tot < 10 && start >= 10) tot = 10;
                if (tot < 0) tot = 0;
                this._messageNotification["cargo_bought"] = tot;
            }
        }
        //-------------------------------------------------------------------------------------------------------------
        this.shipBountyChanged = function (delta, reason) {
            // remove any home system link if the player does something bad to make themselves a fugitive in the system
            switch (reason) {
                case "killed innocent":
                case "killed police":
                case "attacked police":
                case "attacked main station":
                    if (delta > 0 && player.ship.bounty > 50 && this.$isHomeSystem(system.ID) === true) {
                        player.consoleMessage(expandDescription("[homesystem_revoked]"));
                        this.$removeHomeSystem(system.ID);
                    }
                    // reset the player's reputation
                    this._dockCounts[galaxyNumber][system.ID] = 0
                    this._investCounts[galaxyNumber][system.ID] = 0;
                    break;
            }
        }
        //-------------------------------------------------------------------------------------------------------------
        this.shipDied = function (whom, why) {
            this.$stopTimers();
        }
        //-------------------------------------------------------------------------------------------------------------
        this.guiScreenWillChange = function (to, from) {
            if (to === "GUI_SCREEN_SYSTEM_DATA") this.$addInfoToSystemDataScreen();
        }
        //-------------------------------------------------------------------------------------------------------------
        this.guiScreenChanged = function (to, from) {
            if (to === "GUI_SCREEN_SYSTEM_DATA") this.$addInfoToSystemDataScreen();
        }
        //-------------------------------------------------------------------------------------------------------------
        this.infoSystemWillChange = function (to, from) {
            if (guiScreen === "GUI_SCREEN_SYSTEM_DATA") this.$addInfoToSystemDataScreen();
        }
        //-------------------------------------------------------------------------------------------------------------
        this.$askPlayerAboutRemoval = function () {
            let curChoices = {};
            let txt = "";
            let count = this.$homeSystemCount();
            if (count >= this._maxHomeSystems) {
                if (count === 1) {
                    txt = expandDescription("[homesystem_letting_go_single]", { sys: System.systemNameForID(this._homeSystems[galaxyNumber][0]) });
                    curChoices["0_UNSET"] = expandDescription("[homesystem_yes]");
                    curChoices["9_NO"] = expandDescription("[homesystem_no]");
                } else {
                    txt = expandDescription("[homesystem_letting_go_multi]");
                    let set = this._homeSystems[galaxyNumber];
                    for (let i = 0; i < set.length; i++) {
                        curChoices[i + "_UNSET"] = System.systemNameForID(set[i]);
                    }
                    curChoices["9_NO"] = expandDescription("[homesystem_cancel]");
                }
                let opts = {
                    screenID: "oolite-homesystem-question-map",
                    title: expandDescription("[homesystem_screen_title]"),
                    overlay: { name: "hs-question.png", height: 546 },
                    allowInterrupt: false,
                    exitScreen: "GUI_SCREEN_EQUIP_SHIP",
                    choices: curChoices,
                    initialChoicesKey: "9_NO",
                    message: txt
                };
                mission.runScreen(opts, this.$questionHandler, this);
            } else {
                return;
            }
        }
        //-------------------------------------------------------------------------------------------------------------
        this.$questionHandler = function (choice) {
            if (choice == null) return;
            if (choice === "9_NO") {
                // refund the player
                player.credits += Math.floor(parseFloat(this._purchasePrice) * 10) / 10;
                return;
            }
            if (choice.indexOf("_UNSET") >= 0) {
                // unset the system
                let idx = parseInt(choice.substring(0, 1), 10);
                let sys = this._homeSystems[galaxyNumber][idx];
                this.$removeHomeSystem(sys);
                this.$sendGoodbyeEmail(sys);
                player.consoleMessage(expandDescription("[homesystem_removed]", { sys: System.systemNameForID(sys) }));
                // check the count again -- if we're still over, go back and ask again
                let count = this.$homeSystemCount();
                if (count >= this._maxHomeSystems) {
                    this.$askPlayerAboutRemoval();
                    return;
                }
                this.$setHomeSystem(this._purchaseEquipKey);
            }
        }
        //-------------------------------------------------------------------------------------------------------------
        this.$askPlayerAboutInvestment = function () {
            // if there's only one home system to invest in, force that choice
            if (this.$homeSystemCount() === 1) {
                this.$questionHandler2("0_INVEST");
                return;
            }
            let curChoices = {};
            let txt = "";
            txt = expandDescription("[homesystem_transfer]");
            let set = this._homeSystems[galaxyNumber];
            for (let i = 0; i < set.length; i++) {
                curChoices[i + "_INVEST"] = System.systemNameForID(set[i]);
            }
            curChoices["9_NO"] = expandDescription("[homesystem_cancel]");
            let opts = {
                screenID: "oolite-homesystem-question-map",
                title: expandDescription("[homesystem_screen_title]"),
                overlay: { name: "hs-question.png", height: 546 },
                allowInterrupt: false,
                exitScreen: "GUI_SCREEN_EQUIP_SHIP",
                choices: curChoices,
                initialChoicesKey: "9_NO",
                message: txt
            };
            mission.runScreen(opts, this.$questionHandler2, this);
        }
        //-------------------------------------------------------------------------------------------------------------
        this.$questionHandler2 = function (choice) {
            if (choice == null) return;
            if (choice === "9_NO") {
                // refund the player
                player.credits += 10000;
                return;
            }
            if (choice.indexOf("_INVEST") >= 0) {
                let idx = parseInt(choice.substring(0, 1), 10);
                let sys = this._homeSystems[galaxyNumber][idx];
                player.consoleMessage(expandDescription("[homesystem_transferred]", { sys: System.systemNameForID(sys) }), 5);
                if (!this._investCounts[galaxyNumber]) this._investCounts[galaxyNumber] = {};
                let oldLevel = this.$baseLevel(sys);
                let current = this.$investmentCount(galaxyNumber, sys);
                current += 1;
                this._investCounts[galaxyNumber][sys] = current;
                // only send a new benefit email if the player is at that system
                this.$newBenefitEmail(sys, oldLevel, false);
                if (worldScripts.DayDiplomacy_060_Citizenships) {
                    let c = worldScripts.DayDiplomacy_060_Citizenships;
                    let s = System.infoForSystem(galaxyNumber, sys);
                    /*
                        According to the Diplomacy OXP, a citizenship costs somewhere between 900cr and 9000cr.
                        By contrast investment package costs 10000cr.
                        To bring these values into some form of alignment, I'm taking the number of investment packages purchased
                        and multiplying it by 500, then comparing that to the citizenship price for that system.
                        For example: if I've purchased 3 investment options, my equivalent amount would be 3 * 500 = 1500cr
                        That's enough to get you citizenship in some of the lower productivity systems, but not the higher ones.
        
                        The explanation for this is that the investment purchase is largely a commercial transaction, whereas
                        "citizenship" is more of a governmental transaction. However, a portion of the commercial transaction 
                        is allocated to government, so, over time, the government will recognise the value of your commercial
                        input and award the citizenship.
        
                        Note: this is mucking around with the internals of Diplomacy and is liable to break in future versions
                        Note: the citizenship option will only be awarded once (when the equivalent investment amount surpasses
                        the citizenship price). Subsequent investment purchases will not award the citizenship, and if the
                        player revokes their citizenship, it will not be re-awarded. Also, if the player has previously had
                        citizenship, then revokes it, then purchases investment options that ultimately grant citizenship,
                        the previous revoking will not be remembered and citizenship will get purchased again.
                    */
                    let inv = current * 500;
                    let oldinv = (current - 1) * 500;
                    let price = c.$getCitizenshipPrice(s);
                    if (c.$hasPlayerCitizenship(galaxyNumber, sys) === false && (oldinv < price && inv >= price)) {
                        // make sure we have enough credits to buy the citizenship
                        player.credits += price;
                        c._buyCitizenship(galaxyNumber, sys);
                        player.consoleMessage(expandDescription("[homesystem_citizen]", { sys: System.systemNameForID(sys) }), 5);
                    }
                }
            }
        }
        //-------------------------------------------------------------------------------------------------------------
        this.$checkForShips = function $checkForShips() {
            // after transmitting message, turn off "Greet ship" message in BroadcastComms, and add a "Reply" message instead
            let ships = player.ship.checkScanner(true);
            let rnd = Math.random;
            let pirateLevel = this.$checkLevel(system.ID, "pirate");
            if (ships && ships.length > 0) {
                for (let i = 0; i < ships.length; i++) {
                    let shp = ships[i];
                    // police ship
                    if (shp.isPolice && shp.isStation === false && player.ship.bounty === 0 && shp.hasHostileTarget === false && (!shp.script._welcomed || shp.script._welcomed === false)) {
                        // only send a police welcome message every 10 minutes at most
                        if (rnd() > 0.5 && (!this._messageNotification["police"] || clock.adjustedSeconds - this._messageNotification["police"] > 600)) {
                            let txt = expandDescription("[homesystem_policegreeting]");
                            shp.commsMessage(txt, player.ship); // ** expansions required
                            shp.script._welcomed = true;
                            // note the time we did this, so we can avoid spamming the player
                            this._messageNotification["police"] = clock.adjustedSeconds;
                            let bcc = worldScripts.BroadcastCommsMFD;
                            if (bcc) {
                                // turn off the greet ship message
                                bcc.$addShipToArray(shp, bcc._greeted);
                                // add a reply option
                                bcc.$createMessage({
                                    messageName: "hs_transmit_police_reply_" + shp.entityPersonality.toString(),
                                    displayText: expandDescription("[homesystem_reply]"),
                                    messageText: expandDescription("[homesystem_policereply]"), // ** expansions required
                                    ship: shp,
                                    transmissionType: "target",
                                    callbackFunction: this.$messageReplyPolice,
                                    deleteOnTransmit: true,
                                    delayCallback: 0,
                                    hideOnConditionRed: true
                                }
                                );
                            }
                            break;
                        }
                    }
                    // trader
                    // pirate
                    if (pirateLevel === true && shp.bounty >= 0 && shp.hasHostileTarget === false && (Ship.roleIsInCategory(shp.primaryRole, "oolite-pirate") || (shp.AI && shp.AI.toLowerCase().indexOf("pirate") >= 0) || (shp.AIScript && shp.AIScript.name.toLowerCase().indexOf("pirate") >= 0))) {
                        // only send a pirate "welcome" message every 5 minutes at most
                        if ((!shp.script._welcomed || shp.script._welcomed === false) && rnd() > 0.5 && (!this._messageNotification["pirate"] || clock.adjustedSeconds - this._messageNotification["pirate"] > 300)) {
                            let txt = expandDescription("[homesystem_pirategreeting]");
                            shp.commsMessage(txt, player.ship); // ** expansions required
                            shp.script._welcomed = true;
                            // note the time we did this, so we can avoid spamming the player
                            this._messageNotification["pirate"] = clock.adjustedSeconds;
                            let bcc = worldScripts.BroadcastCommsMFD;
                            if (bcc) {
                                // turn off the greet ship message
                                bcc.$addShipToArray(shp, bcc._greeted);
                                // add a reply option
                                bcc.$createMessage({
                                    messageName: "hs_transmit_pirate_reply_" + shp.entityPersonality.toString(),
                                    displayText: expandDescription("[homesystem_reply]"),
                                    messageText: expandDescription("[homesystem_piratereply]"), // ** expansions required
                                    ship: shp,
                                    transmissionType: "target",
                                    callbackFunction: this.$messageReplyPirate,
                                    deleteOnTransmit: true,
                                    delayCallback: 0,
                                    hideOnConditionRed: true
                                }
                                );
                                // ideally, we need a way of disabling this response if any other type is sent by the player to this ship
                            }
                            break;
                        }
                    }
                }
            }
        }
        //-------------------------------------------------------------------------------------------------------------
        this.$messageReplyPolice = function () {
        }
        //-------------------------------------------------------------------------------------------------------------
        this.$messageReplyPirate = function () {
        }
        //-------------------------------------------------------------------------------------------------------------
        this.$startTimer = function () {
            if (this.$isHomeSystem(system.ID) === true && this.$checkLevel(system.ID, "police") === true) {
                // start a timer to being monitoring the scanner for ships that might want to say hi to the player
                this._messageTimer = new Timer(this, this.$checkForShips, 10, 10);
            }
        }
        //-------------------------------------------------------------------------------------------------------------
        this.$stopTimers = function () {
            if (this._messageTimer && this._messageTimer.isRunning) this._messageTimer.stop();
            if (this._dockTimer && this._dockTimer.isRunning) this._dockTimer.stop();
        }
        //-------------------------------------------------------------------------------------------------------------
        this.$autoDock = function () {
            if (this._dockingStation == null) return;
            if (this._dockAvail === true) {
                if (this._dockMessage >= 3) {
                    this._dockingStation.commsMessage(expandDescription("[homesystem_dock_delay]"), player.ship);
                }
                this._dockTimer.stop();
                return;
            }
            this._dockMessage += 1;
            switch (this._dockMessage) {
                case 1:
                    this._dockingStation.commsMessage(expandDescription("[homesystem_dock_clearing]"), player.ship);
                    break;
                case 3:
                    this._dockingStation.commsMessage(expandDescription("[homesystem_dock_auto]"), player.ship);
                    break;
                case 7:
                    this._dockingStation.commsMessage(expandDescription("[homesystem_dock_final]"), player.ship);
                    break;
                case 8:
                    this._dockingStation.dockPlayer();
                    // only a 5 minute time delay
                    clock.addSeconds(300);
            }
        }
        //-------------------------------------------------------------------------------------------------------------
        this.$setHomeSystem = function (equipmentKey) {
            this._homeSystems[galaxyNumber].push(system.ID);
            mission.markSystem({ system: system.ID, name: this.name, markerShape: "MARKER_DIAMOND", markerColor: "purpleColor", markerScale: 2 });
            this.$swapRoles(true);
            // send email welcoming the player "home"
            if (worldScripts.EmailSystem) {
                let ga = worldScripts.GalCopAdminServices;
                let e = worldScripts.EmailSystem;
                e.$createEmail(
                    {
                        sender: expandDescription("[homesystem_station_manager]"),
                        subject: expandDescription("[homesystem_privileges]"),
                        date: clock.adjustedSeconds,
                        sentFrom: system.ID,
                        message: expandDescription("[homesystem_email_" + equipmentKey + "]"),
                        expiryDays: ga._defaultExpiryDays
                    });
                this.$newBenefitEmail(system.ID, this.$baseLevel(system.ID) - 1, true);
            }
            this.$addInvestmentLevelToGalCopReputations();
        }
        //-------------------------------------------------------------------------------------------------------------
        this.$isHomeSystem = function (sysID) {
            return (parseInt(sysID) >= 0 && this._homeSystems[galaxyNumber].indexOf(parseInt(sysID)) >= 0);
        }
        //-------------------------------------------------------------------------------------------------------------
        this.$homeSystemCount = function () {
            return this._homeSystems[galaxyNumber].length;
        }
        //-------------------------------------------------------------------------------------------------------------
        this.$baseLevel = function (sysID) {
            return (parseInt(this._dockCounts[galaxyNumber][parseInt(sysID)]) + (this.$investmentCount(galaxyNumber, parseInt(sysID)) * 2));
        }
        //-------------------------------------------------------------------------------------------------------------
        this.$checkLevel = function (sysID, lvl) {
            let min = this.$levelCalc(sysID, lvl);
            return (this.$baseLevel(sysID) >= min);
        }
        //-------------------------------------------------------------------------------------------------------------
        this.$checkLevelValue = function (sysID, lvl, value) {
            let min = this.$levelCalc(sysID, lvl);
            return (value >= min);
        }
        //-------------------------------------------------------------------------------------------------------------
        this.$levelCalc = function (sysID, lvl) {
            let min = this._levels[lvl];
            let sys = (parseInt(sysID) === system.ID ? system.info : System.infoForSystem(galaxyNumber, parseInt(sysID)));
            let factor = 1 + (sys.government + (7 - sys.economy) * 2 + sys.techlevel * 2) / 55;
            return parseInt(min * factor);
        }
        //-------------------------------------------------------------------------------------------------------------
        this.$removeHomeSystem = function (sysID) {
            let set = this._homeSystems[galaxyNumber];
            let idx = set.indexOf(parseInt(sysID));
            if (idx >= 0) set.splice(idx, 1);
            this._investCounts[galaxyNumber][parseInt(sysID)] = 0;
            mission.unmarkSystem({ system: sysID, name: this.name });
            this.$removeGalCopReputation(sysID);
        }
        //-------------------------------------------------------------------------------------------------------------
        // make all the player's roles "player-home" (or swap them back to what they were originally)
        // by doing this we can ensure a consistent view of the player while they're "home"
        this.$swapRoles = function (swapIn) {
            if (swapIn === true) {
                // copy all existing roles to holding array
                if (player.roleWeights[0] === "player-home" || player.roleWeights[1] === "player-home" || player.roleWeights[2] === "player-home") return; // looks like we've already swapped things in
                this._playerRoles.length = 0;
                for (let i = 0; i < player.roleWeights.length; i++) {
                    this._playerRoles.push(player.roleWeights[i]);
                    player.setPlayerRole("player-home", i);
                }
            } else {
                if (this._playerRoles.length === 0) return; // no data to swap back
                if (player.roleWeights[0] !== "player-home" || player.roleWeights[1] !== "player-home" || player.roleWeights[2] !== "player-home") return; // looks like we haven't swapped anything out yet
                for (let i = 0; i < this._playerRoles.length; i++) {
                    // only update the ones that are still "player-home"
                    // just in case any player action insystem causes a change.
                    if (player.roleWeights[i] === "player-home") player.setPlayerRole(this._playerRoles[i], i);
                }
                this._playerRoles.length = 0;
            }
        }
        //-------------------------------------------------------------------------------------------------------------
        this.$newBenefitEmail = function (sysID, oldLevel, initial) {
            let txt = "";
            let types = "a new benefit";
            let count = 0;
            let e = worldScripts.EmailSystem;
            if (!e) return;
            if (this.$checkLevelValue(sysID, "dock_fee", oldLevel) === false && this.$checkLevel(sysID, "dock_fee") === true && worldScripts["Docking Fee"]) {
                txt += "\n" + expandDescription("[homesystem_benefit_dock_fee]");
                count += 1;
            }
            if (this.$checkLevelValue(sysID, "dock_priority", oldLevel) === false && this.$checkLevel(sysID, "dock_priority") === true) {
                txt += "\n" + expandDescription("[homesystem_benefit_dock_priority]");
                count += 1;
            }
            if (this.$checkLevelValue(sysID, "clear_offender", oldLevel) === false && this.$checkLevel(sysID, "clear_offender") === true) {
                txt += "\n" + expandDescription("[homesystem_benefit_clear_offender]");
                count += 1;
            }
            if (this.$checkLevelValue(sysID, "missions", oldLevel) === false && this.$checkLevel(sysID, "missions") === true && worldScripts.GalCopBB_Missions) {
                txt += "\n" + expandDescription("[homesystem_benefit_missions]");
                count += 1;
            }
            if (this.$checkLevelValue(sysID, "missions_2", oldLevel) === false && this.$checkLevel(sysID, "missions_2") === true && worldScripts.GalCopBB_Missions) {
                txt += "\n" + expandDescription("[homesystem_benefit_missions_2]");
                count += 1;
            }
            if (this.$checkLevelValue(sysID, "contracts", oldLevel) === false && this.$checkLevel(sysID, "contracts") === true) {
                txt += "\n" + expandDescription("[homesystem_benefit_contracts]");
                count += 1;
            }
            if (this.$checkLevelValue(sysID, "fuel", oldLevel) === false && this.$checkLevel(sysID, "fuel") === true) {
                txt += "\n" + expandDescription("[homesystem_benefit_fuel]");
                count += 1;
            }
            if (this.$checkLevelValue(sysID, "trade", oldLevel) === false && this.$checkLevel(sysID, "trade") === true) {
                txt += "\n" + expandDescription("[homesystem_benefit_trade]");
                count += 1;
            }
            if (this.$checkLevelValue(sysID, "equip_rebate", oldLevel) === false && this.$checkLevel(sysID, "equip_rebate") === true) {
                txt += "\n" + expandDescription("[homesystem_benefit_equip_rebate]");
                count += 1;
            }
            if (this.$checkLevelValue(sysID, "ship_rebate", oldLevel) === false && this.$checkLevel(sysID, "ship_rebate") === true) {
                txt += "\n" + expandDescription("[homesystem_benefit_ship_rebate]");
                count += 1;
            }
            if (this.$checkLevelValue(sysID, "equip_rebate_2", oldLevel) === false && this.$checkLevel(sysID, "equip_rebate_2") === true) {
                txt += "\n" + expandDescription("[homesystem_benefit_equip_rebate_2]");
                count += 1;
            }
            if (this.$checkLevelValue(sysID, "ship_rebate_2", oldLevel) === false && this.$checkLevel(sysID, "ship_rebate_2") === true) {
                txt += "\n" + expandDescription("[homesystem_benefit_ship_rebate_2]");
                count += 1;
            }
            if (txt !== "") {
                if (count > 1) types = expandDescription("[homesystem_new_benefits]");
                // something's changed, so send the email
                let ga = worldScripts.GalCopAdminServices;
                let msg = expandDescription("[homesystem_benefit_email]", { benefits: txt, types: types, display_initial: (initial === true ? expandDescription("[homesystem_so_glad]") : "") });
                let sndr = expandDescription("[homesystem_station_manager]");
                let delay = 0;
                if (system.ID != parseInt(sysID)) {
                    sndr = expandDescription("[homesystem_email_stnmgr]", { sys: System.systemNameForID(sysID) });
                    msg = expandDescription("[homesystem_benefit_remote_email]", { benefits: txt, types: types, system: System.systemNameForID(sysID) });
                    let dist = system.info.distanceToSystem(System.infoForSystem(galaxyNumber, sysID));
                    delay = dist * 3600; // 1 hour for every ly
                }
                e.$createEmail({
                    sender: sndr,
                    subject: expandDescription("[homesystem_privileges]"),
                    date: clock.adjustedSeconds + delay,
                    sentFrom: sysID,
                    message: msg,
                    expiryDays: ga._defaultExpiryDays
                });
            }
        }
        //-------------------------------------------------------------------------------------------------------------
        this.$sendGoodbyeEmail = function (sysID) {
            let sysname = System.systemNameForID(sysID);
            let ga = worldScripts.GalCopAdminServices;
            let e = worldScripts.EmailSystem;
            if (!e) return;
            e.$createEmail(
                {
                    sender: expandDescription("[homesystem_email_stnmgr]", { sys: sysname }),
                    subject: expandDescription("[homesystem_privileges]"),
                    date: clock.adjustedSeconds,
                    sentFrom: sysID,
                    message: expandDescription("[homesystem_leaving_email]", { system_name: sysname }),
                    expiryDays: ga._defaultExpiryDays
                });
        }
        //-------------------------------------------------------------------------------------------------------------
        this.$addSpecialParcelContracts = function () {
            // possibly add some high-value contracts
            let numContracts = Math.floor(Math.random() * 3);
            for (let i = 0; i < numContracts; i++) {
                let parcel = {};
                // pick a random system to take the parcel to
                let destination = Math.floor(Math.random() * 256);
                // discard if chose the current system
                if (destination === system.ID) continue;
                // get the SystemInfo object for the destination
                let destinationInfo = System.infoForSystem(galaxyNumber, destination);
                if (destinationInfo.sun_gone_nova) continue;
                // check that a route to the destination exists
                let routeToDestination = system.info.routeToSystem(destinationInfo);
                // if the system cannot be reached, discard the parcel
                if (!routeToDestination) continue;
                // we now have a valid destination, so generate the rest of
                // the parcel details
                parcel.destination = destination;
                // we'll need this again later, and route calculation is slow
                parcel.route = routeToDestination;
                parcel.sender = randomName() + " " + randomName();
                // time allowed for delivery is time taken by "fewest jumps"
                // route, plus 10-110%, plus four hours to make sure all routes
                // are "in time" for a reasonable-length journey in-system.
                let dtime = Math.floor((routeToDestination.time * 3600 * (1.1 + (Math.random())))) + 14400;
                parcel.deadline = clock.adjustedSeconds + dtime;
                parcel.risk = Math.floor(Math.random() * 3);
                if (parcel.risk < 2 && destinationInfo.government <= 1 && Math.random() < 0.5) parcel.risk++;
                parcel.description = expandDescription("[hs_parcel-description-risk" + parcel.risk + "]");
                // total payment is small for these items.
                parcel.payment = Math.floor(
                    // 2-3 credits per LY of route
                    (routeToDestination.distance * (2 + Math.random())) +
                    // additional income for route length based on reputation
                    (Math.pow(routeToDestination.route.length, 1 + (parcel.risk * 0.4) + (0.2 * player.parcelReputationPrecise))) +
                    // small premium for delivery to more dangerous systems
                    (2 * Math.pow(7 - destinationInfo.government, 1.5))
                );
                parcel.payment *= (Math.random() + Math.random() + Math.random() + Math.random()) / 2;
                let prudence = (2 * Math.random()) - 1;
                let desperation = (Math.random() * (0.5 + parcel.risk)) * (1 + 1 / (Math.max(0.5, dtime - (routeToDestination.time * 3600))));
                let competency = Math.max(50, (routeToDestination.route.length - 1) * (0.5 + (parcel.risk * 2)));
                if (parcel.risk == 0) competency -= 30;
                parcel.payment = Math.floor(parcel.payment * (1 + (0.4 * prudence)));
                parcel.payment += (parcel.risk * 200);
                // anywhere from 2 to 5 times the normal payment
                parcel.payment *= Math.floor(Math.random() * (parcel.risk + 1) + 2);
                // paying this little probably can't ask for anyone good
                if (parcel.payment < 100) competency -= 15;
                parcel.skill = competency + 20 * (prudence - desperation);
                // upper limit on skill
                if (parcel.skill > 60) parcel.skill = 60;
                //log(this.name, "parcel " + parcel.payment, parcel.skill, parcel.risk);
                // add parcel to contract list
                worldScripts["oolite-contracts-parcels"]._addParcelToSystem(parcel);
            }
        }
        //-------------------------------------------------------------------------------------------------------------
        this.$addSpecialPassengerContracts = function () {
            let numContracts = Math.floor(Math.random * 4 - 1);
            if (numContracts < 0) numContracts = 0;
            // some of these possible contracts may be discarded later on
            for (let i = 0; i < numContracts; i++) {
                let passenger = {};
                // pick a random system to take the passenger to
                let destination = Math.floor(Math.random() * 256);
                // discard if chose the current system
                if (destination === system.ID) continue;
                // get the SystemInfo object for the destination
                let destinationInfo = System.infoForSystem(galaxyNumber, destination);
                if (destinationInfo.sun_gone_nova) continue;
                let daysUntilDeparture = 1 + (Math.random() * (7 + player.passengerReputationPrecise - destinationInfo.government));
                // loses some more contracts if reputation negative
                if (daysUntilDeparture <= 0) continue;
                // check that a route to the destination exists
                let routeToDestination = system.info.routeToSystem(destinationInfo);
                // if the system cannot be reached, ignore this contract
                if (!routeToDestination) continue;
                // we now have a valid destination, so generate the rest of the passenger details
                passenger.destination = destination;
                // we'll need this again later, and route calculation is slow
                passenger.route = routeToDestination;
                // 50% local inhabitant
                if (Math.random() < 0.5) {
                    passenger.species = system.info.inhabitant;
                } else {
                    // 50% random species (which will be 50%ish human)
                    passenger.species = System.infoForSystem(galaxyNumber, Math.floor(Math.random() * 256)).inhabitant;
                }
                if (passenger.species.match(new RegExp(expandDescription("[human-word]"), "i"))) {
                    passenger.name = expandDescription("%N ") + expandDescription("[nom]");
                } else {
                    passenger.name = randomName() + " " + randomName();
                }
                /* Because passengers with duplicate names won't be accepted,
                 * check for name duplication with either other passengers
                 * here or other passengers carried by the player, and adjust
                 * this passenger's name a little if there's a match */
                do {
                    let okay = true;
                    for (let j = 0; j < player.ship.passengers.length; j++) {
                        if (player.ship.passengers[j].name == passenger.name) {
                            okay = false;
                            break;
                        }
                    }
                    if (okay) {
                        for (let j = 0; j < worldScripts["oolite-contracts-passengers"].$passengers.length; j++) {
                            if (worldScripts["oolite-contracts-passengers"].$passengers[j].name == passenger.name) {
                                okay = false;
                                break;
                            }
                        }
                    }
                    if (!okay) passenger.name += "a";
                } while (!okay);
                passenger.risk = Math.floor(Math.random() * 3);
                passenger.species = expandDescription("[passenger-description-risk" + passenger.risk + "]") + " " + passenger.species;
                // time allowed for delivery is time taken by "fewest jumps"
                // route, plus timer above. Higher reputation makes longer
                // times available.
                let dtime = Math.floor(daysUntilDeparture * 86400) + (passenger.route.time * 3600);
                passenger.deadline = clock.adjustedSeconds + dtime;
                if (passenger.risk < 2 && destinationInfo.government <= 1 && Math.random() < 0.5) passenger.risk++;
                // total payment is:
                passenger.payment = Math.floor(
                    // payment per hop (higher at rep > 5)
                    5 * Math.pow(routeToDestination.route.length - 1, (passenger.risk * 0.2) + (player.passengerReputationPrecise > 5 ? 2.45 : 2.3)) +
                    // payment by route length
                    routeToDestination.distance * (8 + (Math.random() * 8)) +
                    // premium for delivery to more dangerous systems
                    (5 * (7 - destinationInfo.government) * (7 - destinationInfo.government))
                );
                passenger.payment *= (Math.random() + Math.random() + Math.random() + Math.random()) / 2;
                let prudence = (2 * Math.random()) - 1;
                let desperation = (Math.random() * (0.5 + passenger.risk)) * (1 + 1 / (Math.max(0.5, dtime - (routeToDestination.time * 3600))));
                let competency = Math.max(50, (routeToDestination.route.length - 1) * (0.5 + (passenger.risk * 2)));
                if (passenger.risk == 0) competency -= 30;
                passenger.payment = Math.floor(passenger.payment * (1 + (0.4 * prudence)));
                passenger.payment += (passenger.risk * 200);
                passenger.skill = Math.min(60, competency + 20 * (prudence - desperation));
                // anywhere from 2 to 5 times the normal payment
                passenger.payment *= Math.floor(Math.random() * (passenger.risk + 1) + 2);
                passenger.advance = Math.min(passenger.payment * 0.9, Math.max(0, Math.floor(passenger.payment * (0.05 + (0.1 * desperation) + (0.02 * player.passengerReputationPrecise))))); // some% up front
                passenger.payment -= passenger.advance;
                log(this.name, "pass " + passenger.payment, passenger.skill, passenger.risk);
                // add passenger to contract list
                worldScripts["oolite-contracts-passengers"]._addPassengerToSystem(passenger);
            }
        }
        //-------------------------------------------------------------------------------------------------------------
        // makes sure all our home systems are marked on the chart
        this.$checkForMarkedSystems = function () {
            let hs = this._homeSystems[galaxyNumber];
            let ms = mission.markedSystems;
            if (hs.length > 0) {
                // look for any marked system matches
                if (ms.length > 0) {
                    for (let i = 0; i < hs.length; i++) {
                        let found = false;
                        // loop through all the marked systems
                        for (let j = 0; j < ms.length; j++) {
                            if (ms[j].system === hs[i] && ms[j] === this.name) found = true;
                        }
                        // if we didn't get a match, mark this one
                        if (found === false) {
                            mission.markSystem({ system: hs[i], name: this.name, markerShape: "MARKER_DIAMOND", markerColor: "purpleColor", markerScale: 2 });
                        }
                    }
                } else {
                    // if there aren't any marked systems at all, just add all our home systems
                    for (let i = 0; i < hs.length; i++) {
                        mission.markSystem({ system: hs[i], name: this.name, markerShape: "MARKER_DIAMOND", markerColor: "purpleColor", markerScale: 2 });
                    }
                }
            }
        }
        //-------------------------------------------------------------------------------------------------------------
        this.$addInvestmentLevelToGalCopReputations = function $addInvestmentLevelToGalCopReputations() {
            if (!worldScripts.GalCopBB_Reputation) return
            for (let gal = 0; gal <= 7; gal++) {
                let hs = this._homeSystems[gal];
                for (let i = 0; i < hs.length; i++) {
                    let hsID = parseInt(hs[i]);
                    // make sure we don't already have the custom function
                    if (this["$returnInvestmentLevel_" + gal + "_" + hsID]) continue;
                    let sysname = System.infoForSystem(gal, hsID).name;
                    let rep = worldScripts.GalCopBB_Reputation;
                    rep._entities[sysname + " (G" + (gal + 1) + ") Investment Level"] = {
                        name: expandDescription("[homesystem_invest_level]", { sys: sysname }),
                        scope: "system",
                        display: true,
                        regionSystems: [hsID],
                        regionGalaxy: gal,
                        getValueWS: "HomeSystem",
                        getValueFN: "$returnInvestmentLevel_" + gal + "_" + hsID,
                        rewardGrid: [{
                            value: 0,
                            description: ""
                        },
                        {
                            value: 1,
                            description: expandDescription("[hs_investment_level_1]")
                        },
                        {
                            value: 2,
                            description: expandDescription("[hs_investment_level_2]")
                        },
                        {
                            value: 3,
                            description: expandDescription("[hs_investment_level_3]")
                        },
                        {
                            value: 4,
                            description: expandDescription("[hs_investment_level_4]")
                        },
                        {
                            value: 5,
                            description: expandDescription("[hs_investment_level_5]")
                        },
                        {
                            value: 6,
                            description: expandDescription("[hs_investment_level_6]")
                        },
                        {
                            value: 7,
                            description: expandDescription("[hs_investment_level_7]")
                        },
                        {
                            value: 8,
                            description: expandDescription("[hs_investment_level_8]")
                        }]
                    };
                    eval("this.$returnInvestmentLevel_" + gal + "_" + hsID + " = function() { return this.$investmentLevel(" + gal + ", " + hsID + "); }");
                    rep._reputations.push({
                        entity: sysname + " (G" + (gal + 1) + " Investment Level",
                        galaxy: galaxyNumber,
                        system: hsID,
                        reputation: this.$investmentLevel(gal, hsID)
                    });
                }
            }
        }
        //-------------------------------------------------------------------------------------------------------------
        this.$removeGalCopReputation = function $removeGalCopReputation(sysID) {
            if (!worldScripts.GalCopBB_Reputation) return;
            let rep = worldScripts.GalCopBB_Reputation;
            let arr = rep._reputations;
            for (let i = arr.length - 1; i >= 0; i++) {
                if (arr[i].galaxy === galaxyNumber && arr[i].system === parseInt(sysID)) {
                    arr.splice(i, 1);
                    return;
                }
            }
            delete this["$returnInvestmentLevel_" + galaxyNumber + "_" + sysID];
        }
        //-------------------------------------------------------------------------------------------------------------
        this.$getInvestmentLevelDescription = function $getInvestmentLevelDescription(sysID) {
            return expandDescription("[homesystem_level_reached]") + " '" + expandDescription("[hs_investment_level_" + this.$investmentLevel(sysID).toString() + "]") + "'."
        }
        //-------------------------------------------------------------------------------------------------------------
        this.$investmentLevel = function $investmentLevel(galID, sysID) {
            let ic = this.$investmentCount(galID, sysID);
            let result = 0;
            if (ic > 0) {
                if (ic == 1) result = 1;
                else if (ic >= 2 && ic <= 3) result = 2;
                else if (ic >= 4 && ic <= 5) result = 3;
                else if (ic >= 6 && ic <= 8) result = 4;
                else if (ic >= 9 && ic <= 11) result = 5;
                else if (ic >= 12 && ic <= 14) result = 6;
                else if (ic >= 15 && ic <= 18) result = 7;
                else if (ic >= 19) result = 8;
            }
            return result;
        }
        //-------------------------------------------------------------------------------------------------------------
        this.$addInfoToSystemDataScreen = function $addInfoToSystemDataScreen() {
            let sysID = player.ship.targetSystem;
            if (player.ship.hasOwnProperty("infoSystem")) sysID = player.ship.infoSystem;
            // build message
            let msg = "";
            // add the investment level
            if (this.$isHomeSystem(sysID) === true) {
                msg = expandDescription("[homesystem_home]");
                let result = this.$investmentLevel(sysID);
                if (result > 0) msg += " " + expandDescription("[homesystem_level_reached]") + " '" + expandDescription("[hs_investment_level_" + result.toString() + "]") + "'.";
            }
            // add message to top part of screen, if possible
            let added = false;
            if (worldScripts["oolite-system-data-config"] && worldScripts["CompressedF7Layout"]) {
                let ln = 0;
                let dc = worldScripts["oolite-system-data-config"];
                for (let i = 16; i >= 1; i--) {
                    if (ln === 0 && dc.systemDataLineText(i) != "") {
                        ln = i + 1;
                    }
                    if (dc.systemDataLineOwner(i) === this.name) {
                        ln = i;
                        break;
                    }
                }
                if (ln <= 16 && ln > 0) {
                    added = true;
                    dc.setSystemDataLine(ln, msg, (msg != "" ? this.name : ""));
                }
            }
            // if none of the above worked, then just add the message to the bottom part of the screen
            if (added === false && msg != "") mission.addMessageText(msg);
        }
        //-------------------------------------------------------------------------------------------------------------
        // routine to check the combat simulator worldscript, to see if it's running or not
        this.$simulatorRunning = function () {
            let w = worldScripts["Combat Simulator"];
            if (w && w.$checkFight && w.$checkFight.isRunning) return true;
            return false;
        }
        //-------------------------------------------------------------------------------------------------------------
        this.$initInterface = function (station) {
            if (this._stationTypes.indexOf(station.allegiance) >= 0) {
                station.setInterface(this.name, {
                    title: expandDescription("[homesystem_station_title]"),
                    category: expandDescription("[interfaces-category-logs]"),
                    summary: expandDescription("[homesystem_station_summary]"),
                    callback: this.$displayDockCountList.bind(this)
                });
            } else {
                station.setInterface(this.name, null);
            }
        }
        //-------------------------------------------------------------------------------------------------------------
        this.$displayDockCountList = function () {
            function compare(a, b) {
                if (a.count < b.count) return 1;
                if (a.count > b.count) return -1;
                if (a.name < b.name) return -1;
                if (a.name > b.name) return 1;
                return 0;
            }
            this._pageLength = 19;
            if (this.$isBigGuiActive() === true) this._pageLength = 25;
            // build sortable dataset
            let src = this._dockCounts[galaxyNumber];
            let keys = Object.keys(src);
            let data = [];
            for (let i = 0; i < keys.length; i++) {
                data.push({
                    id: keys[i],
                    name: (this.$isHomeSystem(keys[i]) ? "•" : "") + System.systemNameForID(keys[i]),
                    count: src[keys[i]]
                }
                );
            }
            data.sort(compare);
            let text = this.$formatSystemList(data);
            mission.runScreen({
                screenID: "oolite-homesystem-dockcounts-map",
                title: expandDescription("[homesystem_screen_dockcounts]"),
                overlay: { name: "hs-home.png", height: 546 },
                message: text,
                exitScreen: "GUI_SCREEN_INTERFACES"
            });
        }
        //-------------------------------------------------------------------------------------------------------------
        // appends space to currentText to the specified length in 'em'
        this.$padTextRight = function (currentText, desiredLength, leftSwitch) {
            if (currentText == null) currentText = "";
            let hairSpace = String.fromCharCode(31);
            let ellip = "…";
            let currentLength = defaultFont.measureString(currentText.replace(/%%/g, "%"));
            let hairSpaceLength = defaultFont.measureString(hairSpace);
            // calculate number needed to fill remaining length
            let padsNeeded = Math.floor((desiredLength - currentLength) / hairSpaceLength);
            if (padsNeeded < 1) {
                // text is too long for column, so start pulling characters off
                let tmp = currentText;
                do {
                    tmp = tmp.substring(0, tmp.length - 2) + ellip;
                    if (tmp === ellip) break;
                } while (defaultFont.measureString(tmp.replace(/%%/g, "%")) > desiredLength);
                currentLength = defaultFont.measureString(tmp.replace(/%%/g, "%"));
                padsNeeded = Math.floor((desiredLength - currentLength) / hairSpaceLength);
                currentText = tmp;
            }
            // quick way of generating a repeated string of that number
            if (!leftSwitch || leftSwitch === false) {
                return currentText + new Array(padsNeeded).join(hairSpace);
            } else {
                return new Array(padsNeeded).join(hairSpace) + currentText;
            }
        }
        //-------------------------------------------------------------------------------------------------------------
        // appends space to currentText to the specified length in 'em'
        this.$padTextLeft = function (currentText, desiredLength) {
            return this.$padTextRight(currentText, desiredLength, true);
        }
        //-------------------------------------------------------------------------------------------------------------
        // returns true if a HUD with allowBigGUI is enabled, otherwise false
        this.$isBigGuiActive = function () {
            if (oolite.compareVersion("1.83") <= 0) {
                return player.ship.hudAllowsBigGui;
            } else {
                let bigGuiHUD = ["XenonHUD.plist", "coluber_hud_ch01-dock.plist"]; 	// until there is a property we can check, I'll be listing HUD's that have the allow_big_gui property set here
                if (bigGuiHUD.indexOf(player.ship.hud) >= 0) {
                    return true;
                } else {
                    return false;
                }
            }
        }
        //-------------------------------------------------------------------------------------------------------------
        // returns a 4-column list of planets, formatted for display
        this.$formatSystemList = function (list) {
            let lines = [];
            for (let i = 0; i < this._pageLength; i++) lines.push("");
            let point = 0;
            let colwidth = 32;
            let colcount = 1;
            if (list.length > this._pageLength) { colwidth = 16; colcount = 2; }
            if (list.length > (this._pageLength * 2)) { colwidth = 10.3; colcount = 3; }
            if (list.length > (this._pageLength * 3)) { colwidth = 8; colcount = 4; }
            for (let i = 0; i < list.length; i++) {
                lines[point] += this.$padTextRight(list[i].name, colwidth - 3) +
                    this.$padTextLeft((list[i].count <= 999 ? list[i].count.toString() : "999"), 2) +
                    this.$padTextRight(" ", 1);
                point += 1;
                if (point === lines.length) point = 0;
                if ((i + 1) >= (this._pageLength * colcount)) break;
            }
            let result = "";
            for (let i = 0; i < lines.length; i++) {
                result += lines[i] + "\n";
            }
            return result;
        }
        //-------------------------------------------------------------------------------------------------------------
        this.$investmentCount = function (galID, sysID) {
            let amt = 0;
            if (this._investCounts[galID][sysID]) amt = parseInt(this._investCounts[galID][sysID]);
            return amt;
        }
    }).call(this);
 |