Back to Index Page generated: Nov 12, 2024, 11:02:04 PM

Expansion Home System

Content

Manifest

from Expansion Manager's OXP list from Expansion Manifest
Description Adds some flavour text to a variety of messages received by the player in systems designated as their home. As well, there are some other benefits achievable the more players visit their home system. Adds some flavour text to a variety of messages received by the player in systems designated as their home. As well, there are some other benefits achievable the more players visit their home system.
Identifier oolite.oxp.phkb.HomeSystem oolite.oxp.phkb.HomeSystem
Title Home System Home System
Category Ambience Ambience
Author phkb, UK_Eliter, Cody, Diziet Sma phkb, UK_Eliter, Cody, Diziet Sma
Version 0.14 0.14
Tags
Required Oolite Version
Maximum Oolite Version
Required Expansions
Optional Expansions
Conflict Expansions
Information URL https://wiki.alioth.net/index.php/Home_System n/a
Download URL https://wiki.alioth.net/img_auth.php/f/fb/HomeSystem.oxz n/a
License CC-BY-NC-SA 4.0 CC-BY-NC-SA 4.0
File Size n/a
Upload date 1708757420

Documentation

Also read http://wiki.alioth.net/index.php/Home%20System

readme.txt

Home System
By phkb (Nick Rogers), UK_Eliter, Diziet Sma and Cody.

Overview
========
This OXP seeks to add some flavour to systems that the player has made their home. Various entities in the system will now communicate spontaneously with the player, enhancing the idea that the player is well-known to system inhabitants. Benefits will also be given to the player the more they visit the system.

Operation
=========
Up to 3 systems in each sector can be declared as "home". To make a system home, purchase the "Home System Privileges" item from the F3 Equip Ship screen. If you have already purchased 3 Home System Privileges and decide later on to move homes, you can either purchase the "Remove Home System Privileges" item in one of the systems where you have already made your home, or you can simply buy a new Home System Privileges in the new system, and if your maximum has been reached, you will be asked to select one of your current home systems to remove.

Once set, additional messages will be sent to the player by the station (when you are granted docking clearance, after you dock, and after you launch), police vessels, and even pirates.

There are some benefits from making a system your home that will come into play the more you visit your home system:

1. Police will always side with the player in clean-v-clean fights.
2. If you have the "Docking Fees" OXP installed, fees will be waived at all stations in your home system.
3. Purchasing goods from the commodities market will return a small bonus.
4. Purchasing equipment will give you a small rebate.
5. Purchasing a new ship will give you a small rebate.
6. Clearing of any offender status.
7. Auto-refuelling at aligned stations.
8. Additional bonus amounts on missions from "GalCop Missions" OXP (if installed).
9. Special parcel and passenger contracts.

More benefits may be added in future versions.

You also have the opportunity to invest in your home system, which can accelerate your standing with the system and provide access to more home system benefits faster. Investment options can be purchased for 10000cr.

Home systems will be marked on the galactic chart with a purple diamond shape. There will also be a note on the F7 System Data screen.

Note: Firing on stations or police vessels in a system designated as your home will result in all your home system privileges being revoked.

Investment level
----------------
The more you invest in a Home System, the fast you will achieve higher levels of rewards. To see your current level of investment, go to the F7 System Information screen for your Home System, and the investment level will be shown. There are 8 levels of investment:

    Donor           Achieved when 1 investment package has been purchased.
    Backer          Achieved when up to 3 investment packages have been purchased.
    Supporter       Achieved when up to 5 investment packages have been purchased.
    Contributor     Achieved when up to 8 investment packages have been purchased.
    Sponsor         Achieved when up to 11 investment packages have been purchased.
    Benefactor      Achieved when up to 14 investment packages have been purchased.
    Patron          Achieved when up to 18 investment packages have been purchased.
    Champion        Achieved when more than 18 investment packages have been purchased.

Each investment package you purchase counts as 2 docks. That is, it's as if you have docked 2 more times in that system.

Diplomacy
=========
If the Diplomacy OXP is installed, purchasing Investment options for your home system will eventually grant you citizenship.

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/

Thanks to UK_Eliter for his code analysis and adding his ideas into the mix to make the pack better.
Thanks to Diziet Sma for getting the team together.
With special thanks to Cody for the original idea. You rock, El Viejo!

Home image from http://simpleicon.com/home-1.html.
Question mark image from http://simpleicon.com/question_mark_1.html

Version History
===============
0.14
- Small code tweaks.

0.13
- Investments now have a bigger impact on access to benefits.
- The level of investment is now visible of the F7 system info screen, as well as in the "Reputation and Awards" Interface screen (if the "GalCop Missions" OXP is installed).
- (If the "Diplomacy" OXP is installed) Purchasing investment options for a home system will eventually grant you citizenship.
- Home Systems are now marked on the GalCop Station Docking Counts screen.
- Fixed issue where Home System Investment could be offered for sale when the player has no home system chosen.
- Code refactoring.

0.12
- *Really* fixed issue where reply messages with BroadcastComms could end up with a duplicated ID.

0.11
- Fixed issue where reply messages with BroadcastComms could end up with a duplicated ID.

0.10
- Benefit levels are now adjusted based on a systems government, economy and tech level. 
- Investment purchase notification emails for systems other than the current system will now arrive later, rather than instantly.

0.9
- Fixed issue with incorrect emails being sent when purchasing investment option.
- Split out investment option counters, so it's independent of dock counts.

0.8
- Added "Invest in home system" item, to allow players to accelerate their status with their home system.

0.7
- Code changes as suggested by UK_Eliter.
- Fixed issue with home system notification not appearing on F7 screen in all setups.
- Fixed broken priority docking system.
- Fixed issue with offender clearance message being shown when player is not an offender.

0.6
- Disabled docking and launching messages when using the combat simulator.
- Disabled docking messages after using an escape pod.
- Added an F4 screen showing the players number of dockings at GalCop stations.

0.5
- Home systems will now be marked with a purple diamond shape on the galactic chart, and a note will be added to the System Data screen.
- Added link to GalCop Missions to allow an increase in mission bonus value as a new benefit.
- Added special, high-value parcel and passenger contracts as a new benefit.
- Added free, automatic refuelling as a new benefit.
- Police and pirate comms messages now shouldn't happen when they're in the middle of a fight.
- Fixed errors resulting from not having Email System OXP installed.
- Farewell emails will now have their point-of-origin for the email trace set to the appropriate system.
- Set maximum number of home systems to 3.

0.4
- Added some additional benefits (increased rebate on equipment and ship purchases).
- Added email notification of new benefits achieved.
- If the player commits a serious offence in a home system, as well as their privileges being revoked, they will also have their reputation removed (so if they repurchase the privileges, they will have to build up their reputation to recover their benefits).
- Added a farewell email message, for when the player decides to remove their home system privileges.
- Fixed bug where an attack on police or stations when the player doesn't have home system privileges would still send a message about it being revoked.

0.3
- Changed installation time for equipment items to be 15 minutes.
- Fixed issue where the system was not recording the docking counts correctly.
- Added new benefit: clearing offender status (but not fugitive status).

0.2
- Reduced maximum number of home systems per sector to 2.
- Added functionality to ensure police will always side with the player in clean-v-clean fights.
- Added equipment and ship rebates.
- Added levels of benefit, requiring the player to visit the system a certain number of times before benefits come online.
- Added extra text variations.

0.1
- Initial alpha release.

Equipment

Name Visible Cost [deci-credits] Tech-Level
Home System Privileges no 150000 1+
Home System Privileges no 500000 1+
Invest in Home System no 100000 1+
Home System Privileges no 250000 1+
Remove Home System Privileges no 500 1+

Ships

This expansion declares no ships.

Models

This expansion declares no models.

Scripts

Path
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("You have been given a rebate of " + 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("You have been given a rebate of " + 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("You have received a complimentary refuelling, courtesy of your home system.");
            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: "Complimentary Refuelling",
			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("You receive a trading bonus of " + 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("Your home system privileges have been 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 = "In order to make this system your home, you must relinquish your home system privileges on " + System.systemNameForID(this._homeSystems[galaxyNumber][0]) + ". Do you wish to proceed?";
            curChoices["0_UNSET"] = "Yes";
            curChoices["9_NO"] = "No";
        } else {
            txt = "In order to make this system your home, you must relinquish your home system privileges on one of these systems:";
            let set = this._homeSystems[galaxyNumber];
            for (let i = 0; i < set.length; i++) {
                curChoices[i + "_UNSET"] = System.systemNameForID(set[i]);
            }
            curChoices["9_NO"] = "Cancel purchase";
        }
        let opts = {
            screenID: "oolite-homesystem-question-map",
            title: "Home System",
            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("Home system privileges have been removed from " + 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 = "Select the system to which you wish to transfer your investment:";
    let set = this._homeSystems[galaxyNumber];
    for (let i = 0; i < set.length; i++) {
        curChoices[i + "_INVEST"] = System.systemNameForID(set[i]);
    }
    curChoices["9_NO"] = "Cancel purchase";
    let opts = {
        screenID: "oolite-homesystem-question-map",
        title: "Home System",
        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("Investment amount transferred to home system in " + 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("You have been awarded citizenship in " + 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:"Send 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:"Send 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("Sorry for the delay, commander.", player.ship);
        }
        this._dockTimer.stop();
        return;
    }
    this._dockMessage += 1;
    switch (this._dockMessage) {
        case 1:
            this._dockingStation.commsMessage("We're trying to clear a slot for you, Commander [commander_name].", player.ship);
            break;
        case 3:
            this._dockingStation.commsMessage("We may have to auto-dock you to skip the queue. Stand by.", player.ship);
            break;
        case 7:
            this._dockingStation.commsMessage("Starting auto-dock process, commander. Stand-by.", 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:"Station Manager",
            subject:"Home system 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 = "some 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 ? "We're so glad you're continuing to visit our station and make use of our facilities. " : "")});
        let sndr = "Station Manager";
        
        let delay = 0;

        if (system.ID != parseInt(sysID)) {
            sndr = System.systemNameForID(sysID) + " Station Manager";
            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:"Home system 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:sysname + " Station Manager",
        subject:"Home system 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: sysname + " Investment Level",
                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 " Your investment level has 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 = "You have made this system home.";
        let result = this.$investmentLevel(sysID);
        if (result > 0) msg += " Your investment level has 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:"View GalCop station docking counts",
			category:"Logs",
			summary:"Views the number of times you have docked at GalCop stations.",
			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: "GalCop Station Docking Counts",
        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);
Scripts/home_system_conditions.js
(
function(){

"use strict";
this.name        = "HomeSystem_Conditions";
this.author      = "phkb";
this.copyright   = "2017 phkb";
this.description = "Condition script for Home System equipment";
this.license     = "CC BY-NC-SA 4.0";

//-------------------------------------------------------------------------------------------------------------
this.allowAwardEquipment = function(equipment, ship, context) {
    if (context == "scripted") return true;
    let w = worldScripts.HomeSystem;
    if (equipment === "EQ_HOMESYSTEM_INVEST") return (w.$homeSystemCount() > 0)
    if (w._stationTypes.indexOf(player.ship.dockedStation.allegiance) >= 0) {
        if (w.$isHomeSystem(system.ID) === false) {
            if (equipment === "EQ_HOMESYSTEM_CLEAN" && player.ship.bounty === 0) return true;
            if (equipment === "EQ_HOMESYSTEM_OFFENDER" && player.ship.bounty > 0 && player.ship.bounty < 50) return true;
            if (equipment === "EQ_HOMESYSTEM_FUGITIVE" && player.ship.bounty >= 50) return true;
        }
        if (equipment === "EQ_HOMESYSTEM_REMOVE" && w.$isHomeSystem(system.ID) === true) return true; 
    }
	return false;
}

}).call(this);