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

Expansion Smugglers - The Galactic Underworld

Content

Warnings

  1. Required Expansions mismatch between OXP Manifest and Expansion Manager at character position 0071 (DIGIT ZERO vs LATIN SMALL LETTER N)
  2. No version in dependency reference to oolite.oxp.phkb.MarketScriptInterface:null
  3. No version in dependency reference to oolite.oxp.phkb.BlackMarket:null
  4. No version in dependency reference to oolite.oxp.cim.new-cargoes:null
  5. Conflict Expansions mismatch between OXP Manifest and Expansion Manager at character position 0060 (DIGIT ZERO vs LATIN SMALL LETTER N)

Manifest

from Expansion Manager's OXP list from Expansion Manifest
Description Makes any commodity potentially illegal to import in various systems, and adds equipment and contracts for those interested in smuggling goods outside normal legal channels. Makes any commodity potentially illegal to import in various systems, and adds equipment and contracts for those interested in smuggling goods outside normal legal channels.
Identifier oolite.oxp.phkb.Smugglers_TGU oolite.oxp.phkb.Smugglers_TGU
Title Smugglers - The Galactic Underworld Smugglers - The Galactic Underworld
Category Mechanics Mechanics
Author phkb phkb
Version 2.4 2.4
Tags
Required Oolite Version
Maximum Oolite Version
Required Expansions
  • oolite.oxp.phkb.MarketScriptInterface:0
  • oolite.oxp.phkb.BlackMarket:0
  • oolite.oxp.phkb.MarketScriptInterface:
  • oolite.oxp.phkb.BlackMarket:
  • Optional Expansions
    Conflict Expansions
  • oolite.oxp.cim.new-cargoes:0
  • oolite.oxp.cim.new-cargoes:
  • Information URL https://wiki.alioth.net/index.php/Smugglers_-_The_Galactic_Underworld n/a
    Download URL https://wiki.alioth.net/img_auth.php/0/04/Smugglers_2.4.oxz n/a
    License CC-BY-NC-SA 3.0 CC-BY-NC-SA 3.0
    File Size n/a
    Upload date 1709981629

    Documentation

    Also read http://wiki.alioth.net/index.php/Smugglers%20-%20The%20Galactic%20Underworld

    readme.txt

    Smugglers - The Galactic Underworld
    By Nick Rogers
    
    With the help of:
    Layne, Stormrider, Norby, cim, Diziet Sma, Cody, Venator Dha, Harootune, and all the people on the BB who offered suggestions and ideas to make this OXP as good as it could be. If the OXP fails in any way, is it because of my limitations as a programmer, not because of the paucity of your ideas and contributions!
    
    Introduction
    ============
    Historically, in both the original Elite and therefore in Oolite, there were three illegal goods: slaves, narcotics and firearms. These were illegal to export from all GalCop stations, and there was no variation or flexibility in those items. This OXP aims to expand what goods are illegal, making it far more variable. There are new interface screens to show what items are illegal and where, a new Black Market available from Rock Hermits and other stations, and new equipment items to aid in smuggling cargo. 
    
    Background
    ==========
    It started with a single system.
    
    Ceedra, an insignificant confederacy in an unfrequented arm of galactic sector 1. They had been receiving some contaminated imported food from the nearby Anarchy system Isdibi, possibly random or possibly intentional as a way to destabilise the government, it was difficult to tell. But a request was made to GalCop to put an import ban on food and force any traders that bring food to the station to hand it over for destruction.
    
    The impact was almost immediate. Traders were outraged that their harmless cargo of wheat and grains were being taken away, which had never before happened in GalCop history, plus they received a bounty for doing so, doubling the pain. Messages were sent, trade delegations made an outward show of force, but GalCop's hands were tied. This was the decision of the planet in question, and under GalCop law they were perfectly entitled to impose import restrictions, beyond the export restrictions that had long been in place for slaves or narcotics. 
    
    Suddenly the floodgates were opened. System after system found reason to ban goods, from basic staples like food or textiles, up to the traders standard fare of computers, furs and liquor. Even precious metals were banned in some systems, often for rather obscure reasons. And always, once a ban was in place, the price would hit the roof.
    
    Some commentators have suggested there is evidence that certain systems were requesting a good to be made illegal purely for the jump in price that naturally follows and the bump in traffic that accompanies extra profits. While GalCop have been sought for comment, none has been forthcoming, and nothing appears to be able to bring a halt to the practice. If systems are indeed creating spurious trade legalities, GalCop is not preventing them.
    
    Somewhat inevitably, at almost the same time, an entirely new market emerged, beneath the radar of GalCop's far-seeing eyes. As soon as a good was declared illegal to import the price of that good sky-rocketed, and the temptation to circumvent the restrictions GalCop was forced to impose was too great. Dock masters became a particular target, being offered high sums to fiddle manifest registries. Shadier stations began to offer customised smuggling compartments that provided a way to hide cargo from the customs scanning equipment. Contracts to move illicit cargo were also available from these locations.
    
    While it was always present in some form or another, now the Galactic Underworld became almost mainstream. The galaxy had changed.
    
    Overview
    ========
    This OXP aims to broaden the scope and appeal of illegal goods in a number of ways.
    
    1. Illegal goods
    ----------------
    Illegal goods can now be any of the 17 different commodity types. So food might be declared illegal on one planet, computers on another. These things might change as well, so computers might be illegal for a few months, and then conditions are lifted and they are back to normal again. 
    
    Also, illegal trade goods will now be illegal to import(*). If you arrive at a GalCop station with visible illegal goods in your hold, you will be fined and your cargo will be confiscated. See also the section below on Bribing Officials.
    
    Even if you plan to stay on the right side to the law, this OXP can add variety to the way illegal goods are handled by the game, and make the process of shipping cargo across the galaxy more interesting.
    
    Illegal goods notifications can be viewed on the F7 System Data screen, as well as via the F4 interface screen (see below). The F6 Galactic Chart will highlight systems within 7 LY that have illegal goods. Systems with just firearms as illegal will be shown with an orange diamond. Systems with other commodities, or with multiple commodities, will be shown with a yellow diamond.
    
    (*) Slaves remain illegal to export, and they are the only commodity that is illegal to export. The reason for this is that some escape capsules end up being transported as slaves, and it's unfair to penalise the player for potentially rescuing downed pilots. It is recommended the "Illegal Goods Tweak" OXP be installed so that captured slaves can be released.
    
    2. Smuggling Compartment
    ------------------------
    To help traders to work around these import restrictions a new piece of equipment, the smuggling compartment, can be purchased at non-GalCop stations (Rock Hermits in particular, but also other docking ports) in TL4 or greater systems. 
    
    Cargo can be moved into and out of the smuggling compartment via an F4 Interface screen "Smuggling Compartment Configuration". During flight, the "Smuggling Compartment Storage" primable equipment item allows for the transfer of cargo from the standard hold to the smuggling compartment. Cargo cannot be transferred out of the smuggling compartment during flight.
    
    The smuggling compartment has, in most circumstances, around a 25-40% chance of being detected, based on tech level. GalCop is always improving their detection equipment, which means that the scanner resistant technology of the smuggling compartment needs to be updated regularly to cope. About every 30 days a "Version Upgrade" should be purchased (at TL5 or greater systems) for your smuggling compartment, which will install the latest scanner resistant technology and keep the chance of detection as good as possibly can be.
    
    This chance improves slightly once the "Phase Adjustment" equipment (available at TL7 or greater systems) is installed. With the phase adjustment equipment installed the phase setting of the hold to be adjusted, which can improve the chance of being undetected considerably. Phase settings can be picked up at Black Market noticeboard (which might be unreliable), or they can be purchased for a considerable cost. See also the Phase Scanner (below).
    
    If your compartment is damaged during a battle, cargo may be lost, the same as with normal cargo. You will need to find a non-GalCop station to repair the damage, otherwise the entire compartment will be discovered and removed, and you will receive a bounty. If you have a ship overhaul performed at a GalCop station with the smuggling compartment, it will be discovered and removed, and you will receive a bounty. See also the section below on Bribing Officials.
    
    3. New equipment
    ----------------
    Apart from the smuggling compartment and phase adjustment tool, this OXP also introduces the following equipment items.
    
    ** Phase Scanner **
    TL8 or greater Anarchy systems will sometimes have a Phase Scanner available for purchase. The scanner can, when aimed at GalCop main stations, detect what phase setting the manifest scanners will be susceptible to. Once you know what the phase setting is for a particular government and tech level they can set the phase of their smuggling compartment and it will be almost undetectable to GalCop manifest scanners.
    
    The Phase scanner is used by targeting the main station, priming the equipment and then activating it. It will take a few seconds to perform its job, and will consume a large amount of energy in the process - a naval energy unit is required. If you are too close to the station, or you are moving too slowly, the station will detect the scan and a bounty will be placed on you. If you then dock at the station the scanner will be removed. If you are too far away from the main station the scanner will not operate. If a police vessel catches you performing a scan you will receive a bounty and may find yourself under fire.
    
    If your scanner is damaged you must get it repaired at a non-GalCop station, otherwise the equipment will be confiscated and you will receive a bounty. If you have a ship overhaul performed at a GalCop station while the equipment is on board, it will be removed as part of the overhaul and you will receive a bounty. But see also "Bribing Officials" below.
    
    ** Tech version upgrades **
    Technology improves every day, and the scanner resistant technology used in the smuggling compartment needs to be updated to keep in step with the times. So, the tech version upgrade will keep your smuggling compartment up-to-date. Available at TL5 or greater systems.
    
    ** Import Permits **
    While some goods might be declared illegal, you can still purchase a permit to import that good from the planet in question. Usually these permits are quite expensive. 
    
    Permits can only be used once, and will automatically be removed after use.
    
    4. Bribing Officials
    --------------------
    If your smuggling compartment is discovered during a renovation or when damaged, or you dock with illegal cargo in your hold, you can attempt to bribe the station officials. You will be asked to enter the amount of the bribe. If you pay attention to what the official says you can get an idea on whether a smaller amount will be required, but larger amounts will have a greater chance of being successful. If you are successful you will be able to keep all your cargo and equipment. If you are unsuccessful you will lose all smuggling-related equipment and may end up increasing the bounty on your head.
    
    The Black Market noticeboard (see below) can be useful in finding systems where the dock master and other customs officials can be bribed for a reasonable amount.
    
    5. New Interfaces screens
    -------------------------
    ** Dock master **
    When docked at a main station, and when the dock master is available (he doesn't work 24/7!) from this interface you can ask him about any good milk runs he/she is aware of, or you can try to bribe them into changing your cargo manifest for you.
    
    ** Illegal goods information **
    Illegal goods are changing all the time. To try to keep track of the potential chaos, this interface screen will initially show you the latest changes to illegal goods in the sector, plus it offers the following options:
    a. List of all illegal goods in the galaxy. Here you can see it all. 
    b. List of all illegal goods within 7LY of your current system.
    c. List of illegal goods on your current course. If you have a long, cross-chart trip planned, this option will tell you what's illegal on the way, and flagging which ones you have a permit for (if any).
    d. List of all import permits. This is what you are authorised (legally or not) to import. 
    e. List of all Rock Hermit waypoints purchased.
    
    Adding additional commodities
    =============================
    Additional commodities can be added to the system by doing the following:
    
    1. Adding the commodity permit cost, and price factors.
    
        var sbi = worldScripts.Smugglers_Illegal;
        sbi._commodityInfo["my_commodity_key"] = [permitCost, factorIncIllegal, factorIncBM];
        // permitCost        = How much a import permit will cost for this commodity (eg 2000)
        // factorIncIllegal  = The percentage to increase this commodity by when it is flagged as illegal (eg 2.5 = 2.5 times original price)
        // factorIncBM       = The percentage to increase this commodity by at a black market when it is flagged illegal at the main station (eg 1.4 = 1.4 times standard price)
    
    2. Adding illegal possibilities for this commodity to the pool
    
        var sbi = worldScripts.Smugglers_Illegal;
        sbi._possibilities.push({govTypes:"0,3", commodity:"my_commodity_key", properties:"", permit:1, scale:1, period:30, chance:0.5, description:"A brief description of the in-game reason for making this commodity illegal."})
        /* 
            govTypes:		Comma-separated list of government types (0-7) this definition can apply to eg ("0,1,2")
            commodity:		Comma-separated list of commodity names this definition will apply to (eg "food,my_commodity_key").
            properties:		Comma-separated list of text search strings to use against the planetary description.
                            For instance, "civil war" would only select planets that have "civil war" in their description.
                            "drink|brew" would only select planets that have "drink" or "brew" in their description.
            permit:			Boolean value (0 or 1) indicating whether a permit can be purchased for this commodity
            scale:			The legality scale to apply to this commodity. 
                            The scale will apply when calculating the bounty to apply to a player caught smuggling illegal goods as a multiplier.
                            Most illegal goods have a scale of 1. Slaves have 2.
            period:			The period (in days) this definition will be active for.
            chance:			A chance factor, between 0 (never) and 1 (very often), to apply when selecting this definition
            description:	The description to display for this definition.
        */
    
    3. Add the Smugglers market script to your commodity.
        This enables the legal/illegal flags to be set for each commodity.
    
        In the commodity definition in your "trade-goods.plist" file, add the following line:
    		"market_script" = "smugglers_illegalmarket.js";
    
        Note: If your commodity definition already has a "market_script" defined, you will need to copy the code from the updateGeneralCommodityDefinition function into yours, to perform the same function as the Smugglers version.
    
    Notes
    =====
    - This OXP updates the "oolite-contracts-cargo.js" file with a new version of the "_initialiseCargoContractsForSystem" function that excludes illegal goods from being selected for standard cargo contracts. 
    - This OXP also changes the logic of the "Legal" column on the F8 Market screen. Previously, goods that were illegal to export were marked with "Im", and goods illegal to import were marked with "Ex". This logic has been reversed, and the column relabelled as "Ban" to indicate what type of ban is in place. 
    
    Special Note
    ============
    This OXP is what is called a "work-in-progress". What that means is, what you have in your hands may not be the final version of the OXP. In fact there might be parts that are broken and could potentially unbalance the game. This isn't by-design, and hopefully there aren't any issues like that, but be aware that in many ways this is an experimental OXP. I'm changing a lot of core functionality which opens the door for all sorts of unforeseen consequences, particularly when it comes to other OXP's. If you decide to help me in the process of polishing this package, please report any bugs or unexpected outcomes. Don't assume someone else has noticed it - if two people report a bug it makes tracking the bug down so much easier!
    
    And it goes without saying that I'd like to hear from you if you have any suggestions on how the OXP could be improved. 
    
    Requirements
    ============
    The Market Script Interface OXP is a requirement for this pack.
    The Black Market OXP is a requirement for this pack.
    
    Incompatible OXP's
    ==================
    At present, this OXP is incompatible with the following OXPs:
    - New Cargoes (up to version 1.2.3): The system of permits, how it stores cargo, and how it controls illegal goods are all potentially conflicting. However, version 2.0 of New Cargoes is compatible with this pack.
    
    This conflict has been recorded in the manifest file, so if you have it installed the "Smugglers" OXP will not load.
    
    License
    =======
    This OXP is released under the Creative Commons Attribution - Non-Commercial - Share Alike 3.0 license. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/
    
    Skull and crossbones image from http://simpleicon.com/dangerous_sign.html.
    Warning image from http://simpleicon.com/warning-3.html.
    Chat image from http://simpleicon.com/chat-2.html.
    ID image from http://simpleicon.com/id_card.html.
    Lock image from http://simpleicon.com/lock-2.html.
    Cupboard image from http://simpleicon.com/cupboard_4.html.
    Candle image from http://simpleicon.com/candle-2.html.
    Form image from http://simpleicon.com/admite_form_1.html.
    Map marker image from http://simpleicon.com/map_marker.html.
    
    Discussion
    ==========
    This OXP is discussed at this forum link: http://aegidian.org/bb/viewtopic.php?f=4&t=17723
    
    Version History
    ===============
    2.4
    - Bug fixes.
    
    2.3
    - Fixed bug introduced when changing over to use the Market Script Interface.
    - Fixed syntax error in Dock Master script.
    
    2.2
    - Allows color and shape of chart markers for illegal goods to be changed via Library Config.
    - Tweaked the returns and time frames for gold, platinum and gem stones, to not be quite so generous or easy to exploit.
    - Fixed issue where illegal goods were not being created if starting a new game with the "Easy Start" option.
    - Code refactoring.
    
    2.1.1
    - Removed reference to blackmarket script from world-scripts.plist.
    
    2.1
    - Enabled equipment conditions, which had been turned off for testing.
    
    2.0
    - Updated market script process to use new Market Script Interface OXP.
    - Switched pricing change to be inside market script callbacks (so they will now show up on sample price calculations).
    - Made compatible with New Cargos OXP (version 2.0)
    - Split off Black Market into its own OXP, and made it a requirement of this OXP.
    
    1.5.9
    - Will now not show illegal goods info for systems with any sort of concealment.
    - Tweaked grey levels on background overlays for better visibility.
    
    1.5.8
    - Fixed some potential referencing errors with mission screens.
    
    1.5.7
    - Added extra facility for removing the Black Market on stations.
    
    1.5.6
    - Better use of new functions available in later versions of Oolite.
    - Removed code that was deleting TL1 phase scan settings.
    - Bug fixes.
    
    1.5.5
    - List of illegal goods displayed on F7 system data screen will now be shown inflight, but without reasons (ie just a list of illegal goods).
    - Additional checking types added to ship condition checking routine.
    - Bug fixes.
    
    1.5.4
    - Moving kg or g type commodities will now force the Manifest MFD to update.
    - Better integration with Escape Pod Tweaks. Scooped escape pods can no longer be transferred to the smuggling compartment.
    - Equipment.plist corrections. 
    - Updates to readme.txt file.
    
    1.5.3
    - Removed 1t cargo space requirement for in-flight storage equipment, to ensure it is always available when a smuggling compartment is installed.
    - Removed incorrect Naval Energy Unit requirement for in-flight storage equipment.
    - Code refactoring.
    
    1.5.2
    - Fixed issue with the in-flight storage function not handling all conditions when checking scooped cargo.
    
    1.5.1
    - Fixed broken integration with "Escape Pod Tweaks".
    
    1.5.0
    - Added new options for when player is caught in a sting operation: pay a fine amount, accept a legal penalty, or attempt to bribe their way out of trouble.
    - System productivity now has an influence on the amount you need to offer when bribing officials.
    - Calculation of the maximum smuggling compartment size now factors in current smuggling compartment size so all downgrade options are available (not just the ones that would fix in the current available space).
    - Fixed damage/repair process.
    - Fixed issue with smuggling compartment damage resetting the tech level of the compartment.
    - Fixed incorrect description lookup key.
    - Improved integration with the "Escape Pod Tweaks" OXP.
    - Code cleanup.
    
    1.4.2
    - Systems with illegal goods marked on the map will now have their marks removed after loading the game if the setting switch is turned off via Library Config.
    
    1.4.1
    - Primable equipment now given to player who already had a smuggling compartment installed.
    
    1.4.0
    - Added primable equipment which allows cagro to be moved to the smuggling compartment during flight.
    - Fixed issue on "Move cargo out" screen, showing "[avail]" instead of available compartment space.
    - Removed decimal point values from showing for available compartment space.
    - Better handling of kg and g commodities.
    - Fixed issue with outrageous amounts being offered for narcotics and slaves on the Black Market.
    
    1.3.2
    - Better handling of interstellar space conditions.
    
    1.3.1
    - Added options to Library Config to control whether systems with illegal goods are marked on the galaxy map.
    
    1.3.0
    - It is now possible to add new commodities to the list of illegal goods possibilities.
    - The dock master can now relabel cargo as new (ie. non-core) commodities.
    - System map will now be shown when the dock master informs player of a milk run, highlighting the two systems.
    - Illegal goods on route will now handle not having the Advanced Navigation Array correctly.
    - Cleaned up overlay images.
    
    1.2.2
    - Added "display_color" property to some equipment items (Oolite v1.85/86 only).
    - Added integration options for "Bounty System - Most Wanted" OXP.
    - Better handling of interstellar space.
    - Code refactoring.
    
    1.2.1 
    - Fix for crazy prices being applied after a saved game is reloaded.
    
    1.2.0
    - Changed method of adjusting prices to work better with other OXP's that adjust prices.
    - Fixed Javascript error when attempting to sell illegal cargo through the Black Market.
    - Added console message confirming sale of illegal cargo through the Black Market.
    - Code cleanup.
    
    1.1.4
    - Arranged data on illegal goods lists to be in columns.
    - Removed hard-coded scanner range values.
    
    1.1.3
    - Fixed error where function to remove sting ship monkey patches wasn't being passed the correct parameter.
    
    1.1.2
    - Added code for putting smuggling contracts on the Bulletin board system if the Contracts On BB OXP is also installed.
    - Code refactoring.
    
    1.1.1
    - Fixed some spelling mistakes.
    - Added "machinery" to list of commodities the "Visible As" property of a smuggling compartment can be set to.
    - Fixed issue with not being able to move machinery into or out of the smuggling compartment.
    - Fixed issue with not being able to purchase or sell machinery directly from the smuggling compartment.
    
    1.1.0
    - Additional info on F7 screen is now only available when docked to stop the information from overrunning the HUD.
    - Corrected use of "techLevel"/"techlevel" on systemInfo objects.
    - Re-fixed issue with amount of gold, platinum and gemstones taking up the same amount of smuggling cargo space as normal cargo space.
    
    1.0.0
    - Bug fixes.
    
    0.9.0
    - Added new Black Market option, enabling the sale of rare or expensive equipment items (eg naval-sourced equipment)
    - Added ability for 3rd party OXP's to add items for sale to the Black Market.
    - Fixed incorrect label in descriptions.plist.
    - Fixed issue where a smuggling compartment repair item was being displayed when the unit wasn't damaged.
    - Add ability for sting ship to respond to cloaked attacks.
    - Better handling of interstellar space conditions.
    - Some values were not being reset correctly after a galactic jump.
    - Police ship model used on all police-related screens displayed at that station will now be the same model.
    - Sound effects added for mode/activate functions of phase scanner.
    
    0.8.1
    - Fixed issue where opening the "Purchase Smuggling Compartment" screen was turning off the HUD and not turning it back on again.
    
    0.8.0
    - Fixed Javascript error when opening the market info details screen for a commodity in interstellar space.
    - Added $isGoodIllegal function to Smugglers_Illegal.
    
    0.7.9
    - Fixed issue where docking will illegal goods and rescued escape pods was not allowing the player to bribe the station officials.
    
    0.7.8
    - Fixed issue where illegal good definitions were not being applied correctly in every situation.
    
    0.7.7
    - Fixed issue with smuggling compartments of various sizes appearing on the equipment screen.
    
    0.7.6
    - Better handling of situation where another OXP removes the smuggling compartment.
    - Fixed issue with contracts where a Javascript error could occur in rare circumstances.
    - Fixed Javascript error with script attached to police sting ship.
    - Improved sting ship compatibility with other worldScripts.
    - Really (hopefully, maybe) fixed vector error in Blackmarket script.
    - Changed "==" comparisons to "===" for performance improvements.
    
    0.7.5
    - Fixed Javascript vector error in Blackmarket script.
    - Added code to make use of enhanced F7 screen features in Oolite 1.83/4.
    
    0.7.4
    - Reconfigured the cargo contracts fixes to work with the default contract code and with Switech's cargo contract mod OXP.
    - Removed unnecessary "requires.plist" file.
    - Extra checking for the presence of the sting ship to prevent null errors.
    - Fixed issue where smuggling equipment was not being offered for sale anywhere.
    - Fixed issue where the phase scanner was not being offered for sale at any time.
    - Fixed issue where going into the smuggling compartment purchase page multiple times without actually purchasing something was deducting money anyway.
    - Adjusted the price of the phase scanner.
    - Tidying up of some equipment installation rules.
    - F7 screen was turning off HUD when in flight in some circumstances.
    
    0.7.3
    - Fixed issue with incorrect text showing on F8F8 commodity info screen.
    - Included current system in list of planets shown on "Illegal imports on current course" option.
    - Fixed issue with the Black Market welcome text, which was not being correctly reset upon docking.
    
    0.7.2
    - Fixed loop variable pointer error in Black Market script.
    
    0.7.1
    - Fixed timer setup bug in Black Market script.
    
    0.7.0
    - Updated all bounty changes to use "setBounty", rather than just updating the bounty directly, so a description can be passed to other OXP's.
    - Expanded the use of the "properties" element of illegal definitions, so that multiple options can be entered (eg "cuisine,meat,steak").
    - Added a couple of extra illegal goods definitions.
    - Added the ability to sell illegal cargo on the Black Market, for a reduced price. Plus some extra things...
    - Added Seedy Space Bars to list of stations that have a Black Market.
    - Moved a lot of static text into descriptions.plist and missiontext.plist.
    - Fixed issue with only 500kg of gold or platinum taking up 1t of smuggling cargo space, and 500000g of gemstones.
    - Removed redundant items from equipment.plist.
    - Fixed bug when using a HUD that doesn't implement the "Big GUI" option, where the HUD was being turned off when displaying some mission screens, but wasn't being turned back on again.
    - In Oolite 1.83 the bugfix relating to damaged equipment items that take up cargo space is resolved, so code has been added to switch to using the in-built repair methods, rather than keeping a long list of "Repair" equipment items. For backwards compatibility with 1.82, however, those items are still present.
    - Adjusted the costs of repair items to be more in-line with how the core system handles repair costs.
    - Improved integration with Illegal Goods Tweak. When docking with slaves, IGT will always run first, and Smugglers will be called afterwards.
    - Updated screenID's to enable BGS background sounds.
    - Renamed background overlay images to prevent possibility of future duplication.
    - Added checks for null value in station allegiance.
    - Updated the check for "Allow Big GUI".
    - Cleaned up sound function to avoid the need to check for BGS.
    - Toned down overlay images.
    - Code refactoring.
    
    0.6.2
    - Fixed bug with checking for and removing used permits.
    - Fixed issue where Constrictor hunt mission hints were being pushed off the System Data screen by all the illegal goods notifications.
    - Added extra overlay image for purchasing Permits.
    - Added extra overlay image for Rock Hermit waypoint list.
    
    0.6.1
    - Using escape pod will now empty smuggler's compartment.
    - Pared down "trade-goods.plist" to only contain required information, hopefully making it compatible with "Risk based economy" v2 OXP and BlOomberg Markets OXP.
    - Removing the smuggling compartment or phase scanner at a Rock Hermit (or other non-GalCop station) will now refund the full cost, rather than just 70%.
    - Changing the size of the smuggling compartment will refund the full price of the old size first, before purchasing the new size.
    - Fixed bug when starting new game.
    - Fixed issue where the message about delivering illegal goods via a cargo contract was appearing incorrectly.
    
    0.6.0
    - Fixed issue where illegal flags were not appearing on secondary stations, removed need to have planetinfo.plist.
    - Removed log file debug code.
    
    0.5.0
    - Fixed bug with selling relabelled cargo.
    - Fixed issue with delivering a standard cargo contract to a system where the cargo has become illegal after accepting the contract. The player was not being given the opportunity to bribe a docking official to clear their bounty.
    - Fixed issue where, after using the phase scanner and being discovered using it, and then docking, the player was not being given the opportunity to bribe their way out of trouble.
    - Added emails for various actions, when the Email System OXP is in use.
    - Changed skull and crossbones background image for Black Market.
    - Improved integration with Illegal Goods Tweak OXP (v2.2.0). The inital screen IGT displays will now look like an Amnesty Intergalactic screen, rather than a GalCop one, and the player has only two choices: hand over the slaves, or work with GalCop. If they choose the latter, Smugglers will include slaves as part of its import restrictions and the player can bribe as normal if they choose to.
    
    0.4.0
    - Fixed issue where you could purchase any smuggling compartment, regardless of how much credit you have.
    - Added player credit balance to smuggling compartment purchase screen.
    - Added player credit balance to Black Market purchasing screens.
    - Tightened up which stations will have a Black Market.
    - Added government icon and TL to all purchase screens in Black Market.
    - Tweaks to the calculation of phase scan noticeboard items.
    - Improvements to the page handling for illegal goods.
    
    0.3.1
    - Fixed mangled manifest.plist file.
    
    0.3.0
    - Tweaks to the minimum tech level for purchasing smuggling equipment items.
    - Further adjustments to precious metals and gem stones illegal notifications for better economic balance.
    - Added overlay background images to various interface screens.
    - Smuggling compartment repair costs now linked to size of compartment.
    - Arriving in a system where you have a permit but no cargo for that permit will no longer remove the permit.
    - Routine to create new illegal goods will now exclude the current system, to prevent the issue where the engine understands a good is illegal, but the game interface doesn't.
    - Added extra checks to the docking process to ensure a good that the engine understands is illegal is also marked as illegal on the market screen before penalising the player.
    - Added police ship models to all the after-dock screens.
    
    0.2.0 
    - Made the price of RH waypoints, fake permits and phase scans related to the station price factor.
    - Bribing the same station multiple times will increase the required amount of the bribe (by 10 or 20 percent each time).
    - Added the last successful bribe amount to the screen where you enter a bribe amount.
    - Reduced the police vessel detection range for the phase scanner, as their ships wouldn't have the same range as the main station.
    - Main station market quantity of illegal goods under contract now set to zero (so, when arriving short, the player can't just buy the difference, launch and redock to complete the contract).
    - Fixed issue where you could purchase anything from the Black Market, regardless of how much credit you have.
    - Limited smuggling compartment options to standard commodities only.
    - Fixed issue where wrong illegal goods were being picked up if the player followed another ship through a wormhole.
    - Rebalanced prices for precious metals and gem stones.
    - Damaged smuggling compartments and phase scanners can now be repaired, and better handling of damaged equipment in general.
    - Fixed issue where phase scanner would not show up for purchase anywhere.
    - Fixed issue where having 0t available space would not allow transfer of precious metals from smuggling compartment to standard hold.
    - Added a new item to the Illegal Goods interface screen that lists all purchased Rock Hermit waypoints.
    - Fixed issue with tech upgrades, where the option to purchase new ones was always available, no matter how many times you purchased it.
    - Adjusted the price of the tech upgrade.
    - Bug fixes.
    
    0.1.2
    - Added missing semi-colon to planetinfo.plist.
    - Found additional spot where techlevel was showing 1 lower than actual.
    - Fixed issue where phase scans for TL 1 systems would not show up in the list of discovered phase scans.
    
    0.1.1
    - Tech levels on all displays were showing 1 lower than actual.
    - Attempting to bribe a dock master to relabel cargo when you don't have any cargo that can be relabeled would put the game into a locked state.
    - Spelling corrections.
    
    0.1.0
    - Fixed issue with purchasing import permits from main stations, where a timeout was occurring when attempting to open the purchase page.
    - Fixed issue where buying or selling directly into or out of the smuggling compartment was not adding or removing market quantities.
    - Fixed issue where docking with illegals in the compartment, and they go undetected. The right success message is now displayed.
    - Fixed issue where scooping non-commodity-type OXP containers was causing a Javascript error.
    - Fixed Javascript bug when selling cargo directly from smuggling compartment.
    - Fixed issue where, when delivering multiple contracts to a single destination, only one contract would be completed.
    - Added some extra contract completed messages.
    - Cleaned up manifest display for smuggling contracts, making it consistent with other contracts.
    - Fixed issue with attempting to sell cargo from the smuggling compartment over the market capacity.
    - Removed current system from the illegal goods on current course display.
    - When only 1 unit can be transferred, bought or sold, the player now won't be asked to enter a number between 1 and 1.
    - Changed manifest list of smuggling cargo to use two columns.
    - When aborting a data entry page, the previous screen will now be displayed, rather than going all the way out to the Interface list.
    - Added markers to the galactic chart to help identify systems with illegal goods to watch out for.
    - Added the number of available smuggling contracts to the Black Market menu.
    - Smuggling compartment is now really no longer available at RRS stations.
    - Adjustments to the smuggling reputation calculation.
    - Rebalancing of all costs (contracts, equipment and bribing).
    - Bug fixes.
    
    0.0.5
    - New arrival screen for when you have illegal goods and they go undetected by the authorities.
    - Adjustments to the illegal goods arrival reports to better match IGT.
    - Fixed issue where entering 0 or blank for a bribe when docking would allow the player to keep their cargo/equipment.
    - Added buy/sell sound effects when buying/selling cargo directly from the smuggling compartment.
    - Added warning to compartment config page about the age of the tech version of the compartment.
    - Fixed issue with smuggling contracts not being set up with enough time to complete.
    - Added some text to the bribe description to help players determine how much they should be offering.
    - Fixed issue on the various illegal goods lists where some illegal goods were not being separated with a comma.
    - Tweaked the logic for adding new illegal goods.
    - Added ability to have the Black Market appear in selected main stations, based on government and techlevel.
    - Added some additional illegal good definitions.
    - Added government and techlevel descriptions to recent illegal goods notifications page.
    - Smuggling compartment no longer available at RRS stations.
    
    0.0.4
    - Permits will now expire after use.
    - Fixed debug code issue.
    
    0.0.3 
    - Turned off some debug flags that made the Black Market and Smuggling Compartment available everywhere.
    - Updated the comment text for Narcotics.
    - Removed debug code that was automatically adding a 5t smuggling compartment to the player ship.
    - Fixed issue where attempting to purchase a sub-10t sized smuggling compartment would fail.
    - Updated Black Market noticeboard text to be more underworldish (thanks Layne!).
    - Bug fixes.
    
    0.0.2
    - Added ability to remove the phase scanner.
    - Adjusted the price of the phase scanner.
    - Adjusted the prices of the smuggling compartments.
    - Added the content of the smuggling compartment to the F5 Manifest screen.
    - Adjusted the methodology for working out the random chance of there being a phase scanner available in a given Anarchy TL10 system.
    - Fixed bug where visible property of smuggling compartment was not being restored correctly.
    - Fixed bug with calculation of bribe amount, so that the system works as designed, rather than allowing pretty much any bribe to work.
    - Adjusted the calculation of the bribe amount.
    - Adjusted the probabilities for fake permits, giving them a maximum of a 40% chance of success (ie. so it's better to get a smuggling compartment). 
    - Fewer fake permits will now be offered for sale.
    - Added ability to bribe officials when performing a renovation with a smuggling compartment or phase scanner
    - Added ability to bribe officials when docking with illegal cargo.
    - Added ability to bribe officials after they have detected the phase scanner.
    - Added ability to buy and sell commodities directly from the smuggling compartment (ie. without needing to do transfers from the main hold).
    - Phase scan settings will occasionally change for a given government/techlevel.
    - Added a new noticeboard item: phase scan setting changes, to indicate when settings at a particular planet (and therefore, all gov/techlevels of the same type) have changed.
    - Made the phase scan suggestions on the Black Market noticeboard less accurate.
    - Added the "Smuggling Compartment Phase Adjustment" equipment item. Gives a small boost to your chances just by having it installed. You also can't change the phase setting unless this is installed.
    - Updated descriptions of the noticeboard to make them a bit rougher.
    - Redesigned the internal structure of the smuggling compartment, making it simpler to work with.
    - Added some additional illegal good definitions.
    - Added destination marker to galactic map when contract is accepted.
    - Fixed bug with smuggling contracts, where the merchandise was not being given to the player when a contract was accepted.
    - Fixed issues with accepting contracts, where the manifest page was not being updated, and Javascript errors were occurring.
    - Fixed issue where accepted contracts were not being saved to mission variables correctly, meaning they would be lost on reload.
    - Fixed issue where contracts could not be completed successfully, and the wrong text was being displayed, and... well, contracts were broken, and now they're fixed.
    - Changed category from "Missions" to "Mechanics".
    - Bug fixes.
    
    0.0.1 Limited beta release
    

    Equipment

    Name Visible Cost [deci-credits] Tech-Level
    Import Permit no 30000 1+
    Import Permit no 30000 1+
    Customs Phase Scanner yes 420000 1+
    Remove Customs Phase Scanner no 5000 1+
    Smuggling Compartment no 5000 1+
    Smuggling Compartment (1t) yes 5000 1+
    Smuggling Compartment (10t) yes 17380 1+
    Smuggling Compartment (11t) yes 20130 1+
    Smuggling Compartment (12t) yes 23150 1+
    Smuggling Compartment (13t) yes 26450 1+
    Smuggling Compartment (14t) yes 30030 1+
    Smuggling Compartment (15t) yes 33880 1+
    Smuggling Compartment (16t) yes 38000 1+
    Smuggling Compartment (17t) yes 42400 1+
    Smuggling Compartment (18t) yes 47080 1+
    Smuggling Compartment (19t) yes 52030 1+
    Smuggling Compartment (2t) yes 5270 1+
    Smuggling Compartment (20t) yes 58340 1+
    Smuggling Compartment (3t) yes 5820 1+
    Smuggling Compartment (4t) yes 6650 1+
    Smuggling Compartment (5t) yes 7750 1+
    Smuggling Compartment (6t) yes 9120 1+
    Smuggling Compartment (7t) yes 10780 1+
    Smuggling Compartment (8t) yes 12700 1+
    Smuggling Compartment (9t) yes 14900 1+
    Remove Smuggling Compartment no 2000 1+
    Repair: Smuggling Compartment no 2500 1+
    Repair: Smuggling Compartment no 8690 1+
    Repair: Smuggling Compartment no 10065 1+
    Repair: Smuggling Compartment no 11575 1+
    Repair: Smuggling Compartment no 13225 1+
    Repair: Smuggling Compartment no 15015 1+
    Repair: Smuggling Compartment no 16940 1+
    Repair: Smuggling Compartment no 19000 1+
    Repair: Smuggling Compartment no 21200 1+
    Repair: Smuggling Compartment no 23540 1+
    Repair: Smuggling Compartment no 26015 1+
    Repair: Smuggling Compartment no 2635 1+
    Repair: Smuggling Compartment no 29170 1+
    Repair: Smuggling Compartment no 2910 1+
    Repair: Smuggling Compartment no 3325 1+
    Repair: Smuggling Compartment no 3875 1+
    Repair: Smuggling Compartment no 4560 1+
    Repair: Smuggling Compartment no 5390 1+
    Repair: Smuggling Compartment no 6350 1+
    Repair: Smuggling Compartment no 7450 1+
    Smuggling Compartment Storage no 0 1+
    Smuggling Compartment Phase Adjustment yes 7500 1+
    Smuggling Compartment Version Upgrade no 4500 1+

    Ships

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

    Models

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

    Scripts

    Path
    Scripts/oolite-contracts-cargo_hide.js
    /*
    
    oolite-contracts-cargo.js
    
    Script for managing cargo contracts
    
    
    Oolite
    Copyright © 2004-2013 Giles C Williams and contributors
    
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.
    
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    
    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
    MA 02110-1301, USA.
    
    =========================================================================================================
    Note: Additions/changes by Nick Rogers
    1. Adjusted columns widths for wide fonts
    2. Added check for illegal good types when creating possible contracts, so only legal goods at destination are selected
    3. Added check for BigGUIHUD's when determining if the HUD needs to be hidden or not.
    =========================================================================================================
    
    */
    
    
    /*jslint white: true, undef: true, eqeqeq: true, bitwise: true, regexp: true, newcap: true, immed: true */
    /*global galaxyNumber, missionVariables, system*/
    
    
    "use strict";
    
    
    this.name			= "oolite-contracts-cargo";
    this.author			= "cim";
    this.copyright		= "© 2012-2013 the Oolite team.";
    this.description	= "Cargo delivery contracts.";
    
    
    /**** Configuration options and API ****/
    
    /* OXPs which wish to add a background to the summary pages should
       set this value */
    this.$cargoSummaryPageBackground = "";
    /* OXPs which wish to add an overlay to the cargo mission screens
       should set this value */
    this.$cargoPageOverlay = "";
    
    
    /* this._addCargoContractToSystem(cargo)
     * This function adds the defined cargo contract to the local main station's
     * interface list. A contract definition is an object with the following
     * parameters, all required:
     *
     * destination: system ID of destination system
     * commodity:   the cargo type
     * size:        the number of units of cargo
     * deadline:    the deadline for delivery, in clock seconds
     * payment:     the payment for delivery on time, in credits
     *
     * and optionally, the following parameters:
     *
     * deposit:     the deposit payment required by the player (default 0)
     * route:       a route object generated with system.info.routeToSystem
     *              describing the route between the source and destination
     *              systems.
     *
     * If this is not specified, it will be generated automatically.
     *
     * The function will return true if the contract can be added, false
     * otherwise.
     */
    this._addCargoContractToSystem = function(cargo)
    {
    		if (!system.mainStation)
    		{
    				log(this.name,"Contracts require a main station");
    				return false;
    		}
    		if (cargo.destination < 0 || cargo.destination > 255)
    		{
    				log(this.name,"Rejected contract: destination missing or invalid");
    				return false;
    		}
    		if (cargo.deadline <= clock.adjustedSeconds)
    		{
    				log(this.name,"Rejected contract: deadline invalid");
    				return false;
    		}
    		if (cargo.payment < 0)
    		{
    				log(this.name,"Rejected contract: payment invalid");
    				return false;
    		}
    		if (!cargo.size || cargo.size < 1)
    		{
    				log(this.name,"Rejected contract: size invalid");
    				return false;
    		}
    		if (!cargo.commodity)
    		{
    				log(this.name,"Rejected contract: commodity unspecified");
    				return false;
    		}
    		if (!system.mainStation.market[cargo.commodity])
    		{
    				log(this.name,"Rejected contract: commodity invalid");
    				return false;
    		}
    
    		if (!cargo.route)
    		{
    				var destinationInfo = System.infoForSystem(galaxyNumber,cargo.destination);
    				cargo.route = system.info.routeToSystem(destinationInfo);
    				if (!cargo.route)
    				{
    						log(this.name,"Rejected contract: route invalid");
    						return false;
    				}
    		}
    		if (!cargo.deposit)
    		{
    				cargo.deposit = 0;
    		}
    		else if (cargo.deposit >= cargo.payment)
    		{
    				log(this.name,"Rejected contract: deposit higher than total payment");
    				return false;
    		}
    
    		this.$contracts.push(cargo);
    		this._updateMainStationInterfacesList();
    		return true;
    }
    
    
    
    /**** Internal methods. Do not call these from OXPs as they may change
     **** without warning. ****/
    
    /* Event handlers */
    
    this.startUp = function()
    {
    		this.$helper = worldScripts["oolite-contracts-helpers"];
    
    		this.$suspendedDestination = null;
    		this.$suspendedHUD = false;
    
    		// stored contents of local main station's parcel contract list
    		if (missionVariables.oolite_contracts_cargo)
    		{
    				this.$contracts = JSON.parse(missionVariables.oolite_contracts_cargo);
    		}
    		else
    		{
    				this._initialiseCargoContractsForSystem();
    		}
    
    		this._updateMainStationInterfacesList();
    }
    
    
    this.shipWillExitWitchspace = function()
    {
    		if (!system.isInterstellarSpace && !system.sun.hasGoneNova && system.mainStation)
    		{
    				// must be a regular system with a main station
    				this._initialiseCargoContractsForSystem();
    				this._updateMainStationInterfacesList();
    		}
    }
    
    
    this.playerWillSaveGame = function()
    {
    		// encode the contract list to a string for storage in the savegame
    		missionVariables.oolite_contracts_cargo = JSON.stringify(this.$contracts);
    }
    
    
    // when the player exits the mission screens, reset their destination
    // system and HUD settings, which the mission screens may have
    // affected.
    this.shipWillLaunchFromStation = function()
    {
    		this._resetViews();
    }
    
    
    this.guiScreenWillChange = function(to, from)
    {
    		this._resetViews();
    }
    
    
    this.guiScreenChanged = function(to, from)
    {
    		if (to != "GUI_SCREEN_MISSION")
    		{
    				this._resetViews();
    		}
    }
    
    
    /* Interface functions */
    
    // resets HUD and jump destination
    this._resetViews = function()
    {
    		if (this.$suspendedHUD !== false)
    		{
    				player.ship.hudHidden = false;
    				this.$suspendedHUD = false;
    		}
    		if (this.$suspendedDestination !== null)
    		{
    				player.ship.targetSystem = this.$suspendedDestination;
    				this.$suspendedDestination = null;
    		}
    }
    
    // initialise a new cargo contract list for the current system
    this._initialiseCargoContractsForSystem = function()
    {
    		// clear list
    		this.$contracts = [];
    
    		// this is not the same algorithm as in 1.76, but should give
    		// similar results with comparable efficiency.
    
    		// no point in generating too many, as route-finding is slow
    		var numContracts = Math.floor(5*Math.random()+5*Math.random()+5*Math.random()+(player.contractReputationPrecise*Math.random()));
    		if (player.contractReputationPrecise >= 0 && numContracts < 5)
    		{
    				numContracts += 5;
    		}
    		if (numContracts > 16)
    		{
    				numContracts = 16;
    		}
    		else if (numContracts < 0)
    		{
    				numContracts = 0;
    		}
    		// some of these possible contracts may be discarded later on
    
    		for (var i = 0; i < numContracts; i++)
    		{
    				var cargo = new Object;
    
    				// pick a random system to take the goods to
    				var destination = Math.floor(Math.random()*256);
    
    				// discard if chose the current system
    				if (destination === system.ID)
    				{
    						continue;
    				}
    
    				// get the SystemInfo object for the destination
    				var destinationInfo = System.infoForSystem(galaxyNumber,destination);
    			if (destinationInfo.sun_gone_nova)
    			{
    				continue;
    			}
    				var daysUntilDeparture = 1+(Math.random()*(7+player.contractReputationPrecise-destinationInfo.government));
    				if (daysUntilDeparture <= 0)
    				{
    						// loses some more contracts if reputation negative
    						continue;
    				}
    
    				var si = worldScripts.Smugglers_Illegal;
    				var illegal = null;
    				if (si) {
    					illegal = si.$illegalGoodsListCommodityOnly(destination, true);
    					// add slaves to the list
    					illegal.push("slaves");
    				}
    
    				var commodities = Object.keys(system.mainStation.market);
    				var attempts = 0;
    				do {
    						var remotePrice = 0;
    						attempts++;
    						var commodity = commodities[Math.floor(Math.random()*commodities.length)];
    						// sub-tc contracts only available for top rep
    						if (system.mainStation.market[commodity]["quantity_unit"] != 0 && player.contractReputationPrecise < 6.5)
    						{
    						}
    						// ignore commodities with 0 availability here
    						else if (system.mainStation.market[commodity].quantity == 0)
    						{
    						}
    						// ignore any illegal goods
    						else if (illegal && illegal.indexOf(commodity) >= 0)
    						{
    						}
    						else
    						{
    								remotePrice = this._priceForCommodity(commodity,destinationInfo);
    						}
    				} while (remotePrice < system.mainStation.market[commodity].price/20 && attempts < 10);
    				if (attempts == 10)
    				{
    						// failed to find a good one.
    						continue;
    				}
    				cargo.commodity = commodity;
    
    				var amount = 0;
    				while (amount < 30)
    				{
    						var unitsize = 1;
    						// larger unit sizes for kg/g commodities
    						if (system.mainStation.market[commodity]["quantity_unit"] == 1)
    						{
    								unitsize += Math.floor(Math.random()*6)+Math.floor(Math.random()*6)+Math.floor(Math.random()*6);
    						}
    						else if (system.mainStation.market[commodity]["quantity_unit"] == 2)
    						{
    								unitsize += Math.floor(Math.random()*16)+Math.floor(Math.random()*11)+Math.floor(Math.random()*6);
    						}
    						amount += (1+Math.floor(Math.random()*32))*(1+Math.floor(Math.random()*16))*unitsize;
    				}
    
    				if (amount > 125 && system.mainStation.market[commodity]["quantity_unit"] == 0)
    				{
    						// reduce the number of contracts only suitable for Anacondas
    						amount = Math.floor(amount/Math.floor(1+(Math.random()*4)));
    				}
    				cargo.size = amount;
    
    				// adjustment to prices based on quantity (larger = more profitable)
    				var discount = Math.min(10+Math.floor(amount/10),35);
    
    				var unitPrice = system.mainStation.market[commodity].price * (100-discount) / 1000;
    				var localValue = Math.floor(unitPrice * amount);
    				remotePrice = remotePrice * (200+discount) / 200;
    				var remoteValue = Math.floor(remotePrice * amount);
    				var profit = remoteValue-localValue;
    
    				// skip if unprofitable
    				if (profit <= 100)
    				{
    						continue;
    				}
    
    				// check that a route to the destination exists
    				// route calculation is expensive so leave this check to last
    				var 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 parcel details
    
    				cargo.destination = destination;
    				// we'll need this again later, and route calculation is slow
    				cargo.route = routeToDestination;
    
    				// higher share for transporter for longer routes, less safe systems
    				var share = 100 + destinationInfo.government - (10*routeToDestination.route.length);
    				if (share < 10)
    				{
    						share = 10;
    				}
    				share = 100-share;
    
    				// safety: now multiply the fee by 2 compared with 1.76 contracts
    				// prevents exploit discovered by Mad Hollander at
    				// http://aegidian.org/bb/viewtopic.php?p=188127
    				localValue *= 2;
    				// this may need to be raised further
    
    				// absolute value of profit remains the same
    				var fee = localValue + Math.floor(profit * (share/100));
    				fee -= fee % 20; // round to nearest 20 credits;
    
    				cargo.payment = fee;
    				cargo.deposit = localValue - (localValue % 20);
    				if (cargo.deposit >= cargo.payment)
    				{
    						// rare but not impossible; last safety check
    						return;
    				}
    
    				// time allowed for delivery is time taken by "fewest jumps"
    				// route, plus timer above. Higher reputation makes longer
    				// times available.
    				cargo.deadline = clock.adjustedSeconds + Math.floor(daysUntilDeparture*86400)+(cargo.route.time*3600);
    
    				// add parcel to contract list
    				this._addCargoContractToSystem(cargo);
    		}
    
    }
    
    
    // this should be called every time the contents of this.$parcels
    // changes, as it updates the summary of the interface entry.
    this._updateMainStationInterfacesList = function()
    {
    		if (this.$contracts.length === 0)
    		{
    				// no contracts, remove interface if it exists
    				system.mainStation.setInterface("oolite-contracts-cargo",null);
    		}
    		else
    		{
    				var title = expandMissionText("oolite-contracts-cargo-interface-title",{
    						"oolite-contracts-cargo-interface-title-count": this.$contracts.length
    				});
    
    				system.mainStation.setInterface("oolite-contracts-cargo",{
    						title: title,
    						category: expandMissionText("oolite-contracts-cargo-interface-category"),
    						summary: expandMissionText("oolite-contracts-cargo-interface-summary"),
    						callback: this._cargoContractsScreens.bind(this)
    						// could alternatively use "cbThis: this" parameter instead of bind()
    				});
    		}
    }
    
    
    // if the interface is activated, this function is run.
    this._cargoContractsScreens = function(interfaceKey)
    {
    		// the interfaceKey parameter is not used here, but would be useful if
    		// this callback managed more than one interface entry
    
    		this._validateContracts();
    
    		// set up variables used to remember state on the mission screens
    		this.$suspendedDestination = null;
    		this.$suspendedHUD = false;
    		this.$contractIndex = 0;
    		this.$routeMode = "LONG_RANGE_CHART_SHORTEST";
    		this.$lastOptionChosen = "06_EXIT";
    
    		// start on the summary page if more than one contract is available
    		var summary = (this.$contracts.length > 1);
    
    		this._cargoContractsDisplay(summary);
    }
    
    
    // this function is called after the player makes a choice which keeps
    // them in the system, and also on initial entry to the system
    // to select the appropriate mission screen and display it
    this._cargoContractsDisplay = function(summary) {
    
    		// Again. Has to be done on every call to this function, but also
    		// has to be done at the start.
    		this._validateContracts();
    
    		// if there are no contracts (usually because the player has taken
    		// the last one) display a message and quit.
    		if (this.$contracts.length === 0)
    		{
    				var missionConfig = {titleKey: "oolite-contracts-cargo-none-available-title",
    													 messageKey: "oolite-contracts-cargo-none-available-message",
    													 allowInterrupt: true,
    													 screenID: "oolite-contracts-cargo-none",
    													 exitScreen: "GUI_SCREEN_INTERFACES"};
    				if (this.$cargoSummaryPageBackground != "") {
    						missionConfig.background = this.$cargoSummaryPageBackground;
    				}
    				if (this.$cargoPageOverlay != "") {
    						missionConfig.overlay = this.$cargoPageOverlay;
    				}
    				mission.runScreen(missionConfig);
    				// no callback, just exits contracts system
    				return;
    		}
    
    		// make sure that the 'currently selected contract' pointer
    		// is in bounds
    		if (this.$contractIndex >= this.$contracts.length)
    		{
    				this.$contractIndex = 0;
    		}
    		else if (this.$contractIndex < 0)
    		{
    				this.$contractIndex = this.$contracts.length - 1;
    		}
    		// sub functions display either summary or detail screens
    		if (summary)
    		{
    				this._cargoContractSummaryPage();
    		}
    		else
    		{
    				this._cargoContractSinglePage();
    		}
    
    }
    
    
    // display the mission screen for the summary page
    this._cargoContractSummaryPage = function()
    {
    		// column 'tab stops'
    		var columns = [9,15,21,26];
    
    		// column header line
    		var headline = expandMissionText("oolite-contracts-cargo-column-goods");
    		// pad to correct length to give a table-like layout
    		headline += this.$helper._paddingText(headline,columns[0]);
    		headline += expandMissionText("oolite-contracts-cargo-column-destination");
    		headline += this.$helper._paddingText(headline,columns[1]);
    		headline += expandMissionText("oolite-contracts-cargo-column-within");
    		headline += this.$helper._paddingText(headline,columns[2]);
    		headline += expandMissionText("oolite-contracts-cargo-column-deposit");
    		headline += this.$helper._paddingText(headline,columns[3]);
    		headline += expandMissionText("oolite-contracts-cargo-column-fee");
    		// required because of way choices are displayed.
    		headline = " "+headline;
    
    		// setting options dynamically; one contract per line
    		var options = new Object;
    		var i;
    		var anyWithSpace = false;
    		for (i=0; i<this.$contracts.length; i++)
    		{
    				// temp variable to simplify following code
    				var cargo = this.$contracts[i];
    				// write the description, padded to line up with the headers
    				var optionText = this._descriptionForGoods(cargo);
    				optionText += this.$helper._paddingText(optionText, columns[0]);
    				optionText += System.infoForSystem(galaxyNumber, cargo.destination).name;
    				optionText += this.$helper._paddingText(optionText, columns[1]);
    				optionText += this.$helper._timeRemaining(cargo);
    				optionText += this.$helper._paddingText(optionText, columns[2]);
    				// right-align the fee so that the credits signs line up
    				var priceText = formatCredits(cargo.deposit,false,true);
    				priceText = this.$helper._paddingText(priceText, 3.5)+priceText;
    				optionText += priceText
    				optionText += this.$helper._paddingText(optionText, columns[3]);
    				// right-align the fee so that the credits signs line up
    				priceText = formatCredits(cargo.payment-cargo.deposit,false,true);
    				priceText = this.$helper._paddingText(priceText, 3.5)+priceText;
    				optionText += priceText
    
    				// need to pad the number in the key to maintain alphabetical order
    				var istr = i;
    				if (i < 10)
    				{
    						istr = "0"+i;
    				}
    				// needs to be aligned left to line up with the heading
    				options["01_CONTRACT_"+istr] = { text: optionText, alignment: "LEFT" };
    
    				// check if there's space for this contract
    				if (!this._hasSpaceFor(cargo))
    				{
    						options["01_CONTRACT_"+istr].color = "darkGrayColor";
    				}
    				else
    				{
    						anyWithSpace = true;
    						// if there doesn't appear to be sufficient time remaining
    						if (this.$helper._timeRemainingSeconds(cargo) < this.$helper._timeEstimateSeconds(cargo))
    						{
    								options["01_CONTRACT_"+istr].color = "orangeColor";
    						}
    				}
    		}
    		// if we've come from the detail screen, make sure the last
    		// contract shown there is selected here
    		var icstr = this.$contractIndex;
    		if (icstr < 10)
    		{
    				icstr = "0"+this.$contractIndex;
    		}
    		var initialChoice = "01_CONTRACT_"+icstr;
    		// if none of them have any space...
    		if (!anyWithSpace)
    		{
    				initialChoice = "06_EXIT";
    		}
    
    		// next, an empty string gives an unselectable row
    		options["02_SPACER"] = "";
    
    		// numbered 06 to match the option of the same function in the other branch
    		options["06_EXIT"] = expandMissionText("oolite-contracts-cargo-command-quit");
    
    		// now need to add further spacing to fill the remaining rows, or
    		// the options will end up at the bottom of the screen.
    		var rowsToFill = 21;
    		if (player.ship.hudHidden || this.$isBigGuiActive() == true)
    		{
    				rowsToFill = 27;
    		}
    
    		for (i = 4 + this.$contracts.length; i < rowsToFill ; i++)
    		{
    				// each key needs to be unique at this stage.
    				options["07_SPACER_"+i] = "";
    		}
    
    		var missionConfig = {titleKey: "oolite-contracts-cargo-title-summary",
    												 message: headline,
    												 allowInterrupt: true,
    												 screenID: "oolite-contracts-cargo-summary",
    												 exitScreen: "GUI_SCREEN_INTERFACES",
    												 choices: options,
    												 initialChoicesKey: initialChoice};
    		if (this.$cargoSummaryPageBackground != "") {
    				missionConfig.background = this.$cargoSummaryPageBackground;
    		}
    		if (this.$cargoPageOverlay != "") {
    				missionConfig.overlay = this.$cargoPageOverlay;
    		}
    
    		// now run the mission screen
    		mission.runScreen(missionConfig, this._processCargoChoice, this);
    
    }
    
    
    // display the mission screen for the contract detail page
    this._cargoContractSinglePage = function()
    {
    		// temp variable to simplify code
    		var cargo = this.$contracts[this.$contractIndex];
    
    		// This mission screen uses the long range chart as a backdrop.
    		// This means that the first 18 lines are taken up by the chart,
    		// and we can't put text there without overwriting the chart.
    		// We therefore need to hide the player's HUD, to get the full 27
    		// lines.
    
    		if (!player.ship.hudHidden && this.$isBigGuiActive() == false)
    		{
    				this.$suspendedHUD = true; // note that we hid it, for later
    				player.ship.hudHidden = true;
    		}
    
    		// We also set the player's witchspace destination temporarily
    		// so we need to store the old one in a variable to reset it later
    		this.$suspendedDestination = player.ship.targetSystem;
    
    		// That done, we can set the player's destination so the map looks
    		// right.
    		player.ship.targetSystem = cargo.destination;
    
    		// start with 18 blank lines, since we don't want to overlap the chart
    		var message = new Array(18).join("\n");
    
    		message += expandMissionText("oolite-contracts-cargo-long-description",{
    				"oolite-contracts-cargo-longdesc-goods": this._descriptionForGoods(cargo),
    				"oolite-contracts-cargo-longdesc-destination": this.$helper._systemName(cargo.destination),
    				"oolite-contracts-cargo-longdesc-deadline": this.$helper._timeRemaining(cargo),
    				"oolite-contracts-cargo-longdesc-time": this.$helper._timeEstimate(cargo),
    				"oolite-contracts-cargo-longdesc-payment": formatCredits(cargo.payment,false,true),
    				"oolite-contracts-cargo-longdesc-deposit": formatCredits(cargo.deposit,false,true)
    		});
    
    		// use a special background
    		var backgroundSpecial = "LONG_RANGE_CHART";
    
    		// the available options will vary quite a bit, so this rather
    		// than a choicesKey in missiontext.plist
    		var options = new Object;
    		// this is the only option which is always available
    		options["06_EXIT"] = expandMissionText("oolite-contracts-cargo-command-quit");
    
    		// if the player has sufficient space
    		if (this._hasSpaceFor(cargo))
    		{
    				options["05_ACCEPT"] = {
    						text: expandMissionText("oolite-contracts-cargo-command-accept")
    				};
    
    				// if there's not much time left, change the option colour as a warning!
    				if (this.$helper._timeRemainingSeconds(cargo) < this.$helper._timeEstimateSeconds(cargo))
    				{
    						options["05_ACCEPT"].color = "orangeColor";
    				}
    		}
    		else
    		{
    				options["05_UNAVAILABLE"] = {
    						text: expandMissionText("oolite-contracts-cargo-command-unavailable"),
    						color: "darkGrayColor",
    						unselectable: true
    				};
    		}
    
    		// if the ship has a working advanced nav array, can switch
    		// between 'quickest' and 'shortest' routes
    		// (and also upgrade the special background)
    		if (player.ship.equipmentStatus("EQ_ADVANCED_NAVIGATIONAL_ARRAY") === "EQUIPMENT_OK")
    		{
    				backgroundSpecial = this.$routeMode;
    				if (this.$routeMode === "LONG_RANGE_CHART_SHORTEST")
    				{
    						options["01_MODE"] = expandMissionText("oolite-contracts-cargo-command-ana-quickest");
    				}
    				else
    				{
    						options["01_MODE"] = expandMissionText("oolite-contracts-cargo-command-ana-shortest");
    				}
    		}
    		// if there's more than one, need options for forward, back, and listing
    		if (this.$contracts.length > 1)
    		{
    				options["02_BACK"] = expandMissionText("oolite-contracts-cargo-command-back");
    				options["03_NEXT"] = expandMissionText("oolite-contracts-cargo-command-next");
    				options["04_LIST"] = expandMissionText("oolite-contracts-cargo-command-list");
    		}
    		else
    		{
    				// if not, we may need to set a different choice
    				// we never want 05_ACCEPT to end up selected initially
    				if (this.$lastChoice === "02_BACK" || this.$lastChoice === "03_NEXT" || this.$lastChoice === "04_LIST")
    				{
    						this.$lastChoice = "06_EXIT";
    				}
    		}
    
    		var title = expandMissionText("oolite-contracts-cargo-title-detail",{
    				"oolite-contracts-cargo-title-detail-number": this.$contractIndex+1,
    				"oolite-contracts-cargo-title-detail-total": this.$contracts.length
    		});
    
    		// finally, after all that setup, actually create the mission screen
    
    		var missionConfig = {
    				title: title,
    				message: message,
    				allowInterrupt: true,
    				screenID: "oolite-contracts-cargo-details",
    				exitScreen: "GUI_SCREEN_INTERFACES",
    				backgroundSpecial: backgroundSpecial,
    				choices: options,
    				initialChoicesKey: this.$lastChoice
    		};
    
    		if (this.$cargoPageOverlay != "") {
    				missionConfig.overlay = this.$cargoPageOverlay;
    		}
    
    		mission.runScreen(missionConfig,this._processCargoChoice, this);
    
    }
    
    
    this._processCargoChoice = function(choice)
    {
    		this._resetViews();
    		if (choice === null)
    		{
    				// can occur if ship launches mid mission screen
    				return;
    		}
    
    		// now process the various choices
    		if (choice.match(/^01_CONTRACT_/))
    		{
    				// contract selected from summary page
    				// set the index to that contract, and show details
    				var index = parseInt(choice.slice(12),10);
    				this.$contractIndex = index;
    				this.$lastChoice = "04_LIST";
    				this._cargoContractsDisplay(false);
    		}
    		else if (choice === "01_MODE")
    		{
    				// advanced navigation array mode flip
    				this.$routeMode = (this.$routeMode === "LONG_RANGE_CHART_SHORTEST")?"LONG_RANGE_CHART_QUICKEST":"LONG_RANGE_CHART_SHORTEST";
    				this.$lastChoice = "01_MODE";
    				this._cargoContractsDisplay(false);
    		}
    		else if (choice === "02_BACK")
    		{
    				// reduce contract index (cargoContractsDisplay manages wraparound)
    				this.$contractIndex--;
    				this.$lastChoice = "02_BACK";
    				this._cargoContractsDisplay(false);
    		}
    		else if (choice === "03_NEXT")
    		{
    				// increase contract index (cargoContractsDisplay manages wraparound)
    				this.$contractIndex++;
    				this.$lastChoice = "03_NEXT";
    				this._cargoContractsDisplay(false);
    		}
    		else if (choice === "04_LIST")
    		{
    				// display the summary page
    				this._cargoContractsDisplay(true);
    		}
    		else if (choice === "05_ACCEPT")
    		{
    				this._acceptContract();
    				// do not leave the setting as accept for the next contract!
    				this.$lastChoice = "03_NEXT";
    				this._cargoContractsDisplay(false);
    		}
    		// if we get this far without having called cargoContractsDisplay
    		// that means either 'exit' or an unrecognised option was chosen
    }
    
    
    // move goods from the contracts list to the player's ship (if possible)
    this._acceptContract = function()
    {
    		var cargo = this.$contracts[this.$contractIndex];
    
    		if (cargo.deposit > player.credits)
    		{
    				this.$helper._soundFailure();
    				return;
    		}
    
    		// give the cargo to the player
    		var result = player.ship.awardContract(cargo.size,cargo.commodity,system.ID,cargo.destination,cargo.deadline,cargo.payment,cargo.deposit);
    
    		if (result)
    		{
    				// pay the deposit
    				player.credits -= cargo.deposit;
    
    				// remove the contract from the station list
    				this.$contracts.splice(this.$contractIndex,1);
    
    				// update the interface description
    				this._updateMainStationInterfacesList();
    
    				this.$helper._soundSuccess();
    		}
    		else
    		{
    				// else must have had manifest change recently
    				// (unlikely, but another OXP could have done it)
    				this.$helper._soundFailure();
    		}
    }
    
    
    // removes any expired contracts
    this._validateContracts = function()
    {
    		var c = this.$contracts.length-1;
    		var removed = false;
    		// iterate downwards so we can safely remove as we go
    		for (var i=c;i>=0;i--)
    		{
    				// if the time remaining is less than 1/3 of the estimated
    				// delivery time, even in the best case it's probably not
    				// going to get there.
    
    				if (this.$helper._timeRemainingSeconds(this.$contracts[i]) < this.$helper._timeEstimateSeconds(this.$contracts[i]) / 3)
    				{
    						// remove it
    						this.$contracts.splice(i,1);
    						removed = true;
    				}
    		}
    		if (removed)
    		{
    				// update the interface description if we removed any
    				this._updateMainStationInterfacesList();
    		}
    }
    
    
    /* Utility functions */
    
    // calculates a sample price for a commodity in a distant system
    this._priceForCommodity = function(commodity,systeminfo)
    {
    	//sample price returns decicredits, need credits
    	return systeminfo.samplePrice(commodity)/10;
    }
    
    // description of the cargo
    this._descriptionForGoods = function(cargo)
    {
    		var unit = "tons";
    		if (system.mainStation.market[cargo.commodity]["quantity_unit"] == "1")
    		{
    				unit = "kilograms";
    		}
    		else if (system.mainStation.market[cargo.commodity]["quantity_unit"] == "2")
    		{
    				unit = "grams";
    		}
    
    		return cargo.size+expandDescription("[cargo-"+unit+"-symbol]")+" "+displayNameForCommodity(cargo.commodity);
    }
    
    // check if player's ship has space for the cargo and can afford the deposit
    this._hasSpaceFor = function(cargo)
    {
    		if (cargo.deposit > player.credits)
    		{
    				return false;
    		}
    		var amountInTC = cargo.size;
    		if (system.mainStation.market[cargo.commodity]["quantity_unit"] == "1")
    		{
    				var spareSafe = 499-(player.ship.manifest[cargo.commodity] % 1000);
    				amountInTC -= spareSafe;
    				amountInTC = Math.ceil(amountInTC/1000);
    				if (amountInTC < 0)
    				{
    						amountInTC = 0;
    				}
    		}
    		else if (system.mainStation.market[cargo.commodity]["quantity_unit"] == "2")
    		{
    				var spareSafe = 499999-(player.ship.manifest[cargo.commodity] % 1000000);
    				amountInTC -= spareSafe;
    				amountInTC = Math.ceil(amountInTC/1000000);
    				if (amountInTC < 0)
    				{
    						amountInTC = 0;
    				}
    		}
    
    		return (amountInTC <= player.ship.cargoSpaceAvailable);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // 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 {
    		var bigGuiHUD = ["XenonHUD.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;
    		}
    	}
    }
    
    Scripts/permit_conditions.js
    "use strict";
    this.name = "Permit_Conditions";
    this.author = "phkb";
    this.copyright = "2015 phkb";
    this.description = "Condition script for determining whether to include the 'Import permit' item on the F3 screen";
    this.licence = "CC BY-NC-SA 3.0";
    
    //-------------------------------------------------------------------------------------------------------------
    this.allowAwardEquipment = function (equipment, ship, context) {
    
    	if (player.ship.dockedStation.allegiance != "galcop") return false;
    	var result = true;
    
    	switch (equipment) {
    		case "EQ_IMPORT_PERMIT_NC":
    		case "EQ_IMPORT_PERMIT":
    			if (equipment == "EQ_IMPORT_PERMIT_NC" && !worldScripts.CargoTypeExtension) return false;
    			if (equipment == "EQ_IMPORT_PERMIT" && worldScripts.CargoTypeExtension) return false;
    			
    			var si = worldScripts.Smugglers_Illegal;
    			var goods = si.$illegalGoodsList(system.ID);
    
    			// are there any illegal goods in this system
    			if (goods.length === 0) {
    				return false;
    			}
    			// does the player already have permits for all the goods?
    			var count = 0;
    			var permitavail = 0;
    			for (var i = 0; i < goods.length; i++) {
    				if (goods[i].permit === 1) {
    					permitavail += 1;
    					if (si.$playerHasPermit(system.ID, goods[i].commodity) === true) {
    						count += 1;
    						permitavail -= 1;
    					}
    				}
    			}
    			if (count === goods.length) result = false;
    			if (permitavail <= 0) result = false;
    
    			break;
    	}
    
    	// otherwise allowed
    	return result;
    
    }
    Scripts/smugglers_conditions.js
    "use strict";
    this.name = "Smugglers_Conditions";
    this.author = "phkb";
    this.copyright = "2015 phkb";
    this.description = "Condition script for determining when to include smuggling equipment (compartment, scanner, tech version upgrade)";
    this.licence = "CC BY-NC-SA 3.0";
    
    this._debug = false;
    
    // station roles to be excluded
    this._excludeRoles = ["rescue_station"];
    
    //-------------------------------------------------------------------------------------------------------------
    this.allowAwardEquipment = function (equipment, ship, context) {
    	if (context === "scripted") return true;
    	if (context === "npc") return false;
    
    	if (equipment === "EQ_SMUGGLING_COMP_STORAGE") return false;
    
    	var result = true;
    
    	var se = worldScripts.Smugglers_Equipment;
    	var existing = se.$getSmugglingCompartmentEquipment();
    
    	var alleg = ship.dockedStation.allegiance;
    	if (alleg == null) alleg = "neutral";
    
    	// is this station likely to have the equipment?
    	if (context === "purchase" && ship && ship.dockedStation && this._debug === false) {
    		if (se._equipmentList.indexOf(equipment) >= 0 && (existing === "" || player.ship.equipmentStatus(existing) === "EQUIPMENT_OK")) result = false;
    		if (se._smugglingAllegiance.indexOf(alleg) === -1 || this._excludeRoles.indexOf(ship.dockedStation.primaryRole) != -1) result = false;
    		if (result === false) {
    			return result;
    		}
    	}
    
    	// version 1.83 and above fix the issue with repair to items that take cargo space
    	if (oolite.compareVersion("1.83") <= 0) {
    		if (player.ship.equipmentStatus(equipment) === "EQUIPMENT_DAMAGED" && system.info.techlevel >= 2) {
    			return true;
    		}
    	}
    
    	if (equipment.indexOf("COMPARTMENT_REPAIR") >= 0 && oolite.compareVersion("1.83") > 0) {
    		if (system.info.techlevel < 2) result = false;
    		if (se.$isCompartmentDamaged() === false) result = false;
    	}
    
    	switch (equipment) {
    		case "EQ_SMUGGLING_COMPARTMENT":
    			// not available if there's no cargo space at all
    			if (ship.cargoSpaceCapacity === 0) result = false;
    			if (system.info.techlevel < 3) result = false;
    			if (se.$hasSmugglingCompartment() === true && se.$isCompartmentDamaged() === true && oolite.compareVersion("1.83") > 0) result = false;
    			// we should always be able to adjust the tonnage, even if the only way is down.
    			break;
    		case "EQ_PHASE_SCANNER":
    			var tl = 7;
    			if (ship.equipmentStatus(equipment) === "EQUIPMENT_DAMAGED") tl = 6;
    			if (system.government != 0) result = false;
    			if (system.info.techlevel < tl) result = false;
    			if (se._phaseScannerAvailable === false) result = false;
    			break;
    		case "EQ_SMUGGLING_PHASE_ADJUSTMENT":
    			if (system.info.techlevel < 6) result = false;
    			break;
    		case "EQ_SMUGGLING_VERSION_UPGRADE":
    			// higher techlevel systems will get the upgrade sooner
    			if (system.info.techlevel < 4) result = false;
    			if (se._sc_Days < (30 * ((30 - system.info.techlevel) / 30))) result = false;
    			break;
    		default:
    			// is this item damaged?
    			if (ship.equipmentStatus(equipment) === "EQUIPMENT_DAMAGED") {
    				// return true so that the "Repair" items are displayed.
    				result = true;
    			} else {
    				result = false;
    			}
    	}
    	return result;
    }
    Scripts/smugglers_contracts.js
    "use strict";
    this.name = "Smugglers_Contracts";
    this.author = "phkb and cim";
    this.description = "Adds smuggling contracts via an interface screen, with code borrowed heavily from the cargo contracts system, authored by cim.";
    this.licence = "CC BY-NC-SA 3.0";
    
    this._disabled = false;
    
    /*
    Note: The smuggling contract system is independent of the standard cargo contract system. The reason for this is that
    smuggled cargo might exist in a number of different places (smuggling compartment, or hidden under a manifest relabel)
    This means the normal cargo contract system cannot pick up the presence of hidden cargo.
    
    The end result is that a lot of the standard cargo contract core code has been reproduced here as JS.
    
    TODO: add "Negotiate" option to haggle for the fee
    */
    
    //=============================================================================================================
    /**** Configuration options and API ****/
    
    /* OXPs which wish to add a background to the summary pages should
       set this value */
    this._smugglingSummaryPageBackground = "";
    /* OXPs which wish to add an overlay to the parcel mission screens
       should set this value */
    this._smugglingPageOverlay = "";
    this._smugglingPageOverlayHeight = 0;
    
    this._suspendedDestination = null;
    this._suspendedHUD = false;
    this._smugglingContracts = [];
    this._smugglingRepGood = 0;
    this._smugglingRepBad = 0;
    this._smugglingRepUnknown = 0;
    this._maxRep = 70;
    this._checkForCargo = false;
    this._marketrnd = 0;
    
    //=============================================================================================================
    /* this.$addSmugglingContractToSystem(cargo)
     * This function adds the defined smuggling contract to the black market contract list.
     * A contract definition is an object with the following parameters, all required:
     *
     * destination: system ID of destination system
     * commodity:   the cargo type
     * size:        the number of units of cargo
     * deadline:    the deadline for delivery, in clock seconds
     * payment:     the payment for delivery on time, in credits
     *
     * and optionally, the following parameters:
     *
     * deposit:     the deposit payment required by the player (default 0)
     * route:       a route object generated with system.info.routeToSystem
     *              describing the route between the source and destination
     *              systems.
     *
     * If this is not specified, it will be generated automatically.
     *
     * The function will return true if the contract can be added, false
     * otherwise.
     */
    this.$addSmugglingContractToSystem = function $addSmugglingContractToSystem(cargo) {
    	if (cargo.destination < 0 || cargo.destination > 255) {
    		log(this.name, "Rejected contract: destination missing or invalid");
    		return false;
    	}
    	if (cargo.deadline <= clock.adjustedSeconds) {
    		log(this.name, "Rejected contract: deadline invalid");
    		return false;
    	}
    	if (cargo.payment < 0) {
    		log(this.name, "Rejected contract: payment invalid");
    		return false;
    	}
    	if (!cargo.size || cargo.size < 1) {
    		log(this.name, "Rejected contract: size invalid");
    		return false;
    	}
    	if (!cargo.commodity) {
    		log(this.name, "Rejected contract: commodity unspecified");
    		return false;
    	}
    	if (!system.mainStation.market[cargo.commodity]) {
    		log(this.name, "Rejected contract: commodity invalid");
    		return false;
    	}
    	if (!cargo.route) {
    		var destinationInfo = System.infoForSystem(galaxyNumber, cargo.destination);
    		cargo.route = system.info.routeToSystem(destinationInfo);
    		if (!cargo.route) {
    			log(this.name, "Rejected contract: route invalid");
    			return false;
    		}
    	}
    	if (!cargo.deposit) {
    		cargo.deposit = 0;
    	} else if (cargo.deposit >= cargo.payment) {
    		log(this.name, "Rejected contract: deposit higher than total payment");
    		return false;
    	}
    
    	this._contracts.push(cargo);
    	return true;
    }
    
    //=============================================================================================================
    /* Event handlers */
    
    //-------------------------------------------------------------------------------------------------------------
    this.startUpComplete = function () {
    	if (this._disabled === true) {
    		delete this.shipExitedWitchspace;
    		delete this.playerWillSaveGame;
    		delete this.shipWillLaunchFromStation;
    		delete this.guiScreenWillChange;
    		delete this.guiScreenChanged;
    		delete this.startUp;
    		return;
    	}
    
    	this._helper = worldScripts["oolite-contracts-helpers"];
    	var core = worldScripts.Smugglers_CoreFunctions;
    	this.$padTextRight = core.$padTextRight;
    	this.$padTextLeft = core.$padTextLeft;
    	this.$isBigGuiActive = core.$isBigGuiActive;
    	this.$rand = core.$rand;
    
    	if (missionVariables.Smuggling_MarketRnd) this._marketrnd = missionVariables.Smuggling_MarketRnd;
    	if (missionVariables.Smuggling_RepGood) {
    		this._smugglingRepGood = missionVariables.Smuggling_RepGood;
    		this._smugglingRepBad = missionVariables.Smuggling_RepBad;
    		this._smugglingRepUnknown = missionVariables.Smuggling_RepUnknown;
    	}
    	this.$normaliseSmugglingReputation();
    
    	if (missionVariables.Smuggling_ActiveContracts) {
    		this._smugglingContracts = JSON.parse(missionVariables.Smuggling_ActiveContracts);
    	}
    	// stored contents of systems's cargo contract list
    	if (missionVariables.Smuggling_Contracts) {
    		this._contracts = JSON.parse(missionVariables.Smuggling_Contracts);
    	} else {
    		this.$initialiseSmugglingContractsForSystem();
    	}
    
    	// its unclear whether this or ContractsOnBB will get to it's startUpComplete first, so we'll force the issue here
    	if (worldScripts.ContractsOnBB) {
    		worldScripts.ContractsOnBB.$convertSmugglingContracts();
    	}
    
    	var xui = worldScripts.XenonUI;
    	if (xui) xui.$addMissionScreenException("oolite-smuggling-contracts-details"); // smuggling-contracts-details
    	var xrui = worldScripts.XenonReduxUI;
    	if (xrui) xrui.$addMissionScreenException("oolite-smuggling-contracts-details");
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.shipExitedWitchspace = function () {
    	if (!system.isInterstellarSpace && !system.sun.hasGoneNova && system.stations.length > 1) {
    		// must be a regular system with at least one station other than the main one
    		this._marketrnd = this.$rand(256) - 1;
    		this.$erodeSmugglingReputation();
    		this.$initialiseSmugglingContractsForSystem();
    		this.$adjustMarket();
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.playerWillSaveGame = function () {
    	// encode the contract list to a string for storage in the savegame
    	missionVariables.Smuggling_Contracts = JSON.stringify(this._contracts);
    	missionVariables.Smuggling_RepGood = this._smugglingRepGood;
    	missionVariables.Smuggling_RepBad = this._smugglingRepBad;
    	missionVariables.Smuggling_RepUnknown = this._smugglingRepUnknown;
    	missionVariables.Smuggling_ActiveContracts = JSON.stringify(this._smugglingContracts);
    	missionVariables.Smuggling_MarketRnd = this._marketrnd;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // when the player exits the mission screens, reset their destination
    // system and HUD settings, which the mission screens may have
    // affected.
    this.shipWillLaunchFromStation = function () {
    	this.$resetViews();
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.shipDockedWithStation = function (station) {
    	if (player.ship.dockedStation.allegiance === "galcop") this._checkForCargo = true;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.missionScreenOpportunity = function () {
    	if (this._checkForCargo === true) {
    		this._checkForCargo = false;
    		this.$checkForCompletedContracts();
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.guiScreenWillChange = function (to, from) {
    	this.$resetViews();
    	if (to === "GUI_SCREEN_MANIFEST") this.$updateManifest();
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.guiScreenChanged = function (to, from) {
    	if (to != "GUI_SCREEN_MISSION") this.$resetViews();
    }
    
    //=============================================================================================================
    /* Interface functions */
    
    //-------------------------------------------------------------------------------------------------------------
    // runs through all smuggling contracts looking for any completed ones
    this.$checkForCompletedContracts = function $checkForCompletedContracts() {
    
    	if (this._smugglingContracts.length === 0) return;
    
    	var sc = worldScripts.Smugglers_Equipment;
    	var sdm = worldScripts.Smugglers_DockMaster;
    	var p = player.ship;
    
    	var list = [];
    
    	for (var i = this._smugglingContracts.length - 1; i >= 0; i--) {
    		var m = this._smugglingContracts[i];
    		var errorCode = "";
    		var new_payment = 0;
    		var percent_delivered = 0;
    
    		// are we in the right system
    		if (m.destination === system.ID && m.destinationName === system.name) {
    			if (clock.adjustedSeconds <= m.deadline) {
    				var onhand = 0;
    
    				onhand += sdm.$getTotalUnlabelled(m.commodity); // add any cargo currently hiding under a relabel
    				onhand += p.manifest[m.commodity]; // add any visible cargo in the hold
    				onhand -= sdm.$getTotalRelabelled(m.commodity); // take away any cargo that might be masked by a label (ie. cargo that looks like the commodity but really isn't)
    				onhand += sc.$getCargoQuantity(m.commodity); // add any cargo in the smuggling compartment
    				onhand += sc.$checkHyperCargo(m.commodity); // add any cargo in hyper cargo
    
    				if (onhand >= m.size) {
    					errorCode = "success";
    					player.credits += m.fee;
    					this.$removeCargo(m.commodity, m.size);
    					player.setPlayerRole("trader-smuggler");
    				} else {
    					percent_delivered = 100.0 * (onhand / m.size);
    					var acceptable_ratio = 100.0 - 10.0 * system.ID / 256; // down to 90%
    					if (percent_delivered >= acceptable_ratio) {
    						var shortfall = 100 - percent_delivered;
    						new_payment = percent_delivered * (m.fee) / 100.0;
    						player.credits += new_payment;
    						errorCode = "short";
    						this.$removeCargo(m.commodity, m.size);
    						player.setPlayerRole("trader-smuggler");
    					} else {
    						errorCode = "refused_short";
    					}
    				}
    			} else {
    				errorCode = "late";
    			}
    		} else {
    			if (clock.adjustedSeconds > m.deadline) {
    				errorCode = "failed";
    			}
    		}
    
    		var desc = "";
    		// adjust the fee and reputation based on the result
    		switch (errorCode) {
    			case "success":
    				// build a contact
    				var contactHome = this.$rand(256) - 1;
    				var contact = "a " + System.infoForSystem(galaxyNumber, contactHome).inhabitant.toLowerCase() + " from " + System.systemNameForID(contactHome);
    				desc = expandDescription("[smuggled-cargo-delivered-okay]", {
    					cargo: this.$descriptionForGoods(m),
    					fee: formatCredits(m.fee, true, true),
    					contactdesc: contact
    				});
    				this.$increaseSmugglingReputation(10);
    				this.$contractCompletedEmail(errorCode, m.fee, m);
    				if (this.$anyContractsLeftForSystem(i, m.destination) === false)
    					mission.unmarkSystem({
    						system: m.destination,
    						name: "smuggling_contract_" + m.destination
    					});
    				break;
    			case "late":
    				desc = expandDescription("[smuggled-cargo-delivered-late]", {
    					cargo: this.$descriptionForGoods(m)
    				});
    				this.$decreaseSmugglingReputation(10);
    				this.$contractCompletedEmail(errorCode, 0, m);
    				if (this.$anyContractsLeftForSystem(i, m.destination) === false)
    					mission.unmarkSystem({
    						system: m.destination,
    						name: "smuggling_contract_" + m.destination
    					});
    				break;
    			case "failed":
    				desc = expandDescription("[smuggled-cargo-failed]", {
    					cargo: this.$descriptionForGoods(m)
    				});
    				this.$decreaseSmugglingReputation(10);
    				this.$contractCompletedEmail(errorCode, 0, m);
    				if (this.$anyContractsLeftForSystem(i, m.destination) === false)
    					mission.unmarkSystem({
    						system: m.destination,
    						name: "smuggling_contract_" + m.destination
    					});
    				break;
    			case "short":
    				desc = expandDescription("[smuggled-cargo-short]", {
    					cargo: this.$descriptionForGoods(m),
    					fee: formatCredits(new_payment, true, true),
    					percent: percent_delivered
    				});
    				this.$contractCompletedEmail(errorCode, new_payment, m);
    				if (this.$anyContractsLeftForSystem(i, m.destination) === false)
    					mission.unmarkSystem({
    						system: m.destination,
    						name: "smuggling_contract_" + m.destination
    					});
    				break;
    			case "refused_short":
    				desc = expandDescription("[smuggled-cargo-refused-short]", {
    					cargo: this.$descriptionForGoods(m)
    				});
    				// reset the error so we can skip this one - the player still has time to complete the contract
    				errorCode = "";
    				break;
    		}
    		if (desc != "") list.push(desc);
    
    		// if we got to this point with a result of some kind, remove the contract
    		if (errorCode != "") {
    			if (worldScripts.ContractsOnBB) {
    				worldScripts.ContractsOnBB.$removeSmugglingContract(i);
    			}
    			this._smugglingContracts.splice(i, 1);
    		}
    	}
    
    	if (list.length > 0) {
    		// add all contract summary items to the arrival report
    		var text = "";
    		for (var i = 0; i < list.length; i++) {
    			text += list[i] + "\n\n";
    		}
    		mission.runScreen({
    			title: "Smuggling Contracts",
    			message: text,
    			exitScreen: "GUI_SCREEN_STATUS"
    		});
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // removes a specific amount of the commodity, from various locations in this order:
    // 1. relabelled commodities, 2. standard hold, 3. smuggling compartmnt, 4. hypercargo
    // returns quantity not found (should be zero in most circumstances)
    this.$removeCargo = function $removeCargo(commodity, amount) {
    
    	var se = worldScripts.Smugglers_Equipment;
    	var sdm = worldScripts.Smugglers_DockMaster;
    	var p = player.ship;
    	var sum = amount;
    
    	// is there any relabelled cargo?
    	if (sdm.$getTotalRelabelled(commodity) > 0) {
    		// unlabel the cargo so the next bit will pick it up
    		var relbl_remain = sdm.$removeRealCargoQuantity(commodity, sum);
    	}
    
    	// do we have the cargo anywhere? check normal hold
    	if (p.manifest[commodity] >= sum) {
    		p.manifest[commodity] -= sum;
    		sum = 0;
    	} else if (p.manifest[commodity] > 0) {
    		sum -= p.manifest[commodity];
    		p.manifest[commodity] = 0;
    	}
    	// smuggling compartment
    	if (sum > 0 && se.$getCargoQuantity(commodity) > 0) {
    		var remain = se.$removeCargo(commodity, sum);
    		sum = remain;
    	}
    	// and hypercargo
    	if (sum > 0 && se.$checkHyperCargo(commodity) > 0) {
    		var remain = se.$removeHyperCargo(commodity, sum);
    		sum = remain;
    	}
    
    	return sum;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // resets HUD and jump destination
    this.$resetViews = function $resetViews() {
    	if (this._suspendedHUD !== false) {
    		player.ship.hudHidden = false;
    		this._suspendedHUD = false;
    	}
    	if (this._suspendedDestination !== null) {
    		player.ship.targetSystem = this._suspendedDestination;
    		this._suspendedDestination = null;
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // initialise a new smuggling contract list for the current system
    this.$initialiseSmugglingContractsForSystem = function $initialiseSmugglingContractsForSystem() {
    	// clear list
    	this._contracts = [];
    
    	var checklist = [];
    
    	var si = worldScripts.Smugglers_Illegal;
    	var destlist = [];
    
    	if (si._illegalGoods.length > 0) {
    		for (var i = 0; i < si._illegalGoods.length; i++) {
    			if (destlist.indexOf(si._illegalGoods[i].systemID) === -1)
    				destlist.push(si._illegalGoods[i].systemID);
    		}
    	} else {
    		// no illegal goods defined - shouldn't happen, but just in case
    		return;
    	}
    
    	var numContracts = Math.floor(5 * Math.random() + 5 * Math.random() + 5 * Math.random() + (this.$getSmugglingReputationPrecise() * Math.random()));
    	if (this.$getSmugglingReputationPrecise() >= 0 && numContracts < 5) {
    		numContracts += 5;
    	}
    	if (numContracts > 16) {
    		numContracts = 16;
    	} else if (numContracts < 0) {
    		numContracts = 0;
    	}
    
    	for (var i = 0; i < numContracts; i++) {
    		var cargo = new Object;
    
    		// pick a random system to take the goods to
    		var destination = destlist[this.$rand(destlist.length) - 1];
    
    		// discard if chose the current system
    		if (destination === system.ID) continue;
    		if (typeof destination === "undefined") continue;
    
    		// get the SystemInfo object for the destination
    		var destinationInfo = System.infoForSystem(galaxyNumber, destination);
    
    		if (destinationInfo.sun_gone_nova) continue;
    
    		var daysUntilDeparture = 1 + (Math.random() * (7 + this.$getSmugglingReputationPrecise() - destinationInfo.government));
    		if (daysUntilDeparture <= 0) {
    			// loses some more contracts if reputation negative
    			continue;
    		}
    
    		var commodities = si.$illegalGoodsListCommodityOnly(destination, true);
    
    		var attempts = 0;
    		do {
    			var remotePrice = 0;
    			attempts++;
    			var commodity = commodities[Math.floor(Math.random() * commodities.length)];
    
    			// sub-tc contracts only available for top rep
    			if (system.mainStation.market[commodity]["quantity_unit"] != 0 && this.$getSmugglingReputationPrecise() < 6.5) {
    
    			} else {
    				remotePrice = this.$priceForCommodity(commodity, destinationInfo);
    			}
    			//log(this.name, "contract " + i + "--attempt " + attempts + ": " + commodity + " remoteprice = " + remotePrice + ", sourcepriceadj = " + system.mainStation.market[commodity].price / 20);
    		} while (remotePrice < system.mainStation.market[commodity].price / 20 && attempts < 10);
    		// failed to find a good one.
    		if (attempts === 10) continue;
    
    		// don't overload the player with all the same type of contracts
    		var checkcount = 0;
    		for (var j = 0; j < checklist.length; j++) {
    			if (checklist[j] === commodity) checkcount += 1;
    		}
    		if (checkcount >= 3) continue;
    
    		cargo.commodity = commodity;
    
    		var amount = 0;
    		// larger unit sizes for kg/g commodities
    		switch (parseInt(system.mainStation.market[commodity]["quantity_unit"])) {
    			case 1:
    				amount = this.$rand(20) + 10;
    				break;
    			case 2:
    				amount = this.$rand(40) + 20;
    				break;
    			case 0:
    				amount = this.$rand(10);
    				if (amount > 5 && Math.random() > 0.5) amount += (this.$rand(5) - 3);
    				break;
    		}
    		cargo.size = amount;
    
    		// adjustment to prices based on quantity (larger = more profitable)
    		var discount = Math.min(10 + Math.floor(amount / 10), 35);
    		var unitPrice = system.mainStation.market[commodity].price * (100 - discount) / 1000;
    		var localValue = Math.floor(unitPrice * amount);
    		remotePrice = remotePrice * (200 + discount) / 200;
    		var remoteValue = Math.floor(remotePrice * amount);
    		var profit = remoteValue - localValue;
    
    		// skip if unprofitable
    		if (profit <= 100) continue;
    
    		// check that a route to the destination exists
    		// route calculation is expensive so leave this check to last
    		var 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 parcel details
    		cargo.destination = destination;
    		// we'll need this again later, and route calculation is slow
    		cargo.route = routeToDestination;
    
    		// higher share for transporter for longer routes, less safe systems
    		var share = 100 + destinationInfo.government - (10 * routeToDestination.route.length);
    		if (share < 10) share = 10;
    		share = 100 - share;
    
    		// safety: now multiply the fee by 2 compared with 1.76 contracts
    		// prevents exploit discovered by Mad Hollander at
    		// http://aegidian.org/bb/viewtopic.php?p=188127
    		localValue *= 2;
    		// this may need to be raised further
    
    		// absolute value of profit remains the same
    		var fee = (localValue + Math.floor(profit * (share / 100))) * 2;
    
    		fee -= fee % 20; // round to nearest 20 credits;
    
    		cargo.payment = fee;
    		cargo.deposit = localValue - (localValue % 20);
    		// rare but not impossible; last safety check
    		if (cargo.deposit >= cargo.payment) continue;
    
    		// time allowed for delivery is time taken by "fewest jumps"
    		// route, plus timer above. Higher reputation makes longer
    		// times available.
    		cargo.deadline = clock.adjustedSeconds + Math.floor(daysUntilDeparture * 86400) + (cargo.route.time * 3600);
    
    		// make sure the illegal good will be illegal when the player arrives, based on the route time
    		var def = si.$illegalGoodsDefinition(destination, commodity);
    		if (def && def.end < cargo.deadline) continue;
    
    		// add it to the check list so we can make sure we don't offer too many of the same type of contract
    		checklist.push(commodity);
    
    		// add parcel to contract list
    		this.$addSmugglingContractToSystem(cargo);
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // if the interface is activated, this function is run.
    this.$smugglingContractsScreens = function $smugglingContractsScreens(interfaceKey) {
    	// the interfaceKey parameter is not used here, but would be useful if
    	// this callback managed more than one interface entry
    
    	this.$validateSmugglingContracts();
    
    	// set up variables used to remember state on the mission screens
    	this._suspendedDestination = null;
    	this._suspendedHUD = false;
    	this._contractIndex = 0;
    	this._routeMode = "LONG_RANGE_CHART_SHORTEST";
    	this._lastOptionChosen = "06_EXIT";
    
    	// start on the summary page if more than one contract is available
    	var summary = (this._contracts.length > 1);
    
    	this.$smugglingContractsDisplay(summary);
    }
    
    
    //-------------------------------------------------------------------------------------------------------------
    // this function is called after the player makes a choice which keeps
    // them in the system, and also on initial entry to the system
    // to select the appropriate mission screen and display it
    this.$smugglingContractsDisplay = function $smugglingContractsDisplay(summary) {
    
    	// Again. Has to be done on every call to this function, but also
    	// has to be done at the start.
    	this.$validateSmugglingContracts();
    
    	// if there are no contracts (usually because the player has taken
    	// the last one) display a message and quit.
    	if (this._contracts.length === 0) {
    		var missionConfig = {
    			titleKey: "smuggling-contracts-none-available-title",
    			messageKey: "smuggling-contracts-none-available-message",
    			allowInterrupt: true,
    			screenID: "oolite-smuggling-contracts-none",
    			exitScreen: "GUI_SCREEN_INTERFACES"
    		};
    		if (this._smugglingSummaryPageBackground != "") {
    			missionConfig.background = this._smugglingSummaryPageBackground;
    		}
    		if (this._smugglingPageOverlay != "") {
    			if (this._smugglingPageOverlayHeight != 0) {
    				missionConfig.overlay = {
    					name: this._smugglingPageOverlay,
    					height: this._smugglingPageOverlayHeight
    				};
    			} else {
    				missionConfig.overlay = this._smugglingPageOverlay;
    			}
    		}
    		mission.runScreen(missionConfig);
    		// no callback, just exits contracts system
    		return;
    	}
    
    	// make sure that the 'currently selected contract' pointer
    	// is in bounds
    	if (this._contractIndex >= this._contracts.length) {
    		this._contractIndex = 0;
    	} else if (this._contractIndex < 0) {
    		this._contractIndex = this._contracts.length - 1;
    	}
    	// sub functions display either summary or detail screens
    	if (summary) {
    		this.$smugglingContractSummaryPage();
    	} else {
    		this.$smugglingContractSinglePage();
    	}
    
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // display the mission screen for the summary page
    this.$smugglingContractSummaryPage = function $smugglingContractSummaryPage() {
    	var playerrep = this._helper._playerSkill(this.$getSmugglingReputationPrecise());
    	// column 'tab stops'
    	var columns = [9, 15, 21, 26];
    	var anyWithSpace = false;
    
    	// column header line
    	var headline = expandMissionText("smuggling-contracts-column-goods");
    	// pad to correct length to give a table-like layout
    	headline += this._helper._paddingText(headline, columns[0]);
    	headline += expandMissionText("smuggling-contracts-column-destination");
    	headline += this._helper._paddingText(headline, columns[1]);
    	headline += expandMissionText("smuggling-contracts-column-within");
    	headline += this._helper._paddingText(headline, columns[2]);
    	headline += expandMissionText("smuggling-contracts-column-deposit");
    	headline += this._helper._paddingText(headline, columns[3]);
    	headline += expandMissionText("smuggling-contracts-column-fee");
    	// required because of way choices are displayed.
    	headline = " " + headline;
    
    	// setting options dynamically; one contract per line
    	var options = new Object;
    	var i;
    	for (i = 0; i < this._contracts.length; i++) {
    		// temp variable to simplify following code
    		var cargo = this._contracts[i];
    		// write the description, padded to line up with the headers
    		var optionText = this.$descriptionForGoods(cargo);
    		optionText += this._helper._paddingText(optionText, columns[0]);
    		optionText += System.infoForSystem(galaxyNumber, cargo.destination).name;
    		optionText += this._helper._paddingText(optionText, columns[1]);
    		optionText += this._helper._timeRemaining(cargo);
    		optionText += this._helper._paddingText(optionText, columns[2]);
    		// right-align the fee so that the credits signs line up
    		var priceText = formatCredits(cargo.deposit, false, true);
    		priceText = this._helper._paddingText(priceText, 3.5) + priceText;
    		optionText += priceText
    		optionText += this._helper._paddingText(optionText, columns[3]);
    		// right-align the fee so that the credits signs line up
    		priceText = formatCredits(cargo.payment - cargo.deposit, false, true);
    		priceText = this._helper._paddingText(priceText, 3.5) + priceText;
    		optionText += priceText
    
    		// need to pad the number in the key to maintain alphabetical order
    		var istr = i;
    		if (i < 10) {
    			istr = "0" + i;
    		}
    		// needs to be aligned left to line up with the heading
    		options["01_CONTRACT_" + istr] = {
    			text: optionText,
    			alignment: "LEFT"
    		};
    
    		// check if there's space for this contract
    		if (!this.$hasSpaceFor(cargo)) {
    			options["01_CONTRACT_" + istr].color = "darkGrayColor";
    		} else {
    			anyWithSpace = true;
    			// if there doesn't appear to be sufficient time remaining
    			if (this._helper._timeRemainingSeconds(cargo) < this._helper._timeEstimateSeconds(cargo)) {
    				options["01_CONTRACT_" + istr].color = "orangeColor";
    			}
    		}
    	}
    	// if we've come from the detail screen, make sure the last
    	// contract shown there is selected here
    	var icstr = this._contractIndex;
    	if (icstr < 10) icstr = "0" + this._contractIndex;
    
    	var initialChoice = "01_CONTRACT_" + icstr;
    	// if none of them have any space...
    	if (!anyWithSpace) initialChoice = "06_EXIT";
    
    	// next, an empty string gives an unselectable row
    	options["02_SPACER"] = "";
    
    	// now need to add further spacing to fill the remaining rows, or
    	// the options will end up at the bottom of the screen.
    	var rowsToFill = 21;
    	if (player.ship.hudHidden || this.$isBigGuiActive() === true) rowsToFill = 27;
    
    	for (i = 4 + this._contracts.length; i < rowsToFill; i++) {
    		// each key needs to be unique at this stage.
    		options["07_SPACER_" + i] = "";
    	}
    
    	// numbered 06 to match the option of the same function in the other branch
    	options["06_EXIT"] = expandMissionText("smuggling-contracts-command-quit");
    
    	var missionConfig = {
    		titleKey: "smuggling-contracts-title-summary",
    		message: headline,
    		allowInterrupt: true,
    		screenID: "oolite-smuggling-contracts-summary",
    		exitScreen: "GUI_SCREEN_INTERFACES",
    		choices: options,
    		initialChoicesKey: initialChoice
    	};
    
    	if (this._smugglingSummaryPageBackground != "") {
    		missionConfig.background = this._smugglingSummaryPageBackground;
    	}
    	if (this._smugglingPageOverlay != "") {
    		if (this._smugglingPageOverlayHeight != 0) {
    			missionConfig.overlay = {
    				name: this._smugglingPageOverlay,
    				height: this._smugglingPageOverlayHeight
    			};
    		} else {
    			missionConfig.overlay = this._smugglingPageOverlay;
    		}
    	}
    
    	// now run the mission screen
    	mission.runScreen(missionConfig, this.$processSmugglingChoice, this);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // display the mission screen for the contract detail page
    this.$smugglingContractSinglePage = function $smugglingContractSinglePage() {
    
    	var playerrep = this._helper._playerSkill(this.$getSmugglingReputationPrecise());
    
    	// temp variable to simplify code
    	var cargo = this._contracts[this._contractIndex];
    
    	// This mission screen uses the long range chart as a backdrop.
    	// This means that the first 18 lines are taken up by the chart,
    	// and we can't put text there without overwriting the chart.
    	// We therefore need to hide the player's HUD, to get the full 27
    	// lines.
    
    	if (!player.ship.hudHidden && this.$isBigGuiActive() === false) {
    		this._suspendedHUD = true; // note that we hid it, for later
    		player.ship.hudHidden = true;
    	}
    
    	// We also set the player's witchspace destination temporarily
    	// so we need to store the old one in a variable to reset it later
    	this._suspendedDestination = player.ship.targetSystem;
    
    	// That done, we can set the player's destination so the map looks
    	// right.
    	player.ship.targetSystem = cargo.destination;
    
    	// start with 18 blank lines, since we don't want to overlap the chart
    	var message = new Array(18).join("\n");
    
    	message += expandMissionText("smuggling-contracts-long-description", {
    		"smuggling-contracts-longdesc-goods": this.$descriptionForGoods(cargo),
    		"smuggling-contracts-longdesc-destination": this._helper._systemName(cargo.destination),
    		"smuggling-contracts-longdesc-deadline": this._helper._timeRemaining(cargo),
    		"smuggling-contracts-longdesc-time": this._helper._timeEstimate(cargo),
    		"smuggling-contracts-longdesc-payment": formatCredits(cargo.payment, false, true),
    		"smuggling-contracts-longdesc-deposit": formatCredits(cargo.deposit, false, true)
    	});
    
    	// use a special background
    	var backgroundSpecial = "LONG_RANGE_CHART";
    
    	// the available options will vary quite a bit, so this rather
    	// than a choicesKey in missiontext.plist
    	var options = new Object;
    	// this is the only option which is always available
    	options["06_EXIT"] = expandMissionText("smuggling-contracts-command-quit");
    
    	// if the player has sufficient space
    	if (this.$hasSpaceFor(cargo)) {
    		options["05_ACCEPT"] = {
    			text: expandMissionText("smuggling-contracts-command-accept")
    		};
    
    		// if there's not much time left, change the option colour as a warning!
    		if (this._helper._timeRemainingSeconds(cargo) < this._helper._timeEstimateSeconds(cargo)) {
    			options["05_ACCEPT"].color = "orangeColor";
    		}
    	} else {
    		options["05_UNAVAILABLE"] = {
    			text: expandMissionText("smuggling-contracts-command-unavailable"),
    			color: "darkGrayColor",
    			unselectable: true
    		};
    	}
    
    	// if the ship has a working advanced nav array, can switch
    	// between 'quickest' and 'shortest' routes
    	// (and also upgrade the special background)
    	if (player.ship.hasEquipmentProviding("EQ_ADVANCED_NAVIGATIONAL_ARRAY")) {
    		backgroundSpecial = this._routeMode;
    		if (this._routeMode === "LONG_RANGE_CHART_SHORTEST") {
    			options["01_MODE"] = expandMissionText("smuggling-contracts-command-ana-quickest");
    		} else {
    			options["01_MODE"] = expandMissionText("smuggling-contracts-command-ana-shortest");
    		}
    	}
    	// if there's more than one, need options for forward, back, and listing
    	if (this._contracts.length > 1) {
    		options["02_BACK"] = expandMissionText("smuggling-contracts-command-back");
    		options["03_NEXT"] = expandMissionText("smuggling-contracts-command-next");
    		options["04_LIST"] = expandMissionText("smuggling-contracts-command-list");
    	} else {
    		// if not, we may need to set a different choice
    		// we never want 05_ACCEPT to end up selected initially
    		if (this._lastChoice === "02_BACK" || this._lastChoice === "03_NEXT" || this._lastChoice === "04_LIST") {
    			this._lastChoice = "06_EXIT";
    		}
    	}
    
    	var title = expandMissionText("smuggling-contracts-title-detail", {
    		"smuggling-contracts-title-detail-number": this._contractIndex + 1,
    		"smuggling-contracts-title-detail-total": this._contracts.length
    	});
    
    	// finally, after all that setup, actually create the mission screen
    
    	var missionConfig = {
    		title: title,
    		message: message,
    		allowInterrupt: true,
    		screenID: "oolite-smuggling-contracts-details",
    		exitScreen: "GUI_SCREEN_INTERFACES",
    		backgroundSpecial: backgroundSpecial,
    		choices: options,
    		initialChoicesKey: this._lastChoice
    	};
    
    	if (this._smugglingPageOverlay != "") {
    		if (this._smugglingPageOverlayHeight != 0) {
    			missionConfig.overlay = {
    				name: this._smugglingPageOverlay,
    				height: this._smugglingPageOverlayHeight
    			};
    		} else {
    			missionConfig.overlay = this._smugglingPageOverlay;
    		}
    	}
    
    	mission.runScreen(missionConfig, this.$processSmugglingChoice, this);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$processSmugglingChoice = function $processSmugglingChoice(choice) {
    	this.$resetViews();
    	// can occur if ship launches mid mission screen
    	if (choice == null) return;
    
    	// now process the various choices
    	if (choice.match(/^01_CONTRACT_/)) {
    		// contract selected from summary page
    		// set the index to that contract, and show details
    		var index = parseInt(choice.slice(12), 10);
    		this._contractIndex = index;
    		this._lastChoice = "04_LIST";
    		this.$smugglingContractsDisplay(false);
    
    	} else if (choice === "01_MODE") {
    		// advanced navigation array mode flip
    		this._routeMode = (this._routeMode === "LONG_RANGE_CHART_SHORTEST") ? "LONG_RANGE_CHART_QUICKEST" : "LONG_RANGE_CHART_SHORTEST";
    		this._lastChoice = "01_MODE";
    		this.$smugglingContractsDisplay(false);
    
    	} else if (choice === "02_BACK") {
    		// reduce contract index (parcelContractsDisplay manages wraparound)
    		this._contractIndex--;
    		this._lastChoice = "02_BACK";
    		this.$smugglingContractsDisplay(false);
    
    	} else if (choice === "03_NEXT") {
    		// increase contract index (parcelContractsDisplay manages wraparound)
    		this._contractIndex++;
    		this._lastChoice = "03_NEXT";
    		this.$smugglingContractsDisplay(false);
    
    	} else if (choice === "04_LIST") {
    		// display the summary page
    		this.$smugglingContractsDisplay(true);
    
    	} else if (choice === "05_ACCEPT") {
    		this.$acceptContract();
    		// do not leave the setting as accept for the next contract!
    		this._lastChoice = "03_NEXT";
    		this.$smugglingContractsDisplay(false);
    	}
    	// if we get this far without having called smugglingContractsDisplay
    	// that means either 'exit' or an unrecognised option was chosen
    	if (choice === "06_EXIT") {
    		var sbm = worldScripts.Smugglers_BlackMarket;
    		sbm.$initialBlackMarket();
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // move goods from the contracts list to the player's ship (if possible)
    this.$acceptContract = function $acceptContract() {
    	var cargo = this._contracts[this._contractIndex];
    
    	if (cargo.deposit > player.credits) {
    		this._helper._soundFailure();
    		return;
    	}
    
    	// give the cargo to the player
    	var result = this.$awardContract(cargo.size, cargo.commodity, system.ID, cargo.destination, cargo.deadline, cargo.payment, cargo.deposit, cargo.route);
    
    	if (result) {
    		// pay the deposit
    		player.credits -= cargo.deposit;
    		// give the player the merchandise
    		player.ship.manifest[cargo.commodity] += cargo.size;
    		mission.markSystem({
    			system: cargo.destination,
    			name: "smuggling_contract_" + cargo.destination,
    			markerShape: "MARKER_PLUS"
    		});
    
    		// remove the contract from the station list
    		this._contracts.splice(this._contractIndex, 1);
    
    		this._helper._soundSuccess();
    
    	} else {
    		// else must have had manifest change recently
    		// (unlikely, but another OXP could have done it)
    		this._helper._soundFailure();
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // store the contract
    this.$awardContract = function $awardContract(quantity, commodity, source, destination, deadline, payment, deposit, route) {
    	var cargo = {};
    	cargo.size = quantity;
    	cargo.commodity = commodity;
    	cargo.start = source;
    	cargo.startName = System.systemNameForID(source);
    	cargo.destination = destination;
    	cargo.destinationName = System.systemNameForID(destination);
    	cargo.deadline = deadline;
    	cargo.fee = payment;
    	cargo.premium = deposit;
    	cargo.route = route;
    
    	this._smugglingContracts.push(cargo);
    	this.$contractStartedEmail(cargo);
    
    	return true;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // removes any expired parcels
    this.$validateSmugglingContracts = function $validateSmugglingContracts() {
    	var c = this._contracts.length - 1;
    	var removed = false;
    	// iterate downwards so we can safely remove as we go
    	for (var i = c; i >= 0; i--) {
    		// if the time remaining is less than 1/3 of the estimated
    		// delivery time, even in the best case it's probably not
    		// going to get there.
    		if (this._helper._timeRemainingSeconds(this._contracts[i]) < this._helper._timeEstimateSeconds(this._contracts[i]) / 3) {
    			if (worldScripts.ContractsOnBB) {
    				worldScripts.ContractsOnBB.$reindexContracts(34000, i);
    			}
    			// remove it
    			this._contracts.splice(i, 1);
    			removed = true;
    		}
    	}
    }
    
    
    //=============================================================================================================
    /* Utility functions */
    
    //-------------------------------------------------------------------------------------------------------------
    // lower-cases the initial letter of the package contents
    this.$formatPackageName = function $formatPackageName(name) {
    	return name.charAt(0).toLowerCase() + name.slice(1);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // calculates a sample price for a commodity in a distant system
    this.$priceForCommodity = function $priceForCommodity(commodity, systeminfo) {
    	//sample price returns decicredits, need credits
    	var si = worldScripts.Smugglers_Illegal;
    	var goods = si.$illegalGoodsListCommodityOnly(systeminfo.systemID);
    	if (goods.indexOf(commodity) >= 0) {
    		//return Math.round((si._commodityInfo[commodity][1] - (Math.random() * 15)), 2)
    		return (systeminfo.samplePrice(commodity) * si._commodityInfo[commodity][1]) / 10;
    	} else {
    		return systeminfo.samplePrice(commodity) / 10;
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // description of the cargo
    this.$descriptionForGoods = function $descriptionForGoods(cargo) {
    	var unit = "tons";
    	var stn = system.mainStation;
    	if (!stn && system.stations.length > 0) stn = system.stations[0];
    	if (!stn) return "";
    	if (stn.market[cargo.commodity]["quantity_unit"] === "1") {
    		unit = "kilograms";
    	} else if (stn.market[cargo.commodity]["quantity_unit"] === "2") {
    		unit = "grams";
    	}
    	return cargo.size + expandDescription("[cargo-" + unit + "-symbol]") + " " + displayNameForCommodity(cargo.commodity);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // check if player's ship has space for the cargo and can afford the deposit
    this.$hasSpaceFor = function $hasSpaceFor(cargo) {
    	if (cargo.deposit > player.credits) {
    		return false;
    	}
    	var amountInTC = cargo.size;
    	if (system.mainStation.market[cargo.commodity]["quantity_unit"] === "1") {
    		var spareSafe = 499 - (player.ship.manifest[cargo.commodity] % 1000);
    		amountInTC -= spareSafe;
    		amountInTC = Math.ceil(amountInTC / 1000);
    		if (amountInTC < 0) {
    			amountInTC = 0;
    		}
    	} else if (system.mainStation.market[cargo.commodity]["quantity_unit"] === "2") {
    		var spareSafe = 499999 - (player.ship.manifest[cargo.commodity] % 1000000);
    		amountInTC -= spareSafe;
    		amountInTC = Math.ceil(amountInTC / 1000000);
    		if (amountInTC < 0) {
    			amountInTC = 0;
    		}
    	}
    
    	return (amountInTC <= player.ship.cargoSpaceAvailable);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns true if another contracts exists for the given system ID, otherwise false
    this.$anyContractsLeftForSystem = function $anyContractsLeftForSystem(idx_ignore, sysID) {
    	for (var i = 0; i < this._smugglingContracts.length; i++) {
    		if (i != idx_ignore && this._smugglingContracts[i].destination === sysID) return true;
    	}
    	return false;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // update the manifest screen with any contracts
    this.$updateManifest = function $updateManifest() {
    
    	// don't try an update in interstellar space
    	if (system.isInterstellarSpace) return;
    
    	if (this._smugglingContracts.length > 0) {
    		var list = [];
    		list.push("Smuggling Contracts:");
    
    		for (var i = 0; i < this._smugglingContracts.length; i++) {
    			var cargo = this._smugglingContracts[i];
    			var message = expandMissionText("smuggling-contracts-long-descr-manifest", {
    				"smuggling-contracts-longdesc-goods": this.$descriptionForGoods(cargo),
    				"smuggling-contracts-longdesc-destination": cargo.destinationName,
    				"smuggling-contracts-longdesc-deadline": this.$formatTravelTime(this._helper._timeRemainingSeconds(cargo)),
    				"smuggling-contracts-longdesc-time": this._helper._timeEstimate(cargo),
    				"smuggling-contracts-longdesc-payment": formatCredits(cargo.fee, false, true),
    				"smuggling-contracts-longdesc-deposit": formatCredits(cargo.deposit, false, true)
    			});
    			list.push(message);
    		}
    		mission.setInstructions(list, this.name);
    	} else {
    		mission.setInstructions(null, this.name);
    	}
    }
    
    //=============================================================================================================
    // Smuggling reputation functions
    // essentially a JS recreation of the same rep functions in core code to give the player a separate reputation for smuggling
    
    //-------------------------------------------------------------------------------------------------------------
    this.$getSmugglingReputation = function $getSmugglingReputation() {
    	return parseInt(this.$smugglingReputation() / 10);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$getSmugglingReputationPrecise = function $getSmugglingReputationPrecise() {
    	return this.$smugglingReputation() / 10;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$smugglingReputation = function $smugglingReputation() {
    	var good = this._smugglingRepGood;
    	var bad = this._smugglingRepBad;
    	var unknown = this._smugglingRepUnknown;
    
    	if (unknown > 0) {
    		unknown = this._maxRep - (((2 * unknown) + (this._marketrnd % unknown)) / 3);
    	} else {
    		unknown = this._maxRep;
    	}
    
    	return (good + unknown - 3 * bad) / 2;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$decreaseSmugglingReputation = function $decreaseSmugglingReputation(amount) {
    
    	var good = this._smugglingRepGood;
    	var bad = this._smugglingRepBad;
    	var unknown = this._smugglingRepUnknown;
    
    	for (var i = 0; i < amount; i++) {
    		if (good > 0) {
    			// shift a bean from good to bad
    			good--;
    			if (bad < this._maxRep) bad++;
    		} else {
    			// shift a bean from unknown to bad
    			if (unknown > 0) unknown--;
    			if (bad < this._maxRep) bad++;
    		}
    	}
    	this._smugglingRepGood = good;
    	this._smugglingRepBad = bad;
    	this._smugglingRepUnknown = unknown;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$increaseSmugglingReputation = function $increaseSmugglingReputation(amount) {
    	var good = this._smugglingRepGood;
    	var bad = this._smugglignRepBad;
    	var unknown = this._smugglingRepUnknown;
    
    	for (var i = 0; i < amount; i++) {
    		if (bad > 0) {
    			// shift a bean from bad to unknown
    			bad--;
    			if (unknown < this._maxRep) unknown++;
    		} else {
    			// shift a bean from unknown to good
    			if (unknown > 0) unknown--;
    			if (good < this._maxRep) good++;
    		}
    	}
    	this._smugglingRepGood = good;
    	this._smugglingRepBad = bad;
    	this._smugglingRepUnknown = unknown;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$erodeSmugglingReputation = function $erodeSmugglingReputation() {
    	var good = this._smugglingRepGood;
    	var bad = this._smugglingRepBad;
    	var unknown = this._smugglingRepUnknown;
    	if (unknown < this._maxRep) {
    		if (bad > 0) {
    			bad--;
    		} else {
    			if (good > 0) good--;
    		}
    		unknown++;
    	}
    	this._smugglingRepGood = good;
    	this._smugglingRepBad = bad;
    	this._smugglingRepUnknown = unknown;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$normaliseSmugglingReputation = function $normaliseSmugglingReputation() {
    	var good = this._smugglingRepGood;
    	var bad = this._smugglingRepBad;
    	var unknown = this._smugglingRepUnknown;
    
    	var c = good + bad + unknown;
    	if (c === 0) {
    		unknown = this._maxRep;
    	} else if (c != this._maxRep) {
    		good = good * this._maxRep / c;
    		bad = bad * this._maxRep / c;
    		unknown = this._maxRep - good - bad;
    	}
    	this._smugglingRepGood = good;
    	this._smugglingRepBad = bad;
    	this._smugglingRepUnknown = unknown;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // format the travel time
    this.$formatTravelTime = function $formatTravelTime(seconds) {
    	// this function uses an hours-only format
    	// but provides enough information to use a days&hours format if
    	// oolite-contracts-time-format in missiontext.plist is overridden
    
    	// extra minutes are discarded
    	var hours = Math.floor(seconds / 3600);
    
    	var days = Math.floor(hours / 24);
    	var spareHours = hours % 24;
    
    	return expandMissionText("smuggling-time-format", {
    		"smuggling-time-format-hours": hours,
    		"smuggling-time-format-days": days,
    		"smuggling-time-format-spare-hours": spareHours
    	});
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // routine to zero out quantities of any illegal goods under contract
    // so if the player arrives short, he can't just buy one, launch, redock and complete the contract
    this.$adjustMarket = function $adjustMarket() {
    	var stn = system.mainStation;
    	if (stn) {
    		for (var i = this._smugglingContracts.length - 1; i >= 0; i--) {
    			var m = this._smugglingContracts[i];
    			// are we in the right system
    			if (m.destination === system.ID && m.destinationName === system.name) {
    				// set the market quantity to zero
    				stn.setMarketQuantity(m.commodity, 0);
    			}
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // send email to player when a contract is started. (1.81 only)
    this.$contractStartedEmail = function $contractStartedEmail(contract) {
    
    	var w = worldScripts.EmailSystem;
    	if (!w) return;
    
    	var ga = worldScripts.GalCopAdminServices;
    	if (ga._disableContracts === true) return;
    
    	var msg = "";
    	var subj = "";
    	var sndr = "";
    
    	sndr = expandDescription("[smuggling-contract-sender]");
    	subj = "Accepted contract: " + this.$descriptionForGoods(contract);
    	msg = expandDescription("[smuggling-contract-start]", {
    		description: this.$descriptionForGoods(contract),
    		systemname: System.systemNameForID(contract.destination),
    		time: global.clock.clockStringForTime(contract.deadline),
    		fee: contract.fee.toFixed(2)
    	});
    
    	w.$createEmail({
    		sender: sndr,
    		subject: subj,
    		date: global.clock.seconds,
    		message: msg
    	});
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$contractCompletedEmail = function $contractCompletedEmail(result, fee, contract) {
    
    	var w = worldScripts.EmailSystem;
    	if (!w) return;
    
    	var ga = worldScripts.GalCopAdminServices;
    	if (ga._disableContracts === true) return;
    
    	var msg = "";
    	var subj = "";
    	var sndr = "";
    
    	sndr = expandDescription("[smuggling-contract-sender]");
    	msg = expandDescription("[smuggling-contract-" + result + "]", {
    		description: this.$descriptionForGoods(contract),
    		systemname: System.systemNameForID(contract.destination),
    		time: global.clock.clockStringForTime(contract.deadline),
    		fee: (fee / 10).toFixed(2)
    	});
    	subj = expandDescription("[smuggling-contract-" + result + "-subject]");
    
    	w.$createEmail({
    		sender: sndr,
    		subject: subj,
    		date: global.clock.seconds,
    		message: msg,
    		expiryDays: ga._defaultExpiryDays
    	});
    }
    Scripts/smugglers_corefunctions.js
    "use strict";
    this.name = "Smugglers_CoreFunctions";
    this.author = "phkb";
    this.description = "Holds common functions used across all routines";
    this.licence = "CC BY-NC-SA 3.0";
    
    //-------------------------------------------------------------------------------------------------------------
    // returns true if a HUD with allowBigGUI is enabled, otherwise false
    this.$isBigGuiActive = function $isBigGuiActive() {
    	if (oolite.compareVersion("1.83") <= 0) {
    		return player.ship.hudAllowsBigGui;
    	} else {
    		var 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;
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // appends space to currentText to the specified length in 'em'
    this.$padTextRight = function $padTextRight(currentText, desiredLength, leftSwitch) {
    	if (currentText == null) currentText = "";
    	var hairSpace = String.fromCharCode(31);
    	var ellip = "…";
    	var currentLength = defaultFont.measureString(currentText);
    	var hairSpaceLength = defaultFont.measureString(hairSpace);
    	// calculate number needed to fill remaining length
    	var padsNeeded = Math.floor((desiredLength - currentLength) / hairSpaceLength);
    	if (padsNeeded < 1) {
    		// text is too long for column, so start pulling characters off
    		var tmp = currentText;
    		do {
    			tmp = tmp.substring(0, tmp.length - 2) + ellip;
    			if (tmp === ellip) break;
    		} while (defaultFont.measureString(tmp) > desiredLength);
    		currentLength = defaultFont.measureString(tmp);
    		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 $padTextLeft(currentText, desiredLength) {
    	return this.$padTextRight(currentText, desiredLength, true);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // return a random number between 1 and max
    this.$rand = function $rand(max) {
    	return Math.floor((Math.random() * max) + 1)
    }
    Scripts/smugglers_dockmaster.js
    "use strict";
    this.name = "Smugglers_DockMaster";
    this.author = "phkb";
    this.description = "Looks after the Dock Master interface screen.";
    this.licence = "CC BY-NC-SA 3.0";
    
    this._disabled = false;
    this._debug = true;
    
    this._bribeChance = []; // holds the chance a each dock master can be bribed
    this._cargoLabelled = []; // array of original commodity and quantities, and new labels
    // {oldCommodity: name, newCommodity: name, quantity: number of tons relabelled
    this._bribeAttempted = false;
    this._dockMasterStartTime = 0;
    this._dockMasterEndTime = 0;
    
    this._noSwitch = []; // these commodities can't be relabelled - populated through the smugglers_equipment.startUpComplete function
    this._display = 0;
    this._milkRuns = [];
    this._successfulBribes = [];
    this._cargoAtLaunch = [];
    this._bribeCount = [];
    this._menuColor = "orangeColor";
    this._itemColor = "yellowColor";
    this._disabledColor = "darkGrayColor";
    this._galJump = false;
    this._destReleaseCount = 0;
    this._flagged = [];
    this._curPage = 0;
    this._marker = {};
    
    //-------------------------------------------------------------------------------------------------------------
    this.startUpComplete = function () {
    
    	if (this._disabled) {
    		delete this.shipDockedWithStation;
    		delete this.shipLaunchedFromStation;
    		delete this.playerWillSaveGame;
    		delete this.shipExitedWitchspace;
    		delete this.playerEnteredNewGalaxy;
    		delete this.shipSpawned;
    		delete this.shipTakingDamage;
    		delete this.playerSoldCargo;
    		delete this.startUpComplete;
    		delete this.guiScreenChanged;
    		return;
    	}
    
    	// add a mission screen exception to Xenon UI
    	if (worldScripts.XenonUI) {
    		var wx = worldScripts.XenonUI;
    		wx.$addMissionScreenException("oolite-smuggling-dockmaster-milkrun");
    	}
    	// add a mission screen exception to Xenon Redux UI
    	if (worldScripts.XenonReduxUI) {
    		var wxr = worldScripts.XenonReduxUI;
    		wxr.$addMissionScreenException("oolite-smuggling-dockmaster-milkrun");
    	}
    
    	var core = worldScripts.Smugglers_CoreFunctions;
    	this.$padTextRight = core.$padTextRight;
    	this.$padTextLeft = core.$padTextLeft;
    	this.$isBigGuiActive = core.$isBigGuiActive;
    	this.$rand = core.$rand;
    
    	this.$getMilkRuns();
    	if (missionVariables.Smuggling_BribeChance) {
    		this._bribeChance = JSON.parse(missionVariables.Smuggling_BribeChance);
    		delete missionVariables.Smuggling_BribeChance;
    	} else {
    		this.$setupChance();
    	}
    	if (missionVariables.Smuggling_Relabels) {
    		this._cargoLabelled = JSON.parse(missionVariables.Smuggling_Relabels);
    		delete missionVariables.Smuggling_Relabels;
    	}
    	if (missionVariables.Smuggling_SuccessfulBribes) {
    		this._successfulBribes = JSON.parse(missionVariables.Smuggling_SuccessfulBribes);
    		delete missionVariables.Smuggling_SuccessfulBribes;
    	}
    	if (missionVariables.Smuggling_BribeAttempted) this._bribeAttemped = (missionVariables.Smuggling_BribeAttemped === 1 ? true : false);
    
    	if (missionVariables.Smuggling_DockMasterStartTime) {
    		this._dockMasterStartTime = missionVariables.Smuggling_DockMasterStartTime;
    		this._dockMasterEndTime = missionVariables.Smuggling_DockMasterEndTime;
    	} else {
    		this.$setDockMasterHours();
    	}
    
    	if (missionVariables.Smuggling_BribeCount) {
    		this._bribeCount = JSON.parse(missionVariables.Smuggling_BribeCount);
    		delete missionVariables.Smuggling_BribeCount;
    	} else {
    		this.$resetBribeCount();
    	}
    
    	if (missionVariables.Smuggling_DockMasterFlagged) {
    		this._flagged = JSON.parse(missionVariables.Smuggling_DockMasterFlagged);
    		delete missionVariables.Smuggling_DockMasterFlagged;
    	}
    	if (missionVariables.Smuggling_DockMasterUnlockCount) {
    		this._destReleaseCount = missionVariables.Smuggling_DockMasterUnlockCount;
    		delete missionVariables.Smuggling_DockMasterUnlockCount;
    	}
    
    	// set up the interface screen, if required
    	this.$initMainInterface(player.ship.dockedStation);
    	if (this._cargoLabelled.length > 0) {
    		this.$initUnlabelInterface(player.ship.dockedStation);
    	}
    
    	// upgraded features in v1.84
    	if (0 < oolite.compareVersion("1.84")) {
    		delete this.shipDumpedCargo;
    	} else {
    		delete this.shipSpawned;
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.shipDockedWithStation = function (station) {
    	// set up dock master interface screen
    	this.$initMainInterface(station);
    	if (this._cargoLabelled.length > 0) {
    		this.$initUnlabelInterface(station);
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.shipLaunchedFromStation = function (station) {
    	if (missionVariables.Smuggling_BribeChance) delete missionVariables.Smuggling_BribeChance;
    	if (missionVariables.Smuggling_Relabels) delete missionVariables.Smuggling_Relabels;
    	if (missionVariables.Smuggling_SuccessfulBribes) delete missionVariables.Smuggling_SuccessfulBribes;
    	if (missionVariables.Smuggling_BribeCount) delete missionVariables.Smuggling_BribeCount;
    
    	var p = player.ship;
    	var m = p.manifest.list;
    	this._cargoAtLaunch = [];
    	// grab a snapshot of the cargo for comparison later
    	for (var i = 0; i < m.length; i++) {
    		if (m[i].quantity > 0) this._cargoAtLaunch.push({
    			commodity: m[i].commodity,
    			quantity: m[i].quantity
    		});
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.playerWillSaveGame = function () {
    	missionVariables.Smuggling_BribeChance = JSON.stringify(this._bribeChance);
    	missionVariables.Smuggling_Relabels = JSON.stringify(this._cargoLabelled);
    	missionVariables.Smuggling_SuccessfulBribes = JSON.stringify(this._successfulBribes);
    	missionVariables.Smuggling_BribeCount = JSON.stringify(this._bribeCount);
    	missionVariables.Smuggling_BribeAttempted = (this._bribeAttemped ? 1 : 0);
    	if (this._flagged.length > 0) {
    		missionVariables.Smuggling_DockMasterFlagged = JSON.stringify(this._flagged)
    	}
    	missionVariables.Smuggling_DockMasterUnlockCount = this._destReleaseCount;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.shipExitedWitchspace = function () {
    	this._bribeAttempted = false;
    	this._destReleaseCount = 0;
    	this._flagged = [];
    	this.$setDockMasterHours();
    	if (this._galJump === true) {
    		this._galJump = false;
    		this.$setupChance();
    		this.$getMilkRuns();
    		this.$resetBribeCount();
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.playerEnteredNewGalaxy = function (galaxyNumber) {
    	this._successfulBribes = [];
    	this._galJump = true;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.shipSpawned = function (ship) {
    	// did the player just dump relabelled cargo?
    	// if so, reduce the count in the array
    	// and unlabel the dumped cargo
    	var p = player.ship;
    	if (p && ship && ship.isCargo && ship.position && p.position && p.position.distanceTo(ship) < 300) {
    		//gotcha
    		var total = this.$getTotalRelabelled(ship.commodity);
    		if (p.manifest[ship.commodity] < total) {
    			for (var i = 0; i < this._cargoLabelled.length; i++) {
    				// find the first matching relabelled commodity and adjust it
    				if (this._cargoLabelled[i].newCommodity === ship.commodity && this._cargoLabelled[i].quantity > 0) {
    					this._cargoLabelled[i].quantity -= ship.commodityAmount;
    					var check = ship.setCargo(this._cargoLabelled[i].oldCommodity);
    					break;
    				}
    			}
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.shipDumpedCargo = function(cargo) {
    	var p = player.ship;
    	var total = this.$getTotalRelabelled(cargo.commodity);
    	if (p.manifest[cargo.commodity] < total) {
    		for (var i = 0; i < this._cargoLabelled.length; i++) {
    			// find the first matching relabelled commodity and adjust it
    			if (this._cargoLabelled[i].newCommodity === cargo.commodity && this._cargoLabelled[i].quantity > 0) {
    				this._cargoLabelled[i].quantity -= cargo.commodityAmount;
    				var check = ship.setCargo(this._cargoLabelled[i].oldCommodity);
    				break;
    			}
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.shipTakingDamage = function (dmg_amount, whom, type) {
    	// work out if the any of the relabelled cargo was destroyed
    	// if so, reduce the count in the array
    	// basically, relabelled cargo should be destroyed first
    	var p = player.ship;
    	var se = worldScripts.Smugglers_Equipment;
    	for (var i = 0; i < se._commodityIDList.length; i++) {
    		var holdamt = p.manifest[se._commodityIDList[i]];
    		var origamt = this.$launchAmount(se._commodityIDList[i]);
    		var amount = this.$getTotalRelabelled(se._commodityIDList[i]);
    		if (holdamt < origamt && amount > 0) {
    			// how many units do we have to remove?
    			var to_remove = (origamt - holdamt);
    
    			// find the first instance of a relabelled cargo
    			for (var j = 0; j < this._cargoLabelled.length; j++) {
    				if (this._cargoLabelled[j].quantity > 0 && to_remove > 0) {
    					// reduce the cargo by the removed amount
    					this._cargoLabelled[j].quantity -= to_remove;
    					// if we've just gone negative, we've wiped out all of that cargo batch
    					if (this._cargoLabelled[j].quantity < 0) {
    						// pick up the remainder
    						to_remove = this._cargoLabelled[j].quantity * -1;
    						// reset this one back to zero
    						this._cargoLabelled[j].quantity = 0;
    					}
    				}
    			}
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // if we scoop cargo make sure it gets added to the at_launch list
    this.shipScoopedOther = function (whom) {
    	var si = worldScripts.Smugglers_Illegal;
    	var check = 0;
    	if (si._commodityInfo[whom.commodity]) {
    		check = si._commodityInfo[whom.commodity][0];
    		if (check > 0) {
    			var origamt = this.$launchAmount(whom.commodity);
    			if (origamt > 0) {
    				for (var i = 0; i < this._cargoAtLaunch.length; i++) {
    					if (this._cargoAtLaunch[i].commodity === whom.commodity) this._cargoAtLaunch[i].quantity += whom.commodityAmount;
    				}
    			} else {
    				this._cargoAtLaunch.push({
    					commodity: whom.commodity,
    					quantity: whom.commodityAmount
    				});
    			}
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.playerSoldCargo = function (commodity, units, price) {
    	// did the player just sell cargo that has been relabelled?
    	// if so, reverse the sale: add the relabelled units back, give the player their money back, and send a console message
    	var p = player.ship;
    	var relabelled_amount = this.$getTotalRelabelled(commodity);
    	if (relabelled_amount > 0 && p.manifest[commodity] < relabelled_amount) {
    		var goodunits = units - relabelled_amount;
    		if (units === 1) goodunits = 0;
    		var reclaimunits = units - goodunits;
    
    		p.manifest[commodity] = relabelled_amount;
    		p.dockedStation.setMarketQuantity(commodity, p.dockedStation.market[commodity].quantity - reclaimunits);
    
    		player.credits -= ((reclaimunits * price) / 10);
    		player.consoleMessage("Sale of relabelled cargo reversed");
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.guiScreenChanged_hide = function (to, from) {
    	if (guiScreen === "GUI_SCREEN_INTERFACES") {
    		mission.unmarkSystem({
    			system: this._marker.id1,
    			name: this.name + "_mark1"
    		});
    		mission.unmarkSystem({
    			system: this._marker.id2,
    			name: this.name + "_mark2"
    		});
    		if (this._marker.id1 < this._marker.id2) {
    			SystemInfo.setInterstellarProperty(galaxyNumber, this._marker.id1, this._marker.id2, 2, "link_color", null);
    		} else {
    			SystemInfo.setInterstellarProperty(galaxyNumber, this._marker.id2, this._marker.id1, 2, "link_color", null);
    		}
    		this._marker = {};
    		delete this.guiScreenChanged;
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns the total number of units that have been relabelled to the passed commodity type
    this.$getTotalRelabelled = function $getTotalRelabelled(commodity) {
    	var amount = 0;
    	for (var i = 0; i < this._cargoLabelled.length; i++) {
    		if (this._cargoLabelled[i].newCommodity === commodity) amount += this._cargoLabelled[i].quantity;
    	}
    	return amount;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns the total number of units of the original commodity
    this.$getTotalUnlabelled = function $getTotalUnlabelled(commodity) {
    	var amount = 0;
    	for (var i = 0; i < this._cargoLabelled.length; i++) {
    		if (this._cargoLabelled[i].oldCommodity === commodity) amount += this._cargoLabelled[i].quantity;
    	}
    	return amount;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns the amount of a particular commodity we had at launch
    this.$launchAmount = function $launchAmount(commodity) {
    	for (var i = 0; i < this._cargoAtLaunch.length; i++) {
    		if (this._cargoAtLaunch[i].commodity === commodity) return this._cargoAtLaunch[i].quantity;
    	}
    	return 0;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // initialise the dock master F4 screen entries
    this.$initMainInterface = function $initMainInterface(station) {
    	// no station? no interface
    	if (!station) return;
    
    	// not galcop aligned? no interface
    	if (station.allegiance != "galcop") return;
    
    	// did we get to the station when the dock master is on duty?
    	if (clock.hoursComponent < this._dockMasterStartTime || clock.hoursComponent > this._dockMasterEndTime) return;
    
    	if (this._bribeAttempted === false) {
    		station.setInterface(this.name + "dockmaster", {
    			title: "Dock master",
    			category: "Station Interfaces",
    			summary: "Talk to the dock master of the station",
    			callback: this.$initialDockMasterOptions.bind(this)
    		});
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // initialises the remove labelling interface screen
    this.$initUnlabelInterface = function $initUnlabelInterface(station) {
    	station.setInterface(this.name + "unlabel", {
    		title: "Remove cargo relabelling",
    		category: "Ship Systems",
    		summary: "Removes fake manifest labelling applied to your cargo.",
    		callback: this.$removeLabellingList.bind(this)
    	});
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$initialDockMasterOptions = function $initialDockMasterOptions() {
    	this._display = 0;
    	this.$showDockMasterOptions();
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$showDockMasterOptions = function $showDockMasterOptions() {
    
    	var curChoices = {};
    	var text = "";
    	var p = player.ship;
    	var def = "";
    
    	var pagesize = 20;
    	if (this.$isBigGuiActive() === true) pagesize = 26;
    
    	if (this._display === 0) {
    		if (clock.hoursComponent < 12) text += "Good morning";
    		if (clock.hoursComponent >= 12 && clock.hoursComponent < 18) text += "Good afternoon";
    		if (clock.hoursComponent >= 18) text += "Good evening";
    
    		text += " " + expandDescription("[dockmaster-welcome]");
    
    		var itemcount = 1;
    		curChoices["01_MILKRUN"] = {
    			text: "[dockmaster-milkruns]",
    			color: this._menuColor
    		};
    		if (this._bribeAttempted === false && player.credits > 0 && this.$playerHasBribableCargo() === true) {
    			curChoices["02_BRIBE"] = {
    				text: "[dockmaster-bribe-cargo]",
    				color: this._menuColor
    			};
    			itemcount += 1;
    		}
    		if (worldScripts.BountySystem_MostWanted && worldScripts.StationDockControl && p.equipmentStatus("EQ_HUNTER_LICENCE") === "EQUIPMENT_OK" && this._destReleaseCount < 3 && this._flagged.length > 0) {
    			curChoices["05_HUNTER_REQ"] = {
    				text: "[dockmaster-release-dest]",
    				color: this._menuColor
    			};
    			itemcount += 1;
    		}
    		curChoices["98_SPACER"] = "";
    		itemcount += 1;
    
    		curChoices["99_EXIT"] = {
    			text: "Return",
    			color: this._itemColor
    		};
    
    		for (var i = 0; i < ((pagesize - 2) - itemcount); i++) {
    			curChoices["99_SPACER_" + i] = "";
    		}
    
    		def = "99_EXIT";
    
    		var opts = {
    			screenID: "oolite-smuggling-dockmaster-map",
    			title: "Dock Master",
    			allowInterrupt: true,
    			overlay: {
    				name: "stgu-chat.png",
    				height: 546
    			},
    			exitScreen: "GUI_SCREEN_INTERFACES",
    			choices: curChoices,
    			initialChoicesKey: def,
    			message: text
    		};
    
    	}
    
    	// milk runs
    	if (this._display === 1) {
    		text = expandDescription("[milkrun-apology]");
    		var found = false;
    
    		for (var i = this._milkRuns.length - 1; i >= 0; i--) {
    			var dist1 = System.infoForSystem(galaxyNumber, system.ID).distanceToSystem(System.infoForSystem(galaxyNumber, this._milkRuns[i].id1));
    			var dist2 = System.infoForSystem(galaxyNumber, system.ID).distanceToSystem(System.infoForSystem(galaxyNumber, this._milkRuns[i].id2));
    			if (dist1 < 15 || dist2 < 15) {
    				// found one
    				text = "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + expandDescription("[milkrun]", {
    					system1: System.infoForSystem(galaxyNumber, this._milkRuns[i].id1).name,
    					system2: System.infoForSystem(galaxyNumber, this._milkRuns[i].id2).name
    				});
    				this._marker = this._milkRuns[i];
    				this.guiScreenChanged = this.guiScreenChanged_hide;
    				mission.markSystem({
    					system: this._milkRuns[i].id1,
    					name: this.name + "_mark1",
    					markerShape: "MARKER_SQUARE",
    					markerColor: "whiteColor",
    					markerScale: 1.5
    				});
    				mission.markSystem({
    					system: this._milkRuns[i].id2,
    					name: this.name + "_mark2",
    					markerShape: "MARKER_SQUARE",
    					markerColor: "whiteColor",
    					markerScale: 1.5
    				});
    				if (this._milkRuns[i].id1 < this._milkRuns[i].id2) {
    					SystemInfo.setInterstellarProperty(galaxyNumber, this._milkRuns[i].id1, this._milkRuns[i].id2, 2, "link_color", "whiteColor");
    				} else {
    					SystemInfo.setInterstellarProperty(galaxyNumber, this._milkRuns[i].id2, this._milkRuns[i].id1, 2, "link_color", "whiteColor");
    				}
    				found = true;
    				break;
    			}
    		}
    		if (found === false) {
    			var opts = {
    				screenID: "oolite-smuggling-dockmaster-map",
    				title: "Dock Master",
    				allowInterrupt: true,
    				overlay: {
    					name: "stgu-chat.png",
    					height: 546
    				},
    				exitScreen: "GUI_SCREEN_INTERFACES",
    				message: text
    			};
    		} else {
    			var opts = {
    				screenID: "oolite-smuggling-dockmaster-milkrun",
    				title: "Dock Master",
    				allowInterrupt: true,
    				backgroundSpecial: "LONG_RANGE_CHART",
    				exitScreen: "GUI_SCREEN_INTERFACES",
    				message: text
    			};
    		}
    	}
    
    	// display cargo to alter
    	if (this._display === 2) {
    		var cargo = p.manifest.list;
    		var itemcount = 0;
    		var list = [];
    
    		for (var i = 0; i < cargo.length; i++) {
    			// make sure we don't relabel relabelled cargo
    			var q = cargo[i].quantity;
    			if (this._cargoLabelled.length > 0) {
    				for (var j = 0; j < this._cargoLabelled.length; j++) {
    					if (this._cargoLabelled[j].newCommodity === cargo[i].commodity) q -= this._cargoLabelled[j].quantity;
    				}
    			}
    			// make sure we don't relabel New Cargoes
    			var cteCargo = worldScripts.Smugglers_Equipment.$calcCTEcargo(cargo[i].commodity);
    			if (cteCargo > 0) q -= cteCargo;
    			
    			if (q > 0) {
    				if (this._noSwitch.indexOf(cargo[i].commodity) === -1) {
    					list.push({
    						menu: "50_" + (i < 10 ? "0" : "") + i + "_CARGO~" + cargo[i].commodity,
    						text: cargo[i].displayName + " (" + q + cargo[i].unit + ")",
    						color: this._menuColor
    					})
    				}
    			}
    		}
    
    		if (list.length >= pagesize) {
    			pagesize -= 4;
    		} else {
    			pagesize -= 2;
    		}
    
    		for (var i = 0; i < pagesize; i++) {
    			var ref = i + (this._curPage * pagesize);
    			if (ref < list.length) {
    				curChoices[list[ref].menu] = {
    					text: list[ref].text,
    					color: list[ref].color
    				};
    				itemcount += 1;
    			}
    		}
    
    		if (list.length > pagesize) {
    			if (this._curPage < Math.floor(list.length / pagesize)) {
    				curChoices["Z_NEXT"] = {
    					text: "Next page",
    					color: this._itemColor
    				};
    			} else {
    				curChoices["Z_NEXT"] = {
    					text: "Next page",
    					color: this._disabledColor,
    					unselectable: true
    				};
    			}
    			if (this._curPage > 0) {
    				curChoices["Z_PREV"] = {
    					text: "Previous page",
    					color: this._itemColor
    				};
    			} else {
    				curChoices["Z_PREV"] = {
    					text: "Previous page",
    					color: this._disabledColor,
    					unselectable: true
    				};
    			}
    		}
    
    		for (var i = 0; i < ((pagesize + 1) - itemcount); i++) {
    			curChoices["99_SPACER_" + i] = "";
    		}
    
    		var opts = {
    			screenID: "oolite-smuggling-dockmaster-map",
    			title: "Dock Master",
    			overlay: {
    				name: "stgu-chat.png",
    				height: 546
    			},
    			allowInterrupt: false,
    			exitScreen: "GUI_SCREEN_INTERFACES",
    			choices: curChoices,
    			messageKey: "cargo-relabel-accept"
    		};
    	}
    
    	// display options to switch to
    	if (this._display === 3) {
    
    		var se = worldScripts.Smugglers_Equipment;
    
    		text = expandMissionText("cargo-relabel-question", {
    			cargo: displayNameForCommodity(this._tfrCargo)
    		});
    
    		var itemcount = 0;
    		var list = [];
    
    		for (var i = 0; i < se._commodityIDList.length; i++) {
    			if (this._noSwitch.indexOf(se._commodityIDList[i]) === -1 && se._commodityIDList != this._tfrCargo) {
    				list.push({
    					menu: "40_" + (i < 10 ? "0" : "") + i + "~" + se._commodityIDList[i],
    					text: displayNameForCommodity(se._commodityIDList[i]),
    					color: this._menuColor
    				})
    			}
    		}
    		if (list.length >= pagesize) {
    			pagesize -= 4;
    		} else {
    			pagesize -= 2;
    		}
    		for (var i = 0; i < pagesize; i++) {
    			var ref = i + (this._curPage * pagesize);
    			if (ref < list.length) {
    				curChoices[list[ref].menu] = {
    					text: list[ref].text,
    					color: list[ref].color
    				};
    				itemcount += 1;
    			}
    		}
    
    		if (list.length > pagesize) {
    			if (this._curPage < Math.floor(list.length / pagesize)) {
    				curChoices["99_NEXT"] = {
    					text: "Next page",
    					color: this._itemColor
    				};
    			} else {
    				curChoices["99_NEXT"] = {
    					text: "Next page",
    					color: this._disabledColor,
    					unselectable: true
    				};
    			}
    			if (this._curPage > 0) {
    				curChoices["99_PREV"] = {
    					text: "Previous page",
    					color: this._itemColor
    				};
    			} else {
    				curChoices["99_PREV"] = {
    					text: "Previous page",
    					color: this._disabledColor,
    					unselectable: true
    				};
    			}
    		}
    		for (var i = 0; i < ((pagesize + 1) - itemcount); i++) {
    			curChoices["60_SPACER_" + i] = "";
    		}
    
    		var opts = {
    			screenID: "oolite-smuggling-dockmaster-map",
    			title: "Dock Master",
    			overlay: {
    				name: "stgu-chat.png",
    				height: 546
    			},
    			allowInterrupt: false,
    			exitScreen: "GUI_SCREEN_INTERFACES",
    			choices: curChoices,
    			message: text
    		};
    
    	}
    
    	// linking bounty most wanted and SDC
    	if (this._display === 4) {
    		// display list of flagged ships 
    		text = "Which ship are you interested in seeing the destination for?";
    		var itemcount = 0;
    
    		for (var i = 0; i < this._flagged.length; i++) {
    			// if you don't have the dough, it won't let you go
    			// saves having to have another response screen saying "You don't have enough cash"
    			if (player.credits > parseInt(this._flagged[i].cost)) {
    				curChoices["01_FLAGGED~" + i] = {
    					text: this._flagged[i].text,
    					color: this._menuColor,
    					alignment: "LEFT"
    				};
    			} else {
    				curChoices["01_FLAGGED~" + i] = {
    					text: this._flagged[i].text,
    					color: this._disabledColor,
    					alignment: "LEFT",
    					unselectable: true
    				};
    			}
    			itemcount += 1;
    		}
    
    		curChoices["97_SPACER"] = "";
    		itemcount += 1;
    
    		curChoices["98_EXIT"] = {
    			text: "Return",
    			color: this._itemColor
    		};
    
    		for (var i = 0; i < ((pagesize - 2) - itemcount); i++) {
    			curChoices["97_SPACER_" + i] = "";
    		}
    
    		def = "98_EXIT";
    
    		var opts = {
    			screenID: "oolite-smuggling-dockmaster-map",
    			title: "Dock Master",
    			allowInterrupt: true,
    			overlay: {
    				name: "stgu-chat.png",
    				height: 546
    			},
    			exitScreen: "GUI_SCREEN_INTERFACES",
    			choices: curChoices,
    			initialChoicesKey: def,
    			message: text
    		};
    	}
    
    	// ship dest unlock successful
    	if (this._display === 5) {
    		text = "The destination of the ship has been unlocked. Good hunting, commander!";
    
    		curChoices["98A_EXIT"] = {
    			text: "Return",
    			color: this._itemColor
    		};
    		def = "98A_EXIT";
    
    		var opts = {
    			screenID: "oolite-smuggling-dockmaster-map",
    			title: "Dock Master",
    			allowInterrupt: true,
    			overlay: {
    				name: "stgu-chat.png",
    				height: 546
    			},
    			exitScreen: "GUI_SCREEN_INTERFACES",
    			choices: curChoices,
    			initialChoicesKey: def,
    			message: text
    		};
    
    	}
    
    	// ship dest unlock unsuccessful
    	if (this._display === 6) {
    		text = "I'm not sure what's happened, but I can't find that ship in the dock list. It may have already launched.";
    
    		curChoices["98A_EXIT"] = {
    			text: "Return",
    			color: this._itemColor
    		};
    		def = "98A_EXIT";
    
    		var opts = {
    			screenID: "oolite-smuggling-dockmaster-map",
    			title: "Dock Master",
    			allowInterrupt: true,
    			overlay: {
    				name: "stgu-chat.png",
    				height: 546
    			},
    			exitScreen: "GUI_SCREEN_INTERFACES",
    			choices: curChoices,
    			initialChoicesKey: def,
    			message: text
    		};
    	}
    
    	mission.runScreen(opts, this.$screenHandler, this);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$screenHandler = function $screenHandler(choice) {
    
    	if (choice == null) return;
    
    	switch (choice) {
    		case "01_MILKRUN":
    			this._display = 1;
    			break;
    		case "02_BRIBE":
    			this.$getBribeAmount("manifest");
    			return;
    		case "05_HUNTER_REQ":
    			this._display = 4;
    			break;
    		case "98_EXIT":
    			this._display = 1;
    			break;
    		case "98A_EXIT":
    			this._display = 0;
    			break;
    		case "99_NEXT":
    			this._curPage += 1;
    			break;
    		case "99_PREV":
    			this._curPage -= 1;
    			break;
    	}
    
    	if (choice.indexOf("01_FLAGGED") >= 0) {
    		var idx = parseInt(choice.substring(choice.indexOf("~") + 1));
    		var ref = parseInt(this._flagged[idx].index);
    		var shpRef = parseInt(this._flagged[idx].shipIndex);
    		var cost = parseInt(this._flagged[idx].cost);
    		var mw = worldScripts.BountySystem_MostWanted;
    		var found = false;
    		for (var i = 0; i < mw._wanted.length; i++) {
    			if (mw._wanted[i].index === ref) {
    				var sdci = worldScripts.StationDockControl_Interface;
    				var item = sdci._viewing[shpRef];
    				if (item.pilotName === mw._wanted[i].pilotName) {
    					player.credits -= cost;
    					player.consoleMessage("You have been charged " + formatCredits(cost, false, true));
    					item.destinationHidden = false;
    					this._destReleaseCount += 1;
    					this._flagged.splice(idx, 1);
    					found = true;
    					this._display = 5;
    				}
    			}
    		}
    		if (found === false) {
    			this._display = 6;
    		}
    	}
    
    	if (choice.indexOf("50_") >= 0) {
    		this._tfrCargo = choice.substring(choice.indexOf("~") + 1);
    		this._display = 3;
    		this._curPage = 0;
    	}
    
    	if (choice.indexOf("40_") >= 0) {
    		var newCmdty = choice.substring(choice.indexOf("~") + 1);
    		var p = player.ship;
    		var amount = p.manifest[this._tfrCargo];
    
    		// if we already have relabelled the selected cargo, get the un-relabelled portion of it
    		for (var i = 0; i < this._cargoLabelled.length; i++) {
    			if (this._cargoLabelled[i].newCommodity === this._tfrCargo) amount -= this._cargoLabelled[i].quantity;
    		}
    
    		// do we already have some relabelled cargo of the same type (matching old and new
    		var found = false;
    		for (var i = 0; i < this._cargoLabelled.length; i++) {
    			if (this._cargoLabelled[i].newCommodity === newCmdty && this._cargoLabelled[i].oldCommodity === this._tfrCargo) {
    				this._cargoLabelled[i].quantity += amount
    				found = true;
    			}
    		}
    		if (found === false) this._cargoLabelled.push({
    			oldCommodity: this._tfrCargo,
    			newCommodity: newCmdty,
    			quantity: amount
    		});
    
    		p.manifest[this._tfrCargo] -= amount;
    		p.manifest[newCmdty] += amount;
    
    		this.$initUnlabelInterface(p.dockedStation);
    
    		var extramsg = "";
    		var se = worldScripts.Smugglers_Equipment;
    		if (se.$hasSmugglingCompartment() === true) extramsg = expandMissionText("cargo-relabel-reminder");
    
    		mission.runScreen({
    			screenID: "oolite-smuggling-dockmaster-map",
    			title: "Dock Master",
    			overlay: {
    				name: "stgu-chat.png",
    				height: 546
    			},
    			message: expandDescription("[dockmaster-complete]") + extramsg,
    			exitScreen: "GUI_SCREEN_INTERFACES"
    		});
    		p.dockedStation.setInterface(this.name + "dockmaster", null);
    	}
    
    
    	if (choice != "99_EXIT" && choice.indexOf("40_") === -1) {
    		this.$showDockMasterOptions();
    	}
    
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // prompts the user for a new phase
    this.$getBribeAmount = function $getBribeAmount(type) {
    
    	var text = "";
    
    	this._bribeAttempted = true;
    
    	var inttype = Math.floor((this._bribeChance[system.ID] * 4) + 1);
    	var inttypedesc = expandDescription("[interest-type" + inttype + "]");
    	var lastBribe = this.$previousBribeAmount();
    	if (type === "manifest") {
    		text = expandDescription("[dockmaster-question]", {
    				interesttype: inttypedesc
    			}) + "\n\n" +
    			"(" + expandDescription("[dockmaster-cost]", {
    				credits: formatCredits(player.credits, false, true)
    			}) +
    			(lastBribe > 0 ? expandDescription("[dockmaster-lastbribe]", {
    				bribeamount: formatCredits(lastBribe, false, true)
    			}) : "") + ")";
    	} else {
    		text = "So you want the keys to my kingdom, eh? That's a pretty serious offence. " + expandDescription("[dockmaster-question]", {
    				interesttype: inttypedesc
    			}) + "\n\n" +
    			"(" + expandDescription("[dockmaster-cost]", {
    				credits: formatCredits(player.credits, false, true)
    			})
    	}
    
    	var opts = {
    		screenID: "oolite-smuggling-dockmaster-map",
    		title: "Dock Master",
    		allowInterrupt: false,
    		overlay: {
    			name: "stgu-chat.png",
    			height: 546
    		},
    		exitScreen: "GUI_SCREEN_INTERFACES",
    		message: text,
    		textEntry: true
    	};
    
    	mission.runScreen(opts, this.$getBribeAmountInputManifest, this);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // parses the input from the getBribeAmount screen
    this.$getBribeAmountInputManifest = function $getBribeAmountInputManifest(param) {
    	var p = player.ship;
    	if (parseInt(param) >= 1 && parseInt(param) <= player.credits) {
    		var amount = parseInt(param);
    		// will this work
    		var chance = this._bribeChance[system.ID];
    		// higher amounts are more likely to be accepted
    		// chance of 0.5 would mean you would need to offer them 1690 credits
    		//if (this._debug) log(this.name, "min bribe amount = " + (Math.pow(parseInt(chance * 32), 1.8) * (1 + system.productivity / 56300) * this._bribeCount[system.ID]));
    		if ((Math.pow(parseInt(chance * 32), 1.8) * (1 + system.productivity / 56300) * this._bribeCount[system.ID]) <= amount) {
    			player.credits -= amount;
    			this._display = 2;
    			this._curPage = 0;
    			this.$addBribe(system.ID);
    			this._successfulBribes.push({
    				system: system.ID,
    				bribeAmount: amount
    			});
    			this.$showDockMasterOptions();
    		} else {
    			this._display = 0;
    			if (Math.random() > chance) {
    				mission.runScreen({
    					screenID: "oolite-smuggling-dockmaster-map",
    					title: "Dock Master",
    					overlay: {
    						name: "stgu-chat.png",
    						height: 546
    					},
    					message: expandDescription("[dockmaster-angry-nopenalty]"),
    					exitScreen: "GUI_SCREEN_INTERFACES"
    				});
    			} else {
    				var penalty = (this.$rand(10) + 3);
    				p.setBounty(player.bounty + penalty, "attempted bribe");
    				mission.runScreen({
    					screenID: "oolite-smuggling-dockmaster-map",
    					title: "Dock Master",
    					overlay: {
    						name: "stgu-chat.png",
    						height: 546
    					},
    					message: expandDescription("[dockmaster-angry-penalty]"),
    					exitScreen: "GUI_SCREEN_INTERFACES"
    				});
    				// send email (if installed)
    				var email = worldScripts.EmailSystem;
    				if (email) {
    					var ga = worldScripts.GalCopAdminServices;
    					email.$createEmail({
    						sender: "GalCop Customs",
    						subject: "Attempt to bribe official",
    						date: clock.seconds,
    						message: expandDescription("[smugglers-failed-bribe-email]", {
    							legal_penalty: penalty,
    							stationname: player.ship.dockedStation.displayName,
    							systemname: System.systemNameForID(system.ID)
    						}),
    						expiryDays: ga._defaultExpiryDays
    					});
    				}
    			}
    			p.dockedStation.setInterface(this.name + "dockmaster", null);
    		}
    	} else {
    		this._display = 0;
    		mission.runScreen({
    			screenID: "oolite-smuggling-dockmaster-map",
    			title: "Bribing Official",
    			overlay: {
    				name: "stgu-chat.png",
    				height: 546
    			},
    			message: expandDescription("[dockmaster-skipbribe]"),
    			exitScreen: "GUI_SCREEN_STATUS"
    		});
    		p.dockedStation.setInterface(this.name + "dockmaster", null);
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$removeLabellingList = function $removeLabellingList() {
    
    	var curChoices = {};
    	var se = worldScripts.Smugglers_Equipment;
    
    	var pagesize = 20;
    
    	if (this.$isBigGuiActive() === true) pagesize = 26;
    
    	for (var i = 0; i < this._cargoLabelled.length; i++) {
    		curChoices["01_CARGO~" + (i < 10 ? "0" : "") + i] = {
    			text: this._cargoLabelled[i].quantity + se.$getCommodityType(this._cargoLabelled[i].newCommodity) + " " + displayNameForCommodity(this._cargoLabelled[i].newCommodity) +
    				", back to " + displayNameForCommodity(this._cargoLabelled[i].oldCommodity),
    			color: this._menuColor
    		};
    	}
    
    	curChoices["98_SPACER_" + i] = "";
    	curChoices["99_EXIT"] = {
    		text: "Return",
    		color: this._itemColor
    	};
    
    	for (var i = 0; i < ((pagesize - 3) - this._cargoLabelled.length); i++) {
    		curChoices["99_SPACER_" + i] = "";
    	}
    
    	var def = "99_EXIT";
    
    	var opts = {
    		screenID: "oolite-smuggling-dockmaster-map",
    		title: "Remove Cargo Relabelling",
    		allowInterrupt: true,
    		overlay: {
    			name: "stgu-id.png",
    			height: 546
    		},
    		exitScreen: "GUI_SCREEN_INTERFACES",
    		choices: curChoices,
    		initialChoicesKey: def,
    		messageKey: "cargo-relabel-heading"
    	};
    
    	mission.runScreen(opts, this.$removeLabellingHandler, this);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$removeLabellingHandler = function $removeLabellingHandler(choice) {
    	if (choice == null) return;
    
    	if (choice.indexOf("01_") >= 0) {
    		var idx = parseInt(choice.substring(choice.indexOf("~") + 1));
    		this.$removeLabelling(idx);
    		if (this._cargoLabelled.length > 0) this.$removeLabellingList();
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$removeLabelling = function $removeLabelling(idx) {
    
    	var p = player.ship;
    
    	// revert cargo back to original
    	// make sure we still have the right amount
    	if (p.manifest[this._cargoLabelled[idx].newCommodity] < this._cargoLabelled[idx].quantity)
    		this._cargoLabelled[idx].quantity = p.manifest[this._cargoLabelled[idx].newCommodity];
    	// deduct from the new commodity name
    	p.manifest[this._cargoLabelled[idx].newCommodity] -= this._cargoLabelled[idx].quantity;
    	// add to the old commodity name
    	p.manifest[this._cargoLabelled[idx].oldCommodity] += this._cargoLabelled[idx].quantity;
    
    	this._cargoLabelled.splice(idx, 1);
    
    	if (this._cargoLabelled.length === 0) {
    		p.dockedStation.setInterface(this.name + "unlabel", null);
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // sets up the chance a dock master can be bribed, for each of the main stations
    this.$setupChance = function $setupChance() {
    	this._bribeChance = [];
    	for (var i = 0; i <= 255; i++) {
    		this._bribeChance.push(Math.random());
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // sets the time frame where the dock master can be called on - he doesn't work 24 hours a day!
    this.$setDockMasterHours = function $setDockMasterHours() {
    	// set a start time between 7 and 10
    	this._dockMasterStartTime = 6 + this.$rand(4);
    	// set an end time between 7 and 10 hours later
    	this._dockMasterEndTime = this.$dockMasterStartTime + (this.$rand(4) + 6)
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // removes labelling on a specific amount of cargo
    this.$removeRealCargoQuantity = function $removeRealCargoQuantity(commodity, amount) {
    	var p = player.ship;
    	var remainder = amount;
    	for (var i = this._cargoLabelled.length - 1; i >= 0; i--) {
    		if (remainder > 0 && this._cargoLabelled[i].oldCommodity === commodity) {
    			if (this._cargoLabelled[i].quantity >= remainder) {
    				if (p.manifest[this._cargoLabelled[i].newCommodity] >= remainder) {
    					p.manifest[this._cargoLabelled[i].newCommodity] -= remainder;
    					p.manifest[this._cargoLabelled[i].oldCommodity] += remainder;
    					this._cargoLabelled[i].quantity -= remainder;
    					remainder = 0;
    				} else {
    					var subamt = p.manifest[this._cargoLabelled[i].newCommodity];
    					p.manifest[this._cargoLabelled[i].newCommodity] = 0;
    					p.manifest[this._cargoLabelled[i].oldCommodity] += subamt;
    					this._cargoLabelled[i].quantity -= subamt;
    					remainder -= subamt;
    				}
    			} else {
    				var correction = remainder - this._cargoLabelled[i].quantity;
    				if (p.manifest[this._cargoLabelled[i].newCommodity] >= correction) {
    					p.manifest[this._cargoLabelled[i].newCommodity] -= correction;
    					p.manifest[this._cargoLabelled[i].oldCommodity] += correction;
    					this._cargoLabelled[i].quantity -= correction;
    					remainder -= correction;
    				} else {
    					var subamt = p.manifest[this._cargoLabelled[i].oldCommodity];
    					p.manifest[this._cargoLabelled[i].newCommodity] = 0;
    					p.manifest[this._cargoLabelled[i].oldCommodity] += subamt;
    					this._cargoLabelled[i].quantity = 0;
    					remainder -= subamt;
    				}
    			}
    		}
    	}
    	return remainder;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns true if the player cargo that the dock master can relabel, otherwise false
    this.$playerHasBribableCargo = function $playerHasBribableCargo() {
    	var p = player.ship;
    	var cargo = p.manifest.list;
    	var result = false;
    	for (var i = 0; i < cargo.length; i++) {
    		if (this._noSwitch.indexOf(cargo[i].commodity) === -1) result = true;
    	}
    	return result;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // increment the number of times the dm has been bribed
    this.$addBribe = function $addBribe(sysID) {
    	this._bribeCount[sysID] += 0.1 + (system.pseudoRandomNumber > 0.5 ? 0.1 : 0);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // reset the bribecount array
    this.$resetBribeCount = function $resetBribeCount() {
    	this._bribeCount = [];
    	for (var i = 0; i <= 255; i++) {
    		this._bribeCount.push(1);
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns the last successful bribe amount for the current system
    this.$previousBribeAmount = function $previousBribeAmount() {
    	var bribeAmt = 0;
    	for (var i = 0; i < this._successfulBribes.length; i++) {
    		if (this._successfulBribes[i].system === system.ID && this._successfulBribes[i].bribeAmount > bribeAmt) {
    			bribeAmt = this._successfulBribes[i].bribeAmount;
    		}
    	}
    	return bribeAmt;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // loads the mulk run array with data for the current galaxy
    this.$getMilkRuns = function $getMilkRuns() {
    	this._milkRuns = [];
    	switch (galaxyNumber) {
    		case 0:
    			this._milkRuns.push({
    				id1: 99,
    				id2: 84,
    				rating: 70
    			});
    			this._milkRuns.push({
    				id1: 99,
    				id2: 191,
    				rating: 72
    			});
    			this._milkRuns.push({
    				id1: 12,
    				id2: 116,
    				rating: 73
    			});
    			this._milkRuns.push({
    				id1: 163,
    				id2: 192,
    				rating: 73
    			});
    			this._milkRuns.push({
    				id1: 131,
    				id2: 42,
    				rating: 73
    			});
    			this._milkRuns.push({
    				id1: 254,
    				id2: 187,
    				rating: 74
    			});
    			this._milkRuns.push({
    				id1: 158,
    				id2: 116,
    				rating: 75
    			});
    			this._milkRuns.push({
    				id1: 98,
    				id2: 84,
    				rating: 75
    			});
    			this._milkRuns.push({
    				id1: 98,
    				id2: 191,
    				rating: 75
    			});
    			this._milkRuns.push({
    				id1: 195,
    				id2: 63,
    				rating: 76
    			});
    			this._milkRuns.push({
    				id1: 254,
    				id2: 251,
    				rating: 76
    			});
    			this._milkRuns.push({
    				id1: 79,
    				id2: 174,
    				rating: 76
    			});
    			this._milkRuns.push({
    				id1: 49,
    				id2: 116,
    				rating: 76
    			});
    			this._milkRuns.push({
    				id1: 254,
    				id2: 74,
    				rating: 77
    			});
    			this._milkRuns.push({
    				id1: 100,
    				id2: 42,
    				rating: 77
    			});
    			this._milkRuns.push({
    				id1: 207,
    				id2: 116,
    				rating: 77
    			});
    			this._milkRuns.push({
    				id1: 131,
    				id2: 167,
    				rating: 78
    			});
    			this._milkRuns.push({
    				id1: 240,
    				id2: 187,
    				rating: 78
    			});
    			this._milkRuns.push({
    				id1: 79,
    				id2: 191,
    				rating: 79
    			});
    			this._milkRuns.push({
    				id1: 33,
    				id2: 123,
    				rating: 81
    			});
    			this._milkRuns.push({
    				id1: 99,
    				id2: 185,
    				rating: 81
    			});
    			this._milkRuns.push({
    				id1: 82,
    				id2: 244,
    				rating: 83
    			});
    			this._milkRuns.push({
    				id1: 227,
    				id2: 154,
    				rating: 83
    			});
    			this._milkRuns.push({
    				id1: 35,
    				id2: 186,
    				rating: 84
    			});
    			this._milkRuns.push({
    				id1: 221,
    				id2: 185,
    				rating: 84
    			});
    			this._milkRuns.push({
    				id1: 163,
    				id2: 252,
    				rating: 86
    			});
    			this._milkRuns.push({
    				id1: 141,
    				id2: 101,
    				rating: 89
    			});
    			this._milkRuns.push({
    				id1: 250,
    				id2: 101,
    				rating: 89
    			});
    			this._milkRuns.push({
    				id1: 218,
    				id2: 18,
    				rating: 90
    			});
    			this._milkRuns.push({
    				id1: 82,
    				id2: 154,
    				rating: 90
    			});
    			this._milkRuns.push({
    				id1: 246,
    				id2: 18,
    				rating: 93
    			});
    			break;
    		case 1:
    			this._milkRuns.push({
    				id1: 255,
    				id2: 37,
    				rating: 72
    			});
    			this._milkRuns.push({
    				id1: 191,
    				id2: 37,
    				rating: 74
    			});
    			this._milkRuns.push({
    				id1: 128,
    				id2: 165,
    				rating: 74
    			});
    			this._milkRuns.push({
    				id1: 57,
    				id2: 94,
    				rating: 76
    			});
    			this._milkRuns.push({
    				id1: 24,
    				id2: 20,
    				rating: 78
    			});
    			this._milkRuns.push({
    				id1: 237,
    				id2: 174,
    				rating: 79
    			});
    			this._milkRuns.push({
    				id1: 16,
    				id2: 157,
    				rating: 80
    			});
    			this._milkRuns.push({
    				id1: 81,
    				id2: 20,
    				rating: 80
    			});
    			this._milkRuns.push({
    				id1: 167,
    				id2: 94,
    				rating: 81
    			});
    			this._milkRuns.push({
    				id1: 82,
    				id2: 174,
    				rating: 82
    			});
    			this._milkRuns.push({
    				id1: 40,
    				id2: 68,
    				rating: 82
    			});
    			this._milkRuns.push({
    				id1: 106,
    				id2: 211,
    				rating: 83
    			});
    			this._milkRuns.push({
    				id1: 57,
    				id2: 19,
    				rating: 83
    			});
    			this._milkRuns.push({
    				id1: 57,
    				id2: 115,
    				rating: 84
    			});
    			this._milkRuns.push({
    				id1: 185,
    				id2: 38,
    				rating: 84
    			});
    			this._milkRuns.push({
    				id1: 92,
    				id2: 211,
    				rating: 85
    			});
    			this._milkRuns.push({
    				id1: 191,
    				id2: 83,
    				rating: 85
    			});
    			this._milkRuns.push({
    				id1: 16,
    				id2: 34,
    				rating: 87
    			});
    			this._milkRuns.push({
    				id1: 40,
    				id2: 179,
    				rating: 88
    			});
    			this._milkRuns.push({
    				id1: 209,
    				id2: 115,
    				rating: 88
    			});
    			this._milkRuns.push({
    				id1: 81,
    				id2: 97,
    				rating: 90
    			});
    			this._milkRuns.push({
    				id1: 82,
    				id2: 86,
    				rating: 92
    			});
    			this._milkRuns.push({
    				id1: 167,
    				id2: 115,
    				rating: 92
    			});
    			this._milkRuns.push({
    				id1: 231,
    				id2: 147,
    				rating: 94
    			});
    			break;
    		case 2:
    			this._milkRuns.push({
    				id1: 205,
    				id2: 168,
    				rating: 71
    			});
    			this._milkRuns.push({
    				id1: 144,
    				id2: 147,
    				rating: 73
    			});
    			this._milkRuns.push({
    				id1: 34,
    				id2: 57,
    				rating: 73
    			});
    			this._milkRuns.push({
    				id1: 64,
    				id2: 168,
    				rating: 73
    			});
    			this._milkRuns.push({
    				id1: 76,
    				id2: 152,
    				rating: 76
    			});
    			this._milkRuns.push({
    				id1: 76,
    				id2: 66,
    				rating: 76
    			});
    			this._milkRuns.push({
    				id1: 205,
    				id2: 82,
    				rating: 77
    			});
    			this._milkRuns.push({
    				id1: 155,
    				id2: 57,
    				rating: 77
    			});
    			this._milkRuns.push({
    				id1: 116,
    				id2: 152,
    				rating: 77
    			});
    			this._milkRuns.push({
    				id1: 17,
    				id2: 24,
    				rating: 77
    			});
    			this._milkRuns.push({
    				id1: 205,
    				id2: 114,
    				rating: 79
    			});
    			this._milkRuns.push({
    				id1: 17,
    				id2: 41,
    				rating: 79
    			});
    			this._milkRuns.push({
    				id1: 17,
    				id2: 147,
    				rating: 79
    			});
    			this._milkRuns.push({
    				id1: 22,
    				id2: 140,
    				rating: 79
    			});
    			this._milkRuns.push({
    				id1: 64,
    				id2: 215,
    				rating: 79
    			});
    			this._milkRuns.push({
    				id1: 58,
    				id2: 152,
    				rating: 80
    			});
    			this._milkRuns.push({
    				id1: 50,
    				id2: 65,
    				rating: 82
    			});
    			this._milkRuns.push({
    				id1: 42,
    				id2: 163,
    				rating: 83
    			});
    			this._milkRuns.push({
    				id1: 58,
    				id2: 140,
    				rating: 84
    			});
    			this._milkRuns.push({
    				id1: 205,
    				id2: 241,
    				rating: 84
    			});
    			this._milkRuns.push({
    				id1: 43,
    				id2: 98,
    				rating: 84
    			});
    			this._milkRuns.push({
    				id1: 22,
    				id2: 91,
    				rating: 84
    			});
    			this._milkRuns.push({
    				id1: 42,
    				id2: 135,
    				rating: 85
    			});
    			this._milkRuns.push({
    				id1: 54,
    				id2: 118,
    				rating: 86
    			});
    			this._milkRuns.push({
    				id1: 18,
    				id2: 100,
    				rating: 87
    			});
    			this._milkRuns.push({
    				id1: 5,
    				id2: 66,
    				rating: 87
    			});
    			this._milkRuns.push({
    				id1: 79,
    				id2: 65,
    				rating: 89
    			});
    			this._milkRuns.push({
    				id1: 10,
    				id2: 141,
    				rating: 89
    			});
    			this._milkRuns.push({
    				id1: 6,
    				id2: 180,
    				rating: 89
    			});
    			this._milkRuns.push({
    				id1: 58,
    				id2: 66,
    				rating: 89
    			});
    			this._milkRuns.push({
    				id1: 10,
    				id2: 70,
    				rating: 90
    			});
    			this._milkRuns.push({
    				id1: 5,
    				id2: 140,
    				rating: 90
    			});
    			this._milkRuns.push({
    				id1: 116,
    				id2: 122,
    				rating: 92
    			});
    			this._milkRuns.push({
    				id1: 10,
    				id2: 85,
    				rating: 94
    			});
    			break;
    		case 3:
    			this._milkRuns.push({
    				id1: 38,
    				id2: 160,
    				rating: 70
    			});
    			this._milkRuns.push({
    				id1: 25,
    				id2: 24,
    				rating: 72
    			});
    			this._milkRuns.push({
    				id1: 25,
    				id2: 62,
    				rating: 74
    			});
    			this._milkRuns.push({
    				id1: 220,
    				id2: 171,
    				rating: 75
    			});
    			this._milkRuns.push({
    				id1: 137,
    				id2: 105,
    				rating: 76
    			});
    			this._milkRuns.push({
    				id1: 209,
    				id2: 33,
    				rating: 77
    			});
    			this._milkRuns.push({
    				id1: 29,
    				id2: 168,
    				rating: 78
    			});
    			this._milkRuns.push({
    				id1: 58,
    				id2: 24,
    				rating: 78
    			});
    			this._milkRuns.push({
    				id1: 103,
    				id2: 107,
    				rating: 78
    			});
    			this._milkRuns.push({
    				id1: 65,
    				id2: 239,
    				rating: 78
    			});
    			this._milkRuns.push({
    				id1: 103,
    				id2: 105,
    				rating: 79
    			});
    			this._milkRuns.push({
    				id1: 103,
    				id2: 177,
    				rating: 79
    			});
    			this._milkRuns.push({
    				id1: 38,
    				id2: 53,
    				rating: 82
    			});
    			this._milkRuns.push({
    				id1: 42,
    				id2: 16,
    				rating: 83
    			});
    			this._milkRuns.push({
    				id1: 38,
    				id2: 74,
    				rating: 84
    			});
    			this._milkRuns.push({
    				id1: 103,
    				id2: 108,
    				rating: 84
    			});
    			this._milkRuns.push({
    				id1: 103,
    				id2: 239,
    				rating: 84
    			});
    			this._milkRuns.push({
    				id1: 220,
    				id2: 68,
    				rating: 84
    			});
    			this._milkRuns.push({
    				id1: 58,
    				id2: 109,
    				rating: 89
    			});
    			this._milkRuns.push({
    				id1: 220,
    				id2: 90,
    				rating: 90
    			});
    			break;
    		case 4:
    			this._milkRuns.push({
    				id1: 34,
    				id2: 186,
    				rating: 70
    			});
    			this._milkRuns.push({
    				id1: 34,
    				id2: 250,
    				rating: 70
    			});
    			this._milkRuns.push({
    				id1: 66,
    				id2: 58,
    				rating: 70
    			});
    			this._milkRuns.push({
    				id1: 2,
    				id2: 63,
    				rating: 71
    			});
    			this._milkRuns.push({
    				id1: 226,
    				id2: 31,
    				rating: 71
    			});
    			this._milkRuns.push({
    				id1: 130,
    				id2: 250,
    				rating: 72
    			});
    			this._milkRuns.push({
    				id1: 162,
    				id2: 250,
    				rating: 73
    			});
    			this._milkRuns.push({
    				id1: 34,
    				id2: 183,
    				rating: 74
    			});
    			this._milkRuns.push({
    				id1: 162,
    				id2: 183,
    				rating: 74
    			});
    			this._milkRuns.push({
    				id1: 137,
    				id2: 191,
    				rating: 75
    			});
    			this._milkRuns.push({
    				id1: 98,
    				id2: 123,
    				rating: 75
    			});
    			this._milkRuns.push({
    				id1: 130,
    				id2: 108,
    				rating: 76
    			});
    			this._milkRuns.push({
    				id1: 12,
    				id2: 81,
    				rating: 76
    			});
    			this._milkRuns.push({
    				id1: 213,
    				id2: 81,
    				rating: 77
    			});
    			this._milkRuns.push({
    				id1: 115,
    				id2: 150,
    				rating: 78
    			});
    			this._milkRuns.push({
    				id1: 162,
    				id2: 149,
    				rating: 78
    			});
    			this._milkRuns.push({
    				id1: 254,
    				id2: 183,
    				rating: 79
    			});
    			this._milkRuns.push({
    				id1: 212,
    				id2: 46,
    				rating: 80
    			});
    			this._milkRuns.push({
    				id1: 198,
    				id2: 31,
    				rating: 80
    			});
    			this._milkRuns.push({
    				id1: 254,
    				id2: 250,
    				rating: 82
    			});
    			this._milkRuns.push({
    				id1: 130,
    				id2: 149,
    				rating: 82
    			});
    			this._milkRuns.push({
    				id1: 194,
    				id2: 88,
    				rating: 83
    			});
    			this._milkRuns.push({
    				id1: 198,
    				id2: 63,
    				rating: 84
    			});
    			this._milkRuns.push({
    				id1: 99,
    				id2: 91,
    				rating: 84
    			});
    			this._milkRuns.push({
    				id1: 254,
    				id2: 149,
    				rating: 85
    			});
    			this._milkRuns.push({
    				id1: 99,
    				id2: 200,
    				rating: 85
    			});
    			this._milkRuns.push({
    				id1: 184,
    				id2: 150,
    				rating: 86
    			});
    			this._milkRuns.push({
    				id1: 89,
    				id2: 87,
    				rating: 88
    			});
    			this._milkRuns.push({
    				id1: 249,
    				id2: 16,
    				rating: 90
    			});
    			break;
    		case 5:
    			this._milkRuns.push({
    				id1: 243,
    				id2: 57,
    				rating: 70
    			});
    			this._milkRuns.push({
    				id1: 243,
    				id2: 228,
    				rating: 71
    			});
    			this._milkRuns.push({
    				id1: 182,
    				id2: 160,
    				rating: 71
    			});
    			this._milkRuns.push({
    				id1: 14,
    				id2: 249,
    				rating: 73
    			});
    			this._milkRuns.push({
    				id1: 240,
    				id2: 30,
    				rating: 75
    			});
    			this._milkRuns.push({
    				id1: 20,
    				id2: 57,
    				rating: 75
    			});
    			this._milkRuns.push({
    				id1: 243,
    				id2: 231,
    				rating: 75
    			});
    			this._milkRuns.push({
    				id1: 243,
    				id2: 101,
    				rating: 75
    			});
    			this._milkRuns.push({
    				id1: 243,
    				id2: 98,
    				rating: 76
    			});
    			this._milkRuns.push({
    				id1: 109,
    				id2: 57,
    				rating: 76
    			});
    			this._milkRuns.push({
    				id1: 243,
    				id2: 103,
    				rating: 77
    			});
    			this._milkRuns.push({
    				id1: 34,
    				id2: 24,
    				rating: 78
    			});
    			this._milkRuns.push({
    				id1: 69,
    				id2: 167,
    				rating: 78
    			});
    			this._milkRuns.push({
    				id1: 144,
    				id2: 61,
    				rating: 79
    			});
    			this._milkRuns.push({
    				id1: 109,
    				id2: 228,
    				rating: 79
    			});
    			this._milkRuns.push({
    				id1: 182,
    				id2: 167,
    				rating: 79
    			});
    			this._milkRuns.push({
    				id1: 240,
    				id2: 234,
    				rating: 80
    			});
    			this._milkRuns.push({
    				id1: 144,
    				id2: 253,
    				rating: 81
    			});
    			this._milkRuns.push({
    				id1: 144,
    				id2: 97,
    				rating: 81
    			});
    			this._milkRuns.push({
    				id1: 109,
    				id2: 98,
    				rating: 81
    			});
    			this._milkRuns.push({
    				id1: 20,
    				id2: 244,
    				rating: 81
    			});
    			this._milkRuns.push({
    				id1: 86,
    				id2: 131,
    				rating: 81
    			});
    			this._milkRuns.push({
    				id1: 109,
    				id2: 101,
    				rating: 82
    			});
    			this._milkRuns.push({
    				id1: 28,
    				id2: 108,
    				rating: 82
    			});
    			this._milkRuns.push({
    				id1: 1,
    				id2: 108,
    				rating: 82
    			});
    			this._milkRuns.push({
    				id1: 115,
    				id2: 215,
    				rating: 83
    			});
    			this._milkRuns.push({
    				id1: 109,
    				id2: 231,
    				rating: 83
    			});
    			this._milkRuns.push({
    				id1: 20,
    				id2: 103,
    				rating: 84
    			});
    			this._milkRuns.push({
    				id1: 1,
    				id2: 181,
    				rating: 86
    			});
    			this._milkRuns.push({
    				id1: 240,
    				id2: 140,
    				rating: 88
    			});
    			this._milkRuns.push({
    				id1: 14,
    				id2: 113,
    				rating: 88
    			});
    			this._milkRuns.push({
    				id1: 144,
    				id2: 13,
    				rating: 88
    			});
    			this._milkRuns.push({
    				id1: 102,
    				id2: 98,
    				rating: 88
    			});
    			this._milkRuns.push({
    				id1: 17,
    				id2: 108,
    				rating: 91
    			});
    			this._milkRuns.push({
    				id1: 6,
    				id2: 181,
    				rating: 92
    			});
    			this._milkRuns.push({
    				id1: 40,
    				id2: 215,
    				rating: 94
    			});
    			this._milkRuns.push({
    				id1: 17,
    				id2: 181,
    				rating: 96
    			});
    			this._milkRuns.push({
    				id1: 50,
    				id2: 232,
    				rating: 98
    			});
    			break;
    		case 6:
    			this._milkRuns.push({
    				id1: 170,
    				id2: 105,
    				rating: 75
    			});
    			this._milkRuns.push({
    				id1: 170,
    				id2: 190,
    				rating: 75
    			});
    			this._milkRuns.push({
    				id1: 64,
    				id2: 160,
    				rating: 75
    			});
    			this._milkRuns.push({
    				id1: 13,
    				id2: 160,
    				rating: 76
    			});
    			this._milkRuns.push({
    				id1: 6,
    				id2: 174,
    				rating: 77
    			});
    			this._milkRuns.push({
    				id1: 183,
    				id2: 164,
    				rating: 79
    			});
    			this._milkRuns.push({
    				id1: 28,
    				id2: 223,
    				rating: 79
    			});
    			this._milkRuns.push({
    				id1: 119,
    				id2: 190,
    				rating: 79
    			});
    			this._milkRuns.push({
    				id1: 151,
    				id2: 180,
    				rating: 80
    			});
    			this._milkRuns.push({
    				id1: 64,
    				id2: 81,
    				rating: 80
    			});
    			this._milkRuns.push({
    				id1: 55,
    				id2: 105,
    				rating: 80
    			});
    			this._milkRuns.push({
    				id1: 195,
    				id2: 159,
    				rating: 80
    			});
    			this._milkRuns.push({
    				id1: 55,
    				id2: 190,
    				rating: 80
    			});
    			this._milkRuns.push({
    				id1: 86,
    				id2: 127,
    				rating: 80
    			});
    			this._milkRuns.push({
    				id1: 172,
    				id2: 160,
    				rating: 81
    			});
    			this._milkRuns.push({
    				id1: 199,
    				id2: 233,
    				rating: 82
    			});
    			this._milkRuns.push({
    				id1: 151,
    				id2: 73,
    				rating: 82
    			});
    			this._milkRuns.push({
    				id1: 181,
    				id2: 95,
    				rating: 83
    			});
    			this._milkRuns.push({
    				id1: 103,
    				id2: 58,
    				rating: 83
    			});
    			this._milkRuns.push({
    				id1: 71,
    				id2: 160,
    				rating: 83
    			});
    			this._milkRuns.push({
    				id1: 66,
    				id2: 190,
    				rating: 84
    			});
    			this._milkRuns.push({
    				id1: 135,
    				id2: 80,
    				rating: 84
    			});
    			this._milkRuns.push({
    				id1: 23,
    				id2: 233,
    				rating: 84
    			});
    			this._milkRuns.push({
    				id1: 66,
    				id2: 105,
    				rating: 84
    			});
    			this._milkRuns.push({
    				id1: 215,
    				id2: 95,
    				rating: 86
    			});
    			this._milkRuns.push({
    				id1: 247,
    				id2: 202,
    				rating: 86
    			});
    			this._milkRuns.push({
    				id1: 82,
    				id2: 105,
    				rating: 87
    			});
    			this._milkRuns.push({
    				id1: 82,
    				id2: 190,
    				rating: 87
    			});
    			this._milkRuns.push({
    				id1: 172,
    				id2: 81,
    				rating: 88
    			});
    			this._milkRuns.push({
    				id1: 71,
    				id2: 81,
    				rating: 88
    			});
    			this._milkRuns.push({
    				id1: 67,
    				id2: 209,
    				rating: 88
    			});
    			this._milkRuns.push({
    				id1: 23,
    				id2: 246,
    				rating: 89
    			});
    			this._milkRuns.push({
    				id1: 7,
    				id2: 209,
    				rating: 89
    			});
    			this._milkRuns.push({
    				id1: 215,
    				id2: 133,
    				rating: 91
    			});
    			this._milkRuns.push({
    				id1: 183,
    				id2: 133,
    				rating: 92
    			});
    			this._milkRuns.push({
    				id1: 247,
    				id2: 133,
    				rating: 93
    			});
    			this._milkRuns.push({
    				id1: 151,
    				id2: 5,
    				rating: 95
    			});
    			break;
    		case 7:
    			this._milkRuns.push({
    				id1: 56,
    				id2: 240,
    				rating: 70
    			});
    			this._milkRuns.push({
    				id1: 56,
    				id2: 170,
    				rating: 72
    			});
    			this._milkRuns.push({
    				id1: 82,
    				id2: 100,
    				rating: 75
    			});
    			this._milkRuns.push({
    				id1: 31,
    				id2: 25,
    				rating: 77
    			});
    			this._milkRuns.push({
    				id1: 147,
    				id2: 121,
    				rating: 77
    			});
    			this._milkRuns.push({
    				id1: 41,
    				id2: 35,
    				rating: 78
    			});
    			this._milkRuns.push({
    				id1: 244,
    				id2: 138,
    				rating: 78
    			});
    			this._milkRuns.push({
    				id1: 192,
    				id2: 133,
    				rating: 78
    			});
    			this._milkRuns.push({
    				id1: 151,
    				id2: 230,
    				rating: 78
    			});
    			this._milkRuns.push({
    				id1: 137,
    				id2: 121,
    				rating: 80
    			});
    			this._milkRuns.push({
    				id1: 206,
    				id2: 25,
    				rating: 80
    			});
    			this._milkRuns.push({
    				id1: 232,
    				id2: 86,
    				rating: 81
    			});
    			this._milkRuns.push({
    				id1: 93,
    				id2: 185,
    				rating: 81
    			});
    			this._milkRuns.push({
    				id1: 93,
    				id2: 196,
    				rating: 81
    			});
    			this._milkRuns.push({
    				id1: 192,
    				id2: 35,
    				rating: 81
    			});
    			this._milkRuns.push({
    				id1: 161,
    				id2: 255,
    				rating: 82
    			});
    			this._milkRuns.push({
    				id1: 14,
    				id2: 25,
    				rating: 83
    			});
    			this._milkRuns.push({
    				id1: 110,
    				id2: 172,
    				rating: 84
    			});
    			this._milkRuns.push({
    				id1: 82,
    				id2: 179,
    				rating: 84
    			});
    			this._milkRuns.push({
    				id1: 41,
    				id2: 233,
    				rating: 85
    			});
    			this._milkRuns.push({
    				id1: 251,
    				id2: 30,
    				rating: 85
    			});
    			this._milkRuns.push({
    				id1: 218,
    				id2: 71,
    				rating: 89
    			});
    			this._milkRuns.push({
    				id1: 93,
    				id2: 186,
    				rating: 90
    			});
    			this._milkRuns.push({
    				id1: 206,
    				id2: 71,
    				rating: 90
    			});
    			this._milkRuns.push({
    				id1: 251,
    				id2: 233,
    				rating: 90
    			});
    			this._milkRuns.push({
    				id1: 93,
    				id2: 92,
    				rating: 92
    			});
    			this._milkRuns.push({
    				id1: 232,
    				id2: 81,
    				rating: 92
    			});
    			this._milkRuns.push({
    				id1: 110,
    				id2: 177,
    				rating: 93
    			});
    			this._milkRuns.push({
    				id1: 232,
    				id2: 252,
    				rating: 93
    			});
    			break
    	}
    }
    Scripts/smugglers_equipment.js
    "use strict";
    this.name = "Smugglers_Equipment";
    this.author = "phkb";
    this.description = "Looks after the smuggling compartment functions.";
    this.licence = "CC BY-NC-SA 3.0";
    
    /* TODO:
    - Add very occasional messages from traders entering system ("Let hope that setting of 234 is worth the money we paid.")
    - When docking with a standard contract that will be completed, but the cargo is now illegal, still give the player the opportunity to bribe
    - allow non-core commodities to be put in the compartment
    
    ISSUE:
    - bribe opportunity not appearing in scenario where player docks at non-main station galcop station, with a scooped escape pod and illegal cargo (not slaves)
    
    */
    
    this._disabled = false;
    this._debug = false;
    
    // array of all possible installed compartment equipment keys
    this._equipmentList = ["EQ_SMUGGLING_COMPARTMENT_1", "EQ_SMUGGLING_COMPARTMENT_2", "EQ_SMUGGLING_COMPARTMENT_3", "EQ_SMUGGLING_COMPARTMENT_4",
    	"EQ_SMUGGLING_COMPARTMENT_5", "EQ_SMUGGLING_COMPARTMENT_6", "EQ_SMUGGLING_COMPARTMENT_7", "EQ_SMUGGLING_COMPARTMENT_8",
    	"EQ_SMUGGLING_COMPARTMENT_9", "EQ_SMUGGLING_COMPARTMENT_10", "EQ_SMUGGLING_COMPARTMENT_11", "EQ_SMUGGLING_COMPARTMENT_12",
    	"EQ_SMUGGLING_COMPARTMENT_13", "EQ_SMUGGLING_COMPARTMENT_14", "EQ_SMUGGLING_COMPARTMENT_15", "EQ_SMUGGLING_COMPARTMENT_16",
    	"EQ_SMUGGLING_COMPARTMENT_17", "EQ_SMUGGLING_COMPARTMENT_18", "EQ_SMUGGLING_COMPARTMENT_19", "EQ_SMUGGLING_COMPARTMENT_20"
    ];
    
    this._commodityIDList = [];
    
    // station types where the smuggling compartment can be purchased.
    this._smugglingAllegiance = ["pirate", "chaotic", "neutral"];
    this._maxTonnage = 20; // maximum possible size of the hold
    this._doCompartmentCheck = false; // flag to indicate that a compartment check will be performed after docking
    this._backdoor = false;
    
    this._sc_Phase = 1; // the current phase setting (value between 1 and 999)
    this._sc_TechVersion = 1; // the current tech level of the compartment
    this._sc_VisibleAs = "food"; // string (commodity name) that the hold will display as for scanners
    this._sc_Cargo = []; // array of commodities in hold (commodity:string, quantity:number, unit:t/kg/g);
    this._sc_Days = 0; // number of days since the last tech upgrade
    this._phaseUpdateDays = 0;
    this._phaseScanDiscovery = []; // array of objects of discovered phase scan settings (from purchase or from phase scanner)
    
    this._smugglingScreen = false; // flag to indicate that a mission screen is open
    this._originalCredits = 0; // value to hold the players credits (used to refund the cost of the initial item)
    this._display = 0;
    this._oldday = 0;
    
    this._phaseScannerAvailable = false; // indicates that this system has the phase scanner available
    this._phaseScannerDetected = false; // indicates that the player was caught using the phase scanner
    this._systemPhase = []; // array of objects (gov, tl, phase)
    this._phaseUpdates = []; // stores list of governments/techLevels that have been updated
    this._toRemove = []; // list of source and commodities to be removed
    this._removeCompartment = false; // flag to indicate that the smuggling compartment should be removed after docking
    this._playerFine = 0; // amount to fine the player
    this._menuColor = "orangeColor";
    this._itemColor = "yellowColor";
    this._disabledColor = "darkGrayColor";
    this._galJump = false;
    this._policeModel = "";
    this._NCInstalled = false;
    this._IGTInstalled = false;
    this._doCargoCheck = false;
    this._dockingWithSlaves = false;
    this._dockingWithSlavesCount = 0;
    
    //-------------------------------------------------------------------------------------------------------------
    this.startUp = function () {
    	if (this._disabled) {
    		delete this.playerWillSaveGame;
    		delete this.playerBoughtEquipment;
    		delete this.shipWillDockWithStation;
    		delete this.shipDockedWithStation;
    		delete this.missionScreenOpportunity;
    		delete this.shipExitedWitchspace;
    		delete this.shipLaunchedEscapePod;
    		delete this.guiScreenChanged;
    		delete this.guiScreenWillChange;
    		delete this.playerEnteredNewGalaxy;
    		delete this.playerRescuedEscapePod;
    		delete this.dayChanged;
    		delete this.equipmentDamaged;
    		delete this.equipmentRemoved;
    		delete this.startUpComplete;
    		delete this.startUp;
    		return;
    	}
    	this.$createSystemDataArray();
    	if (worldScripts.CargoTypeExtension) this._NCInstalled = true;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.startUpComplete = function () {
    	function compare(a, b) {
    		if (a.order < b.order) return -1;
    		if (a.order > b.order) return 1;
    		return 0;
    	}
    
    	// check for illegal goods tweak
    	if (worldScripts["illegal_goods_tweak"]) this._IGTInstalled = true;
    
    	// build a list of available commodities
    	var m = null;
    	// find a station to get market data from
    	if (system.mainStation) {
    		m = system.mainStation.market;
    	} else if (player.ship.dockedStation) {
    		m = playership.dockedStation.market;
    	} else if (system.stations.length > 0) {
    		m = system.stations[0].market;
    	}
    	if (m) {
    		var sbd = worldScripts.Smugglers_DockMaster;
    		var c = Object.keys(m);
    		var list = [];
    		for (var i = 0; i < c.length; i++) {
    			list.push({
    				commodity: c[i],
    				order: (m[c[i]].sort_order ? m[c[i]].sort_order : 0)
    			});
    			// add any non-t commodities (kg or g, ie gold, platinum, gemstones) to the dockmaster noswitch list
    			if (m[c[i]].quantity_unit != 0) sbd._noSwitch.push(c[i]);
    		}
    		list.sort(compare);
    		for (var i = 0; i < list.length; i++) {
    			this._commodityIDList.push(list[i].commodity);
    		}
    	}
    
    	var core = worldScripts.Smugglers_CoreFunctions;
    	this.$padTextRight = core.$padTextRight;
    	this.$padTextLeft = core.$padTextLeft;
    	this.$isBigGuiActive = core.$isBigGuiActive;
    	this.$rand = core.$rand;
    
    	if (missionVariables.SmugglingCompartment_Phase) this._sc_Phase = missionVariables.SmugglingCompartment_Phase;
    	if (missionVariables.SmugglingCompartment_TechVersion) this._sc_TechVersion = missionVariables.SmugglingCompartment_TechVersion;
    	if (missionVariables.SmugglingCompartment_VisibleAs) this._sc_VisibleAs = missionVariables.SmugglingCompartment_VisibleAs;
    	if (missionVariables.SmugglingCompartment_Cargo) this._sc_Cargo = JSON.parse(missionVariables.SmugglingCompartment_Cargo);
    	this.$addExtraField(); // make sure the "extra" property is added if it's not there yet
    	if (missionVariables.SmugglingCompartment_Days) this._sc_Days = missionVariables.SmugglingCompartment_Days;
    	if (missionVariables.Smuggling_SystemPhase) this._systemPhase = JSON.parse(missionVariables.Smuggling_SystemPhase);
    	if (missionVariables.Smuggling_PhaseScannerAvailable) this._phaseScannerAvailable = (missionVariables.Smuggling_PhaseScannerAvailable === 1 ? true : false);
    	if (missionVariables.Smuggling_PhaseDiscovery) this._phaseScanDiscovery = JSON.parse(missionVariables.Smuggling_PhaseDiscovery);
    	if (missionVariables.Smuggling_PhaseUpdates) this._phaseUpdates = JSON.parse(missionVariables.Smuggling_PhaseUpdates);
    	if (missionVariables.Smuggling_PhaseUpdateDays) this._phaseUpdateDays = missionVariables.Smuggling_PhaseUpdateDays;
    
    	delete missionVariables.SmugglingCompartment_Phase;
    	delete missionVariables.SmugglingCompartment_TechVersion;
    	delete missionVariables.SmugglingCompartment_VisibleAs;
    	delete missionVariables.SmugglingCompartment_Cargo;
    
    	if (this._systemPhase.length === 0) this.$createSystemPhase();
    
    	this._policeModel = this.$getPoliceModel();
    
    	// set up the interface screen, if required
    	this.$initInterface(player.ship.dockedStation);
    
    	// check that the inflight eq is given to the player if they have a smuggling compartment
    	if (this.$hasSmugglingCompartment() === true) {
    		player.ship.awardEquipment("EQ_SMUGGLING_INFLIGHT_STORAGE");
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.playerWillSaveGame = function () {
    	if (this.$hasSmugglingCompartment() === true) {
    		missionVariables.SmugglingCompartment_Phase = this._sc_Phase;
    		missionVariables.SmugglingCompartment_TechVersion = this._sc_TechVersion;
    		missionVariables.SmugglingCompartment_VisibleAs = this._sc_VisibleAs;
    		missionVariables.SmugglingCompartment_Cargo = JSON.stringify(this._sc_Cargo);
    		missionVariables.SmugglingCompartment_Days = this._sc_Days;
    	} else {
    		delete missionVariables.SmugglingCompartment_Phase;
    		delete missionVariables.SmugglingCompartment_TechVersion;
    		delete missionVariables.SmugglingCompartment_VisibleAs;
    		delete missionVariables.SmugglingCompartment_Cargo;
    		delete missionVariables.SmugglingCompartment_Days;
    	}
    	missionVariables.Smuggling_SystemPhase = JSON.stringify(this._systemPhase);
    	missionVariables.Smuggling_PhaseScannerAvailable = (this._phaseScannerAvailable ? 1 : 0);
    	missionVariables.Smuggling_PhaseDiscovery = JSON.stringify(this._phaseScanDiscovery);
    	if (this._phaseUpdates.length > 0) missionVariables.Smuggling_PhaseUpdates = JSON.stringify(this._phaseUpdates);
    	missionVariables.Smuggling_PhaseUpdateDays = this._phaseUpdateDays;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.playerBoughtEquipment = function (equipmentKey) {
    	var p = player.ship;
    
    	// repairing the phase scanner
    	if (equipmentKey === "EQ_PHASE_SCANNER_REPAIR") {
    		p.removeEquipment(equipmentKey);
    		p.setEquipmentStatus("EQ_PHASE_SCANNER", "EQUIPMENT_OK");
    		return;
    	}
    
    	// repairing the smuggling compartment
    	if (equipmentKey.indexOf("EQ_SMUGGLING_COMPARTMENT_REPAIR") >= 0) {
    		p.removeEquipment(equipmentKey);
    		var eq = this.$getSmugglingCompartmentEquipment();
    		p.setEquipmentStatus(eq, "EQUIPMENT_OK");
    		return;
    	}
    
    	var stn = p.dockedStation;
    
    	// removing the phase scanner
    	if (equipmentKey === "EQ_PHASE_SCANNER_REMOVAL") {
    		p.removeEquipment(equipmentKey);
    		if (stn.allegiance != "galcop") {
    			// give the player a refund if we're not at a galcop station
    			var tradeinfactor = 1.0;
    			// less refund for damaged scanner
    			if (p.equipmentStatus("EQ_PHASE_SCANNER") != "EQUIPMENT_OK") tradinfactor = 0.3;
    
    			var oldEqInfo = EquipmentInfo.infoForKey("EQ_PHASE_SCANNER");
    			player.credits += ((oldEqInfo.price / 10) * stn.equipmentPriceFactor) * tradeinfactor;
    		}
    		p.removeEquipment("EQ_PHASE_SCANNER");
    	}
    
    	// removing the smuggling compartment
    	if (equipmentKey === "EQ_SMUGGLING_COMPARTMENT_REMOVAL") {
    		p.removeEquipment(equipmentKey);
    		var eqKey = this.$getSmugglingCompartmentEquipment();
    		// do a refund, but only if we're not at a galcop station, and only if the existing equipment is undamaged
    		if (stn.allegiance != "galcop" && eqKey != "") {
    			var tradeinfactor = 1.0;
    			// less refund for damaged compartment
    			if (p.equipmentStatus(eqKey) != "EQUIPMENT_OK") tradinfactor = 0.3;
    
    			var oldEqInfo = EquipmentInfo.infoForKey(eqKey);
    			player.credits += ((oldEqInfo.price / 10) * stn.equipmentPriceFactor) * tradeinfactor;
    		}
    		if (p.equipmentStatus("EQ_SMUGGLING_PHASE_ADJUSTMENT") === "EQUIPMENT_OK") {
    			p.removeEquipment("EQ_SMUGGLING_PHASE_ADJUSTMENT");
    		}
    		player.ship.removeEquipment("EQ_SMUGGLING_INFLIGHT_STORAGE");
    		this._backdoor = true;
    		if (eqKey != "") p.removeEquipment(eqKey);
    		this._backdoor = false;
    
    		this.$resetCompartmentInfo(stn);
    	}
    
    	// buying a new (or expanding/contracting an existing) smuggling compartment
    	if (equipmentKey === "EQ_SMUGGLING_COMPARTMENT") {
    		p.removeEquipment(equipmentKey);
    		// give the player back the original amount, in case they decide not to purchase
    		if (this._originalCredits != 0) {
    			player.credits = this._originalCredits;
    			this._originalCredits = player.credits;
    		}
    		// mission screen to ask how many TC to allocate
    		this._hudHidden = player.ship.hudHidden;
    		this.$showPurchasePage();
    	}
    
    	if (equipmentKey === "EQ_SMUGGLING_VERSION_UPGRADE") {
    		p.removeEquipment(equipmentKey);
    		this._sc_TechVersion += 1;
    		this._sc_Days = 0;
    	}
    
    	if (stn.allegiance === "galcop" && equipmentKey === "EQ_RENOVATION") {
    		// is the phase scanner on board
    		var msgtype = 0;
    		if (p.equipmentStatus("EQ_PHASE_SCANNER") === "EQUIPMENT_OK" || p.equipmentStatus("EQ_PHASE_SCANNER") === "EQUIPMENT_DAMAGED") {
    			// oh dear - caught!
    			p.setBounty(player.bounty + (this.$rand(10) + 10), "illegal equipment");
    			msgtype += 1;
    		}
    		// is the smuggling compartment installed?
    		if (this.$hasSmugglingCompartment() === true) {
    			msgtype += 2;
    			// increase bounty
    			p.setBounty(player.bounty + (this.$rand(10) + 10), "illegal equipment");
    		}
    		var msgBody = "";
    		switch (msgtype) {
    			case 0:
    				break;
    			case 1:
    				msgBody = expandDescription("[illegal-equipment-1]");
    				break;
    			case 2:
    				msgBody = expandDescription("[illegal-equipment-2]", {
    					size: this.$smugglingCompartmentSize()
    				});
    				break;
    			case 3:
    				msgBody = expandDescription("[illegal-equipment-3]", {
    					size: this.$smugglingCompartmentSize()
    				});
    				break;
    		}
    
    		if (msgBody != "") {
    			var curChoices = {};
    			if (player.credits > 0) {
    				curChoices["01_BRIBE"] = {
    					text: "[illegal-attempt-bribe]",
    					color: this._menuColor
    				};
    			}
    			curChoices["98_ACCEPT"] = {
    				text: "[illegal-accept-penalty]",
    				color: this._menuColor
    			};
    
    			this._bribeType = 2;
    			var opts = {
    				title: "Illegal Equipment Discovered",
    				message: msgBody,
    				model: "[" + this._policeModel + "]",
    				allowInterrupt: false,
    				exitScreen: "GUI_SCREEN_EQUIP_SHIP",
    				choices: curChoices,
    				initialChoicesKey: "98_ACCEPT"
    			};
    
    			mission.runScreen(opts, this.$bribeScreenHandler, this);
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    /*this.equipmentRemoved = function(equipmentKey) {
    	if (this._backdoor === true) return;
    	if (this._equipmentList.indexOf(equipmentKey) >= 0) {
    		this.$resetCompartmentInfo(player.ship.dockedStation);
    		return;
    	}
    }*/
    
    //-------------------------------------------------------------------------------------------------------------
    this.shipWillDockWithStation = function (station) {
    	this._policeModel = this.$getPoliceModel();
    
    	this._savedBounty = player.bounty;
    
    	// grab a snapshot of the players illegal cargo
    	this._dockIllegal = [];
    	this._dockingWithSlaves = false;
    	// if the player has slaves on board, and IGT is installed, then turn on the flag to indicate we're docking with slaves
    	var escapePods = 0;
    	if (worldScripts.EscapePodTweaks) {
    		// if the _escapePods array have data, what means the escape pod tweaks "shipWillDOckWithStation" function hasn't been executed yet
    		// in that case we need to know how many slaves to ignore
    		escapePods = worldScripts.EscapePodTweaks._escapePods.length;
    		// if its been run already, the value will be 0 anyway
    	}
    	if (player.ship.manifest["slaves"] && (player.ship.manifest["slaves"] - escapePods) > 0 && this._IGTInstalled === true) {
    		this._dockingWithSlaves = true;
    		// make a note of the slaves count, because if we dock with rescued escape pods, the number will have to be reduced during "playerRescuedEscapePod";
    		this._dockingWithSlavesCount = (player.ship.manifest["slaves"] - escapePods);
    	}
    
    	this._playerHadIllegals = false;
    	if (this.$playerHasIllegalCargo(station) === true) {
    		var p = player.ship;
    		var m = p.manifest;
    		this._dockIllegal = [];
    		for (var i = 0; i < m.list.length; i++) {
    			if (m.list[i].quantity > 0 && station.market[m.list[i].commodity].legality_import > 0) this._dockIllegal.push({
    				commodity: m.list[i].commodity,
    				quantity: m.list[i].quantity
    			});
    		}
    		this._playerHadIllegals = true;
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.shipDockedWithStation = function (station) {
    	if (station.allegiance === "galcop") {
    		if (this._dockingWithSlaves === false) this._doCargoCheck = true;
    	}
    	// set up smuggling interface screen
    	this.$initInterface(station);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.shipLaunchedEscapePod = function (escapepod) {
    	// clear out smugglers compartment when escape pod is used
    	this._sc_Cargo = [];
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.missionScreenOpportunity = function () {
    	// if IGT is installed, let it run first. it will call the performCargoCheck when it's finished.
    	if (this._doCargoCheck === true) {
    		this.$performCargoCheck();
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.playerRescuedEscapePod = function (fee, reason, occupant) {
    	if (this._dockingWithSlavesCount > 0) {
    		this._dockingWithSlavesCount -= 1;
    		if (this._dockingWithSlavesCount === 0) this._dockingWithSlaves = false;
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.shipExitedWitchspace = function () {
    	if (this._galJump === true) {
    		this._galJump = false;
    		this.$createSystemPhase();
    		this.$createSystemDataArray();
    	}
    	this._phaseScannerAvailable = false;
    	this._phaseScannerDetected = false;
    	if (system.government === 0 && system.info.techlevel >= 8) {
    		// will this system provide a phase scanner?
    		if (system.scrambledPseudoRandomNumber(system.ID) > 0.6) this._phaseScannerAvailable = true;
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.guiScreenWillChange = function (to, from) {
    	if (to === "GUI_SCREEN_MANIFEST") this.$updateManifest();
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.guiScreenChanged = function (to, from) {
    	if (guiScreen === "GUI_SCREEN_EQUIP_SHIP") {
    		this._originalCredits = player.credits;
    		this._savedBounty = player.bounty;
    	}
    	if (from === "GUI_SCREEN_MISSION" && this._smugglingScreen) {
    		this._smugglingScreen = false;
    		if (this._hudHidden === false && player.ship.hudHidden === true) player.ship.hudHidden = this._hudHidden;
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.playerEnteredNewGalaxy = function (galaxyNumber) {
    	this._systemPhase = [];
    	this._phaseUpdates = [];
    	this._phaseScanDiscovery = [];
    	this._galJump = true;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.dayChanged = function (newday) {
    	if (newday != this._oldday) {
    		this._sc_Days += 1;
    		this._oldday = newday;
    		this._phaseUpdateDays += 1;
    		if (this._phaseUpdateDays >= 10) {
    			this._phaseUpdateDays = 0;
    			var item = this._systemData[this.$rand(this._systemData.length - 1)];
    			var gov = parseInt(item / 100);
    			var tl = item - gov;
    			var newPhase = this.$rand(999);
    			// remove from the update list
    			for (var i = this._phaseUpdates.length - 1; i >= 0; i--) {
    				if (this._phaseUpdates[i].gov === gov && this._phaseUpdates[i].tl === tl) {
    					this._phaseUpdates.splice(i, 1);
    				}
    			}
    			this.$updatePhase(gov, tl, newPhase);
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.equipmentDamaged = function (equipment) {
    	if (this._equipmentList.indexOf(equipment) >= 0) {
    		// work out how many items will be damaged
    		var remove = this.$rand(3);
    		var report = [];
    		var count = 0;
    		do {
    			for (var i = 0; i < this._sc_Cargo.length; i++) {
    				if (count < remove && this._sc_Cargo[i].quantity > 0) {
    					// nc checks
    					if (this._sc_Cargo[i].extra != "") {
    						var dta = [];
    						log(this.name, "pre: " + this._sc_Cargo[i].extra);
    						dta = this._sc_Cargo[i].extra.split("/");
    						if ((this._sc_Cargo[i].quantity - dta.length) == 0) {
    							dta.shift();
    							this._sc_Cargo[i].extra = dta.join("/");
    							log(this.name, "post: " + this._sc_Cargo[i].extra);
    						}
    					}
    					this._sc_Cargo[i].quantity -= 1;
    					report.push(this._sc_Cargo[i].displayName + " destroyed!");
    					count += 1;
    				}
    			}
    		} while (count < remove && this._sc_Cargo.length > 0);
    		this.$cargoCleanUp();
    		// give the player the bad news
    		if (report.length > 0) {
    			for (var i = 0; i < report.length; i++) {
    				player.consoleMessage(report[i]);
    			}
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$addExtraField = function() {
    	if (this._sc_Cargo.length == 0) return;
    	for (var i = 0; i < this._sc_Cargo.length; i++) {
    		if (!this._sc_Cargo[i].hasOwnProperty("extra")) this._sc_Cargo[i].extra = "";
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$performCargoCheck = function $performCargoCheck() {
    	var msgtype = 0;
    	var p = player.ship;
    
    	this._toRemove = [];
    	this._removeCompartment = false;
    	this._playerFine = 0;
    
    	if (this._doCargoCheck === true) {
    		this._doCargoCheck = false;
    
    		if (this._phaseScannerDetected === true) {
    			if (player.credits > 1000) {
    				this._playerFine += 1000;
    			} else {
    				this._playerFine = player.credits;
    			}
    			// player has already had a legal penalty when the scan was detected, so we don't need to do it again here
    		}
    
    		// is our compartment damaged (and therefore visible?
    		if (this.$hasSmugglingCompartment() === true && this.$isCompartmentDamaged() === true) {
    			msgtype = 1;
    			this._removeCompartment = true;
    			// increase bounty
    			p.setBounty(player.bounty + (this.$rand(10) + 10), "illegal equipment");
    			if (player.credits > 1000) {
    				this._playerFine += 1000;
    			} else {
    				this._playerFine = player.credits;
    			}
    		}
    
    		// get the list of illegal goods for this system
    		var si = worldScripts.Smugglers_Illegal;
    		var goods = si.$illegalGoodsList(system.ID, true);
    		// if IGT is installed, add slaves onto the list of illegal goods
    		if (this._IGTInstalled) {
    			goods.push({
    				systemID: system.ID,
    				commodity: "slaves",
    				started: 0,
    				end: 0,
    				description: "",
    				permit: 0,
    				scale: 1
    			});
    		}
    
    		if (msgtype === 0) {
    			for (var i = 0; i < goods.length; i++) {
    				// if the visibleAs property equal to an illegal good on this planet? oh dear...it's not going to matter what the chance is...
    				if (this.$hasSmugglingCompartment() === true && goods[i].commodity.indexOf(this._sc_VisibleAs) >= 0 && si.$playerHasPermit(system.ID, goods[i].commodity, true) === false) {
    					msgtype = 2;
    					this._removeCompartment = true;
    					// increase bounty
    					p.setBounty(player.bounty + (this.$rand(10) + 10), "illegal equipment");
    					// fine player
    					if (player.credits > 1000) {
    						this._playerFine += 1000;
    					} else {
    						this._playerFine = player.credits;
    					}
    				}
    			}
    		}
    
    		// now, goods in the hold that are illegal to import should now automatically fine the player with a bounty increase.
    		// so we shouldn't have to worry about bounty from this point on.
    		// what we will do here is remove cargo and possibly fine the player a monetary amount
    
    		// base chance is 26% (for tech level 1), but increasing with a systems tech level, to a max of 40% for a tl 15 system
    		var base = 25 + system.info.techlevel;
    		var phase_diff = base;
    		// work out the chance of detection
    		// check for phase adjuster
    		if (p.equipmentStatus("EQ_SMUGGLING_PHASE_ADJUSTMENT") === "EQUIPMENT_OK") {
    			// if we've got the phase adjuster, there's a small boost the the base value, from 22% to 37%
    			base = 22 + system.info.techlevel;
    			phase_diff = Math.abs(this.$getSystemPhase(system.ID) - this._sc_Phase) - 3;
    			// if we're within +/- 3 then it's good enough
    			if (phase_diff < 0) phase_diff = 0;
    			// anything over base is considered inconsequential
    			if (phase_diff > base) phase_diff = base;
    		}
    		var chance = phase_diff / 100;
    
    		if (this._debug) log(this.name, "Smuggling compartment init chance: " + chance);
    
    		// now apply tech level offset. could be 0 through to an extra 12.5% (30/240 = 0.125)
    		chance += (this._sc_Days > 30 ? 30 : this._sc_Days) / 240;
    
    		if (this._debug) log(this.name, "Smuggling compartment final chance: " + chance);
    
    		var s_cargo = this.$getSmugglingCargo();
    		var msg = "";
    		var permit_failed = false;
    		var permit_list = "";
    		var permit_multi = 0;
    		var permit_ok = "";
    		var permit_okcount = 0;
    		var displaycheck = false;
    		var removecount = 0;
    		var successcount = 0;
    		var successmsg = "";
    
    		var smuggling_found = false;
    		// do this here so we only do it once
    		if (Math.random() < chance) smuggling_found = true;
    
    		for (var i = 0; i < goods.length; i++) {
    			// are there any illegal goods in our standard hold
    
    			// get a list of commodities (handles case where there are multiple commdoties in the illegal goods entry
    			var cmdtylist = goods[i].commodity.split(",");
    			var quantity_to_check = 0;
    
    			for (var j = 0; j < cmdtylist.length; j++) {
    				// only check if the good is actually marked as illegal
    				if (this.$commodityIllegalScale(cmdtylist[j]) > 0) {
    					if (p.manifest[cmdtylist[j]] > 0) {
    						quantity_to_check += p.manifest[cmdtylist[j]];
    					}
    					var hyper = this.$getHyperCargoQuantity(cmdtylist[j]);
    					if (hyper > 0) {
    						quantity_to_check += hyper;
    					}
    					for (var k = 0; k < s_cargo.length; k++) {
    						if (cmdtylist[j] === s_cargo[k].commodity && smuggling_found === true) {
    							quantity_to_check += s_cargo[k].quantity;
    						}
    					}
    				}
    			}
    
    			if (quantity_to_check > 0) {
    				if (si.$playerHasPermit(system.ID, goods[i].commodity, true) === false) {
    					// will get here if the player had a permit but it didn't work, or if they didn't have a permit at all.
    
    					// check if a permit permit failed to work
    					if (si.$playerHasPermit(system.ID, goods[i].commodity, false) === true) {
    						permit_failed = true;
    						permit_list += (permit_list === "" ? "" : ", ") + si.$translateCommodityList(goods[i].commodity);
    						permit_multi += 1;
    
    						if (goods[i].commodity.indexOf(",") >= 0) permit_multi += 1;
    
    						// remove any permit whether it worked or not
    						si.$removePermit(system.ID, goods[i].commodity);
    					}
    
    					for (var j = 0; j < cmdtylist.length; j++) {
    						// check the visible carge
    						if (p.manifest[cmdtylist[j]] > 0) {
    							// found!
    							msg += " - " + p.manifest[cmdtylist[j]] + " " + this.$getCommodityType(cmdtylist[j]) + " × " + displayNameForCommodity(cmdtylist[j]) + "\n";
    							this._toRemove.push({
    								source: "hold",
    								commodity: cmdtylist[j]
    							});
    							removecount += 1;
    							// stanndard slave bounty gets added here, because it's not set to be flagged as illegal to import by default
    							if (this._IGTInstalled === true && cmdtylist[j] === "slaves") {
    								p.setBounty(player.bounty + (p.manifest[cmdtylist[j]] * goods[i].scale), "illegal imports");
    							}
    						}
    						// check hypercargo
    						var hyper = this.$checkHyperCargo(cmdtylist[j]);
    						if (hyper != "") {
    							msg += hyper + "\n";
    							p.setBounty(player.bounty + (this.$getHyperCargoQuantity(cmdtylist[j]) * goods[i].scale), "illegal imports");
    							this._toRemove.push({
    								source: "hyper",
    								commodity: cmdtylist[j]
    							});
    							removecount += 1;
    						}
    						// try to check the smuggling compartment (but only if it hasn't already been discovered)
    						if (this._removeCompartment === false) {
    							for (var k = 0; k < s_cargo.length; k++) {
    								if (cmdtylist[j] === s_cargo[k].commodity) {
    									if (smuggling_found === true) {
    										// found!
    										msg += " - " + s_cargo[k].quantity + this.$getCommodityType(s_cargo[k].commodity) + " × " + displayNameForCommodity(s_cargo[k].commodity) + "\n";
    										// apply the bounty here, otherwise it won't get applied
    										p.setBounty(player.bounty + (s_cargo[k].quantity * goods[i].scale), "illegal imports");
    										//s_cargo[k].quantity = 0;
    										this._removeCompartment = true;
    										removecount += 1;
    									} else {
    										successcount += 1;
    										successmsg += (successmsg === "" ? "" : " and ") + displayNameForCommodity(s_cargo[k].commodity);
    									}
    								}
    							}
    						}
    					}
    				} else {
    					var found = false;
    					// for any visible cargo (ie not in the cargo hold) for which the player has a permit, we need to refund the bounty
    					for (var j = 0; j < cmdtylist.length; j++) {
    						// do we have any of this commodity in our hold?
    						if (p.manifest[cmdtylist[j]] > 0) {
    							p.setBounty(player.bounty - (p.manifest[cmdtylist[j]] * goods[i].scale), "import permit");
    							found = true;
    						} else {
    							// check that this cargo wasn't removed by the contracts system
    							for (var k = 0; k < this._dockIllegal.length; k++) {
    								if (this._dockIllegal[k].commodity === cmdtylist[j]) found = true;
    							}
    						}
    						// check hypercargo
    						var hyper = this.$checkHyperCargo(cmdtylist[j]);
    						if (hyper != "") {
    							found = true;
    						}
    
    						if (this._removeCompartment === false) {
    							for (var k = 0; k < s_cargo.length; k++) {
    								if (cmdtylist[j] === s_cargo[k].commodity && smuggling_found === true) {
    									// found
    									found = true;
    								}
    							}
    						}
    					}
    
    					// if we had permitted cargo,
    					if (found === true) {
    						permit_ok += (permit_ok === "" ? "" : ", ") + si.$translateCommodityList(goods[i].commodity);
    						permit_okcount += 1;
    						if (goods[i].commodity.indexOf(",") > 0) permit_okcount += 1;
    						// turn on flat to make sure a mission screen appears if we've adjusted the player's bounty
    						// otherwise the status screen will show an incorrect value in "Legal Status"
    						if (msgtype === 0) msgtype = 4;
    						// remove any permit whether it worked or not
    						si.$removePermit(system.ID, goods[i].commodity);
    					}
    				}
    			}
    		}
    
    		// special case: where we have illegal cargo just before docking, but don't now, and no permit.
    		// this would mean the contract system removed the cargo after docking
    		// we still need to give the player an opportunity to bribe their way to a clean slate.
    		if (msgtype === 0 && this._playerHadIllegals === true && this.$playerHasIllegalCargo(p.dockedStation) === false) {
    			msgtype = 6;
    		}
    
    		// found illegal cargo
    		if (msg != "") msgtype = 3;
    
    		// switch to the phase scanner message if that was detected but without any cargo
    		if (this._phaseScannerDetected === true) {
    			if (msgtype === 0 || msgtype === 4) msgtype = 5;
    		}
    
    		if (permit_list != "") permit_list = permit_list.replace(", ", " and ");
    
    		var msgTitle = "";
    		var msgBody = "";
    		var bigScreen = false;
    
    		switch (msgtype) {
    			case 0:
    				if (successcount > 0) {
    					msgTitle = "Smuggling Contraband";
    					msgBody = expandDescription("[smuggling-success]", {
    						cargo: successmsg
    					});
    				}
    				break;
    			case 1: //
    				msgTitle = "GalCop Customs Alert";
    				msgBody = expandDescription("[smuggling-fail-1]", {
    						size: this.$smugglingCompartmentSize()
    					}) +
    					(this._phaseScannerDetected === false ? "" : expandDescription("[phase-scanner-removal]")) +
    					expandDescription("[end-warning]");
    				break;
    			case 2:
    				msgTitle = "GalCop Customs Alert";
    				msgBody = expandDescription("[smuggling-fail-2]", {
    						size: this.$smugglingCompartmentSize(),
    						commodity: displayNameForCommodity(this._sc_VisibleAs)
    					}) +
    					(this._phaseScannerDetected === false ? "" : expandDescription("[phase-scanner-removal]")) +
    					expandDescription("[end-warning]");
    				break;
    
    			case 3:
    				var sections = 0;
    				if (permit_failed) sections += 1;
    				if (this._removeCompartment) sections += 1;
    				if (this._phaseScannerDetected) sections += 1;
    				if (sections === 3 || (sections === 2 && removecount > 4)) bigScreen = true;
    
    				msgTitle = "GalCop Customs Alert";
    				msgBody = expandDescription("[smuggling-fail-3]", {
    						insert: msg
    					}) +
    					(permit_failed === false ? "" :
    						"\n\nIt should be noted that the permit" + (permit_multi > 1 ? "s" : "") + " you had for " + permit_list + (permit_multi === 1 ? " was" : " were") +
    						" found to be " + (permit_multi > 1 ? "fabrications." : "a fabrication.")) +
    					(this._removeCompartment === false ? "" : expandDescription("[smuggling-compartment-removal]", {
    						size: this.$smugglingCompartmentSize()
    					})) +
    					(this._phaseScannerDetected === false ? "" : expandDescription("[phase-scanner-removal]")) +
    					expandDescription("[end-warning]");
    				break;
    			case 4:
    				msgTitle = "GalCop Customs";
    				msgBody = expandDescription("[smuggling_permit]", {
    					cargo: permit_ok,
    					permits: (permit_okcount === 1 ? "permit" : "permits")
    				});
    				break;
    			case 5:
    				msgTitle = "GalCop Customs Alert";
    				msgBody = expandDescription("[phase-scanner-removal-main]");
    				break;
    			case 6:
    				msgTitle = "GalCop Customs Alert";
    				msgBody = expandDescription("[smuggling-fail-4]");
    				break;
    		}
    
    		if (msgtype >= 1 && msgtype <= 6 && msgtype != 4) {
    			this._hudHidden = p.hudHidden;
    			if (bigScreen === true && this.$isBigGuiActive() === false) {
    				p.hudHidden = true;
    				this._smugglingScreen = true;
    			}
    
    			var curChoices = {};
    			curChoices["01_BRIBE"] = {
    				text: "[illegal-attempt-bribe]",
    				color: this._menuColor
    			};
    			curChoices["99_ACCEPT"] = {
    				text: "[illegal-accept-penalty]",
    				color: this._menuColor
    			};
    
    			this._bribeType = 1;
    			if (msgtype === 6) this._bribeType = 3;
    
    			var opts = {
    				screenID: "docking_with_illegals",
    				title: msgTitle,
    				message: msgBody,
    				allowInterrupt: false,
    				model: "[" + this._policeModel + "]",
    				exitScreen: "GUI_SCREEN_STATUS",
    				choices: curChoices,
    				initialChoicesKey: "99_ACCEPT"
    			};
    
    			mission.runScreen(opts, this.$bribeScreenHandler, this);
    		}
    
    		if (msgtype === 4 || (msgtype === 0 && successcount > 0)) {
    			mission.runScreen({
    				screenID: "dock_successful",
    				title: msgTitle,
    				message: msgBody,
    				model: "[" + this._policeModel + "]",
    				exitScreen: "GUI_SCREEN_STATUS"
    			});
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$bribeScreenHandler = function $bribeScreenHandler(choice) {
    	if (!choice) return;
    	if (choice === "99_ACCEPT") {
    		this.$processRemovals();
    	}
    	if (choice === "98_ACCEPT") {
    		this.$processEquipmentRemovals();
    	}
    	if (choice === "01_BRIBE") {
    		this.$getBribeAmount()
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // initialise the F4 screen entries
    this.$initInterface = function $initInterface(station) {
    	if (this.$hasSmugglingCompartment() === true) {
    		station.setInterface(this.name, {
    			title: "Smuggling compartment configuration",
    			category: "Ship Systems",
    			summary: "Move cargo into or out of the smuggling compartment, and change the settings of the compartment",
    			callback: this.$showInitialSmugglingConfig.bind(this)
    		});
    	} else {
    		station.setInterface(this.name, null);
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns true if the smuggling compartment is damaged
    this.$isCompartmentDamaged = function $isCompartmentDamaged() {
    	var eq = this.$getSmugglingCompartmentEquipment();
    	if (player.ship.equipmentStatus(eq) === "EQUIPMENT_DAMAGED") return true;
    	return false;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns true if the smuggling compartment is installed
    this.$hasSmugglingCompartment = function $hasSmugglingCompartment() {
    	for (var i = 1; i <= this._maxTonnage; i++) {
    		var check = player.ship.equipmentStatus("EQ_SMUGGLING_COMPARTMENT_" + i.toString());
    		if (check === "EQUIPMENT_OK" || check === "EQUIPMENT_DAMAGED") return true;
    	}
    	return false;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns the equipment key of the installed compartment
    this.$getSmugglingCompartmentEquipment = function $getSmugglingCompartmentEquipment() {
    	for (var i = 1; i <= this._maxTonnage; i++) {
    		var check = player.ship.equipmentStatus("EQ_SMUGGLING_COMPARTMENT_" + i.toString());
    		if (check === "EQUIPMENT_OK" || check === "EQUIPMENT_DAMAGED") return "EQ_SMUGGLING_COMPARTMENT_" + i.toString();
    	}
    	return "";
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns the max size of the current compartment
    this.$smugglingCompartmentSize = function $smugglingCompartmentSize() {
    	for (var i = 1; i <= this._maxTonnage; i++) {
    		var check = player.ship.equipmentStatus("EQ_SMUGGLING_COMPARTMENT_" + i.toString());
    		if (check === "EQUIPMENT_OK" || check === "EQUIPMENT_DAMAGED") return i;
    	}
    	return 0;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns true if the phase scanner is damaged, otherwise false
    this.$isPhaseScannerDamaged = function $isPhaseScannerDamaged() {
    	if (player.ship.equipmentStatus("EQ_PHASE_SCANNER") === "EQUIPMENT_DAMAGED") return true;
    	return false;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns true if the phase scanner is installed (even if damaged), otherwise false
    this.$isPhaseScannerInstalled = function $isPhaseScannerInstalled() {
    	var check = player.ship.equipmentStatus("EQ_PHASE_SCANNER");
    	if (check === "EQUIPMENT_OK" || check === "EQUIPMENT_DAMAGED") return true;
    	return false;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // resets smuggling compartment internal data after a compartment is removed
    this.$resetCompartmentInfo = function $resetCompartmentInfo(station) {
    	var p = player.ship;
    	// move any cargo into the main hold
    	if (this._sc_Cargo.length > 0) {
    		for (var i = 0; i < this._sc_Cargo.length; i++) {
    			p.manifest[this._sc_Cargo[i].commodity] += this._sc_Cargo[i].quantity;
    		}
    		this._sc_Cargo = [];
    	}
    	this._sc_VisibleAs = "Food";
    	this._sc_Phase = 1;
    	this._sc_TechVersion = 1;
    	this.$initInterface(station);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns the amount of used space in the compartment
    this.$usedSpace = function $usedSpace() {
    	var t = 0.0;
    	for (var i = 0; i < this._sc_Cargo.length; i++) {
    		switch (this._sc_Cargo[i].unit) {
    			case "t":
    				t += this._sc_Cargo[i].quantity;
    				break;
    			case "kg":
    				t += (this._sc_Cargo[i].quantity / 1000);
    				break;
    			case "g":
    				t += (this._sc_Cargo[i].quantity / 1000000);
    				break;
    		}
    	}
    	return t;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns the amount of available space in the compartment
    this.$availableSpace = function $availableSpace() {
    	var total = this.$smugglingCompartmentSize();
    	return (total - this.$usedSpace());
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // clean up any zero amounts
    this.$cargoCleanUp = function $cargoCleanUp() {
    	for (var i = this._sc_Cargo.length - 1; i >= 0; i--) {
    		if (this._sc_Cargo[i].quantity === 0) {
    			this._sc_Cargo.splice(i, 1);
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // shows the installation page to select a compartment size
    this.$showPurchasePage = function $showPurchasePage() {
    
    	var curChoices = {};
    	var p = player.ship;
    	var stn = p.dockedStation;
    	var rowAdjust = 0;
    	var pagesize = 0;
    
    	//this._hudHidden = p.hudHidden;
    	if (this.$isBigGuiActive() === false) {
    		p.hudHidden = true;
    	}
    	this._smugglingScreen = true;
    
    	var text = expandDescription("[purchase-page-header]", {
    		cash: formatCredits(player.credits, true, true)
    	});
    	var curr_size = this.$smugglingCompartmentSize();
    
    	// what is the maximum allowed for this vessel (35% of available space, maxing out at 20t)
    	var shipmax = parseInt((p.cargoSpaceCapacity + curr_size) * 0.35);
    	// make sure we can get at least 1tc
    	if ((p.cargoSpaceCapacity + curr_size) > 0 && shipmax === 0) shipmax = 1;
    	// make sure we don't go over the 20 tc limit
    	if (shipmax > this._maxTonnage) shipmax = this._maxTonnage;
    
    	var max;
    	var check = (p.cargoSpaceAvailable + curr_size);
    	if (check < 0) {
    		max += check;
    	} else {
    		max = check;
    	}
    	if (max > this._maxTonnage) max = this._maxTonnage;
    
    	if (curr_size > 0) {
    		text += expandDescription("[downgrade-warning]");
    		rowAdjust += 2;
    	}
    
    	pagesize = 23 - rowAdjust;
    
    	var count = 0;
    
    	for (var i = 1; i <= this._maxTonnage; i++) {
    		var eq = EquipmentInfo.infoForKey("EQ_SMUGGLING_COMPARTMENT_" + i.toString());
    		var inst_time = ((600 + (eq.price * stn.equipmentPriceFactor)) / 60) / 60;
    		var price = (eq.price / 10) * stn.equipmentPriceFactor;
    		var menuitem = this.$padTextLeft(i + " t capacity", 6) + this.$padTextLeft(formatCredits(price, true, true), 7) + this.$padTextLeft(inst_time.toFixed(2) + " hours installation time", 15);
    		if (i <= shipmax) {
    			count += 1;
    			if (i != curr_size && i <= max && player.credits >= price) {
    				curChoices["01_HOLD_" + (i < 10 ? "0" : "") + i + "~" + i] = {
    					text: menuitem,
    					alignment: "LEFT",
    					color: this._menuColor
    				};
    			} else {
    				curChoices["01_HOLD_" + (i < 10 ? "0" : "") + i + "~" + i] = {
    					text: menuitem,
    					alignment: "LEFT",
    					color: this._disabledColor,
    					unselectable: true
    				};
    			}
    		}
    	}
    
    	curChoices["98_SPACER"] = "";
    	curChoices["99_EXIT"] = {
    		text: "[exit-no-change]",
    		color: this._itemColor
    	};
    	var def = "99_EXIT";
    
    	if ((count + 1) < pagesize) {
    		for (var i = (pagesize - (count + 1)); i > 0; i--) {
    			curChoices["99_SPACER_" + (i < 10 ? "0" : "") + i] = "";
    		}
    	}
    
    	var opts = {
    		screenID: "oolite-smuggling-purchase-map",
    		title: "Smuggling Compartment Installation",
    		allowInterrupt: true,
    		overlay: {
    			name: "stgu-cupboard.png",
    			height: 546
    		},
    		exitScreen: "GUI_SCREEN_EQUIP_SHIP",
    		choices: curChoices,
    		initialChoicesKey: def,
    		message: text
    	};
    
    	mission.runScreen(opts, this.$purchaseScreenHandler, this);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$purchaseScreenHandler = function $purchaseScreenHandler(choice) {
    
    	if (!choice) return;
    
    	if (choice.indexOf("01_HOLD") >= 0) {
    		var p = player.ship;
    		var stn = p.dockedStation;
    
    		var newsize = parseInt(choice.substring(choice.indexOf("~") + 1));
    
    		var newEq = this._equipmentList[newsize - 1];
    		var oldEq = this.$getSmugglingCompartmentEquipment();
    
    		// truncate any existing cargo
    		if (newsize < this.$usedSpace()) {
    			var diff = this.$usedSpace - newsize;
    			do {
    				for (var i = this._sc_Cargo.length; i >= 0; i--) {
    					if (diff > 0) {
    						switch (this._sc_Cargo[i].unit) {
    							case "t":
    								this._sc_Cargo[i].quantity -= 1;
    								diff -= 1;
    								break;
    							case "kg":
    								if (this._sc_Cargo[i].quantity > 1000) {
    									this._sc_Cargo[i].quantity -= 1000;
    									diff -= 1;
    								}
    								break;
    							case "g":
    								if (this._sc_Cargo[i].quantity > 1000000) {
    									this._sc_Cargo[i].quantity -= 1000000;
    									diff -= 1;
    								}
    						}
    					}
    				}
    			} while (diff > 0);
    
    			this.$cargoCleanUp();
    		}
    
    		// remove the old one (if present)
    		if (oldEq != "") {
    			this._backdoor = true;
    			p.removeEquipment(oldEq);
    			this._backdoor = false;
    			// refund the price of the old version
    			var oldEqInfo = EquipmentInfo.infoForKey(oldEq);
    			player.credits += ((oldEqInfo.price / 10) * stn.equipmentPriceFactor);
    		}
    		// install the new one
    		p.awardEquipment(newEq);
    		p.awardEquipment("EQ_SMUGGLING_INFLIGHT_STORAGE");
    
    		var eq = EquipmentInfo.infoForKey(newEq);
    		var inst_time = 600 + (eq.price * stn.equipmentPriceFactor);
    		var price = (eq.price / 10) * stn.equipmentPriceFactor;
    
    		// charge the player
    		player.credits -= price;
    		// add the smuggler role to the player
    		player.setPlayerRole("trader-smuggler");
    		// apply the install time
    		clock.addSeconds(inst_time);
    
    		// if StationDockControl is installed, tell it to check for launched ships and repopulate
    		var sdc = worldScripts.StationDockControl;
    		if (sdc && sdc._disable === false) sdc.$checkForLaunchedShips();
    
    		this.$initInterface(stn);
    		this._sc_Days = 0;
    
    		// make sure the right email is sent to the player
    		var email = worldScripts.GalCopAdminServices;
    		if (email) {
    			email._boughtKey = newEq;
    			email.$playerBoughtEquip();
    		}
    	}
    
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // initial entry point for the smuggling compartment config screen
    this.$showInitialSmugglingConfig = function $showInitialSmugglingConfig() {
    	this._display = 0;
    	this.$showSmugglingConfig();
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // main config screen display
    this.$showSmugglingConfig = function $showSmugglingConfig() {
    	function compare(a, b) {
    		if ((a.gov * 100) + a.tl < (b.gov * 100) + b.tl) return -1;
    		if ((a.gov * 100) + a.tl > (b.gov * 100) + b.tl) return 1;
    		return 0;
    	}
    
    	var text = "";
    	var curChoices = {};
    	var p = player.ship;
    
    	var pagesize = 26;
    	var phaseSetting = false;
    	if (p.equipmentStatus("EQ_SMUGGLING_PHASE_ADJUSTMENT") === "EQUIPMENT_OK") phaseSetting = true;
    
    	this._hudHidden = p.hudHidden;
    	if (this.$isBigGuiActive() === false) p.hudHidden = true;
    	this._smugglingScreen = true;
    
    	if (this.$isCompartmentDamaged() === true) this._display = 10;
    
    	// main display
    	if (this._display === 0) {
    		var lines = 5;
    		text = expandDescription("[total-capacity-header]") + this.$smugglingCompartmentSize() + "t    " + Math.floor(this.$usedSpace()) + "t used.\n";
    		text += expandDescription("[visible-as-header]") + displayNameForCommodity(this._sc_VisibleAs) + "     ";
    		if (phaseSetting) text += "Phase: " + this._sc_Phase + " mHz     ";
    		text += expandDescription("[tech-version-header]") + this._sc_TechVersion + ".0\n";
    		if (this._sc_Days >= 20) {
    			text += expandDescription("[tech-version-warning]");
    			lines += 1;
    		}
    		text += "\n";
    		lines += 6;
    		if (phaseSetting === false) lines -= 1;
    
    		var cargo = this.$getSmugglingCargo();
    		if (cargo.length > 0) {
    			text += expandDescription("[smuggling-current-contents]");
    			lines += 1;
    			var additem = "";
    			for (var i = 0; i < this._sc_Cargo.length; i++) {
    				if (additem === "") {
    					additem += "  " + this.$padTextRight(this._sc_Cargo[i].quantity + this._sc_Cargo[i].unit + " × " + displayNameForCommodity(this._sc_Cargo[i].commodity) + (this._sc_Cargo[i].extra != "" ? "*" : ""), 17);
    				} else {
    					additem += this._sc_Cargo[i].quantity + this._sc_Cargo[i].unit + " × " + displayNameForCommodity(this._sc_Cargo[i].commodity) + (this._sc_Cargo[i].extra != "" ? "*" : "");
    					text += additem + "\n";
    					lines += 1;
    					additem = "";
    				}
    			}
    			if (additem != "") {
    				text += additem + "\n";
    				lines += 1;
    			}
    		} else {
    			text += expandDescription("[smuggling-no-contents]");
    			lines += 2;
    		}
    
    		if (phaseSetting) {
    			curChoices["01_SET_PHASE"] = {
    				text: "[smuggling-set-phase]",
    				color: this._menuColor
    			};
    		}
    		curChoices["02_VIEW_DISCOVERED"] = {
    			text: "[smuggling-view-phase]",
    			color: this._menuColor
    		};
    		curChoices["03_SET_VISIBLE"] = {
    			text: "[smuggling-set-visible-as]",
    			color: this._menuColor
    		};
    
    		var avail = this.$availableSpace();
    		var used = this.$usedSpace();
    
    		if (this.$playerHasCargo() === true && avail > 0) {
    			curChoices["04_MOVE_CARGO_IN"] = {
    				text: "[smuggling-move-in]",
    				color: this._menuColor
    			};
    		} else {
    			curChoices["04_MOVE_CARGO_IN"] = {
    				text: "[smuggling-move-in]",
    				color: this._disabledColor,
    				unselectable: true
    			};
    		}
    		if (used > 0) {
    			curChoices["05_MOVE_CARGO_OUT"] = {
    				text: "[smuggling-move-out]",
    				color: this._menuColor
    			};
    		} else {
    			curChoices["05_MOVE_CARGO_OUT"] = {
    				text: "[smuggling-move-out]",
    				color: this._disabledColor,
    				unselectable: true
    			};
    		}
    		if (used > 0) {
    			curChoices["06_SELL_CARGO"] = {
    				text: "[smuggling-sell-from]",
    				color: this._menuColor
    			};
    		} else {
    			curChoices["06_SELL_CARGO"] = {
    				text: "[smuggling-sell-from]",
    				color: this._disabledColor,
    				unselectable: true
    			};
    		}
    		if (avail > 0) {
    			curChoices["07_BUY_CARGO"] = {
    				text: "[smuggling-buy-in]",
    				color: this._menuColor
    			};
    		} else {
    			curChoices["07_BUY_CARGO"] = {
    				text: "[smuggling-buy-in]",
    				color: this._disabledColor,
    				unselectable: true
    			};
    		}
    
    		//curChoices["70_SPACER"] = "";
    		curChoices["99_EXIT"] = {
    			text: "[smuggling-exit-config]",
    			color: this._itemColor
    		};
    
    		pagesize = 26;
    		if (lines < pagesize) {
    			for (var i = (pagesize - lines); i > 0; i--) {
    				curChoices["99_SPACER_" + (i < 10 ? "0" : "") + i] = "";
    			}
    		}
    
    		var def = "99_EXIT";
    
    		var opts = {
    			screenID: "oolite-smuggling-config-summary",
    			title: "Smuggling Compartment Configuration",
    			allowInterrupt: true,
    			overlay: {
    				name: "stgu-cupboard.png",
    				height: 546
    			},
    			exitScreen: "GUI_SCREEN_INTERFACES",
    			choices: curChoices,
    			initialChoicesKey: def,
    			message: text
    		};
    
    	}
    
    	// set visible property
    	if (this._display === 1) {
    		text = expandDescription("[visible-as-property]", {
    			commodity: displayNameForCommodity(this._sc_VisibleAs)
    		});
    
    		for (var i = 0; i < this._commodityIDList.length; i++) {
    			if (this._sc_VisibleAs === this._commodityIDList[i]) {
    				curChoices["40_" + (i < 10 ? "0" : "") + i + "~" + this._commodityIDList[i]] = {
    					text: displayNameForCommodity(this._commodityIDList[i]),
    					color: this._disabledColor,
    					unselectable: true
    				};
    			} else {
    				curChoices["40_" + (i < 10 ? "0" : "") + i + "~" + this._commodityIDList[i]] = {
    					text: displayNameForCommodity(this._commodityIDList[i]),
    					color: this._menuColor
    				};
    			}
    		}
    
    		curChoices["97_SPACER"] = "";
    		curChoices["98_EXIT"] = {
    			text: "[exit-no-change]",
    			color: this._itemColor
    		};
    
    		def = "98_EXIT";
    
    		var opts = {
    			screenID: "oolite-smuggling-config-map",
    			title: "Smuggling Compartment Configuration",
    			allowInterrupt: true,
    			overlay: {
    				name: "stgu-cupboard.png",
    				height: 546
    			},
    			exitScreen: "GUI_SCREEN_INTERFACES",
    			choices: curChoices,
    			initialChoicesKey: def,
    			message: text
    		};
    
    	}
    
    	if (this._display === 2) { // move cargo in
    		// todo: deal with new cargoes
    
    		var avail = this.$availableSpace();
    		text = expandDescription("[move-cargo-in]", {
    			smuggling: Math.floor(avail),
    			standard: p.cargoSpaceAvailable
    		});
    
    		pagesize = 20;
    
    		var cargo = p.manifest.list;
    		var sdm = worldScripts.Smugglers_DockMaster;
    		var itemcount = 0;
    
    		for (var i = 0; i < cargo.length; i++) {
    			// only show standard (non-OXP) commodity items
    			if (this._commodityIDList.indexOf(cargo[i].commodity) >= 0) {
    				var q = cargo[i].quantity;
    				// make sure we don't move relabelled cargo
    				if (sdm._cargoLabelled.length > 0) {
    					for (var j = 0; j < sdm._cargoLabelled.length; j++) {
    						if (sdm._cargoLabelled[j].newCommodity === cargo[i].commodity) q -= sdm._cargoLabelled[j].quantity;
    					}
    				}
    				// don't allow scooped escape pods to be moved
    				if (cargo[i].commodity === "slaves" && cargo[i].quantity > 0 && worldScripts.EscapePodTweaks && worldScripts.EscapePodTweaks._heldPods.length > 0) {
    					q -= worldScripts.EscapePodTweaks._heldPods;
    				}
    
    				if (q > 0) {
    					curChoices["50_" + (i < 10 ? "0" : "") + i + "_CARGO~" + cargo[i].commodity] = {
    						text: cargo[i].displayName + " (" + q + cargo[i].unit + ")" + (this.$calcCTEcargo(cargo[i].commodity) > 0 ? "*" : ""),
    						color: this._menuColor
    					};
    					itemcount += 1;
    				}
    			}
    		}
    
    		curChoices["97_SPACER"] = "";
    		curChoices["98_EXIT"] = {
    			text: "[illegal-return]",
    			color: this._itemColor
    		};
    
    		for (var i = 1; i <= (pagesize - itemcount); i++) {
    			curChoices["99_SPACER_" + (i < 10 ? "0" : "") + i] = "";
    		}
    
    		def = "98_EXIT";
    
    		var opts = {
    			screenID: "oolite-smuggling-movecargoin-map",
    			title: "Smuggling Compartment Cargo",
    			allowInterrupt: true,
    			overlay: {
    				name: "stgu-cupboard.png",
    				height: 546
    			},
    			exitScreen: "GUI_SCREEN_INTERFACES",
    			choices: curChoices,
    			initialChoicesKey: def,
    			message: text
    		};
    
    	}
    
    	if (this._display === 3) { // move cargo out
    		// todo: deal with new cargoes
    
    		var avail = this.$availableSpace();
    		text = expandDescription("[move-cargo-out]", {
    			smuggling: Math.floor(avail),
    			standard: p.cargoSpaceAvailable
    		});
    
    		pagesize = 20;
    
    		var cargo = this.$getSmugglingCargo();
    
    		for (var i = 0; i < cargo.length; i++) {
    			if ((p.cargoSpaceAvailable === 0 && cargo[i].unit != "t") || p.cargoSpaceAvailable > 0) {
    				curChoices["51_" + (i < 10 ? "0" : "") + i + "_CARGO~" + cargo[i].commodity] = {
    					text: cargo[i].quantity + " " + cargo[i].unit + " × " + cargo[i].displayName + (cargo[i].extra != "" ? "*" : ""),
    					color: this._menuColor
    				};
    			}
    		}
    
    		curChoices["97_SPACER"] = "";
    		curChoices["98_EXIT"] = {
    			text: "[illegal-return]",
    			color: this._itemColor
    		};
    
    		for (var i = 1; i <= (pagesize - cargo.length); i++) {
    			curChoices["99_SPACER_" + (i < 10 ? "0" : "") + i] = "";
    		}
    		def = "98_EXIT";
    
    		var opts = {
    			screenID: "oolite-smuggling-movecargoout-map",
    			title: "Smuggling Compartment Cargo",
    			allowInterrupt: true,
    			overlay: {
    				name: "stgu-cupboard.png",
    				height: 546
    			},
    			exitScreen: "GUI_SCREEN_INTERFACES",
    			choices: curChoices,
    			initialChoicesKey: def,
    			message: text
    		};
    
    	}
    
    	if (this._display === 4) { // view discovered phase settings
    
    		// look for and remove any TL0 items in the list
    		// why are we doing this???
    		//for (var i = this._phaseScanDiscovery.length - 1; i >= 0; i--) {
    		//	if (this._phaseScanDiscovery[i].tl === 0) {
    		//		this._phaseScanDiscovery.splice(i, 1);
    		//	}
    		//}
    		// sort the list by government/techlevel
    		this._phaseScanDiscovery.sort(compare);
    
    		var govs = new Array();
    		for (var i = 0; i < 8; i++)
    			govs.push(String.fromCharCode(i));
    		var spc = String.fromCharCode(31);
    
    		text = expandDescription("[discovered-phase-scans]");
    
    		if (this._phaseScanDiscovery.length === 0) {
    			text += "  None";
    		} else {
    			for (var i = 0; i < 24; i++) {
    				if (this._phaseScanDiscovery.length - 1 >= i && this._phaseScanDiscovery[i].phase != 0) {
    					text += this.$padTextRight(govs[this._phaseScanDiscovery[i].gov] + spc + "TL" + (this._phaseScanDiscovery[i].tl + 1) + ":", 3.7) + this.$padTextRight(this._phaseScanDiscovery[i].phase, 3);
    				}
    				if (this._phaseScanDiscovery.length - 1 >= (i + 24) && this._phaseScanDiscovery[i + 24].phase != 0) {
    					text += this.$padTextRight(govs[this._phaseScanDiscovery[i + 24].gov] + spc + "TL" + (this._phaseScanDiscovery[i + 24].tl + 1) + ":", 3.7) + this.$padTextRight(this._phaseScanDiscovery[i + 24].phase, 3);
    				}
    				if (this._phaseScanDiscovery.length - 1 >= (i + 48) && this._phaseScanDiscovery[i + 48].phase != 0) {
    					text += this.$padTextRight(govs[this._phaseScanDiscovery[i + 48].gov] + spc + "TL" + (this._phaseScanDiscovery[i + 48].tl + 1) + ":", 3.7) + this.$padTextRight(this._phaseScanDiscovery[i + 48].phase, 3);
    				}
    				if (this._phaseScanDiscovery.length - 1 >= (i + 72) && this._phaseScanDiscovery[i + 72].phase != 0) {
    					text += this.$padTextRight(govs[this._phaseScanDiscovery[i + 72].gov] + spc + "TL" + (this._phaseScanDiscovery[i + 72].tl + 1) + ":", 3.7) + this.$padTextRight(this._phaseScanDiscovery[i + 72].phase, 3);
    				}
    				if (this._phaseScanDiscovery.length - 1 >= (i + 96) && this._phaseScanDiscovery[i + 96].phase != 0) {
    					text += this.$padTextRight(govs[this._phaseScanDiscovery[i + 96].gov] + spc + "TL" + (this._phaseScanDiscovery[i + 96].tl + 1) + ":", 3.7) + this._phaseScanDiscovery[i + 96].phase;
    				}
    				text += "\n";
    			}
    		}
    
    		curChoices["98_EXIT"] = {
    			text: "[illegal-return]",
    			color: this._itemColor
    		};
    
    		def = "98_EXIT";
    
    		var opts = {
    			screenID: "oolite-smuggling-discovered-map",
    			title: "Smuggling Compartment Configuration",
    			allowInterrupt: true,
    			overlay: {
    				name: "stgu-cupboard.png",
    				height: 546
    			},
    			exitScreen: "GUI_SCREEN_INTERFACES",
    			choices: curChoices,
    			initialChoicesKey: def,
    			message: text
    		};
    
    	}
    
    	// sell cargo from compartment
    	if (this._display === 5) {
    		// deal with new cargoes
    		text = expandDescription("[sell-smuggling-cargo]");
    
    		pagesize = 23;
    
    		if (this._extraMessage) {
    			text += "\n(Note: " + this._extraMessage + ")";
    			pagesize -= 1;
    			delete this._extraMessage;
    		}
    
    		var cargo = this.$getSmugglingCargo();
    		var mkt = p.dockedStation.market;
    
    		for (var i = 0; i < cargo.length; i++) {
    			curChoices["52_" + (i < 10 ? "0" : "") + i + "_CARGO~" + cargo[i].commodity] = {
    				text: this.$padTextRight(cargo[i].quantity + " " + cargo[i].unit + " × " + cargo[i].displayName, 12) +
    					(cargo[i].extra != "" ? "*" : "") +
    					this.$padTextLeft(formatCredits((mkt[cargo[i].commodity].price / 10), true, true), 5),
    				color: this._menuColor
    			};
    		}
    
    		curChoices["97_SPACER"] = "";
    		curChoices["98_EXIT"] = {
    			text: "[illegal-return]",
    			color: this._itemColor
    		};
    
    		for (var i = 1; i <= (pagesize - cargo.length); i++) {
    			curChoices["99_SPACER_" + (i < 10 ? "0" : "") + i] = "";
    		}
    		def = "98_EXIT";
    
    		var opts = {
    			screenID: "oolite-smuggling-sellcargo-map",
    			title: "Smuggling Compartment Sell Cargo",
    			allowInterrupt: true,
    			overlay: {
    				name: "stgu-cupboard.png",
    				height: 546
    			},
    			exitScreen: "GUI_SCREEN_INTERFACES",
    			choices: curChoices,
    			initialChoicesKey: def,
    			message: text
    		};
    	}
    
    	// buy cargo directly to compartment
    	if (this._display === 6) {
    		text = expandDescription("[buy-smuggling-cargo]");
    
    		pagesize = 23;
    
    		if (this._insufficientFunds && this._insufficientFunds === true) {
    			text += "\n(Note: Insufficient funds to purchase " + displayNameForCommodity(this._tfrCargo) + ".)";
    			pagesize = 22;
    		}
    
    		var mkt = p.dockedStation.market;
    
    		var itemorder = 0;
    		var itemcount = 0;
    
    		for (var i in mkt) {
    			if (this._commodityIDList.indexOf(i) >= 0) {
    				if (mkt[i].quantity > 0) {
    					itemorder = parseInt(mkt[i].sort_order / 100);
    					curChoices["53_" + (itemorder < 10 ? "0" : "") + itemorder + "_CARGO~" + i] = {
    						text: this.$padTextRight(displayNameForCommodity(i), 8) +
    							this.$padTextLeft(formatCredits((mkt[i].price / 10), true, true), 4) +
    							this.$padTextLeft(mkt[i].quantity, 4) + " " +
    							this.$padTextRight(this.$getCommodityType(i), 2),
    						color: this._menuColor
    					};
    					itemcount += 1;
    				}
    			}
    		}
    
    		curChoices["97_SPACER"] = "";
    		curChoices["98_EXIT"] = {
    			text: "[illegal-return]",
    			color: this._itemColor
    		};
    
    		for (var i = 1; i <= (pagesize - itemcount); i++) {
    			curChoices["99_SPACER_" + (i < 10 ? "0" : "") + i] = "";
    		}
    		def = "98_EXIT";
    
    		var opts = {
    			screenID: "oolite-smuggling-buycargo-map",
    			title: "Smuggling Compartment Buy Cargo",
    			allowInterrupt: true,
    			overlay: {
    				name: "stgu-cupboard.png",
    				height: 546
    			},
    			exitScreen: "GUI_SCREEN_INTERFACES",
    			choices: curChoices,
    			initialChoicesKey: def,
    			message: text
    		};
    
    	}
    
    	if (this._display === 10) {
    		text = expandDescription("[smuggling-compartment-damaged]");
    
    		curChoices["99_EXIT"] = {
    			text: "[smuggling-exit-config]",
    			color: this._itemColor
    		};
    
    		var opts = {
    			screenID: "smuggling_compartment_damaged",
    			title: "Smuggling Compartment Configuration",
    			message: text,
    			choices: curChoices,
    			overlay: {
    				name: "stgu-lock.png",
    				height: 546
    			},
    			exitScreen: "GUI_SCREEN_INTERFACES"
    		};
    
    	}
    	mission.runScreen(opts, this.$configScreenHandler, this);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$configScreenHandler = function $configScreenHandler(choice) {
    
    	if (!choice) return;
    
    	switch (choice) {
    		case "01_SET_PHASE":
    			this.$setPhase();
    			break;
    		case "02_VIEW_DISCOVERED":
    			this._display = 4;
    			break;
    		case "03_SET_VISIBLE":
    			this._display = 1;
    			break;
    		case "04_MOVE_CARGO_IN":
    			this._display = 2;
    			break;
    		case "05_MOVE_CARGO_OUT":
    			this._display = 3;
    			break;
    		case "06_SELL_CARGO":
    			this._display = 5;
    			break;
    		case "07_BUY_CARGO":
    			this._display = 6;
    			break;
    		case "98_EXIT":
    			this._display = 0;
    			break;
    	}
    
    	if (choice.indexOf("40_") >= 0) {
    		this._sc_VisibleAs = choice.substring(choice.indexOf("~") + 1);
    		this._display = 0;
    	}
    
    	var showMain = true;
    
    	if (choice.indexOf("50_") >= 0) {
    		showMain = false;
    		this._tfrCargo = choice.substring(choice.indexOf("~") + 1);
    		var cargo = player.ship.manifest.list;
    		var sdm = worldScripts.Smugglers_DockMaster;
    
    		for (var i = 0; i < cargo.length; i++) {
    			if (cargo[i].commodity === this._tfrCargo) {
    				var q = cargo[i].quantity;
    
    				// make sure we don't move relabelled cargo
    				if (sdm._cargoLabelled.length > 0) {
    					for (var j = 0; j < sdm._cargoLabelled.length; j++) {
    						if (sdm._cargoLabelled[j].newCommodity === cargo[i].commodity) q -= sdm._cargoLabelled[j].quantity;
    					}
    				}
    
    				// don't allow scooped escape pods to be moved
    				if (cargo[i].commodity === "slaves" && cargo[i].quantity > 0 && worldScripts.EscapePodTweaks && worldScripts.EscapePodTweaks._heldPods.length > 0) {
    					q -= worldScripts.EscapePodTweaks._heldPods;
    				}
    
    				this._tfrCargoName = cargo[i].displayName;
    				this._tfrMaxAmount = q;
    				this._tfrUnit = cargo[i].unit;
    				this._tfrExtra = this.$calcCTEcargo(cargo[i].commodity).toString();
    				var avail = this.$smugglingCompartmentSize();
    				var used = this.$usedSpace();
    				// make sure our max amount set isn't greater than the cargo space limit
    				switch (this._tfrUnit) {
    					case "t":
    						if (this._tfrMaxAmount > (avail - used)) {
    							this._tfrMaxAmount = avail - used;
    						}
    						break;
    					case "kg":
    						var sub_avail = avail * 1000;
    						var sub_used = used * 1000;
    						if (this._tfrMaxAmount > (sub_avail - sub_used)) this._tfrMaxAmount = sub_avail - sub_used;
    						break;
    					case "g":
    						var sub_avail = avail * 1000000;
    						var sub_used = used * 1000000;
    						if (this._tfrMaxAmount > (sub_avail - sub_used)) this._tfrMaxAmount = sub_avail - sub_used;
    						break;
    				}
    				this.$getTransferAmount(1);
    			}
    		}
    	}
    
    	if (choice.indexOf("51_") >= 0) {
    		showMain = false;
    		this._tfrCargo = choice.substring(choice.indexOf("~") + 1);
    		var cargo = this.$getSmugglingCargo();
    		for (var i = 0; i < cargo.length; i++) {
    			if (cargo[i].commodity === this._tfrCargo) {
    				this._tfrCargoName = cargo[i].displayName;
    				this._tfrMaxAmount = cargo[i].quantity;
    				this._tfrUnit = cargo[i].unit;
    				this._tfrExtra = cargo[i].extra.split(";").length.toString();
    				var avail = player.ship.cargoSpaceAvailable;
    				// make sure our max amount set isn't greater than the cargo space limit
    				switch (this._tfrUnit) {
    					case "t":
    						if (this._tfrMaxAmount > avail) this._tfrMaxAmount = avail;
    						break;
    					case "kg":
    						var sub_avail = avail * 1000;
    						if (this._tfrMaxAmount > sub_avail) this._tfrMaxAmount = sub_avail;
    						break;
    					case "g":
    						var sub_avail = avail * 1000000;
    						if (this._tfrMaxAmount > sub_avail) this._tfrMaxAmount = sub_avail;
    						break;
    
    				}
    				this.$getTransferAmount(2);
    			}
    		}
    	}
    
    	if (choice.indexOf("52_") >= 0) {
    		showMain = false;
    		this._tfrCargo = choice.substring(choice.indexOf("~") + 1);
    		var cargo = this.$getSmugglingCargo();
    		for (var i = 0; i < cargo.length; i++) {
    			if (cargo[i].commodity === this._tfrCargo) {
    				this._tfrCargoName = cargo[i].displayName;
    				this._tfrMaxAmount = cargo[i].quantity;
    				this._tfrExtra = cargo[i].extra.split(";").length.toString();
    				var stn = player.ship.dockedStation;
    				// limit the max amount by the market capacity
    				var check = stn.market[this._tfrCargo].capacity - stn.market[this._tfrCargo].quantity;
    				if (this._tfrMaxAmount > check) this._tfrMaxAmount = check;
    				// if the max amount we can transfer is zero, return to the previous menu.
    				if (this._tfrMaxAmount === 0) {
    					this._extraMessage = "Market capacity reached - no more " + cargo[i].displayName + " can be sold.";
    					this.$showSmugglingConfig();
    					return;
    				}
    				this._tfrUnit = cargo[i].unit;
    				this.$getTransferAmount(3);
    			}
    		}
    	}
    
    	if (choice.indexOf("53_") >= 0) {
    		showMain = false;
    		this._insufficientFunds = false;
    		this._tfrCargo = choice.substring(choice.indexOf("~") + 1);
    		var cargoItem = player.ship.dockedStation.market[this._tfrCargo];
    		this._tfrCargoName = displayNameForCommodity(this._tfrCargo);
    		this._tfrUnit = this.$getCommodityType(this._tfrCargo);
    		this._tfrMaxAmount = cargoItem.quantity;
    		this._tfrExtra = "";
    		var avail = this.$availableSpace();
    		switch (this._tfrUnit) {
    			case "t":
    				if (avail < this._tfrMaxAmount) this._tfrMaxAmount = avail;
    				break;
    			case "kg":
    				var sub_avail = avail * 1000;
    				if (sub_avail < this._tfrMaxAmount) this._tfrMaxAmount = sub_avail;
    				break;
    			case "g":
    				var sub_avail = avail * 1000000;
    				if (sub_avail < this._tfrMaxAmount) this._tfrMaxAmount = sub_avail;
    				break;
    		}
    		if ((this._tfrMaxAmount * (cargoItem.price / 10)) > player.credits) {
    			do {
    				this._tfrMaxAmount -= 1;
    			} while ((this._tfrMaxAmount * (cargoItem.price / 10)) > player.credits)
    		}
    		if (this._tfrMaxAmount > 0) {
    			this.$getTransferAmount(4);
    		} else {
    			showMain = true;
    			this._insufficientFunds = true;
    		}
    	}
    
    	if (choice != "99_EXIT" && choice != "01_SET_PHASE" && showMain === true) {
    		this.$showSmugglingConfig();
    	}
    
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns true if the player has any cargo at all
    this.$playerHasCargo = function $playerHasCargo() {
    	var m = player.ship.manifest;
    	for (var i = 0; i < m.list.length; i++) {
    		if (m.list[i].quantity > 0) return true;
    	}
    	return false;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns true if the player has any illegal cargo in the visible cargo hold
    this.$playerHasIllegalCargo = function $playerHasIllegalCargo(station) {
    	var m = player.ship.manifest;
    	for (var i = 0; i < m.list.length; i++) {
    		if (m.list[i].quantity > 0 && station.market[m.list[i].commodity].legality_import > 0) {
    			return true;
    		}
    	}
    	return false;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns true if the player has any illegal cargo in their smuggling compartment
    this.$playerHasHiddenIllegalCargo = function $playerHasHiddenIllegalCargo(station) {
    	var c = this.$getSmugglingCargo();
    	var found = false;
    	for (var i = 0; i < c.length; i++) {
    		if (station.market[c[i].commodity].legality_import > 0) found = true;
    	}
    	return found;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // prompts the user for a new phase
    this.$setPhase = function $setPhase() {
    
    	var text = "";
    
    	text = expandDescription("[set-phase]", {
    		current: this._sc_Phase
    	});
    
    	var opts = {
    		screenID: "oolite-smuggling-setphase-map",
    		title: "Smuggling Compartment Configuration",
    		allowInterrupt: false,
    		exitScreen: "GUI_SCREEN_INTERFACES",
    		message: text,
    		textEntry: true
    	};
    	mission.runScreen(opts, this.$getNewPhase, this);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // parses the input from the setPhase screen
    this.$getNewPhase = function $getNewPhase(param) {
    	if (parseInt(param) >= 1 && parseInt(param) <= 999) {
    		this._sc_Phase = parseInt(param);
    	}
    	this.$showSmugglingConfig();
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // prompts the user for the transfer amount
    this.$getTransferAmount = function $getTransferAmount(type) {
    
    	var text = "";
    
    	// don't show a screen when only 1 unit is possible
    	if (this._tfrMaxAmount === 1) {
    		switch (type) {
    			case 1:
    				this.$getCargoTransferIn(1);
    				return;
    			case 2:
    				this.$getCargoTransferOut(1);
    				return;
    			case 3:
    				this.$getCargoSell(1);
    				return;
    			case 4:
    				this.$getCargoBuy(1);
    				return;
    		}
    	}
    
    	switch (type) {
    		case 1:
    			text = expandDescription("[enter-quantity-smuggling]", {
    				commodity: this._tfrCargoName,
    				quantity: parseInt(this._tfrMaxAmount),
    				cte_note: (this._tfrExtra != "" ? "\n\nNote: " + this._tfrExtra + " units of this cargo comes from the specialty market." : "")
    			});
    			break;
    		case 2:
    			text = expandDescription("[enter-quantity-standard]", {
    				commodity: this._tfrCargoName,
    				quantity: parseInt(this._tfrMaxAmount),
    				cte_note: (this._tfrExtra != "" ? "\n\nNote: " + this._tfrExtra + " units of this cargo comes from the specialty market." : "")
    			});
    			break;
    		case 3:
    			text = expandDescription("[enter-quantity-sell]", {
    				commodity: this._tfrCargoName,
    				quantity: parseInt(this._tfrMaxAmount),
    				cte_note: (this._tfrExtra != "" ? "\n\nNote: " + this._tfrExtra + " units of this cargo comes from the specialty market." : "")
    			});
    			break;
    		case 4:
    			text = expandDescription("[enter-quantity-buy]", {
    				commodity: this._tfrCargoName,
    				quantity: parseInt(this._tfrMaxAmount)
    			});
    			break;
    	}
    
    	var opts = {
    		screenID: "oolite-smuggling-transfer-map",
    		title: "Smuggling Compartment Cargo",
    		allowInterrupt: false,
    		exitScreen: "GUI_SCREEN_INTERFACES",
    		message: text,
    		textEntry: true
    	};
    
    	switch (type) {
    		case 1:
    			mission.runScreen(opts, this.$getCargoTransferIn, this);
    			break;
    		case 2:
    			mission.runScreen(opts, this.$getCargoTransferOut, this);
    			break;
    		case 3:
    			mission.runScreen(opts, this.$getCargoSell, this);
    			break;
    		case 4:
    			mission.runScreen(opts, this.$getCargoBuy, this);
    			break;
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // parses the input from the getTransferAmount screen
    this.$getCargoTransferIn = function $getCargoTransferIn(param) {
    	if (!param) {
    		this.$showSmugglingConfig();
    		return;
    	}
    	if (parseInt(param) >= 1 && parseInt(param) <= this._tfrMaxAmount) {
    		var p = player.ship;
    		var max = p.manifest[this._tfrCargo];
    		var amount = parseInt(param);
    		var extra = "";
    		if (this._NCInstalled) {
    			extra = this.$extractCargoFromCTE(this._tfrCargo, amount, max);
    		}
    		this.$addCargoToCompartment(this._tfrCargo, this._tfrCargoName, amount, this._tfrUnit, extra);
    		// take the cargo out of the hold
    		var p = player.ship;
    		p.manifest[this._tfrCargo] -= amount;
    	}
    	this._display = 2;
    	if (this.$availableSpace() === 0) this._display = 0;
    	this.$showSmugglingConfig();
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$calcCTEcargo = function (generic) {
    	var cte = worldScripts.CargoTypeExtension;
    	// if there isn't enough generic cargo of a particular type
    	// throw out special cargo entries of that type until there is
    	var totalspecial = 0;
    	for (var j = 0; j < cte.specialCargoCarried[generic].length; j++) {
    		if (cte.specialCargoRegister[cte.specialCargoCarried[generic][j].type]) {
    			totalspecial += cte.specialCargoCarried[generic][j].quantity;
    		}
    	}
    	return totalspecial;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // parses the input from the getTransferAmount screen
    this.$getCargoTransferOut = function $getCargoTransferOut(param) {
    	if (!param) {
    		this.$showSmugglingConfig();
    		return;
    	}
    	if (parseInt(param) >= 1 && parseInt(param) <= this._tfrMaxAmount) {
    		var p = player.ship;
    		var amount = parseInt(param);
    		var starting = p.manifest[this._tfrCargo];
    		// put the cargo into the main hold
    		p.manifest[this._tfrCargo] += amount;
    		// check that the hold actually took all the amount
    		if (p.manifest[this._tfrCargo] != (starting + amount)) {
    			// if not, adjust the amount of transfer
    			amount = p.manifest[this._tfrCargo] - starting;
    		}
    		for (var i = 0; i < this._sc_Cargo.length; i++) {
    			if (this._sc_Cargo[i].commodity === this._tfrCargo) {
    				if (this._NCInstalled) {
    					var ncmanifest = this.$checkForAndRemoveCTECargo(this._tfrCargo, amount);
    					if (ncmanifest != "") {
    						var cte = worldScripts.CargoTypeExtension;
    						cte.mergePlayerManifest(ncmanifest);
    					}
    				}
    				this._sc_Cargo[i].quantity -= amount;
    				break;
    			}
    		}
    		this.$cargoCleanUp();
    	}
    	this._display = 3;
    	if (this.$usedSpace() === 0) this._display = 0;
    	this.$showSmugglingConfig();
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // parses the input from the getTransferAmount screen
    this.$getCargoSell = function $getCargoSell(param) {
    	if (!param) {
    		this.$showSmugglingConfig();
    		return;
    	}
    	if (parseInt(param) >= 1 && parseInt(param) <= this._tfrMaxAmount) {
    		var amount = parseInt(param);
    		var count = 0;
    		var check = 0;
    		var p = player.ship;
    		var stn = p.dockedStation;
    		var mkt = stn.market;
    		for (var i = this._sc_Cargo.length - 1; i >= 0; i--) {
    			if (this._sc_Cargo[i].commodity === this._tfrCargo) {
    				check = mkt[this._tfrCargo].quantity;
    				var newamount = check + amount;
    				var actual = amount;
    				// check that we aren't going over the market capacity
    				if (newamount > mkt[this._tfrCargo].capacity) {
    					newamount = mkt[this._tfrCargo].capacity;
    					actual = newamount - check;
    				}
    				// only do an sell if the actual amount that can be sold is greater than zero
    				if (actual > 0) {
    					stn.setMarketQuantity(this._tfrCargo, newamount);
    
    					this.$checkForAndRemoveCTECargo(this._tfrCargo, actual);
    
    					this._sc_Cargo[i].quantity -= actual;
    					count += actual;
    					player.credits += (mkt[this._tfrCargo].price / 10) * actual;
    					this.$playSound("sell");
    				}
    				break;
    			}
    		}
    		this.$cargoCleanUp();
    	}
    	this._display = 5;
    	if (this.$usedSpace() === 0) this._display = 0;
    	this.$showSmugglingConfig();
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // parses the input from the getTransferAmount screen
    this.$getCargoBuy = function $getCargoBuy(param) {
    	if (!param) {
    		this.$showSmugglingConfig();
    		return;
    	}
    	if (parseInt(param) >= 1 && parseInt(param) <= this._tfrMaxAmount) {
    		var amount = parseInt(param);
    		this.$addCargoToCompartment(this._tfrCargo, this._tfrCargoName, amount, this._tfrUnit, "");
    		// take the cargo out of the market
    		var stn = player.ship.dockedStation;
    		var mkt = stn.market;
    		var newamount = mkt[this._tfrCargo].quantity - amount;
    		stn.setMarketQuantity(this._tfrCargo, newamount);
    		player.credits -= (mkt[this._tfrCargo].price / 10) * amount;
    		this.$playSound("buy");
    	}
    	this._display = 6;
    	if (this.$availableSpace() === 0) this._display = 0;
    	this.$showSmugglingConfig();
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // adds cargo to smuggling compartment
    this.$addCargoToCompartment = function $addCargoToCompartment(cmdty, name, amount, unit, extra) {
    	var found = false;
    	for (var i = 0; i < this._sc_Cargo.length; i++) {
    		if (this._sc_Cargo[i].commodity === cmdty) {
    			// found it
    			found = true;
    			this._sc_Cargo[i].quantity += amount;
    			if (extra != "") {
    				if (this._sc_Cargo[i].extra == "") {
    					this._sc_Cargo[i].extra = extra;
    				} else {
    					this._sc_Cargo[i].extra += ";" + extra;
    				}
    			}
    		}
    	}
    	if (found === false) {
    		this._sc_Cargo.push({
    			commodity: cmdty,
    			displayName: name,
    			quantity: amount,
    			unit: unit,
    			extra: extra
    		});
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // create system phases
    this.$createSystemPhase = function $createSystemPhase() {
    	for (var i = 0; i <= 7; i++) {
    		for (var j = 1; j <= 15; j++) {
    			this._systemPhase.push({
    				gov: i,
    				tl: j,
    				phase: this.$rand(999)
    			});
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // updates the phase setting for a particular government/techlevel
    this.$updatePhase = function $updatePhase(govType, techlevel, phase) {
    	for (var i = 0; i < this._systemPhase.length; i++) {
    		if (this._systemPhase[i].gov === govType && this._systemPhase[i].tl === techlevel) {
    			this._systemPhase[i].phase = phase;
    			this._phaseUpdates.push({
    				gov: govType,
    				tl: techlevel
    			});
    			break;
    		}
    	}
    	for (var i = 0; i < this._phaseScanDiscovery.length; i++) {
    		if (this._phaseScanDiscovery[i].gov === govType && this._phaseScanDiscovery[i].tl === techlevel && this._phaseScanDiscovery[i].source < 3) this._phaseScanDiscovery[i].source = 4;
    	}
    
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns the phase for a given system ID
    this.$getSystemPhase = function $getSystemPhase(systemID) {
    	var sysinfo = System.infoForSystem(galaxyNumber, systemID);
    	var result = 0;
    	for (var i = 0; i < this._systemPhase.length; i++) {
    		if (this._systemPhase[i].gov === sysinfo.government && this._systemPhase[i].tl === sysinfo.techlevel) result = this._systemPhase[i].phase;
    	}
    	return result;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // removes the current smuggling compartment and empties the cargo stored in  it
    this.$removeCompartment = function $removeCompartment() {
    	player.ship.removeEquipment("EQ_SMUGGLING_PHASE_ADJUSTMENT");
    	player.ship.removeEquipment("EQ_SMUGGLING_INFLIGHT_STORAGE");
    	var eq = this.$getSmugglingCompartmentEquipment();
    	if (eq === "") return;
    	this._backdoor = true;
    	player.ship.removeEquipment(eq);
    	this._backdoor = false;
    	this._sc_Cargo = [];
    	this._sc_VisibleAs = "Food";
    	this._sc_Phase = 1;
    	this._sc_TechVersion = 1;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns a summarised version of the smuggling compartment cargo
    this.$getSmugglingCargo = function $getSmugglingCargo() {
    	var cargo = [];
    	for (var i = 0; i < this._sc_Cargo.length; i++) {
    		var found = false;
    		for (var j = 0; j < cargo.length; j++) {
    			if (cargo[j].commodity === this._sc_Cargo[i].commodity) {
    				cargo[j].quantity += this._sc_Cargo[i].quantity;
    				found = true;
    			}
    		}
    		if (found === false) {
    			cargo.push({
    				commodity: this._sc_Cargo[i].commodity,
    				displayName: this._sc_Cargo[i].displayName,
    				quantity: this._sc_Cargo[i].quantity,
    				unit: this._sc_Cargo[i].unit,
    				extra: this._sc_Cargo[i].extra
    			});
    		}
    	}
    	return cargo;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns how much of a particular commodity is in the smuggling compartment
    this.$getCargoQuantity = function $getCargoQuantity(commodity) {
    	var cargo = this.$getSmugglingCargo();
    	var result = 0;
    	for (var i = 0; i < cargo.length; i++) {
    		if (cargo[i].commodity === commodity) result = cargo[i].quantity;
    	}
    	return result;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // removes an given amount of a particular commodity
    this.$removeCargo = function $removeCargo(commodity, quantity) {
    	var sum = quantity;
    	for (var i = this._sc_Cargo.length - 1; i >= 0; i--) {
    		if (this._sc_Cargo[i].commodity === commodity && sum > 0) {
    			if (this._sc_Cargo[i].quantity >= sum) {
    				this.$checkForAndRemoveCTECargo(commodity, sum);
    				this._sc_Cargo[i].quantity -= sum;
    				sum = 0;
    			} else {
    				sum -= this._sc_Cargo[i].quantity;
    				this._sc_Cargo[i].quantity = 0;
    			}
    		}
    	}
    	this.$cargoCleanUp();
    	// return any remainder
    	return sum;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // extracts an certain amount of a particular commodity from new cargoes
    this.$extractCargoFromCTE = function(commodity, amount, max) {
    	var cte = worldScripts.CargoTypeExtension;
    	var special = this.$calcCTEcargo(commodity);
    	var normal = max - special;
    	if (amount - normal > 0) {
    		// we're transferring some special cargo
    		var storage = [];
    		var ncmanifest = cte.suspendPlayerManifest(commodity);
    		var nc = ncmanifest.split("|");
    		var lst = nc[1].split("/");
    		var counter = 0;
    		
    		for (var i = 0; i < lst.length; i++) {
    			if (lst[i] != "") {
    				var itms = lst[i].split(";");
    				for (var j = 0; j < itms.length; j++) {
    					if (itms[j] != "") {
    						// reduce the original
    						var sub = itms[j].split("=");
    						var removed = 0;
    						while (sub[1] > 0) {
    							sub[1]--;
    							counter++;
    							removed++;
    							if ((amount - normal) - counter == 0) break;
    						}
    						itms[j] = sub.join("=");
    						
    						//storage.push(sub[0] + "=" + removed + "=" + sub[2]);
    						// make a copy of what we're taking out
    						// we'll store them a 1 unit items to make the return process easier
    						for (var k = 0; k < removed; k++) {
    							var st = "";
    							for (var m = 0; m < sub.length; m++) {
    								st += (st == "" ? "" : "=") + (m == 1 ? 1 : sub[m]);
    							}
    							storage.push(st);
    						}
    					}
    				}
    				lst[i] = itms.join(";");
    			}
    			if ((amount - normal) - counter == 0) break;
    		}
    
    		nc[1] = lst.join("/");
    		ncmanifest = nc.join("|");
    		// tell NC about what we've done
    		cte.mergePlayerManifest(ncmanifest);
    		// pull our storage data together
    		extra = storage.join(";");
    	}
    	return extra;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$checkForAndRemoveCTECargo = function(commodity, quantity) {
    	var extra = "";
    	for (var i = 0; i < this._sc_Cargo.length; i++) {
    		if (this._sc_Cargo[i].commodity == commodity) {		
    			if (this._sc_Cargo[i].extra != "") {
    				var dta = [];
    				dta = this._sc_Cargo[i].extra.split(";");
    				var normal = this._sc_Cargo[i].quantity - dta.length;
    				if (quantity - normal > 0) {
    					var diff = quantity - normal;
    					// compile a list of the cargo items to re-merge with nc
    					while (diff > 0) {
    						extra += (extra == "" ? "" : ";") + dta.shift();
    						diff--;
    					}
    					this._sc_Cargo[i].extra = dta.join(";");
    				}
    			}
    		}
    	}
    	// because we store everything as 1 unit items, we need to compile it back into totals so NC has good data
    	var hold = this.$compileCTEValues(extra);
    	var ncmanifest = "2|" + this.$buildMergeManifest(commodity, hold);
    	return ncmanifest;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // compile multiple 1 unit items down to one entry with a 1+ unit count
    this.$compileCTEValues = function(extra) {
    	var items = extra.split(";");
    	var list = {};
    	for (var i = 0; i < items.length; i++) {
    		if (!list[items[i]]) {
    			list[items[i]] = 1;
    		} else {
    			list[items[i]] = list[items[i]] + 1;
    		}
    		log(this.name, "tracking " + list[items[i]]);
    	}
    	var keys = Object.keys(list);
    	var final = "";
    	for (var i = 0; i < keys.length; i++) {
    		var subs = keys[i].split("=");
    		final = (final == "" ? "" : ";") + subs[0] + "=" + list[keys[i]] + "=" + subs[2];
    	}
    	return final;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // put the manifest string together
    // blank entries in all but the item we are processing the commodity for
    this.$buildMergeManifest = function(commodity, extra) {
    	var cte = worldScripts.CargoTypeExtension;
    	var goodsList = cte.cargoTypes;
    	var serial = "";
    	for (var i = 0; i < goodsList.length; i++) {
    		if (goodsList[i] == commodity) {
    			serial += extra;
    		}
    		if (i + 1 < goodsList.length) {
    			serial += "/";
    		}
    	}
    	return serial;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // adds a phase scan setting to our discovered list
    // source 1 = from phase scanner, 2 = purchased from black market, 3 = overheard in rumours, 4 = updated after scan
    this.$addPhaseScan = function $addPhaseScan(phase, systemID, source) {
    	//log(this.name, "checking " + system.ID);
    	//log(this.name, "data: " + systemID + ", " + source);
    	var sysinfo = System.infoForSystem(galaxyNumber, systemID);
    	var found = false;
    	for (var i = 0; i < this._phaseScanDiscovery.length; i++) {
    		if (this._phaseScanDiscovery[i].gov === sysinfo.government && this._phaseScanDiscovery[i].tl === sysinfo.techlevel) {
    			found = true;
    			this._phaseScanDiscovery[i].phase = phase;
    			// if the previous value was just a remour, or changed, update it now
    			if (this._phaseScanDiscovery[i].source >= 3 && source < 3) this._phaseScanDiscovery[i].source = source;
    		}
    	}
    	if (found === false) {
    		this._phaseScanDiscovery.push({
    			gov: sysinfo.government,
    			tl: sysinfo.techlevel,
    			phase: phase,
    			source: source
    		});
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns true if the player has a reliable phase scan for a given system
    this.$playerHasPhaseScan = function $playerHasPhaseScan(sysID) {
    	var sysinfo = System.infoForSystem(galaxyNumber, sysID);
    	var result = false;
    	for (var i = 0; i < this._phaseScanDiscovery.length; i++) {
    		if (this._phaseScanDiscovery[i].gov === sysinfo.government && this._phaseScanDiscovery[i].tl === sysinfo.techlevel && this._phaseScanDiscovery[i].source < 3) result = true;
    	}
    	return result;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns the number of sellable phase scans the player has
    this.$sellablePhaseScans = function $sellablePhaseScans() {
    	var list = this.$getSellablePhaseScans();
    	return list.length;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // get a list of sellable phase scan settings for the black market
    // note: will only return scans within 7ly of current system
    this.$getSellablePhaseScans = function $getSellablePhaseScans() {
    	function compare(a, b) {
    		if ((a.gov * 100) + a.tl < (b.gov * 100) + b.tl) return -1;
    		if ((a.gov * 100) + a.tl > (b.gov * 100) + b.tl) return 1;
    		return 0;
    	}
    
    	this._phaseScanDiscovery.sort(compare);
    
    	var govs = new Array();
    	for (var i = 0; i < 8; i++)
    		govs.push(String.fromCharCode(i));
    	var spc = String.fromCharCode(31);
    	var list = [];
    	var added = [];
    	// you can only sell phase scans for the local area
    	var sys = System.infoForSystem(galaxyNumber, system.ID).systemsInRange(7);
    
    	for (var i = 0; i < this._phaseScanDiscovery.length; i++) {
    		if (this._phaseScanDiscovery[i].source === 1) {
    			for (var j = 0; j < sys.length; j++) {
    				// does this scan match a local system, and have we already added this variation?
    				if (sys[j].government === this._phaseScanDiscovery[i].gov && sys[j].techlevel === this._phaseScanDiscovery[i].tl && added.indexOf((sys[j].government * 100) + sys[j].techlevel) === -1) {
    					list.push({
    						text: this.$padTextRight(govs[this._phaseScanDiscovery[i].gov] + spc + "TL" + (this._phaseScanDiscovery[i].tl + 1) + ":", 3.7) + this.$padTextRight(this._phaseScanDiscovery[i].phase, 3),
    						gov: this._phaseScanDiscovery[i].gov,
    						tl: this._phaseScanDiscovery[i].tl
    					});
    					// add the signature to the added list so we can easily check if it's there already
    					added.push((sys[j].government * 100) + sys[j].techlevel);
    				}
    			}
    		}
    	}
    	return list;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // switches a source = 1 to source = 0 for a particular phase scan
    this.$sellPhaseScan = function $sellPhaseScan(govrn, techlvl) {
    	for (var i = 0; i < this._phaseScanDiscovery.length; i++) {
    		if (this._phaseScanDiscovery[i].gov === govrn && this._phaseScanDiscovery[i].tl === techlvl && this._phaseScanDiscovery[i].source === 1) {
    			this._phaseScanDiscovery[i].source = 0;
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // gets the commodity type for a particular commodity (t , kg or g)
    this.$getCommodityType = function $getCommodityType(good) {
    	if (system.isInterstellarSpace) {
    		var m = player.ship.manifest;
    		if (m[good] > 0) {
    			for (var i = 0; i < m.list.length; i++) {
    				if (m.list[i].commodity == good) return m.list[i].unit;
    			}
    		} else {
    			return "t";
    		}
    	}
    	var types = ["t", "kg", "g"];
    	return types[system.mainStation.market[good].quantity_unit];
    }
    
    //=============================================================================================================
    // hypercargo integration routines
    
    //-------------------------------------------------------------------------------------------------------------
    // returns a list of commodity items that are illegal, based on a commodity list
    this.$checkHyperCargo = function $checkHyperCargo(commodity) {
    	var returnData = "";
    	if (missionVariables.hyperCargoMemory === "EMPTY") return "";
    	if (!worldScripts["HyperCargo"]) return ""; // just in case hypercargo OXP was removed after being installed for a while
    	var hc = worldScripts["HyperCargo"];
    	for (var i = 0; i < 14; i++) {
    		var cargoItem = hc.cargoNameArray[i];
    		if (commodity === cargoItem && hc.storedArray[i] > 0) {
    			var cargoCount = hc.storedArray[i];
    			returnData = " - " + cargoCount + " " + this.$getCommodityType(cargoItem) + " × " + displayNameForCommodity(cargoItem) + " (stored in HyperCargo)";
    		}
    	}
    	return returnData;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns the quantity of a particular commodity
    this.$getHyperCargoQuantity = function $getHyperCargoQuantity(commodity) {
    	var returnData = "";
    	if (missionVariables.hyperCargoMemory === "EMPTY") return 0;
    	if (!worldScripts["HyperCargo"]) return 0; // just in case hypercargo OXP was removed after being installed for a while
    	var hc = worldScripts["HyperCargo"];
    	for (var i = 0; i < 14; i++) {
    		var cargoItem = hc.cargoNameArray[i];
    		if (commodity === cargoItem && hc.storedArray[i] > 0) {
    			return hc.storedArray[i];
    		}
    	}
    	return -1;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // removes illegal cargo from hypercargo and applies a bounty
    this.$updateHyperCargo = function $updateHyperCargo(commodity, def) {
    	if (missionVariables.hyperCargoMemory === "EMPTY") return;
    	if (!worldScripts["HyperCargo"]) return; // just in case hypercargo OXP was removed after being installed for a while
    	var hc = worldScripts["HyperCargo"];
    	var p = player.ship;
    	for (var i = 0; i < 14; i++) {
    		var cargoItem = hc.cargoNameArray[i];
    		if (commodity === cargoItem && hc.storedArray[i]) {
    			p.setBounty(player.bounty + (hc.storedArray[i] * def.scale), "illegal imports");
    			hc.storedArray[i] = 0;
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // removes an amount of cargo from hypercargo
    this.$removeHyperCargo = function $removeHyperCargo(commodity, amount) {
    	if (missionVariables.hyperCargoMemory === "EMPTY") return 0;
    	if (!worldScripts["HyperCargo"]) return 0; // just in case hypercargo OXP was removed after being installed for a while
    	var hc = worldScripts["HyperCargo"];
    	for (var i = 0; i < 14; i++) {
    		var cargoItem = hc.cargoNameArray[i];
    		if (commodity === cargoItem && hc.storedArray[i] > 0) {
    			if (hc.storedArray[i] >= amount) {
    				hc.storedArray[i] -= amount;
    				return 0;
    			} else {
    				var remain = amount - hc.storedArray[i];
    				hc.storedArray[i] = 0;
    				return remain;
    			}
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // prompts the user for a new phase
    this.$getBribeAmount = function $getBribeAmount() {
    
    	var text = "";
    	var sdm = worldScripts.Smugglers_DockMaster;
    	var inttype = Math.floor((sdm._bribeChance[system.ID] * 4) + 1);
    	var inttypedesc = expandDescription("[interest-type" + inttype + "]");
    	var lastBribe = sdm.$previousBribeAmount();
    	text = expandDescription("[dockmaster-question]", {
    			interesttype: inttypedesc
    		}) + "\n\n" +
    		"(" + expandDescription("[dockmaster-cost]", {
    			credits: formatCredits(player.credits, false, true)
    		}) +
    		(lastBribe > 0 ? expandDescription("[dockmaster-lastbribe]", {
    			bribeamount: formatCredits(lastBribe, false, true)
    		}) : "") + ")";
    
    	var opts = {
    		screenID: "smuggling_bribe",
    		title: "Bribing Official",
    		allowInterrupt: false,
    		model: "[" + this._policeModel + "]",
    		exitScreen: "GUI_SCREEN_STATUS",
    		message: text,
    		textEntry: true
    	};
    
    	mission.runScreen(opts, this.$getBribeAmountInput, this);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // parses the input from the getBribeAmount screen
    this.$getBribeAmountInput = function $getBribeAmountInput(param) {
    
    	var sdm = worldScripts.Smugglers_DockMaster;
    	var p = player.ship;
    	sdm._bribeAttempted = true;
    
    	if (parseInt(param) >= 1 && parseInt(param) <= player.credits) {
    		var amount = parseInt(param);
    		// will this work
    		var chance = sdm._bribeChance[system.ID];
    		// higher amounts are more likely to be accepted
    		// chance of 0.5 would mean you would need to offer them 256 credits
    		//if (this._debug) log(this.name, "min bribe amount = " + (Math.pow(parseInt(chance * 32), 1.8) * (1 + system.productivity / 56300) * sdm._bribeCount[system.ID]));
    		if ((Math.pow(parseInt(chance * 32), 1.8) * (1 + system.productivity / 56300) * sdm._bribeCount[system.ID]) <= amount) {
    			player.credits -= amount;
    			sdm.$addBribe(system.ID);
    			sdm._successfulBribes.push({
    				system: system.ID,
    				bribeAmount: amount
    			});
    			p.setBounty(this._savedBounty, "bribe");
    			mission.runScreen({
    				screenID: "smuggling_bribe",
    				title: "Bribing Official",
    				model: "[" + this._policeModel + "]",
    				message: expandDescription("[dockmaster-complete]"),
    				exitScreen: "GUI_SCREEN_STATUS"
    			});
    		} else {
    			if (this._bribeType === 1) this.$processRemovals();
    			if (this._bribeType === 2) this.$processEquipmentRemovals();
    			if (Math.random() > chance) {
    				mission.runScreen({
    					screenID: "smuggling_bribe",
    					title: "Bribing Official",
    					model: "[" + this._policeModel + "]",
    					message: expandDescription("[dockmaster-angry-nopenalty]"),
    					exitScreen: "GUI_SCREEN_STATUS"
    				});
    
    			} else {
    				var penalty = (worldScripts.Smugglers_CoreFunctions.$rand(10) + 3);
    				p.setBounty(player.bounty + penalty, "attempted bribe");
    				mission.runScreen({
    					screenID: "smuggling_bribe",
    					title: "Bribing Official",
    					model: "[" + this._policeModel + "]",
    					message: expandDescription("[dockmaster-angry-penalty]"),
    					exitScreen: "GUI_SCREEN_STATUS"
    				});
    
    				// send email (if installed)
    				var email = worldScripts.EmailSystem;
    				if (email) {
    					var ga = worldScripts.GalCopAdminServices;
    					email.$createEmail({
    						sender: "GalCop Customs",
    						subject: "Attempt to bribe official",
    						date: clock.seconds,
    						message: expandDescription("[smugglers-failed-bribe-email]", {
    							legal_penalty: penalty,
    							stationname: player.ship.dockedStation.displayName,
    							systemname: System.systemNameForID(system.ID)
    						}),
    						expiryDays: ga._defaultExpiryDays
    					});
    				}
    			}
    		}
    	} else {
    		if (this._bribeType === 1) this.$processRemovals();
    		if (this._bribeType === 2) this.$processEquipmentRemovals();
    		mission.runScreen({
    			screenID: "smuggling_bribe",
    			title: "Bribing Official",
    			model: "[" + this._policeModel + "]",
    			message: expandDescription("[dockmaster-skipbribe]"),
    			exitScreen: "GUI_SCREEN_STATUS"
    		});
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // processes any cargo and/or smuggling compartment removals after docking
    this.$processRemovals = function $processRemovals() {
    	var emailText = "";
    	var p = player.ship;
    	if (this._removeCompartment === true) {
    		// remove smuggling compartment and contents
    		emailText += "- removal of scanner-resistant cargo compartment.\n";
    		this.$removeCompartment();
    	}
    	if (this._phaseScannerDetected === true) {
    		emailText += "- removal of illegal phase scanner.\n";
    		p.removeEquipment("EQ_PHASE_SCANNER");
    	}
    	for (var i = 0; i < this._toRemove.length; i++) {
    		if (this._toRemove[i].source === "hold") {
    			emailText += "- confiscation of " + p.manifest[this._toRemove[i].commodity] + this.$getCommodityType(this._toRemove[i].commodity) + " × " + displayNameForCommodity(this._toRemove[i].commodity) + " from your cargo hold.\n";
    			p.manifest[this._toRemove[i].commodity] = 0;
    		}
    		if (this._toRemove[i].source === "hyper") {
    			emailText += "- confiscation of " + p.manifest[this._toRemove[i].commodity] + this.$getCommodityType(this._toRemove[i].commodity) + " × " + displayNameForCommodity(this._toRemove[i].commodity) + " from your hypercargo hold.\n";
    			this.$removeHyperCargo(this._toRemove[i].commodity, this.$getHyperCargoQuantity(this._toRemove[i].commodity));
    		}
    	}
    	if (this._playerFine > 0) {
    		if (player.credits > this._playerFine) {
    			emailText += "- applied a fine of " + formatCredits(this._playerFine, true, true) + "\n";
    			player.credits -= this._playerFine;
    		} else {
    			emailText += "- applied a fine of " + formatCredits(player.credits, true, true) + "\n";
    			player.credits = 0;
    		}
    	}
    	this._removeCompartment = false;
    	this._phaseScannerDetected = false;
    
    	// send email (if installed)
    	if (emailText != "") {
    		var email = worldScripts.EmailSystem;
    		if (email) {
    			var ga = worldScripts.GalCopAdminServices;
    			email.$createEmail({
    				sender: "GalCop Customs",
    				subject: "Contraband removal",
    				date: clock.seconds,
    				message: expandDescription("[smugglers-removal-email]", {
    					contraband: emailText,
    					stationname: player.ship.dockedStation.displayName,
    					systemname: System.systemNameForID(system.ID)
    				}),
    				expiryDays: ga._defaultExpiryDays
    			});
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // processes any equipment removals after a renovation
    this.$processEquipmentRemovals = function $processEquipmentRemovals() {
    	var p = player.ship;
    	var emailText = "";
    
    	if (this.$isPhaseScannerInstalled() === true) {
    		emailText = "- removal of illegal phase scanner.\n";
    		p.removeEquipment("EQ_PHASE_SCANNER");
    	}
    
    	if (this.$hasSmugglingCompartment() === true) {
    		// remove compartment and contents
    		emailText += "- removal of scanner-resistant cargo compartment.\n";
    		this.$removeCompartment();
    	}
    
    	// send email (if installed)
    	if (emailText != "") {
    		var email = worldScripts.EmailSystem;
    		if (email) {
    			var ga = worldScripts.GalCopAdminServices;
    			email.$createEmail({
    				sender: "GalCop Customs",
    				subject: "Contraband removal",
    				date: clock.seconds,
    				message: expandDescription("[smugglers-removal-email]", {
    					contraband: emailText,
    					stationname: player.ship.dockedStation.displayName,
    					systemname: System.systemNameForID(system.ID)
    				}),
    				expiryDays: ga._defaultExpiryDays
    			});
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // build up an array of values that can be used to determine how many government/techlevel types exist in the system
    this.$createSystemDataArray = function $createSystemDataArray() {
    	this._systemData = [];
    	for (var i = 0; i <= 255; i++) {
    		var sys = System.infoForSystem(galaxyNumber, i);
    		var item = sys.government * 100 + sys.techlevel;
    		if (this._systemData.indexOf(item) === -1) this._systemData.push(item);
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // updates the manifest screen entry for the smuggling hold contents
    this.$updateManifest = function $updateManifest() {
    	var list = [];
    	var cargo_size = this.$smugglingCompartmentSize();
    	if (cargo_size > 0) {
    		var damaged = this.$isCompartmentDamaged();
    		list.push("Smuggling Compartment Cargo " + this.$usedSpace().toFixed(0) + " t (" + cargo_size + " t)" + (damaged === true ? " *DAMAGED*" : "") + ":");
    		var additem = "";
    		for (var i = 0; i < this._sc_Cargo.length; i++) {
    			if (additem === "") {
    				additem += this.$padTextRight(this._sc_Cargo[i].quantity + " " + this._sc_Cargo[i].unit + " × " + displayNameForCommodity(this._sc_Cargo[i].commodity), 17);
    			} else {
    				additem += this._sc_Cargo[i].quantity + " " + this._sc_Cargo[i].unit + " × " + displayNameForCommodity(this._sc_Cargo[i].commodity);
    				list.push(additem);
    				additem = "";
    			}
    		}
    		if (additem != "") list.push(additem);
    
    		if (this._sc_Cargo.length > 0) {
    			mission.setInstructions(list, this.name);
    		} else {
    			list.push("No cargo carried.");
    			mission.setInstructions(list, this.name);
    		}
    	} else {
    		mission.setInstructions(null, this.name);
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // play the buy/sell sound effects
    this.$playSound = function $playSound(soundtype) {
    	var mySound = new SoundSource;
    
    	switch (soundtype) {
    		case "buy":
    			mySound.sound = "[buy-commodity]";
    			break;
    		case "sell":
    			mySound.sound = "[sell-commodity]";
    			break;
    		case "mode":
    			mySound.sound = "[@click]";
    			break;
    		case "activate":
    			mySound.sound = "[@beep]";
    			break;
    		case "stop":
    			mySound.sound = "[@boop]";
    			break;
    	}
    	mySound.loop = false;
    	mySound.play();
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns the illegal scale factor for a particular commodity at the current docked station
    this.$commodityIllegalScale = function $commodityIllegalScale(commodity) {
    	var p = player.ship;
    	var stn = p.dockedStation;
    	var scale = parseInt(stn.market[commodity].legality_import);
    	if (scale === 0) scale = parseInt(stn.market[commodity].legality_export);
    	return scale;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // gets a list of all police ship models so we can use them consistently on smuggling screens
    this.$getPoliceModel = function $getPoliceModel() {
    	var shipkeys = Ship.keysForRole("police");
    
    	if (shipkeys.length === 0) return "viper";
    	var key = "";
    	var tries = 0;
    	do {
    		key = shipkeys[Math.floor(Math.random() * shipkeys.length)];
    		if (this.$modelIsAllowed(key) === false) key = "";
    		tries += 1;
    	} while (key === "" && tries < 5)
    	if (key === "") key = "viper";
    	return key;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$modelIsAllowed = function $modelIsAllowed(shipkey) {
    	var shipdata = Ship.shipDataForKey(shipkey);
    	// are we allowed to include this data key in this system? check the conditions if there are some
    	var include = true;
    	if (shipdata.conditions) {
    		var cond = shipdata.conditions.toString().split(",");
    		//1,systemGovernment_number equal 4,systemGovernment_number,0,0,4,
    		//1,systemEconomy_number notequal 4,systemEconomy_number,1,0,4
    		//1,systemEconomy_number lessthan 4,systemEconomy_number,2,0,4
    		//1,systemEconomy_number greaterthan 4,systemEconomy_number,3,0,4
    		var offset = 0;
    		var finish = false;
    		var checking = -1;
    		do {
    			// get the value we're checking
    			checking = -1;
    			if (cond[offset + 2].substring(0, 6) === "mission") {
    				if (missionVariables[cond[offset + 2].replace("mission_", "")]) {
    					checking = missionVariables[cond[offset + 2].replace("mission_", "")];
    					//log(this.name, "field = " + cond[offset + 2] + ", value = " + checking);
    				} else {
    					log(this.name, "!!NOTE: Condition value mission variable not set: " + cond[offset + 2]);
    				}
    			} else {
    				switch (cond[offset + 2]) {
    					case "systemGovernment_number":
    						checking = system.government;
    						break;
    					case "systemEconomy_number":
    						checking = system.economy;
    						break;
    					case "systemTechLevel_number":
    						checking = system.techLevel;
    						break;
    					case "score_number":
    						checking = player.score;
    						break;
    					case "galaxy_number":
    						checking = galaxyNumber;
    						break;
    					case "planet_number":
    						checking = system.ID;
    						break;
    					default:
    						log(this.name, "!!NOTE: Condition value not catered for: " + cond[offset + 2]);
    						break;
    				}
    			}
    			// in case a mission variable is a text value of some sort
    			if (isNaN(parseInt(checking)) && isNaN(parseFloat(checking))) {
    				switch (cond[offset + 3]) {
    					case "0": // equals
    						if (checking != cond[offset + 5]) include = false;
    						break;
    					case "1": // not equals
    						if (checking == cond[offset + 5]) include = false;
    						break;
    					default:
    						log(this.name, "!!NOTE: Condition comparison not catered for: " + cond[offset + 3]);
    						break;
    				}
    			} else {
    				if (checking >= 0) {
    					// work out the type of check, but in negative (or opposite)
    					switch (cond[offset + 3]) {
    						case "0": // equals
    							if (checking !== parseInt(cond[offset + 5])) include = false;
    							break;
    						case "1": // not equals
    							if (checking === parseInt(cond[offset + 5])) include = false;
    							break;
    						case "2": // lessthan
    							if (checking >= parseInt(cond[offset + 5])) include = false;
    							break;
    						case "3": // greaterthan
    							if (checking <= parseInt(cond[offset + 5])) include = false;
    							break;
    						default:
    							log(this.name, "!!NOTE: Condition comparison not catered for: " + cond[offset + 3]);
    							break;
    							// others?
    					}
    				}
    			}
    			offset += 6;
    			if (offset >= cond.length - 1) finish = true;
    		} while (finish === false);
    	} else if (shipdata.condition_script) {
    		// or the condition script
    		// create a dummy object to attach the script to so it can be executed
    		if (system.sun && system.mainPlanet) {
    			var temppos = system.sun.position.cross(system.mainPlanet.position).direction().multiply(4E9).subtract(system.mainPlanet.position);
    			var tempalloy = system.addShips("alloy", 1, temppos, 0);
    			tempalloy[0].setScript(shipdata.condition_script);
    			include = tempalloy[0].script.allowSpawnShip(shipkey);
    			tempalloy[0].remove(true);
    		} else {
    			include = true;
    		}
    	} else {
    		// otherwise we're free to play
    		include = true;
    	}
    	return include;
    }
    Scripts/smugglers_illegal.js
    "use strict";
    this.name = "Smugglers_Illegal";
    this.author = "phkb";
    this.description = "Looks after the new illegal goods.";
    this.licence = "CC BY-NC-SA 3.0";
    
    /* TODO:
    - add more illegal trade items to the possibilities list
    */
    
    this._disabled = false;
    this._debug = false;
    this._enableMarkedSystems = true;
    this._markedSystemRange = 7;
    this._IGTInstalled = false;
    
    /* template: {govTypes:"", commodity:"", properties:"", permit:1, scale:1, period:n, chance:1, description:""},
    	govTypes:		Comma-separated list of goverment types (0-7) this definition can apply to
    	commodity:		Comma-separated list of commodity names this definition will apply to.
    	properties:		Comma-separated list of text search strings to use against the planetary description.
    					For instance, "civil war" would only select planets that have "civil war" in their description.
    					"drink|brew" would only select planets that have "drink" or "brew" in their description.
    	permit:			Boolean value (0 or 1) indicating whether a permit can be purchased for this commodity
    	scale:			The legality scale to apply to this commodity. The scale will apply when calculating the bounty to apply to a player caught smuggling illegal goods as a multiplier.
    					Most illegal goods have a scale of 1. Slaves have 2.
    	period:			The period (in days) this definition will be active for.
    	chance:			A chance factor, between 0 (never) and 1 (very often), to apply when selecting this definition
    	description:	The description to display for this definition.
    */
    this._possibilities = [{
    		govTypes: "0,1,2",
    		commodity: "firearms",
    		properties: "",
    		permit: 0,
    		scale: 2,
    		period: -1,
    		chance: 0,
    		description: "Firearms have been declared illegal to reduce the proliferation of arms in dangerous systems."
    	},
    	{
    		govTypes: "3,4,5,6,7",
    		commodity: "firearms",
    		properties: "civil war",
    		permit: 0,
    		scale: 1,
    		period: -1,
    		chance: 0,
    		description: "Firearms have been declared illegal to reduce the impact of the civil war on this system."
    	},
    	{
    		govTypes: "0",
    		commodity: "computers",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 120,
    		chance: 1,
    		description: "Computer technology has been restricted to limit illegal hacking."
    	},
    	{
    		govTypes: "0,1",
    		commodity: "radioactives",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 200,
    		chance: 1,
    		description: "Recent upswing in dirty bombs and highly destructive weapons has led to strict controls on radioactives."
    	},
    	{
    		govTypes: "0,1",
    		commodity: "machinery",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 90,
    		chance: 1,
    		description: "GalCop has restricted the importation of machinery to apply pressure to the government."
    	},
    	{
    		govTypes: "1",
    		commodity: "food",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 60,
    		chance: 1,
    		description: "Food imported from outside is feared to be coming from lower classes and is inappropriate for distribution and consumption."
    	},
    	{
    		govTypes: "1",
    		commodity: "textiles",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 90,
    		chance: 1,
    		description: "The under classes have been purchasing cheap clothes that mimic upper class styles and fabrics via imported sources. To keep the classes distinct, all imported textiles have been banned."
    	},
    	{
    		govTypes: "1",
    		commodity: "liquor_wines",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 60,
    		chance: 1,
    		description: "A number of alcohol-fuelled riots in lower class cities has led to a ban on all imported liquor."
    	},
    	{
    		govTypes: "1",
    		commodity: "computers",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 90,
    		chance: 1,
    		description: "The priest class has declared use of the latest computers is taboo."
    	},
    	{
    		govTypes: "1",
    		commodity: "machinery",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 120,
    		chance: 1,
    		description: "Machinery was seen as providing a way for lower classes to rise up against their superiors, so all foreign machinery has been declared illegal."
    	},
    	{
    		govTypes: "1",
    		commodity: "furs",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 90,
    		chance: 1,
    		description: "Imported furs were cutting into a market monopoly held by certain members of the upper class, and so an import ban has been put in place."
    	},
    	{
    		govTypes: "1",
    		commodity: "gold,platinum",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 12,
    		chance: 0.5,
    		description: "Prominent Barons have decried the devaluation of their investments due to foreign influence, and have brought about a temporary ban on precious metals."
    	},
    	{
    		govTypes: "2",
    		commodity: "computers",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 90,
    		chance: 1,
    		description: "Computers have been declared illegal in order to, it is suspected, undermine the efficiency of an opposing factions military forces."
    	},
    	{
    		govTypes: "2",
    		commodity: "alien_items",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 120,
    		chance: 1,
    		description: "Reports of fake alien items has led to a ban pending authentication measures."
    	},
    	{
    		govTypes: "2",
    		commodity: "food",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 60,
    		chance: 1,
    		description: "Reports of contaminants in food supplies in nearby systems has prompted an embargo."
    	},
    	{
    		govTypes: "2",
    		commodity: "alloys",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 120,
    		chance: 1,
    		description: "A temporary embargo on imported alloys has been put in place as a way of bolstering local development."
    	},
    	{
    		govTypes: "2",
    		commodity: "liquor_wines",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 120,
    		chance: 1,
    		description: "All imported alcoholic beverages have been banned as a political favour to a local distributor."
    	},
    	{
    		govTypes: "2",
    		commodity: "machinery",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 120,
    		chance: 1,
    		description: "Imported machinery was seen to be giving a production boost to an opposing faction and have been declared illegal."
    	},
    	{
    		govTypes: "2",
    		commodity: "gold,platinum",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 12,
    		chance: 0.5,
    		description: "An embargo has been placed on all precious metal imports, to cripple the markets of an opposing faction."
    	},
    	{
    		govTypes: "2",
    		commodity: "firearms",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 90,
    		chance: 1,
    		description: "In an attempt to undermine the military strength of opposing factions, all firearm have been declared illegal."
    	},
    	{
    		govTypes: "3",
    		commodity: "luxuries",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 90,
    		chance: 1,
    		description: "Imported luxuries have been declared illegal as they are, according to local government representatives, 'weak and unnecessary'."
    	},
    	{
    		govTypes: "3",
    		commodity: "radioactives",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 120,
    		chance: 1,
    		description: "A hazardous waste scare has prompted a review of radioactives used in power stations."
    	},
    	{
    		govTypes: "0,3",
    		commodity: "food",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 60,
    		chance: 1,
    		description: "GalCop has put pressure on the government by restricting food imports."
    	},
    	{
    		govTypes: "3",
    		commodity: "machinery",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 120,
    		chance: 1,
    		description: "Imported machinery was seen to be altering the balance of power, and have been banned."
    	},
    	{
    		govTypes: "3",
    		commodity: "computers",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 90,
    		chance: 1,
    		description: "Technology imports have been declared illegal as they were causing the general population to become dissatisfied with their lot."
    	},
    	{
    		govTypes: "3",
    		commodity: "minerals",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 120,
    		chance: 1,
    		description: "The discovery of a rich mineral deposit has prompted an embargo on imported minerals."
    	},
    	{
    		govTypes: "3",
    		commodity: "alien_items",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 90,
    		chance: 1,
    		description: "A inter-racial dispute in political circles has led to a ban on imported alien items."
    	},
    	{
    		govTypes: "4",
    		commodity: "minerals",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 90,
    		chance: 1,
    		description: "Political dissidents do all internal system mining as punishment-- so foreign imports have been banned."
    	},
    	{
    		govTypes: "4",
    		commodity: "machinery",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 120,
    		chance: 1,
    		description: "Automation is contrary to the communist work ethic, so all foreign machinery has been declared illegal."
    	},
    	{
    		govTypes: "4",
    		commodity: "computers",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 120,
    		chance: 1,
    		description: "Foreign computers cannot be trusted to give good communists the right answers, and have been banned."
    	},
    	{
    		govTypes: "4",
    		commodity: "luxuries",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 90,
    		chance: 1,
    		description: "The system has stated that strong communists don't need the decadent luxuries from weaker systems."
    	},
    	{
    		govTypes: "4",
    		commodity: "liquor_wines",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 60,
    		chance: 1,
    		description: "Consumption of spirits other than locally brewed beverages have been declared to be treasonous."
    	},
    	{
    		govTypes: "4",
    		commodity: "furs",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 90,
    		chance: 1,
    		description: "Furs are seen to be a decadent fashion item of the enemies of the people, and have been banned."
    	},
    	{
    		govTypes: "4",
    		commodity: "food",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 60,
    		chance: 1,
    		description: "The system has declared that local growers will supply everything they need-- or else."
    	},
    	{
    		govTypes: "4",
    		commodity: "textiles",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 90,
    		chance: 1,
    		description: "Locally produced clothing is superior to imported goods, and provides political prisoners with a livelihood. All textile imports are banned."
    	},
    	{
    		govTypes: "4",
    		commodity: "gold,platinum,gem_stones",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 12,
    		chance: 0.5,
    		description: "The influence of foreign money has been decreed to be against the Communist ethic, leading to a ban on all precious metals and gem stones."
    	},
    	{
    		govTypes: "5",
    		commodity: "food",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 60,
    		chance: 1,
    		description: "Agricultural imports are barred to limit the risk of an invasive pest species that has been spreading in nearby systems."
    	},
    	{
    		govTypes: "5",
    		commodity: "gold,platinum",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 12,
    		chance: 0.5,
    		description: "A drop in the money market has led to temporary ban on imported precious metals."
    	},
    	{
    		govTypes: "5",
    		commodity: "computers",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 90,
    		chance: 1,
    		description: "A nationalist movement proclaiming the importance of local products have orchestrated a ban on imported computer technology."
    	},
    	{
    		govTypes: "5",
    		commodity: "furs",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 90,
    		chance: 1,
    		description: "Imported furs were competing against local tanners and furriers and have been declared illegal."
    	},
    	{
    		govTypes: "5",
    		commodity: "alien_items",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 120,
    		chance: 1,
    		description: "Rumours of possible negotiations with the Thargoids have led to alien items being restricted."
    	},
    	{
    		govTypes: "5",
    		commodity: "machinery",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 120,
    		chance: 1,
    		description: "An industrial accident blamed on imported machinery has led to a total ban on any imports, pending further investigations."
    	},
    	{
    		govTypes: "5",
    		commodity: "gem_stones",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 12,
    		chance: 0.5,
    		description: "A diamond cartel has bought political favours and arranged for an embargo on imported gem-stones."
    	},
    	{
    		govTypes: "5,6,7",
    		commodity: "food",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 90,
    		chance: 1,
    		description: "To encourage and bolster local growers, imported food has been banned."
    	},
    	{
    		govTypes: "6",
    		commodity: "alien_items",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 120,
    		chance: 1,
    		description: "A pro-Thargoid political movement has gained power and banned sales of war-trophies."
    	},
    	{
    		govTypes: "6",
    		commodity: "furs",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 90,
    		chance: 1,
    		description: "Animal rights activists have gained power and put a halt to the import of furs."
    	},
    	{
    		govTypes: "6",
    		commodity: "alloys",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 90,
    		chance: 1,
    		description: "Environmental extremists have demanded more use of recycled metals and supplies in system to reduce waste."
    	},
    	{
    		govTypes: "6",
    		commodity: "liquor_wines",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 90,
    		chance: 1,
    		description: "A temperance movement has gained political influence and issued a ban on all alcoholic beverages."
    	},
    	{
    		govTypes: "6",
    		commodity: "radioactives",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 90,
    		chance: 1,
    		description: "An environmentalist movement has opposed the risks of radiation for industrial uses."
    	},
    	{
    		govTypes: "6",
    		commodity: "textiles",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 60,
    		chance: 1,
    		description: "An embarrassing media incident has rendered imported textiles a social taboo."
    	},
    	{
    		govTypes: "6",
    		commodity: "food",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 90,
    		chance: 1,
    		description: "Unhealthy foreign foods have been banned by locals."
    	},
    	{
    		govTypes: "6",
    		commodity: "gold,platinum",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 12,
    		chance: 0.5,
    		description: "Gold and platinum imports were seen as a destabilising influence on the local money market, so they have been banned."
    	},
    	{
    		govTypes: "7",
    		commodity: "alien_items",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 120,
    		chance: 1,
    		description: "Collectors of rare alien items have forced the government to block the flood of 'cheap counterfeits' to the market."
    	},
    	{
    		govTypes: "7",
    		commodity: "gold,platinum",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 12,
    		chance: 0.5,
    		description: "In an effort to reduce inflation, the system is restricting precious metal imports."
    	},
    	{
    		govTypes: "7",
    		commodity: "alloys",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 90,
    		chance: 1,
    		description: "The system has negotiated an exclusive contract for a specific manufactured alloy only, others are barred."
    	},
    	{
    		govTypes: "7",
    		commodity: "computers",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 90,
    		chance: 1,
    		description: "New computer models from the corporation are superior to foreign imports and have been given a local monopoly."
    	},
    	{
    		govTypes: "7",
    		commodity: "radioactives",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 90,
    		chance: 1,
    		description: "Due to a recent radioactive accident by the corporation, imports of materials are restricted pending safety investigations."
    	},
    	{
    		govTypes: "7",
    		commodity: "food",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 60,
    		chance: 1,
    		description: "'Naturally grown' foods have been replaced with synthetic foods produced by the corporation."
    	},
    	{
    		govTypes: "7",
    		commodity: "furs",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 90,
    		chance: 1,
    		description: "Imported furs have been banned, pending an investigation into a strain of disease-bearing mites found in some imports."
    	},
    	{
    		govTypes: "6,7",
    		commodity: "gem_stones",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 12,
    		chance: 0.5,
    		description: "A series of fake gem-stones making their way onto the market has prompted an embargo."
    	},
    	{
    		govTypes: "0,1,2,3,4,5,6,7",
    		commodity: "textiles",
    		properties: "solar",
    		permit: 1,
    		scale: 1,
    		period: 90,
    		chance: 1,
    		description: "Imported clothing provides inadequate protection from local solar activity, and has been banned."
    	},
    	{
    		govTypes: "0,1,2,3,4,5,6,7",
    		commodity: "liquor_wines",
    		properties: "brandy,brew,gargle blasters",
    		permit: 1,
    		scale: 1,
    		period: 120,
    		chance: 1,
    		description: "Imported liquor and wines were seen to be negatively impacting producers of local specialities and have been declared illegal."
    	},
    	{
    		govTypes: "0,1,2,3,4,5,6,7",
    		commodity: "food",
    		properties: "cuisine,soup,burgers,meat,cutlet,steak",
    		permit: 1,
    		scale: 1,
    		period: 90,
    		chance: 1,
    		description: "Imported food was influencing tastes away from local specialities, prompting a temporary trade embargo."
    	},
    	{
    		govTypes: "3,4,5,6,7",
    		commodity: "firearms",
    		properties: "",
    		permit: 1,
    		scale: 1,
    		period: 120,
    		chance: 0.5,
    		description: "Due to an increased number of firearm-related crimes, firearms have been declared illegal."
    	}
    ];
    
    // these commodities will be included when checking for illegal imports on docking, but as they are global defaults we won't add them via the normal mechanisms
    // slaves are not included here, as they are being kept as illegal to export only. This is (a) because some escape pods get stored as slaves,
    // and (b) to keep the Illegal Goods Tweak in the loop
    this._defaultAdditions = ["narcotics"];
    
    // extra commodityInfo
    //		element 0 is permit costs
    //		element 1 is % over base price when item is illegal
    //		element 2 is % over base price for the black market price
    this._commodityInfo = {
    	"food": [2500, 6.5, 2.2],
    	"textiles": [3000, 4.0, 2.0],
    	"radioactives": [5000, 2.5, 1.3],
    	"slaves": [4000, 0, 0],
    	"liquor_wines": [5000, 2.2, 1.1],
    	"luxuries": [6000, 2.1, 1.1],
    	"narcotics": [8000, 0, 0],
    	"computers": [6000, 2.1, 1.1],
    	"machinery": [5000, 2.2, 1.1],
    	"alloys": [3000, 2.2, 1.1],
    	"firearms": [5000, 2.2, 1.1],
    	"furs": [7000, 2.5, 1.4],
    	"minerals": [3000, 6.0, 2.1],
    	"gold": [8000, 1.3, 0.6],
    	"platinum": [9000, 1.1, 0.6],
    	"gem_stones": [8000, 1.8, 0.8],
    	"alien_items": [6000, 2.8, 1.3]
    };
    
    // holds all illegal good definitions for this sector
    this._illegalGoods = []; // illegal goods currently in play in sector
    this._permits = []; // permits currently held by player
    this._originalCredits = 0; // value to hold the players credits (used to refund the cost of the initial item)
    this._display = 0;
    this._curpage = 0;
    this._dataLoaded = false; // flag to indicate when data has been loaded
    this._initialRun = true; // flag to indicate when the initial data set has been created
    this._originating = -1;
    this._startUpRunning = true;
    this._markedSystems = [];
    this._witchspaceDestination = -1;
    this._done = false;
    this._menuColor = "orangeColor";
    this._itemColor = "yellowColor";
    this._disabledColor = "darkGrayColor";
    this._trueValues = ["yes", "1", 1, "true", true];
    this._markerColors = ["whiteColor", "yellowColor", "orangeColor", "greenColor", "redColor", "blueColor", "purpleColor", "cyanColor"];
    this._markerShapes = ["MARKER_X", "MARKER_PLUS", "MARKER_SQUARE", "MARKER_DIAMOND"];
    this._firearmsColor = 2;
    this._firearmsShape = 3;
    this._mixedIllegalColor = 1;
    this._mixedIllegalShape = 3;
    
    // configuration settings for use in Lib_Config
    this._siConfig = {
    	Name: this.name,
    	Alias: "Smugglers",
    	Display: "Config",
    	Alive: "_siConfig",
    	Notify: "$markSystems",
    	Bool: {
    		B0: {
    			Name: "_enableMarkedSystems",
    			Def: true,
    			Desc: "Mark nearby systems"
    		},
    		Info: "Turns on option of marking nearby systems with illegal goods on the galaxy map."
    	},
    	SInt: {
    		S0: {
    			Name: "_markedSystemRange",
    			Def: 7,
    			Min: 0,
    			Max: 15,
    			Desc: "Marked sys range"
    		},
    		S1: {
    			Name: "_firearmsColor",
    			Def: 2,
    			Min: 0,
    			Max: 7,
    			Desc: "Firearms illegal color"
    		},
    		S2: {
    			Name: "_mixedIllegalColor",
    			Def: 2,
    			Min: 0,
    			Max: 7,
    			Desc: "Mixed illegal color"
    		},
    		S3: {
    			Name: "_firearmsShape",
    			Def: 3,
    			Min: 0,
    			Max: 3,
    			Desc: "Firearm marker shape"
    		},
    		S4: {
    			Name: "_mixedIllegalShape",
    			Def: 3,
    			Min: 0,
    			Max: 3,
    			Desc: "Mixed illegal marker shape"
    		},
    
    		Info: "0: Defines range (LY) for marking illegal systems\n1/2: Mark color for illegal firearms/mixed (0=white, 1=yellow, 2=orange, 3=green, 4=red, 5=blue, 6=purple, 7=cyan)\n3/4: Mark shape for illegal firearms/mixed (0=X, 1=+, 2=square, 3=diamond)"
    	},
    };
    
    //-------------------------------------------------------------------------------------------------------------
    this.$updateGeneralCommodityDefinition = function (goodDefinition, station, systemID) {
    	var si = worldScripts.Smugglers_Illegal;
    	if (goodDefinition.key == "slaves") {
    		goodDefinition.legality_export = 1;
    		goodDefinition.legality_import = 0;
    		return goodDefinition;
    	}
    	if (goodDefinition.key == "narcotics") {
    		goodDefinition.legality_export = 1;
    		goodDefinition.legality_import = 0;
    		goodDefinition.comment = expandDescription("[narcotics-legality-description]");
    		return goodDefinition;
    	}
    	if (goodDefinition.key == "firearms") {
    		goodDefinition.legality_export = 0;
    		goodDefinition.legality_import = 0;
    		goodDefinition.comment = expandDescription("[oolite-commodity-no-comment]");
    	}
    	if (si) {
    		// initialise the data if it hasn't been already
    		if (si._dataLoaded === false) si.$initialiseData();
    		if ((station === 0 || station == null) && systemID && systemID != -1) {
    			if (si._dataLoaded === true) {
    				if (si.$isGoodIllegal(systemID, goodDefinition.key)) {
    					goodDefinition.legality_import = si.$goodIllegalScale(systemID, goodDefinition.key);
    				}
    			}
    		}
    	}
    	return goodDefinition;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$updateLocalCommodityDefinition = function (goodDefinition, station, systemID) {
    	var si = worldScripts.Smugglers_Illegal;
    	if (si && station && station.allegiance === "galcop") {
    		// apply price boost
    		if (si.$isGoodIllegal(systemID, goodDefinition.key)) {
    			// calculate the new price, based on the price limits in _commodityInfo
    			var newprice = Math.round(goodDefinition.price * (si._commodityInfo[goodDefinition.key][1] + ((system.scrambledPseudoRandomNumber(systemID) * 0.6) - 0.3)), 2);
    			// adjust the price property
    			if (si._debug) log(this.name, "changing price on " + goodDefinition.key + " from " + goodDefinition.price + " to " + newprice);
    			goodDefinition.price = newprice;
    		}
    	}
    	return goodDefinition;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.startUp = function () {
    	var msi = worldScripts.MarketScriptInterface_Main;
    	msi.$addMarketInterface("tradegoods_general", "$updateGeneralCommodityDefinition", this.name);
    	msi.$addMarketInterface("system_local", "$updateLocalCommodityDefinition", this.name, "lowest");
    
    	if (this._disabled) {
    		delete this.shipDockedWithStation;
    		delete this.playerBoughtEquipment;
    		delete this.shipWillEnterWitchspace;
    		delete this.shipExitedWitchspace;
    		delete this.playerEnteredNewGalaxy;
    		delete this.playerWillSaveGame;
    		delete this.guiScreenWillChange;
    		delete this.guiScreenChanged;
    		delete this.updateLocalCommodityDefinition;
    		delete this.dayChanged;
    		delete this.startUpComplete;
    		delete this.startUp;
    		return;
    	}
    
    	var core = worldScripts.Smugglers_CoreFunctions;
    	this.$padTextRight = core.$padTextRight;
    	this.$padTextLeft = core.$padTextLeft;
    	this.$isBigGuiActive = core.$isBigGuiActive;
    	this.$rand = core.$rand;
    
    	if (missionVariables.Smuggling_Permits) {
    		this._permits = JSON.parse(missionVariables.Smuggling_Permits);
    		delete missionVariables.Smuggling_Permits;
    	}
    	this._originating = system.ID;
    	delete this.startUp;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.startUpComplete = function () {
    	// register our settings, if Lib_Config is present
    	if (worldScripts.Lib_Config) worldScripts.Lib_Config._registerSet(this._siConfig);
    
    	// check for illegal goods tweak
    	if (worldScripts["illegal_goods_tweak"]) this._IGTInstalled = true;
    
    	// set up the interface screen, if required
    	this.$initMainInterface(player.ship.dockedStation);
    	this.$correctStartEndDates();
    	this._startUpRunning = false;
    
    	if (missionVariables.Smuggling_InitialRun) {
    		this._initialRun = missionVariables.Smuggling_InitialRun;
    	}
    
    	if (missionVariables.Smuggling_MarkedSystems) {
    		this._markedSystems = JSON.parse(missionVariables.Smuggling_MarkedSystems);
    		delete missionVariables.Smuggling_MarkedSystems;
    	}
    
    	// add our repair items to the email systems list of repair equipment
    	var w = worldScripts.EmailSystem;
    	if (w) {
    		var ga = worldScripts.GalCopAdminServices;
    		for (var i = 1; i <= 20; i++) {
    			ga._maint_repair_equip.push("EQ_SMUGGLING_COMPARTMENT_REPAIR_" + i);
    		}
    	}
    	// only load these settings if Library is installed
    	if (worldScripts.Lib_Config) {
    		if (missionVariables.Smugglers_MarkSystems) this._enableMarkedSystems = (this._trueValues.indexOf(missionVariables.Smugglers_MarkSystems) >= 0 ? true : false);
    		if (missionVariables.Smugglers_MarkSystemsRange) this._markedSystemRange = parseInt(missionVariables.Smugglers_MarkSystemsRange);
    		if (missionVariables.Smugglers_FirearmsColor) this._firearmsColor = parseInt(missionVariables.Smugglers_FirearmsColor);
    		if (missionVariables.Smugglers_MixedIllegalColor) this._mixedIllegalColor = parseInt(missionVariables.Smugglers_MixedIllegalColor);
    		if (missionVariables.Smugglers_FirearmsShape) this._firearmsShape = parseInt(missionVariables.Smugglers_FirearmsShape);
    		if (missionVariables.Smugglers_MixedIllegalShape) this._mixedIllegalShape = parseInt(missionVariables.Smugglers_MixedIllegalShape);
    	}
    	this.$markSystems();
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.playerWillSaveGame = function () {
    	missionVariables.Smuggling_IllegalGoods = JSON.stringify(this._illegalGoods);
    	missionVariables.Smuggling_Permits = JSON.stringify(this._permits);
    	missionVariables.Smuggling_MarkedSystems = JSON.stringify(this._markedSystems);
    	missionVariables.Smuggling_InitialRun = this._initialRun;
    	// only save these settings if Library is installed
    	if (worldScripts.Lib_Config) {
    		missionVariables.Smugglers_MarkSystems = this._enableMarkedSystems;
    		missionVariables.Smugglers_MarkSystemsRange = this._markedSystemRange;
    		missionVariables.Smugglers_FirearmsColor = this._firearmsColor;
    		missionVariables.Smugglers_MixedIllegalColor = this._mixedIllegalColor;
    		missionVariables.Smugglers_FirearmsShape = this._firearmsShape;
    		missionVariables.Smugglers_MixedIllegalShape = this._mixedIllegalShape;
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.shipDockedWithStation = function (station) {
    	this.$initMainInterface(station);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.shipWillLaunchFromStation = function (station) {
    	if (missionVariables.Smuggling_IllegalGoods) delete missionVariables.Smuggling_IllegalGoods;
    	if (missionVariables.Smuggling_Permits) delete missionVariables.Smuggling_Permits;
    	if (missionVariables.Smuggling_MarkedSystems) delete missionVariables.Smuggling_MarkedSystems;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.playerBoughtEquipment = function (equipmentKey) {
    	var p = player.ship;
    
    	if (equipmentKey === "EQ_IMPORT_PERMIT" || equipmentKey == "EQ_IMPORT_PERMIT_NC") {
    		p.removeEquipment(equipmentKey);
    		// give the player back the original amount, in case they decide not to purchase
    		if (this._originalCredits != 0) {
    			player.credits = this._originalCredits;
    			this._originalCredits = 0;
    		}
    		// mission screen to ask how many TC to allocate
    		this.$showPurchasePage();
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.shipWillEnterWitchspace = function (cause, destination) {
    	this._witchspaceDestination = destination;
    	this._initialRun = true;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.shipExitedWitchspace = function () {
    	this._originating = system.ID;
    	this._done = false;
    	this.$markSystems();
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.playerEnteredNewGalaxy = function (galNumber) {
    	// hopefully this is early enough in the sequence to establish illegal goods for the "smugglers_illegalmarket"
    	this._illegalGoods = [];
    	this._dataLoaded = false;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.dayChanged = function (newday) {
    	this.$cleanup();
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.guiScreenWillChange = function (to, from) {
    	if (to === "GUI_SCREEN_MARKET") this.$updateDescriptions();
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.guiScreenChanged = function (to, from) {
    	if (guiScreen === "GUI_SCREEN_EQUIP_SHIP") this._originalCredits = player.credits;
    
    	// This code puts all the illegal goods on the F7 screen. However, with the description in the F8F8 screen this code is possibly unnecessary.
    	// However, if there is feedback that says it would be useful I'll but it back in.
    	if (from === "GUI_SCREEN_SYSTEM_DATA" && guiScreen != "GUI_SCREEN_SYSTEM_DATA") {
    		// unhide the hud, if required
    		if (player.ship.hudHidden != this._hudHidden) {
    			player.ship.hudHidden = this._hudHidden;
    		}
    	}
    	if (guiScreen === "GUI_SCREEN_SYSTEM_DATA" && from != "GUI_SCREEN_SYSTEM_DATA") {
    		if (player.ship.isInSpace === false) {
    			// hide the hud to give us more room for text
    			this._hudHidden = player.ship.hudHidden;
    			if (this.$isBigGuiActive() === false) {
    				player.ship.hudHidden = true;
    				// force an update in XenonUI (if installed)
    				var xui = worldScripts.XenonUI;
    				if (xui) xui.guiScreenChanged("", "");
    				var xrui = worldScripts.XenonReduxUI;
    				if (xrui) xrui.guiScreenChanged("", "");
    			}
    		}
    	}
    	if (guiScreen === "GUI_SCREEN_SYSTEM_DATA") {
    		// load data onto screen at the end
    		if (!this._dataLoadDelay) this._dataLoadDelay = new Timer(this, this.$dataLoadDelay, 0.2, 0);
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.infoSystemChanged = function (to, from) {
    	if (guiScreen === "GUI_SCREEN_SYSTEM_DATA") {
    		// load data onto screen at the end
    		if (!this._dataLoadDelay) this._dataLoadDelay = new Timer(this, this.$dataLoadDelay, 0.2, 0);
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$dataLoadDelay = function $dataLoadDelay() {
    	delete this._dataLoadDelay;
    	// append any system-specific illegal goods to the display
    	if (guiScreen != "GUI_SCREEN_SYSTEM_DATA") return;
    
    	var sysID = player.ship.targetSystem;
    	if (player.ship.hasOwnProperty("infoSystem")) sysID = player.ship.infoSystem;
    
    	var concealment = System.infoForSystem(galaxyNumber, sysID).concealment;
    	// if there is some form of concealment in effect, don't show anything
    	if (concealment > 0) return;
    
    	var goods = this.$illegalGoodsList(sysID);
    	var dis_list = "";
    	if (this._IGTInstalled === true) {
    		dis_list = expandDescription("[illegal-goods-f7-igt" + (player.ship.isInSpace === false ? "" : "-inflight") + "]");
    	} else {
    		dis_list = expandDescription("[illegal-goods-f7-std" + (player.ship.isInSpace === false ? "" : "-inflight") + "]");
    	}
    	if (player.ship.isInSpace === false) {
    		for (var i = 0; i < goods.length; i++) {
    			dis_list += "\nIllegal import -- " + this.$translateCommodityList(goods[i].commodity) + ": " + goods[i].description;
    		}
    	} else {
    		for (var i = 0; i < goods.length; i++) {
    			dis_list += ", " + this.$translateCommodityList(goods[i].commodity);
    		}
    	}
    	mission.addMessageText(dis_list);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$initialiseData = function $initialiseData() {
    	this._dataLoaded = true;
    
    	// if the initial run flag isn't yet stored in the data, but there are other definitions, reset the flag to false
    	// that should prevent any extreme prices.
    	if (!missionVariables.Smuggling_InitialRun && missionVariables.Smuggling_IllegalGoods) {
    		this._initialRun = false;
    	}
    
    	if (missionVariables.Smuggling_IllegalGoods) {
    		this._illegalGoods = JSON.parse(missionVariables.Smuggling_IllegalGoods);
    		delete missionVariables.Smuggling_IllegalGoods;
    	}
    	if (this._illegalGoods.length == 0) {
    		this.$initialSetup();
    		this.$markSystems();
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // shows the installation page to select a compartment size
    this.$showPurchasePage = function $showPurchasePage() {
    
    	var curChoices = {};
    	var p = player.ship;
    	var stn = p.dockedStation;
    	var rowAdjust = 0;
    
    	var pagesize = 18;
    	if (this.$isBigGuiActive() === true) pagesize = 24;
    
    	var key = "[permit-purchase-header]";
    	if (worldScripts["CargoTypeExtension"]) key = "[permit-purchase-header-nc]";
    
    	var text = expandDescription(key, {cash: formatCredits(player.credits, true, true)});
    	text += this.$padTextRight("Item", 20) + this.$padTextLeft("Cost", 10);
    
    	var goods = this.$illegalGoodsList(system.ID);
    	// remove any goods for which the player either already has a permit, or that no permit is allowed
    	for (var i = goods.length - 1; i >= 0; i--) {
    		if (goods[i].permit === 1) {
    			// if the player already has a permit for this item, remove it.
    			if (this.$playerHasPermit(system.ID, goods[i].commodity, false) === true) {
    				goods.splice(i, 1);
    			}
    		} else {
    			// if the item doesn't allow permits, remove it
    			goods.splice(i, 1);
    		}
    	}
    
    	for (var i = 0; i < goods.length; i++) {
    		var list = goods[i].commodity.split(",");
    		var cost = 0;
    		for (var j = 0; j < list.length; j++) {
    			cost += this._commodityInfo[list[j]][0] * (1 + (system.government + 1) / 8);
    		}
    		var menuitem = this.$padTextRight(this.$translateCommodityList(goods[i].commodity), 20) + this.$padTextLeft(formatCredits(cost, true, true), 10);
    		curChoices["01_PERMIT~" + goods[i].commodity] = {
    			text: menuitem,
    			alignment: "LEFT",
    			color: this._menuColor
    		};
    	}
    
    	curChoices["97_SPACER"] = "";
    
    	curChoices["99_EXIT"] = {
    		text: "[exit-no-change]",
    		color: this._itemColor
    	};
    	var def = "99_EXIT";
    
    	for (var i = 0; i < ((pagesize - 4) - goods.length); i++) {
    		curChoices["99_SPACER_" + i] = "";
    	}
    
    	var opts = {
    		screenID: "oolite-smugglers-permit-map",
    		title: "Import Permit Purchasing",
    		allowInterrupt: true,
    		exitScreen: "GUI_SCREEN_EQUIP_SHIP",
    		overlay: {
    			name: "stgu-form.png",
    			height: 546
    		},
    		choices: curChoices,
    		initialChoicesKey: def,
    		message: text
    	};
    
    	mission.runScreen(opts, this.$purchaseScreenHandler, this);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$purchaseScreenHandler = function $purchaseScreenHandler(choice) {
    
    	if (!choice) return;
    
    	if (choice.indexOf("01_PERMIT") >= 0) {
    		var cmdty = choice.substring(choice.indexOf("~") + 1);
    		var list = cmdty.split(",");
    		var cost = 0;
    		for (var j = 0; j < list.length; j++) {
    			cost += this._commodityInfo[list[j]][0] * (1 + (system.government + 1) / 8);
    		}
    
    		this._permits.push({
    			systemID: system.ID,
    			commodity: cmdty,
    			fake: 0
    		});
    
    		// charge the player
    		player.credits -= cost;
    
    		// make sure the right email is sent to the player
    		var email = worldScripts.EmailSystem;
    		if (email) {
    			// send a notification email
    			var msg = expandDescription("[permit-purchase-email]", {
    				commodity: this.$translateCommodityList(cmdty).replace(", ", " and "),
    				amount: formatCredits(cost, true, true)
    			});
    
    			email.$createEmail({
    				sender: "GalCop Permit Authority",
    				subject: "Purchase of import permit",
    				date: clock.seconds,
    				message: msg
    			});
    		}
    
    		// apply the install time (15 minutes)
    		clock.addSeconds(900);
    
    	}
    
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // initialise the illegal goods F4 screen entries
    this.$initMainInterface = function $initMainInterface(station) {
    	station.setInterface(this.name, {
    		title: "Illegal goods information",
    		category: "Station Interfaces",
    		summary: "Information relating to illegal goods in this sector.",
    		callback: this.$initialIllegalGoodsOptions.bind(this)
    	});
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$initialIllegalGoodsOptions = function $initialIllegalGoodsOptions() {
    	this._display = 0;
    	this._curpage = 0;
    	this.$illegalGoodsOptions();
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$illegalGoodsOptions = function $illegalGoodsOptions() {
    	function compareDate(a, b) {
    		if (a.started < b.started) return -1;
    		if (a.started > b.started) return 1;
    		return 0;
    	}
    
    	function compareSystem(a, b) {
    		if (a.systemID < b.systemID) return -1;
    		if (a.systemID > b.systemID) return 1;
    		return 0;
    	}
    
    	function compareSystemName(a, b) {
    		if (a.name < b.name) return -1;
    		if (a.name > b.name) return 1;
    		return 0;
    	}
    
    	var curChoices = {};
    	var text = "";
    	var p = player.ship;
    	var def = "";
    	var sbm = worldScripts.Smugglers_BlackMarket;
    	var col1 = 6;
    	var col2 = 10;
    	var pagesize = 20;
    
    	if (this.$isBigGuiActive() === true) {
    		pagesize = 26;
    	}
    
    	var govs = new Array();
    	for (var i = 0; i < 8; i++)
    		govs.push(String.fromCharCode(i));
    	var spc = String.fromCharCode(31);
    
    	// main menu
    	if (this._display === 0) {
    		text = "";
    		var recent = [];
    
    		for (var i = 0; i < this._illegalGoods.length; i++) {
    			// list all new items (less that 5 days since start
    			if (this._illegalGoods[i].started != 0 && ((clock.adjustedSeconds - this._illegalGoods[i].started) / 86400) < 7) {
    				recent.push({
    					name: System.systemNameForID(this._illegalGoods[i].systemID),
    					systemID: this._illegalGoods[i].systemID,
    					commodity: this.$translateCommodityList(this._illegalGoods[i].commodity),
    					description: this._illegalGoods[i].description,
    					started: this._illegalGoods[i].started
    				});
    			}
    		}
    
    		text = expandDescription("[recent-declarations]");
    		var lines = 3;
    		if (recent.length > 0) {
    			recent.sort(compareDate);
    
    			for (var i = 0; i < recent.length; i++) {
    				var info = System.infoForSystem(galaxyNumber, recent[i].systemID);
    				var rt = system.info.routeToSystem(info);
    				if (rt) {
    					var dist = rt.distance;
    				} else {
    					var dist = system.info.distanceToSystem(info);
    				}
    				var textitem = recent[i].name + " (" + govs[info.government] + spc + "TL" + (info.techlevel + 1) + ", dist: " + dist.toFixed(1) + "ly) -- " + recent[i].commodity + ": " + recent[i].description;
    				text += textitem + "\n";
    				var w = defaultFont.measureString(textitem);
    				lines += Math.ceil(w / 32);
    				if (lines >= (pagesize - 6)) break;
    			}
    
    		} else {
    			text += expandDescription("[recent-declarations-none]");
    			lines += 1;
    		}
    
    		text += expandDescription("[offer-selections]");
    
    		curChoices["01_INFO"] = {
    			text: "[illegal-sector]",
    			color: this._menuColor
    		};
    		curChoices["02_INRANGE"] = {
    			text: "[illegal-7ly]",
    			color: this._menuColor
    		};
    		curChoices["03_PLOT"] = {
    			text: "[illegal-course]",
    			color: this._menuColor
    		};
    		if (this._permits.length > 0) {
    			curChoices["04_PERMITS"] = {
    				text: "[list-permits]",
    				color: this._menuColor
    			};
    			lines += 1;
    		}
    		if (sbm._waypoints.length > 0) {
    			curChoices["05_WAYPOINTS"] = {
    				text: "[list-waypoints]",
    				color: this._menuColor
    			};
    			lines += 1;
    		}
    
    		curChoices["99_EXIT"] = {
    			text: "[illegal-return]",
    			color: this._itemColor
    		};
    
    		for (var i = 0; i < ((pagesize - 5) - lines); i++) {
    			curChoices["99_SPACER_" + i] = "";
    			//	text += "\n";
    		}
    
    
    		this._curpage = 0;
    
    		def = "99_EXIT";
    
    		var opts = {
    			screenID: "oolite-smuggling-illegal-map",
    			title: "Illegal Goods Information",
    			allowInterrupt: true,
    			overlay: {
    				name: "stgu-warning.png",
    				height: 546
    			},
    			exitScreen: "GUI_SCREEN_INTERFACES",
    			choices: curChoices,
    			initialChoicesKey: def,
    			message: text
    		};
    	}
    
    	// illegal goods in sector
    	if (this._display === 1) {
    
    		var list = [];
    		var checking = [];
    
    		for (var i = 0; i < this._illegalGoods.length; i++) {
    			if (checking.indexOf(this._illegalGoods[i].systemID) === -1) {
    				list.push({
    					systemID: this._illegalGoods[i].systemID,
    					name: System.systemNameForID(this._illegalGoods[i].systemID)
    				});
    				checking.push(this._illegalGoods[i].systemID);
    			}
    		}
    
    		list.sort(compareSystemName);
    
    		var redux = 6;
    		var maxpages = Math.ceil(list.length / (pagesize - redux));
    		var pagestart = this._curpage * (pagesize - redux);
    		var pageend = this._curpage * (pagesize - redux) + (pagesize - redux);
    		if (pageend > list.length) pageend = list.length;
    
    		text = "Illegal import notifications - Page " + (this._curpage + 1) + " of " + (maxpages > 0 ? maxpages : "1") + "\n";
    		if (this._IGTInstalled === true) {
    			text += expandDescription("[notifications-extra-igt]");
    		} else {
    			text += expandDescription("[notifications-extra-std]");
    		}
    
    		for (var i = pagestart; i < pageend; i++) {
    			var info = System.infoForSystem(galaxyNumber, list[i].systemID);
    			var rt = system.info.routeToSystem(info);
    			if (rt) {
    				var dist = rt.distance;
    			} else {
    				var dist = system.info.distanceToSystem(info);
    			}
    			text += this.$padTextRight(info.name, col1) + this.$padTextRight("(" + govs[info.government] + spc + "TL" + (info.techlevel + 1) + ", dist: " + dist.toFixed(1) + "ly)", col2);
    
    			var iglist = "";
    			var goods = this.$illegalGoodsList(list[i].systemID);
    			for (var j = 0; j < goods.length; j++) {
    				var cmdty = goods[j].commodity;
    				var subitems = cmdty.split(",");
    				for (var k = 0; k < subitems.length; k++) {
    					iglist += (iglist === "" ? "" : ", ") + displayNameForCommodity(subitems[k]);
    					if (this.$playerHasPermit(list[i].systemID, subitems[k], false) === true) iglist += " (P)";
    				}
    			}
    			text += iglist + "\n";
    		}
    
    		if (this._curpage === maxpages - 1) {
    			curChoices["10_NEXTPAGE"] = {
    				text: "[illegal-nextpage]",
    				color: this._disabledColor,
    				unselectable: true
    			};
    		} else {
    			curChoices["10_NEXTPAGE"] = {
    				text: "[illegal-nextpage]",
    				color: this._menuColor
    			};
    		}
    		if (this._curpage === 0) {
    			curChoices["11_PREVPAGE"] = {
    				text: "[illegal-prevpage]",
    				color: this._disabledColor,
    				unselectable: true
    			};
    		} else {
    			curChoices["11_PREVPAGE"] = {
    				text: "[illegal-prevpage]",
    				color: this._menuColor
    			};
    		}
    		curChoices["98_EXIT"] = {
    			text: "[illegal-return]",
    			color: this._itemColor
    		};
    
    		def = "98_EXIT";
    
    		var opts = {
    			screenID: "oolite-smuggling-illegal-map",
    			title: "Illegal Goods Information",
    			allowInterrupt: true,
    			overlay: {
    				name: "stgu-warning.png",
    				height: 546
    			},
    			exitScreen: "GUI_SCREEN_INTERFACES",
    			choices: curChoices,
    			initialChoicesKey: this._lastChoice ? this._lastChoice : def,
    			message: text
    		};
    	}
    
    	// illegal goods on course
    	if (this._display === 2) {
    		var target = p.targetSystem;
    		var source = system.ID;
    		var myRoute = null;
    		if (source >= 0) {
    			if (p.hasEquipmentProviding("EQ_ADVANCED_NAVIGATIONAL_ARRAY")) {
    				myRoute = System.infoForSystem(galaxyNumber, source).routeToSystem(System.infoForSystem(galaxyNumber, target), p.routeMode);
    			} else {
    				myRoute = {};
    				myRoute.route = [];
    				if (source !== target) myRoute.route.push(source);
    				myRoute.route.push(target);
    			}
    		}
    		if (myRoute != null) {
    			var list = myRoute.route;
    
    			var redux = 6;
    			var maxpages = Math.ceil(list.length / (pagesize - redux));
    			var pagestart = this._curpage * (pagesize - redux);
    			var pageend = this._curpage * (pagesize - redux) + (pagesize - redux);
    			if (pageend > list.length) pageend = list.length;
    
    			text = "Illegal imports on current route" + (list.length > 0 ? " - Page " + (this._curpage + 1) + " of " + maxpages : "") + "\n";
    			if (this._IGTInstalled === true) {
    				text += expandDescription("[notifications-extra-igt]");
    			} else {
    				text += expandDescription("[notifications-extra-std]");
    			}
    
    			for (var i = pagestart; i < pageend; i++) {
    				var info = System.infoForSystem(galaxyNumber, list[i]);
    				var rt = system.info.routeToSystem(info);
    				if (rt && p.hasEquipmentProviding("EQ_ADVANCED_NAVIGATIONAL_ARRAY")) {
    					var dist = rt.distance;
    				} else {
    					var dist = system.info.distanceToSystem(info);
    				}
    				text += this.$padTextRight(info.name, col1) + this.$padTextRight("(" + govs[info.government] + spc + "TL" + (info.techlevel + 1) + ", dist: " + dist.toFixed(1) + "ly)", col2);
    
    				var iglist = "";
    				var goods = this.$illegalGoodsList(list[i]);
    				if (goods.length > 0) {
    					for (var j = 0; j < goods.length; j++) {
    						var cmdty = goods[j].commodity;
    						var subitems = cmdty.split(",");
    						for (var k = 0; k < subitems.length; k++) {
    							iglist += (iglist === "" ? "" : ", ") + displayNameForCommodity(subitems[k]);
    							if (this.$playerHasPermit(list[i], subitems[k], false) === true) iglist += " (P)";
    						}
    					}
    					text += iglist;
    				} else {
    					text += "None";
    				}
    				text += "\n";
    			}
    		} else {
    			var maxpages = 0;
    			var list = [];
    			if (system.ID === -1) {
    				text = "Illegal imports on current route\n\nError: Route information unable to be calculated in interstellar space.\n";
    			} else {
    				text = "Illegal imports on current route\n\nError: Route information unable to be calculated.\n";
    			}
    		}
    
    		if (this._curpage === maxpages - 1 || list.length === 0) {
    			curChoices["10_NEXTPAGE"] = {
    				text: "[illegal-nextpage]",
    				color: this._disabledColor,
    				unselectable: true
    			};
    		} else {
    			curChoices["10_NEXTPAGE"] = {
    				text: "[illegal-nextpage]",
    				color: this._menuColor
    			};
    		}
    		if (this._curpage === 0 || list.length === 0) {
    			curChoices["11_PREVPAGE"] = {
    				text: "[illegal-prevpage]",
    				color: this._disabledColor,
    				unselectable: true
    			};
    		} else {
    			curChoices["11_PREVPAGE"] = {
    				text: "[illegal-prevpage]",
    				color: this._menuColor
    			};
    		}
    		curChoices["98_EXIT"] = {
    			text: "[illegal-return]",
    			color: this._itemColor
    		};
    
    		def = "98_EXIT";
    
    		var opts = {
    			screenID: "oolite-smuggling-illegal-map",
    			title: "Illegal Goods Information",
    			allowInterrupt: true,
    			overlay: {
    				name: "stgu-warning.png",
    				height: 546
    			},
    			exitScreen: "GUI_SCREEN_INTERFACES",
    			choices: curChoices,
    			initialChoicesKey: this._lastChoice ? this._lastChoice : def,
    			message: text
    		};
    	}
    
    	// purchased (legal or otherwise) permits
    	if (this._display === 3) {
    
    		var redux = 6;
    		var maxpages = Math.ceil(this._permits.length / (pagesize - redux));
    		if (maxpages === 0) maxpages = 1;
    
    		text = "Import permits acquried" + (this._permits.length > 0 ? " - Page " + (this._curpage + 1) + " of " + maxpages : "") + "\n\n";
    
    		if (this._permits.length > 0) {
    			var pagestart = this._curpage * (pagesize - redux);
    			var pageend = this._curpage * (pagesize - redux) + (pagesize - redux);
    			if (pageend > this._permits.length) pageend = this._permits.length;
    
    			for (var i = pagestart; i < pageend; i++) {
    				var info = System.infoForSystem(galaxyNumber, this._permits[i].systemID);
    				text += "  Import " + this.$translateCommodityList(this._permits[i].commodity) + " on " + info.name + "\n";
    			}
    		} else {
    			text += "  None";
    		}
    
    		if (this._curpage === maxpages - 1 || this._permits.length === 0) {
    			curChoices["10_NEXTPAGE"] = {
    				text: "[illegal-nextpage]",
    				color: this._disabledColor,
    				unselectable: true
    			};
    		} else {
    			curChoices["10_NEXTPAGE"] = {
    				text: "[illegal-nextpage]",
    				color: this._menuColor
    			};
    		}
    		if (this._curpage === 0 || this._permits.length === 0) {
    			curChoices["11_PREVPAGE"] = {
    				text: "[illegal-prevpage]",
    				color: this._disabledColor,
    				unselectable: true
    			};
    		} else {
    			curChoices["11_PREVPAGE"] = {
    				text: "[illegal-prevpage]",
    				color: this._menuColor
    			};
    		}
    		curChoices["98_EXIT"] = {
    			text: "[illegal-return]",
    			color: this._itemColor
    		};
    
    		def = "98_EXIT";
    
    		var opts = {
    			screenID: "oolite-smuggling-illegal-map",
    			title: "Illegal Goods Information",
    			allowInterrupt: true,
    			overlay: {
    				name: "stgu-form.png",
    				height: 546
    			},
    			exitScreen: "GUI_SCREEN_INTERFACES",
    			choices: curChoices,
    			initialChoicesKey: this._lastChoice ? this._lastChoice : def,
    			message: text
    		};
    
    	}
    
    	// illegal goods in range
    	if (this._display === 4) {
    		var list = system.info.systemsInRange(7);
    
    		var redux = 6;
    		var maxpages = Math.ceil(list.length / (pagesize - redux));
    		var pagestart = this._curpage * (pagesize - redux);
    		var pageend = this._curpage * (pagesize - redux) + (pagesize - redux);
    		if (pageend > list.length) pageend = list.length;
    
    		text = "Illegal imports in 7LY range" + (list.length > 0 ? " - Page " + (this._curpage + 1) + " of " + maxpages : "") + "\n";
    		if (this._IGTInstalled === true) {
    			text += expandDescription("[notifications-extra-igt]");
    		} else {
    			text += expandDescription("[notifications-extra-std]");
    		}
    
    		for (var i = pagestart; i < pageend; i++) {
    			var rt = system.info.routeToSystem(list[i]);
    			if (rt) {
    				var dist = rt.distance;
    			} else {
    				var dist = system.info.distanceToSystem(list[i]);
    			}
    			text += this.$padTextRight(list[i].name, col1) + this.$padTextRight("(" + govs[list[i].government] + spc + "TL" + (list[i].techlevel + 1) + ", dist: " + dist.toFixed(1) + "ly)", col2);
    
    			var iglist = "";
    			var goods = this.$illegalGoodsList(list[i].systemID);
    			if (goods.length > 0) {
    				for (var j = 0; j < goods.length; j++) {
    					var cmdty = goods[j].commodity;
    					var subitems = cmdty.split(",");
    					for (var k = 0; k < subitems.length; k++) {
    						iglist += (iglist === "" ? "" : ", ") + displayNameForCommodity(subitems[k]);
    						if (this.$playerHasPermit(list[i].systemID, subitems[k], false) === true) iglist += " (P)";
    					}
    				}
    				text += iglist;
    			} else {
    				text += "None";
    			}
    			text += "\n";
    		}
    
    		if (this._curpage === maxpages - 1 || list.length === 0) {
    			curChoices["10_NEXTPAGE"] = {
    				text: "[illegal-nextpage]",
    				color: this._disabledColor,
    				unselectable: true
    			};
    		} else {
    			curChoices["10_NEXTPAGE"] = {
    				text: "[illegal-nextpage]",
    				color: this._menuColor
    			};
    		}
    		if (this._curpage === 0 || list.length === 0) {
    			curChoices["11_PREVPAGE"] = {
    				text: "[illegal-prevpage]",
    				color: this._disabledColor,
    				unselectable: true
    			};
    		} else {
    			curChoices["11_PREVPAGE"] = {
    				text: "[illegal-prevpage]",
    				color: this._menuColor
    			};
    		}
    		curChoices["98_EXIT"] = {
    			text: "[illegal-return]",
    			color: this._itemColor
    		};
    
    		def = "98_EXIT";
    
    		var opts = {
    			screenID: "oolite-smuggling-illegal-map",
    			title: "Illegal Goods Information",
    			allowInterrupt: true,
    			overlay: {
    				name: "stgu-warning.png",
    				height: 546
    			},
    			exitScreen: "GUI_SCREEN_INTERFACES",
    			choices: curChoices,
    			initialChoicesKey: this._lastChoice ? this._lastChoice : def,
    			message: text
    		};
    	}
    
    	// rock hermit waypoints purchased
    	if (this._display === 5) {
    		var redux = 6;
    		var maxpages = Math.ceil(sbm._waypoints.length / (pagesize - redux));
    		if (maxpages === 0) maxpages = 1;
    
    		text = "Rock Hermit waypoints acquired" + (sbm._waypoints.length > 0 ? " - Page " + (this._curpage + 1) + " of " + maxpages : "") + "\n\n";
    
    		if (sbm._waypoints.length > 0) {
    			var pagestart = this._curpage * (pagesize - redux);
    			var pageend = this._curpage * (pagesize - redux) + (pagesize - redux);
    			if (pageend > sbm._waypoints.length) pageend = sbm._waypoints.length;
    
    			for (var i = pagestart; i < pageend; i++) {
    				var info = System.infoForSystem(galaxyNumber, sbm._waypoints[i]);
    				var rt = system.info.routeToSystem(info);
    				if (rt) {
    					var dist = rt.distance;
    				} else {
    					var dist = system.info.distanceToSystem(info);
    				}
    				text += info.name + " (" + govs[info.government] + spc + "TL" + (info.techlevel + 1) + ", dist: " + dist.toFixed(1) + "ly)\n";
    			}
    		} else {
    			text += "  None";
    		}
    
    		if (this._curpage === maxpages - 1 || sbm._waypoints.length === 0) {
    			curChoices["10_NEXTPAGE"] = {
    				text: "[illegal-nextpage]",
    				color: this._disabledColor,
    				unselectable: true
    			};
    		} else {
    			curChoices["10_NEXTPAGE"] = {
    				text: "[illegal-nextpage]",
    				color: this._menuColor
    			};
    		}
    		if (this._curpage === 0 || sbm._waypoints.length === 0) {
    			curChoices["11_PREVPAGE"] = {
    				text: "[illegal-prevpage]",
    				color: this._disabledColor,
    				unselectable: true
    			};
    		} else {
    			curChoices["11_PREVPAGE"] = {
    				text: "[illegal-prevpage]",
    				color: this._menuColor
    			};
    		}
    		curChoices["98_EXIT"] = {
    			text: "[illegal-return]",
    			color: this._itemColor
    		};
    
    		def = "98_EXIT";
    
    		var opts = {
    			screenID: "oolite-smuggling-waypoints-map",
    			title: "Rock Hermit Waypoints",
    			allowInterrupt: true,
    			overlay: {
    				name: "stgu-map_marker.png",
    				height: 546
    			},
    			exitScreen: "GUI_SCREEN_INTERFACES",
    			choices: curChoices,
    			initialChoicesKey: this._lastChoice ? this._lastChoice : def,
    			message: text
    		};
    
    	}
    
    	mission.runScreen(opts, this.$screenHandler, this);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$screenHandler = function $screenHandler(choice) {
    
    	delete this._lastChoice;
    
    	switch (choice) {
    		case "01_INFO":
    			this._display = 1;
    			break;
    		case "02_INRANGE":
    			this._display = 4;
    			break;
    		case "03_PLOT":
    			this._display = 2;
    			this._curpage = 0;
    			break;
    		case "04_PERMITS":
    			this._display = 3;
    			this._curpage = 0;
    			break;
    		case "05_WAYPOINTS":
    			this._display = 5;
    			this._curpage = 0;
    			break;
    		case "10_NEXTPAGE":
    			this._curpage += 1;
    			break;
    		case "11_PREVPAGE":
    			this._curpage -= 1;
    			break;
    		case "98_EXIT":
    			this._display = 0;
    			break;
    	}
    
    	this._lastChoice = choice;
    
    	if (choice != "99_EXIT") {
    		this.$illegalGoodsOptions();
    	}
    
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns list of illegal goods for system id
    this.$illegalGoodsList = function $illegalGoodsList(sysID, include) {
    
    	var list = [];
    	for (var i = 0; i < this._illegalGoods.length; i++) {
    		if (this._illegalGoods[i].systemID === sysID) list.push(this._illegalGoods[i]);
    	}
    	// include default definitions when running a check on docking
    	if (include && include === true) {
    		for (var i = 0; i < this._defaultAdditions.length; i++) {
    			list.push({
    				systemID: sysID,
    				commodity: this._defaultAdditions[i],
    				started: 0,
    				end: 0,
    				description: "",
    				permit: 0,
    				scale: 2
    			});
    		}
    	}
    	return list;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns list of illegal goods for system id, but just the commodity names (split out if necessary)
    this.$illegalGoodsListCommodityOnly = function $illegalGoodsListCommodityOnly(sysID, include) {
    	var list = [];
    	for (var i = 0; i < this._illegalGoods.length; i++) {
    		if (this._illegalGoods[i].systemID === sysID) {
    			var splitlist = this._illegalGoods[i].commodity.split(",");
    			for (var j = 0; j < splitlist.length; j++) {
    				list.push(splitlist[j]);
    			}
    		}
    	}
    	// include default definitions when running a check on docking
    	if (include && include === true) {
    		for (var i = 0; i < this._defaultAdditions.length; i++) {
    			list.push(this._defaultAdditions[i]);
    		}
    	}
    	return list;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns a particular illegal goods definition
    this.$illegalGoodsDefinition = function $illegalGoodsDefinition(sysID, commodity) {
    	for (var i = 0; i < this._illegalGoods.length; i++) {
    		if (this._illegalGoods[i].systemID === sysID && this._illegalGoods[i].commodity.indexOf(commodity) >= 0) return this._illegalGoods[i];
    	}
    	return null;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns true if a particular good is illegal in a particular system, otherwise false;
    this.$isGoodIllegal = function $isGoodIllegal(sysID, commodity) {
    	for (var i = 0; i < this._illegalGoods.length; i++) {
    		if (this._illegalGoods[i].systemID === sysID && this._illegalGoods[i].commodity.indexOf(commodity) >= 0) return true;
    	}
    	return false;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$goodIllegalScale = function $goodIllegalScale(sysID, commodity) {
    	for (var i = 0; i < this._illegalGoods.length; i++) {
    		if (this._illegalGoods[i].systemID === sysID && this._illegalGoods[i].commodity.indexOf(commodity) >= 0) return this._illegalGoods[i].scale;
    	}
    	return 0;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // initial setup of illegal goods in sector
    this.$initialSetup = function $initialSetup() {
    	// first, add all default/permanent items
    	for (var i = 0; i < this._possibilities.length; i++) {
    		var item = this._possibilities[i];
    		if (item.period === -1) {
    			var govs = item.govTypes.split(",");
    			for (var j = 0; j < govs.length; j++) {
    				if (govs[j] != "") {
    					var planetlist = this.$getSystemsByGovernment(parseInt(govs[j]), item.properties);
    					for (var k = 0; k < planetlist.length; k++) {
    						if (planetlist[k].sun_gone_nova) continue;
    						this.$applyDefinition(planetlist[k].systemID, item, 0);
    					}
    				}
    			}
    		}
    	}
    
    	// next pick a random number of planets and apply an illegal good for them
    	// add the definitions, between 30 and 60 ** TODO: work out if this is too much or too little
    	if (!this.$rand) this.$rand = worldScripts.Smugglers_CoreFunctions.$rand; // make sure our rand function is available
    	for (var i = 1; i <= (this.$rand(30) + 30); i++) {
    		this.$addNewItem(this.$rand(20));
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // add a new random item to the list
    this.$addNewItem = function $addNewItem(startDays, excludeSystem) {
    
    	var planetid = -1;
    	var sys;
    	var tries = 0;
    	do {
    		// pick a random planet
    		planetid = this.$rand(256) - 1;
    		sys = System.infoForSystem(galaxyNumber, planetid);
    		// don't create items in nova systems
    		if (sys.sun_gone_nova) planetid = -1;
    		// don't create items in the excluded system
    		if (planetid === excludeSystem) planetid = -1;
    
    		if (planetid >= 0) {
    			// check if this planet has any existing declarations
    			var existing = this.$illegalGoodsList(planetid);
    			var count = 0;
    			for (var i = 0; i < existing.length; i++) {
    				// count all the non-default items
    				if (existing[i].end != 0) count += 1;
    			}
    			// if this planet already has an item, skip it.
    			if (count > 0) planetid = -1;
    		}
    		tries += 1;
    	} while (planetid === -1 && tries < 5);
    	if (planetid === -1 && this._debug) log(this.name, "attempt to add illegal good failed");
    	// if we didn't end up finding a planet, exit now
    	if (planetid === -1) return;
    
    	// get a list of possibilities
    	var poss = this.$getPossibilitiesByGovernment(sys.government, sys.description);
    	// pick one from the list
    	var pick = this.$rand(poss.length) - 1;
    	// add the definition
    	this.$applyDefinition(planetid, poss[pick], startDays);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // get a list of possible illegal goods for a particular government type (0-7)
    this.$getPossibilitiesByGovernment = function $getPossibilitiesByGovernment(govType, planetDescription) {
    	var list = [];
    	for (var i = 0; i < this._possibilities.length; i++) {
    		if (this._possibilities[i].period != -1 && this._possibilities[i].govTypes.indexOf(govType.toString()) != -1 &&
    			(this._possibilities[i].properties === "" || this.$checkPlanetDescription(planetDescription, this._possibilities[i].properties) === true) &&
    			Math.random() < this._possibilities[i].chance) {
    			list.push(this._possibilities[i]);
    		}
    	}
    	return list;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // checks the planet description to see if the property text is somewhere in it.
    // properties can be a single piece of text (eg "something"), or split with a "," character (eg "something,somethingelse")
    this.$checkPlanetDescription = function $checkPlanetDescription(descr, properties) {
    	if (properties.indexOf(",") === -1) {
    		if (descr.indexOf(properties) >= 0) {
    			return true;
    		} else {
    			return false;
    		}
    	} else {
    		var proplist = properties.split(",");
    		var result = false;
    		for (var i = 0; i < proplist.length; i++) {
    			if (descr.indexOf(proplist[i]) >= 0) result = true;
    		}
    		return result;
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns a list of planets of a particular government type (0-7) and (optionally) with a description that contains the "option" parameter (eg "civil war")
    this.$getSystemsByGovernment = function $getSystemsByGovernment(govType, options) {
    	var planets = SystemInfo.filteredSystems(this, function (other) {
    		return (other.government === govType && ((options == null || options === "") || other.description.indexOf(options) >= 0));
    	});
    	return planets;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // adds a definition to the active list
    // sysID = planet ID
    // def   = illegal goods definition
    // startDays = number of days to take off the current date, to start this item in the past. This is to simulate an active galaxy, so everything doesn't start from now.
    this.$applyDefinition = function $applyDefinition(sysID, def, startDays) {
    
    	//log(this.name, "adding illegal good definition for system " + sysID + " for commodity " + def.commodity);
    
    	// does this definition already exist for this planet?
    	for (var i = 0; i < this._illegalGoods.length; i++) {
    		if (this._illegalGoods[i].systemID === sysID && this._illegalGoods[i].commodity === def.commodity) return;
    	}
    
    	var stime = 0;
    	var etime = 0;
    
    	// if we haven't been passed a start time, just do it for sometime in the last day
    	if (def.period != -1) {
    		if (this._startUpRunning === false) {
    			if (startDays == null || startDays === 0) {
    				stime = clock.seconds - this.$rand(86400);
    			} else {
    				// otherwise, the starttime is subtracted from the current clock (as days), converted to seconds
    				stime = clock.seconds - (startDays * 86400);
    			}
    			// set up the end point
    			etime = stime + ((def.period + (this.$rand(10) - 5)) * 86400);
    		} else {
    			// if this is running during the startup routine, we need to reapply start and end times after it's completed
    			if (startDays != 0) stime = (startDays * 86400) * -1;
    			// set up the end point
    			etime = ((def.period + (this.$rand(10) - 5)) * 86400);
    		}
    	} else {
    		stime = 0;
    		etime = 0;
    	}
    	if (def.period != -1 && this._debug) log(this.name, "adding illegal good definition for system " + sysID + " for commodity " + def.commodity + ", starting " + clock.clockStringForTime(stime) + ", ending " + clock.clockStringForTime(etime));
    
    	this._illegalGoods.push({
    		systemID: sysID,
    		commodity: def.commodity,
    		started: stime,
    		end: etime,
    		description: def.description,
    		permit: def.permit,
    		scale: def.scale
    	});
    
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // update start/end times created during the startup process
    this.$correctStartEndDates = function $correctStartEndDates() {
    	for (var i = 0; i < this._illegalGoods.length; i++) {
    		if (this._illegalGoods[i].started < 0) {
    			var stime = clock.adjustedSeconds + this._illegalGoods[i].started;
    			var etime = stime + this._illegalGoods[i].end;
    			this._illegalGoods[i].started = stime;
    			this._illegalGoods[i].end = etime;
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // look for any expired items and remove them from the array, and possibly add a new one
    this.$cleanup = function $cleanup() {
    	// remove expired items
    	for (var i = this._illegalGoods.length - 1; i >= 0; i--) {
    		if (this._illegalGoods[i].end != 0 && clock.adjustedSeconds > this._illegalGoods[i].end) {
    			if (this._debug) log(this.name, "removing item: " + this._illegalGoods[i].systemID + ", " + this._illegalGoods[i].commodity + " ended: " + clock.clockStringForTime(this._illegalGoods[i].end))
    			// do we have a permit for this one? If so, delete it
    			for (var j = this._permits.length - 1; j >= 0; j--) {
    				if (this._permits[j].systemID === this._illegalGoods[i].systemID && this._illegalGoods[i].commodity.indexOf(this._permits[j].commodity) >= 0) {
    					this._permits.splice(j, 1);
    				}
    			}
    			this._illegalGoods.splice(i, 1);
    		}
    	}
    
    	// add new items
    	var total = this.$rand(5) - this.$rand(3);
    	if (total > 0) {
    		if (this._debug) log(this.name, "going to try adding " + total + " new items");
    		for (var i = 1; i <= total; i++) {
    			// add new items, but not in the current system, because the illegal market script won't pick them up until the game is reloaded
    			this.$addNewItem(1, system.ID);
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // checks if the player has a permit for the system
    // todo: add newcargos permit check
    this.$playerHasPermit = function $playerHasPermit(sysID, cmdty, check) {
    	for (var i = 0; i < this._permits.length; i++) {
    		if (this._permits[i].systemID === sysID && this._permits[i].commodity === cmdty) {
    			// are we doing a validity check
    			if (check === true && this._permits[i].fake != 0) {
    				// did it pass?
    				if (Math.random() > this._permits[i].fake) {
    					// oh dear, looks like it was discovered as fake
    					return false;
    				} else {
    					// phew! passed!
    					return true;
    				}
    			} else {
    				// it's a bought one (or we're not bothering with how fake it is), so it's ok
    				return true;
    			}
    		}
    	}
    	return false;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // removes a permits from the list
    this.$removePermit = function $removePermit(sysID, cmdty) {
    	for (var i = this._permits.length - 1; i >= 0; i--) {
    		if (this._permits[i].systemID === sysID && this._permits[i].commodity === cmdty) {
    			this._permits.splice(i, 1);
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns display version of commodity list
    this.$translateCommodityList = function $translateCommodityList(cmdty_list) {
    	var list = cmdty_list.split(",");
    	var ret = "";
    	for (var i = 0; i < list.length; i++) {
    		ret += (ret === "" ? "" : ", ") + displayNameForCommodity(list[i]);
    	}
    	return ret;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // adds the F8F8 comment for illegal goods
    this.$updateDescriptions = function $updateDescriptions() {
    	// note: for some reason descriptions added via the illegal market script are not being displayed. So we're forcing the issue here.
    	var nocomment = expandDescription("[oolite-commodity-no-comment]");
    	if (player.ship.docked) {
    		var sMarket = player.ship.dockedStation.market;
    	} else {
    		if (player.ship.target && player.ship.target.isStation) {
    			var bcMarket = Ship.shipDataForKey(player.ship.target.dataKey)["market_broadcast"];
    			if (!bcMarket) bcMarket = true;
    			else {
    				var trues = ["1", "true", "yes"];
    				bcMarket = bcMarket.toLowerCase();
    				if (trues.indexOf(bcMarket) !== -1) bcMarket = true;
    				else bcMarket = false;
    			}
    			if (bcMarket) {
    				var sMarket = player.ship.target.market;
    			} else {
    				var sMarket = system.mainStation.market;
    			}
    		} else {
    			if (system.isInterstellarSpace) return;
    			var sMarket = system.mainStation.market;
    		}
    	}
    	var goods = [];
    	if (system.ID != -1) {
    		goods = this.$illegalGoodsList(system.ID, true);
    	} else {
    		return;
    	}
    
    	for (var cmdty in sMarket) {
    		// work out what the comment should be for this commodity
    		var cmt = nocomment;
    		// special cases
    		if (cmdty === "slaves") cmt = expandDescription("[oolite-commodity-illegal]");
    		if (this._defaultAdditions.indexOf(cmdty) >= 0) cmt = expandDescription("[" + cmdty + "-legality-description]");
    		// if this is an illegal commodity at this station go and grab the description for it from our data
    		if (sMarket[cmdty].legality_import != 0 || sMarket[cmdty].legality_export != 0) {
    			for (var i = 0; i < goods.length; i++) {
    				if (goods[i].commodity.indexOf(cmdty) >= 0 && goods[i].description != "") cmt = goods[i].description;
    			}
    		} else {
    			// if it's legal here, but not legal at the main station, put up a warning message
    			if (system.mainStation.market[cmdty].legality_import != 0 || system.mainStation.market[cmdty].legality_export != 0) {
    				cmt = expandDescription("[main-station-illegal]");
    			} else {
    				// otherwise, just use the "no comment" comment.
    				cmt = nocomment;
    			}
    		}
    		// get the current comment
    		var orig = manifest.comment(cmdty);
    		// in most cases that's all we'll need
    		var curr_cmt = orig;
    		// but if another OXP has added their own comments, we'll need to get the first line
    		// if the OXP adds comments without first adding a linebreak this won't work, but it should be good for most circumstances
    		if (orig.indexOf("\n") >= 0) curr_cmt = orig.substring(0, orig.indexOf("\n"));
    		// replace the current comment with our new one
    		orig = orig.replace(curr_cmt, cmt);
    		// plonk it back into the comment field for this commodity
    		manifest.setComment(cmdty, orig);
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // mark local systems on the galactic chart that have illegal goods
    this.$markSystems = function $markSystems() {
    	// clear out existing marks
    	for (var i = 0; i < this._markedSystems.length; i++) {
    		mission.unmarkSystem({
    			system: this._markedSystems[i],
    			name: "illegalgoods"
    		});
    	}
    	// reset the array
    	this._markedSystems.length = 0;
    
    	if (this._enableMarkedSystems === false) return;
    	// get all systems in 10 ly
    	var list = system.info.systemsInRange(this._markedSystemRange);
    	// mark systems that have illegal goods (other than narcs or slaves)
    	// system: firearms only = orange, everything else yellow
    	for (var i = 0; i < list.length; i++) {
    		var goods = this.$illegalGoodsList(list[i].systemID);
    		if (goods.length > 0) {
    			this._markedSystems.push(list[i].systemID);
    			if (goods.length > 1) {
    				// if there's more than one illegal, mark it with yellow
    				mission.markSystem({
    					system: list[i].systemID,
    					name: "illegalgoods",
    					markerColor: this._markerColors[this._mixedIllegalColor],
    					markerShape: this._markerShapes[this._mixedIllegalShape]
    				});
    			} else {
    				// if the only illegal is firearms, mark it with orange
    				if (goods[0].commodity === "firearms") {
    					mission.markSystem({
    						system: list[i].systemID,
    						name: "illegalgoods",
    						markerColor: this._markerColors[this._firearmsColor],
    						markerShape: this._markerShapes[this._firearmsShape]
    					});
    				} else {
    					// otherwise mark it with yellow
    					mission.markSystem({
    						system: list[i].systemID,
    						name: "illegalgoods",
    						markerColor: this._markerColors[this._mixedIllegalColor],
    						markerShape: this._markerShapes[this._mixedIllegalShape]
    					});
    				}
    			}
    		}
    	}
    }
    Scripts/smugglers_movecargo.js
    "use strict";
    this.name = "Smugglers_MoveCargo";
    this.author = "phkb";
    this.description = "Looks after the process of moving cargo into the smuggling compartment during flight.";
    this.licence = "CC BY-NC-SA 3.0";
    
    this._scoopedCargo = [];
    this._commodityUnits = {};
    this._cycleIndex = -1;
    this._selected = null;
    
    //-------------------------------------------------------------------------------------------------------------
    this.startUpComplete = function () {
        var m = system.mainStation.market;
        if (m) {
            var list = Object.keys(m);
            for (var i = 0; i < list.length; i++) {
                var c = m[list[i]];
                this._commodityUnits[c.key] = (c.quantity_unit === "0" ? "t" : (c.quantity_unit === "1" ? "kg" : "g"));
            }
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.shipDockedAtStation = function (station) {
        this._scoopedCargo.length = 0;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.mode = function () {
        // cycle through available, non-labelled cargo, including scooped items (but not escape pods)
        // group precious items: existing cargo at launch as amounts (eg 15g gemstones), scooped cargo amounts (eg 9kg gold)
        var p = player.ship;
        var list = [];
        var cargo = p.manifest.list;
        var sdm = worldScripts.Smugglers_DockMaster;
        var sde = worldScripts.Smugglers_Equipment;
        var sdmc = worldScripts.Smugglers_MoveCargo;
        var itemcount = 0;
    
        // add the scooped items first
        for (var i = 0; i < sdmc._scoopedCargo.length; i++) {
            var item = sdmc._scoopedCargo[i];
            if (item.isValid && item.status === "STATUS_IN_HOLD") {
                list.push({
                    text: "Move " + item.commodityAmount + sdmc._commodityUnits[item.commodity] + " × " + displayNameForCommodity(item.commodity) + " (scooped)",
                    scoopedItem: item,
                    commodity: item.commodity,
                    quantity: item.commodityAmount,
                    unit: sdmc._commodityUnits[item.commodity]
                });
            }
        }
    
        // then the items in the hold.
        var existing = [];
        for (var i = 0; i < cargo.length; i++) {
            // only show standard (non-OXP) commodity items
            if (sde._commodityIDList.indexOf(cargo[i].commodity) >= 0) {
                var q = cargo[i].quantity - sdmc.$scoopedCargoAmount(cargo[i].commodity);
                // don't allow scooped escape pods to be moved
                if (cargo[i].commodity === "slaves" && q > 0 && worldScripts.EscapePodTweaks && worldScripts.EscapePodTweaks._escapePods.length > 0) {
                    q -= worldScripts.EscapePodTweaks._escapePods;
                }
                // todo: how to handle scooped escape pods when escape pod tweaks isn't installed
                
                // make sure we don't move relabelled cargo
                if (sdm._cargoLabelled.length > 0) {
                    for (var j = 0; j < sdm._cargoLabelled.length; j++) {
                        if (sdm._cargoLabelled[j].newCommodity === cargo[i].commodity) q -= sdm._cargoLabelled[j].quantity;
                    }
                }
                if (q > 0) {
                    switch (cargo[i].unit) {
                        case "t":
                            if (existing.indexOf(cargo[i].commodity) === -1) {
                                list.push({
                                    text: "Move 1" + cargo[i].unit + " × " + cargo[i].displayName,
                                    scoopedItem: null,
                                    commodity: cargo[i].commodity,
                                    quantity: 1,
                                    unit: cargo[i].unit
                                });
                                existing.push(cargo[i].commodity);
                            }
                            break;
                        case "kg":
                            var step = 1000;
                            if (step > q) step = q;
                            var checktot = q;
                            for (var k = 1; k <= q; k += step) {
                                checktot -= step;
                                if (checktot < 0) {
                                    list.push({
                                        text: "Move " + (step + checktot).toString() + cargo[i].unit + " × " + cargo[i].displayName,
                                        scoopedItem: null,
                                        commodity: cargo[i].commodity,
                                        quantity: (step + checktot),
                                        unit: cargo[i].unit
                                    });
                                } else {
                                    if (existing.indexOf(cargo[i].commodity) === -1) {
                                        list.push({
                                            text: "Move " + step.toString() + cargo[i].unit + " × " + cargo[i].displayName,
                                            scoopedItem: null,
                                            commodity: cargo[i].commodity,
                                            quantity: step,
                                            unit: cargo[i].unit
                                        });
                                        existing.push(cargo[i].commodity);
                                    }
                                }
                            }
                            break;
                        case "g":
                            var step = 1000000;
                            if (step > q) step = q;
                            var checktot = q;
                            for (var k = 1; k <= q; k += step) {
                                checktot -= step;
                                if (checktot < 0) {
                                    list.push({
                                        text: "Move " + (step + checktot).toString() + cargo[i].unit + " × " + cargo[i].displayName,
                                        scoopedItem: null,
                                        commodity: cargo[i].commodity,
                                        quantity: (step + checktot),
                                        unit: cargo[i].unit
                                    });
                                } else {
                                    if (existing.indexOf(cargo[i].commodity) === -1) {
                                        list.push({
                                            text: "Move " + step.toString() + cargo[i].unit + " × " + cargo[i].displayName,
                                            scoopedItem: null,
                                            commodity: cargo[i].commodity,
                                            quantity: step,
                                            unit: cargo[i].unit
                                        });
                                        existing.push(cargo[i].commodity);
                                    }
                                }
                            }
                            break;
                    }
                }
            }
        }
    
        if (list.length === 0) return;
    
        sdmc._cycleIndex += 1;
        if (sdmc._cycleIndex >= list.length) sdmc._cycleIndex = 0;
        sdmc._selected = list[sdmc._cycleIndex];
        player.consoleMessage(list[sdmc._cycleIndex].text);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.activated = function () {
        // move the selected item 
        var sde = worldScripts.Smugglers_Equipment;
        var sdmc = worldScripts.Smugglers_MoveCargo;
        if (sdmc._selected != null) {
            var item = sdmc._selected;
            // any space left for transfer?
            var checkAmount = item.quantity;
            if (item.unit === "kg") checkAmount /= 1000;
            if (item.unit === "g") checkAmount /= 1000000;
            if (sde.$availableSpace() < checkAmount) {
                player.consoleMessage("Insufficient space in smuggling compartment");
                return;
            }
            sde.$addCargoToCompartment(item.commodity, displayNameForCommodity(item.commodity), item.quantity, item.unit);
            if (item.scoopedItem != null) {
                for (var i = 0; i < sdmc._scoopedCargo.length; i++) {
                    if (sdmc._scoopedCargo[i] == item.scoopedItem) {
                        sdmc._scoopedCargo.splice(i, 1);
                        break;
                    }
                }
                item.scoopedItem.remove(true);
            }
            var p = player.ship;
            if (sde._NCInstalled == true) {
                var tfr = item.quantity;
                var storage = [];
                var ncmanifest = cte.suspendPlayerManifest(item.commodity);
                var nc = ncmanifest.split("|");
                var lst = nc[1].split("/");
                for (var i = 0; i < lst.length; i++) {
                    // reduce the original
                    var sub = lst[i].split("=");
                    sub[1]--;
                    lst[i] = sub.join("=");
    
                    // make a copy of what we're taking out
                    // we'll store this as i unit entries, rather than trying to sum them up here,
                    // to make it easier at the other end when we're taking stuff out
                    var st = "";
                    for (var j = 0; j < sub.length; j++) {
                        st += (st == "" ? "" : "=") + (j == 1 ? 1 : sub[j]);
                    }
                    storage.push(st);
                    tfr--;
                    if (tfr == 0) break;
                }
                nc[1] = lst.join("/");
                ncmanifest = nc.join("|");
    
                // tell NC about what we've done
                cte.mergePlayerManifest(ncmanifest);
                // pull our storage data together
                extra = storage.join("/");
            }
    
            // the manifest of any "kg" or "g" type commodities can't be reduced (in-flight only)
            // so add 1 to the value so Oolite with consider the commodity as being out of the hold
            var bugfix = false;
            if (item.unit === "kg" || item.unit === "g") {
                p.manifest[item.commodity] += 1;
                bugfix = true;
            }
            // if we added one, make sure we add it so we can take it away again.
            p.manifest[item.commodity] -= (item.quantity + (bugfix === true ? 1 : 0));
            player.consoleMessage(item.quantity + item.unit + " × " + displayNameForCommodity(item.commodity) + " transferred");
            sdmc._selected = null;
            sdmc._cycleIndex = -1;
            // force an update of the manifest MFD, as it won't detect changes in kg or g type commodities
            if (item.unit != "t" && worldScripts["manifest_mfd"] && p.equipmentStatus("EQ_MANIFEST_MFD") === "EQUIPMENT_OK") {
                worldScripts["manifest_mfd"].$updateManifestMFD();
            }
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.shipScoopedOther = function (cargo) {
        // we don't move escape pods (which will have a crew)
        if (cargo.isValid === false) return;
        if (cargo.commodity != "slaves" || (!cargo.crew || cargo.crew.length === 0)) {
            if (cargo.script.shipWasDumped) cargo.script.$sdmovr_shipWasDumped = cargo.script.shipWasDumped;
            cargo.script.shipWasDumped = this.$sdm_shipWasDumped;
            this._scoopedCargo.push(cargo);
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$sdm_shipWasDumped = function $sdm_shipWasDumped(dumper) {
        if (this.ship.script.$sdmovr_shipWasDumped) this.ship.script.$sdmovr_shipWasDumped(dumper);
        if (dumper.isPlayer) {
            var sdmc = worldScripts.Smugglers_MoveCargo;
            for (var i = 0; i < sdmc._scoopedCargo.length; i++) {
                if (sdmc._scoopedCargo[i] == this.ship) {
                    sdmc._scoopedCargo.splice(i, 1);
                    break;
                }
            }
        }
        // detach our custom script
        delete this.ship.script.shipWasDumped;
        if (this.ship.script.$sdmovr_shipWasDumped) {
            this.ship.script.shipWasDumped = this.ship.script.$sdmovr_shipWasDumped;
            delete this.ship.script.$sdmovr_shipWasDumped;
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$scoopedCargoAmount = function $scoopedCargoAmount(cmdty) {
        var sdmc = worldScripts.Smugglers_MoveCargo;
        if (sdmc._scoopedCargo.length === 0) return 0;
        var amt = 0;
        var sc = sdmc._scoopedCargo;
        for (var i = 0; i < sc.length; i++) {
            if (sc[i].commodity === cmdty) amt += sc[i].commodityAmount;
        }
        return amt;
    }
    Scripts/smugglers_oolite_contracts_fix.js
    "use strict";
    this.name = "Smugglers_Oolite_Contracts_Fix";
    this.author = "phkb";
    this.description = "Fixes the Oolite Cargo contracts system so it doesn't pick illegal commodities.";
    this.licence = "CC BY-NC-SA 3.0";
    
    this.startUp = function () {
    	var w = worldScripts["oolite-contracts-cargo"];
    	w.$hide = w._initialiseCargoContractsForSystem;
    	if (w.author.indexOf("Switeck") >= 0) {
    		w._initialiseCargoContractsForSystem = this.$switech_initialiseCargoContractsForSystem;
    	} else {
    		w._initialiseCargoContractsForSystem = this.$core_initialiseCargoContractsForSystem;
    	}
    }
    
    // initialise a new cargo contract list for the current system
    this.$core_initialiseCargoContractsForSystem = function $core_initialiseCargoContractsForSystem() {
    	// clear list
    	this.$contracts = [];
    
    	// this is not the same algorithm as in 1.76, but should give
    	// similar results with comparable efficiency.
    
    	// no point in generating too many, as route-finding is slow
    	var numContracts = Math.floor(5 * Math.random() + 5 * Math.random() + 5 * Math.random() + (player.contractReputationPrecise * Math.random()));
    	if (player.contractReputationPrecise >= 0 && numContracts < 5) {
    		numContracts += 5;
    	}
    	if (numContracts > 16) {
    		numContracts = 16;
    	} else if (numContracts < 0) {
    		numContracts = 0;
    	}
    	// some of these possible contracts may be discarded later on
    
    	for (var i = 0; i < numContracts; i++) {
    		var cargo = new Object;
    
    		// pick a random system to take the goods to
    		var destination = Math.floor(Math.random() * 256);
    
    		// discard if chose the current system
    		if (destination === system.ID) {
    			continue;
    		}
    
    		// get the SystemInfo object for the destination
    		var destinationInfo = System.infoForSystem(galaxyNumber, destination);
    		if (destinationInfo.sun_gone_nova) {
    			continue;
    		}
    		var daysUntilDeparture = 1 + (Math.random() * (7 + player.contractReputationPrecise - destinationInfo.government));
    		if (daysUntilDeparture <= 0) {
    			// loses some more contracts if reputation negative
    			continue;
    		}
    
    		var si = worldScripts.Smugglers_Illegal;
    		var illegal = null;
    		if (si) {
    			illegal = si.$illegalGoodsListCommodityOnly(destination, true);
    			// add slaves to the list
    			illegal.push("slaves");
    		}
    
    		var commodities = Object.keys(system.mainStation.market);
    		var attempts = 0;
    		do {
    			var remotePrice = 0;
    			attempts++;
    			var commodity = commodities[Math.floor(Math.random() * commodities.length)];
    			// sub-tc contracts only available for top rep
    			if (system.mainStation.market[commodity]["quantity_unit"] != 0 && player.contractReputationPrecise < 6.5) {}
    			// ignore commodities with 0 availability here
    			else if (system.mainStation.market[commodity].quantity === 0) {}
    			// ignore any illegal goods
    			else if (illegal && illegal.indexOf(commodity) >= 0) {} else {
    				remotePrice = this._priceForCommodity(commodity, destinationInfo);
    			}
    		} while (remotePrice < system.mainStation.market[commodity].price / 20 && attempts < 10);
    		if (attempts === 10) {
    			// failed to find a good one.
    			continue;
    		}
    		cargo.commodity = commodity;
    
    		var amount = 0;
    		while (amount < 30) {
    			var unitsize = 1;
    			// larger unit sizes for kg/g commodities
    			if (system.mainStation.market[commodity]["quantity_unit"] === 1) {
    				unitsize += Math.floor(Math.random() * 6) + Math.floor(Math.random() * 6) + Math.floor(Math.random() * 6);
    			} else if (system.mainStation.market[commodity]["quantity_unit"] === 2) {
    				unitsize += Math.floor(Math.random() * 16) + Math.floor(Math.random() * 11) + Math.floor(Math.random() * 6);
    			}
    			amount += (1 + Math.floor(Math.random() * 32)) * (1 + Math.floor(Math.random() * 16)) * unitsize;
    		}
    
    		if (amount > 125 && system.mainStation.market[commodity]["quantity_unit"] === 0) {
    			// reduce the number of contracts only suitable for Anacondas
    			amount = Math.floor(amount / Math.floor(1 + (Math.random() * 4)));
    		}
    		cargo.size = amount;
    
    		// adjustment to prices based on quantity (larger = more profitable)
    		var discount = Math.min(10 + Math.floor(amount / 10), 35);
    
    		var unitPrice = system.mainStation.market[commodity].price * (100 - discount) / 1000;
    		var localValue = Math.floor(unitPrice * amount);
    		remotePrice = remotePrice * (200 + discount) / 200;
    		var remoteValue = Math.floor(remotePrice * amount);
    		var profit = remoteValue - localValue;
    
    		// skip if unprofitable
    		if (profit <= 100) {
    			continue;
    		}
    
    		// check that a route to the destination exists
    		// route calculation is expensive so leave this check to last
    		var 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 parcel details
    
    		cargo.destination = destination;
    		// we'll need this again later, and route calculation is slow
    		cargo.route = routeToDestination;
    
    		// higher share for transporter for longer routes, less safe systems
    		var share = 100 + destinationInfo.government - (10 * routeToDestination.route.length);
    		if (share < 10) {
    			share = 10;
    		}
    		share = 100 - share;
    
    		// safety: now multiply the fee by 2 compared with 1.76 contracts
    		// prevents exploit discovered by Mad Hollander at
    		// http://aegidian.org/bb/viewtopic.php?p=188127
    		localValue *= 2;
    		// this may need to be raised further
    
    		// absolute value of profit remains the same
    		var fee = localValue + Math.floor(profit * (share / 100));
    		fee -= fee % 20; // round to nearest 20 credits;
    
    		cargo.payment = fee;
    		cargo.deposit = localValue - (localValue % 20);
    		if (cargo.deposit >= cargo.payment) {
    			// rare but not impossible; last safety check
    			return;
    		}
    
    		// time allowed for delivery is time taken by "fewest jumps"
    		// route, plus timer above. Higher reputation makes longer
    		// times available.
    		cargo.deadline = clock.adjustedSeconds + Math.floor(daysUntilDeparture * 86400) + (cargo.route.time * 3600);
    
    		// add parcel to contract list
    		this._addCargoContractToSystem(cargo);
    	}
    
    }
    
    // initialise a new cargo contract list for the current system
    this.$switech_initialiseCargoContractsForSystem = function $switech_initialiseCargoContractsForSystem() {
    	// clear list
    	this.$contracts = [];
    
    	// this is not the same algorithm as in 1.76, but should give similar results with comparable efficiency.
    
    	// no point in generating too many, as route-finding is slow
    	var numContracts = Math.floor(5 * (Math.random() + Math.random()) + (system.government + player.contractReputationPrecise - player.bounty * 0.02) * Math.random());
    	// I added that bounty can reduce the number of contracts. Corporate state systems can add extra contracts.
    	if (numContracts < 0) numContracts = 0;
    	if (player.contractReputationPrecise >= 0 && numContracts < 5) numContracts += 5;
    	if (numContracts > 18) numContracts = 18;
    
    	// some of these possible contracts may be discarded later on
    	var nearbysystems = system.info.systemsInRange(80 - player.contractReputationPrecise * 7); // 31-80-129 LY range...done here to save recalculations time.
    
    	log(this.name, " player.contractReputation= " + player.contractReputationPrecise + " numContracts= " + numContracts + " nearbysystems.length= " + nearbysystems.length);
    	//	log(this.name," player.contractReputation= "+player.contractReputation+" numContracts= "+numContracts+" nearbysystems.length= "+nearbysystems.length+" nearbysystems= "+nearbysystems);
    
    	for (var i = 0; i < numContracts; i++) {
    		var scratchval = 1;
    		var attempts = 0;
    		do {
    			var scratchval = 1;
    			attempts++;
    			// pick a random system to take the goods to
    			//			var destination = Math.floor(Math.random()*256);	// ORIGINAL -- FOR TESTING ONLY!
    			var destination = nearbysystems[Math.floor(Math.random() * nearbysystems.length)].systemID;
    			//		log(this.name," destination = "+destination);
    
    			// discard if chose the current system
    			if (destination === system.ID) {
    				var scratchval = 0;
    			} else {
    				// get the SystemInfo object for the destination
    				var destinationInfo = System.infoForSystem(galaxyNumber, destination);
    
    				// Decides firsthand to drop a bad system and choose another one?
    				if (Math.abs(system.economy - destinationInfo.economy) < (1 + player.contractReputationPrecise * 0.28) && player.contractReputationPrecise < 6.5 && system.economy < 7) {
    					var scratchval = 0; // Discarded, since identical or "worse" economies leave little opportunity for profitable trading.
    
    				} else {
    					// check that a route to the destination exists
    					// route calculation is expensive so leave this check to last
    					var routeToDestination = system.info.routeToSystem(destinationInfo);
    					//log(this.name," routeToDestination = "+routeToDestination);
    					// if the system cannot be reached, ignore this contract
    					if (!routeToDestination) {
    						var scratchval = 0; // No route, no point trying!
    					} else {
    						var daysUntilDeparture = 1 + (Math.random() * (7 + player.contractReputationPrecise - destinationInfo.government)); // NEEDS a better estimate based on time needed to reach target!
    						if (daysUntilDeparture < 0) var scratchval = 0; // negative reputation can make a contract out-of-time failed before starting.
    					}
    				}
    			}
    		} while (scratchval === 0 && attempts < 10);
    		if (attempts > 9) continue; // failed to find a good destination.
    
    		// we now have a valid destination, so generate the rest of the cargo contract details
    
    		//log(this.name," daysUntilDeparture = "+daysUntilDeparture);
    		//		var commodities = Object.keys(system.mainStation.market);
    		var attempts = 0;
    		do {
    			var unitPrice = 0;
    			var unitMinPrice = 0;
    			var unitMaxPrice = 0;
    			var remotePrice = 0;
    			var remoteMinPrice = 0;
    			attempts++;
    
    			// if destinationInfo.economy < system.economy	means destination is more industrial than start locaction.
    			// if system.economy <4 then it's "industrial" so choose comp,lux,mach,alloys (+firearms?)
    			// else it's "agricultural" so choose food,textiles,radioactives,liquor_wines,furs,minerals (+slaves, narcotics?)
    			// Odds get lowered if offender/fugitive, raised if high reputation...forcing more controlled items if "bad" and more gold/plat/gems if "good".
    
    			//			var commodities = ["food","textiles","radioactives","slaves","liquor_wines","luxuries","narcotics","computers","machinery","alloys","firearms","furs","minerals","gold","platinum","gem_stones"];
    			//			var commodity = commodities[Math.floor(Math.random()*commodities.length)];	// Chooses random commondity
    			if (system.economy > destinationInfo.economy || system.economy > 5) {
    				if (system.economy < destinationInfo.economy + 1 + player.contractReputationPrecise * 0.4) { // Eliminates marginal profit commodities from similar agri economies
    					var commodities = ["slaves", "narcotics", "liquor_wines", "alloys", "furs", "platinum", "gem_stones"]; // 7 entries, removed Gold!
    					var preccutoff = 5;
    					var scratchval = Math.floor(Math.random() * (commodities.length - 3) + 3); // Drops the controlled items and low profit stuff.
    				} else {
    					var commodities = ["slaves", "narcotics", "food", "minerals", "textiles", "radioactives", "liquor_wines", "furs", "gold", "platinum", "gem_stones"]; // 11 entries, sorted from worst to best
    					var preccutoff = 8;
    					var scratchval = Math.floor(Math.random() * (commodities.length - 5) + 5); // Drops the controlled items and low profit stuff.
    				}
    			} else if (system.economy > destinationInfo.economy - Math.max(0, player.contractReputationPrecise * 0.3)) { // Eliminates low-profit commodities when start economy === destination (identical ind economies)
    				var commodities = ["alloys", "furs", "gold", "platinum", "gem_stones"]; // 5 entries
    				var preccutoff = 2;
    				var scratchval = Math.floor(Math.random() * commodities.length);
    			} else if (system.economy > destinationInfo.economy - 1 - player.contractReputationPrecise * 0.2) { // Eliminates low-profit commodities when start economy is only slightly more industrial than destination.
    				var commodities = ["narcotics", "firearms", "computers", "alloys", "furs", "platinum", "gem_stones"]; // 7 entries, removed Gold!
    				var preccutoff = 5;
    				var scratchval = Math.floor(Math.random() * (commodities.length - 2) + 2); // Drops the controlled items and low profit stuff.
    			} else {
    				var commodities = ["narcotics", "firearms", "alloys", "machinery", "luxuries", "computers", "platinum", "gem_stones"]; // 8 entries, sorted from worst to best, removed Gold!
    				var preccutoff = 6;
    				var scratchval = Math.floor(Math.random() * (commodities.length - 2) + 2); // Drops the controlled items and low profit stuff.
    			}
    
    			if (player.contractReputationPrecise < 6.5 - Math.random() * 5) { // At rep>2, there's a chance of getting ONLY good contracts. The odds of this are much better by rep>5.
    				var scratchval = Math.floor(Math.random() * (preccutoff + player.contractReputationPrecise * 0.1 - player.bounty * 0.01));
    			}
    
    			if (scratchval < 0) var scratchval = 0; // If bad rep plus bad bounty pushes off leftside of the chart, give player "worst" contract.
    			if (scratchval >= preccutoff && player.contractReputationPrecise < 6.5) var scratchval = preccutoff - 1; // Prevents Gold contracts before player.contractReputation === 7
    			if (scratchval > commodities.length - 1) var scratchval = commodities.length - 1; // If high rep pushes off rightside of the chart, give player "best" contract.
    
    			//			var commodities = ["gold","platinum","gem_stones"];	// 3 entries, tests only Gold/Plat/Gems!
    			//			var scratchval = Math.floor(Math.random()*commodities.length);
    			var commodity = commodities[scratchval];
    
    			var si = worldScripts.Smugglers_Illegal;
    			var illegal = null;
    			if (si) {
    				illegal = si.$illegalGoodsListCommodityOnly(destination, true);
    				// add slaves to the list
    				illegal.push("slaves");
    			}
    
    			if (illegal && illegal.indexOf(commodity) >= 0) { // don't pick commodities illegal at the destination
    				if (system.mainStation.market[commodity].quantity > 0) { // ignore commodities with 0 availability here
    					var unitPrice = Number((system.mainStation.market[commodity].price * 0.1).toFixed(1));
    					var unitMinPrice = Number((this._priceForCommodity(system.mainStation.market[commodity], System.infoForSystem(galaxyNumber, system.ID).economy, 0)).toFixed(1));
    					var unitMaxPrice = Number((this._priceForCommodity(system.mainStation.market[commodity], System.infoForSystem(galaxyNumber, system.ID).economy, 255)).toFixed(1));
    					var remoteMinPrice = Number((this._priceForCommodity(system.mainStation.market[commodity], destinationInfo.economy, 0)).toFixed(1)); // ,0 means return smallest value
    					var remotePrice = Number((this._priceForCommodity(system.mainStation.market[commodity], destinationInfo.economy, 255)).toFixed(1)); // ,255 means return largest value
    					//				var remotePrice = Number((this._priceForCommodity(system.mainStation.market[commodity],destinationInfo.economy,Math.floor(Math.random()*256))).toFixed(1)); // random prices!
    
    					//		log(this.name," # "+attempts+", "+commodities+", "+preccutoff+", "+commodity+" val1="+scratchval+" Price="+unitPrice+" MinPrice="+unitMinPrice+" MaxPrice="+unitMaxPrice+" remMin="+remoteMinPrice+" remP= "+remotePrice+", "+system.economy+", "+destinationInfo.economy);
    
    					// This can happen with narcotics!
    					if (remotePrice < remoteMinPrice) {
    						var scratchval = remoteMinPrice;
    						var remoteMinPrice = remotePrice;
    						var remotePrice = scratchval;
    					}
    					if (unitMaxPrice < unitMinPrice) {
    						var scratchval = unitMaxPrice;
    						var unitMaxPrice = unitMinPrice;
    						var unitMinPrice = scratchval;
    					}
    					if (unitPrice < unitMinPrice) unitMinPrice = unitPrice;
    					if (unitPrice > unitMaxPrice) unitMaxPrice = unitPrice;
    				}
    			}
    		} while ((remotePrice - unitMinPrice) < Math.max(1, player.contractReputationPrecise * 0.5 - system.mainStation.market[commodity].quantityUnit * 3) && attempts < 10); // Allows Gold more often!
    		if (attempts > 9) continue; // failed to find a good commodity.
    
    		var cargo = new Object;
    		cargo.commodity = commodity;
    
    		var amount = 0;
    		var unitsize = 1;
    		// larger unit sizes for kg/g commodities
    		if (system.mainStation.market[commodity].quantityUnit === 1) {
    			unitsize += Math.ceil((Math.random() + Math.random() + Math.random()) * routeToDestination.route.length); // Adds more if routeToDestination.route.length > 5
    		} else if (system.mainStation.market[commodity].quantityUnit === 2) {
    			unitsize += Math.ceil((Math.random() + Math.random() + Math.random()) * routeToDestination.route.length * 2);
    		}
    		amount += Math.max(Math.ceil((1 + Math.random() * 32) * (1 + Math.random() * 16)), Math.floor(30 + Math.random() * 6)) * unitsize; // forced minimum amount to always be at least 30-35. Eliminates while-looping here!
    
    		if (amount > 125 && amount > player.ship.cargoSpaceCapacity && player.contractReputationPrecise >= 0 && system.mainStation.market[commodity].quantityUnit === 0) { // reduce the number of contracts only suitable for Anacondas
    			amount = Math.floor(amount / Math.floor(1 + (Math.random() * 4)));
    		}
    
    		cargo.size = amount;
    
    		var localValue = Math.floor(unitPrice * amount); // Total cost at Current/Original Price
    		var remoteValue = Math.floor(remotePrice * amount); // Remote max value
    		var profitMax = remoteValue - Math.floor(unitMinPrice * amount);
    
    		// discount adjustment to prices. Higher reputation and bulk quantities should make this more profitable
    		var discount = Number((Math.ceil(Math.max(50, Math.min(100, 20 + player.contractReputationPrecise * 10 + routeToDestination.route.length * 10 + amount * 0.1 + 50000 / profitMax - player.bounty * 0.1 - destinationInfo.government - system.government))) * 0.01).toFixed(2));
    		var profit = Math.ceil(profitMax * discount); // Uses discount directly on max profit to determine profit.
    		//		if (profit < 100 + player.contractReputationPrecise*100 || profit > profitMax) profit = profitMax;	// Ensures higher profits if payout would otherwise be too low.
    
    		// Formulas to generate ORIGINAL PROFIT AMOUNTS!
    		/*
    				var discount = Math.min(10+Math.floor(amount/10),35);
    				var unitPrice = system.mainStation.market[commodity].price * (100-discount) / 1000;
    				var localValue = Math.floor(unitPrice * amount);
    				remotePrice = remotePrice * (200+discount) / 200;
    				var remoteValue = Math.floor(remotePrice * amount);
    				var profit = remoteValue-localValue;
    		*/
    		var scratchval = this._priceForCommodity(system.mainStation.market[commodity], destinationInfo.economy, Math.floor(Math.random() * 256)); // random prices!
    		//		var scratchval = remotePrice; // max selling price!
    		var discount2 = Number(Math.min(0.1 + Math.floor(amount * 0.1) * 0.01, 0.35).toFixed(2));
    		var profitORG = Math.floor(scratchval * (1 + 0.5 * discount2) * amount) - Math.floor(unitPrice * (1 - discount2) * amount); // Original results for profit!
    		var profitORGmax = Math.floor(remotePrice * (1 + 0.5 * discount2) * amount) - Math.floor(unitMinPrice * (1 - discount2) * amount); // Theoretical MAX Original results for profit!
    
    		// higher share for transporter for longer routes, less safe systems, and Higher reputation. lower share for high bounty! 100 bounty = 10% share loss!
    		var share = 100 + destinationInfo.government - (10 * routeToDestination.route.length);
    		if (share < 10) share = 10;
    		if (share > 100) share = 100;
    		share = 100 - share;
    		var profitORGmax = Math.floor(profitORGmax * share * 0.01);
    
    		// safety: now multiply the fee by 2 compared with 1.76 contracts
    		// prevents exploit discovered by Mad Hollander at
    		// http://aegidian.org/bb/viewtopic.php?p=188127
    		//	localValue *= 2;	// This amount is so high in the event of a failed contract that it's practically PUNATIVE PUNISHMENT for those doing contracts.
    		// this may need to be raised further
    
    		// fee is total selling price.
    		// This IF-ELSE makes the cargo deposit equal or greater than the current selling value for the commodity.
    		if (localValue > remoteValue - profit) {
    			var fee = Math.ceil((localValue + profit) * 0.1) * 10; // round to nearest 10 credits
    		} else {
    			var fee = Math.ceil(remoteValue * 0.1) * 10; // round to nearest 10 credits
    		}
    		cargo.payment = fee;
    		cargo.deposit = Math.ceil((fee - profit) * 0.1) * 10; // round to nearest 10 credits
    
    		// logs nearly everything
    		log(this.name,
    			"Rep=" + player.contractReputationPrecise +
    			", " + commodity +
    			" size=" + cargo.size +
    			" econ=" + system.economy +
    			" DestEcon=" + destinationInfo.economy +
    			" jumps=" + routeToDestination.route.length +
    			" unitPrice=" + unitPrice +
    			" MinP=" + unitMinPrice +
    			" MaxP=" + unitMaxPrice +
    			" remMinP=" + remoteMinPrice +
    			" remP=" + remotePrice +
    			" locVal=" + localValue +
    			" deposit=" + cargo.deposit +
    			" profit=" + profit +
    			" pMax=" + profitMax +
    			" *" + discount +
    			" pORG=" + profitORG +
    			" pORGmax=" + profitORGmax +
    			" share=" + share +
    			" discount=" + discount2 +
    			" DestVal=" + remoteValue +
    			" Total=" + fee
    		);
    
    		cargo.destination = destination;
    		// we'll need this again later, and route calculation is slow
    		cargo.route = routeToDestination;
    
    		// time allowed for delivery is time taken by "fewest jumps"
    		// route, plus timer above. Higher reputation makes longer
    		// times available.
    		cargo.deadline = clock.adjustedSeconds + Math.floor(daysUntilDeparture * 86400) + (cargo.route.time * 3600);
    
    		// add parcel to contract list
    		if (cargo.deposit < cargo.payment) this._addCargoContractToSystem(cargo);
    	}
    }
    Scripts/smugglers_phasescanner.js
    "use strict";
    this.name = "Smugglers_PhaseScanner";
    this.author = "phkb";
    this.description = "Looks after the new phase scanning equipment activation.";
    this.licence = "CC BY-NC-SA 3.0";
    
    this._scanCount = 0; // percentage of scan completed
    this._speedCount = 0; // counter for when player ship speed drops below 100 m/s.
    this._scanTimer = null; // timer to perform scan
    this._energy = null; // timer to deduct energy
    this._energyReduction = 4; // amount of energy to deduct each quarter second
    
    //-------------------------------------------------------------------------------------------------------------
    this.activated = function () {
    
    	var core = worldScripts.Smugglers_CoreFunctions;
    	this.$rand = core.$rand;
    
    	var se = worldScripts.Smugglers_Equipment;
    
    	if (this._scanTimer && this._scanTimer.isRunning) {
    		this._scanTimer.stop();
    		this._energy.stop();
    		player.consoleMessage("Scanning stopped.");
    		se.$playSound("stop");
    		return;
    	}
    
    	var p = player.ship;
    
    	// is the equipment OK?
    	if (p.equipmentStatus("EQ_PHASE_SCANNER") != "EQUIPMENT_OK") {
    		se.$playSound("stop");
    		return;
    	}
    
    	// do we have a station target and is it a main station?
    	if (p.target != system.mainStation) {
    		player.consoleMessage("Scanner will only work when targeting main stations.");
    		se.$playSound("stop");
    		return;
    	}
    	if (p.isCloaked) {
    		player.consoleMessage("Scanner will not work when cloaked.");
    		se.$playSound("stop");
    		return;
    	}
    
    	player.consoleMessage("Scanning started.");
    
    	this._scanCount = 0;
    	this._speedCount = 0;
    	if (this._scanTimer && this._scanTimer.isRunning) this._scanTimer.stop();
    	this._scanTimer = new Timer(this, this.$scanProcess, 3, 3);
    	if (this._energy && this._energy.isRunning) this._energy.stop();
    	this._energy = new Timer(this, this.$depleteEnergy, 0.25, 0.25);
    
    	se.$playSound("activate");
    
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // find all police or main stations within 15500km of of ship
    this.$findLawVessels = function $findLawVessels(npc) {
    	function _ships(entity) {
    		return entity.isShip && entity.isPolice && !entity.isPlayer && !entity.isStation && !entity.isMainStation;
    	}
    	return system.filteredEntities(this, _ships, npc, npc.scannerRange * 0.6);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$scanProcess = function $scanProcess() {
    
    	var p = player.ship;
    
    	// do we have a station target and is it a main station?
    	if (p.target != system.mainStation) {
    		player.consoleMessage("Scanner will only work when targeting main stations.");
    		this._scanTimer.stop();
    		this._energy.stop();
    		return;
    	}
    
    	if (p.isCloaked) {
    		player.consoleMessage("Scanner will not work when cloaked.");
    		this._scanTimer.stop();
    		this._energy.stop();
    		return;
    	}
    
    	var se = worldScripts.Smugglers_Equipment;
    
    	// how far away are we?
    	var penalty = 0;
    	var msg = "";
    	var dist = p.position.distanceTo(p.target);
    	if (dist < (p.scannerRange * 0.78625) && se._phaseScannerDetected === false) {
    		// too close
    		msg = expandDescription("[illegal-scan-detected-station]");
    		p.target.commsMessage(msg, p);
    		penalty = this.$rand(20) + 10;
    		p.setBounty(player.bounty + penalty, "seen by police");
    		this.$sendEmail(penalty);
    		se._phaseScannerDetected = true;
    	}
    	// is there any police vessel in range?
    	var police = this.$findLawVessels(p);
    	if (police.length > 0 && se._phaseScannerDetected === false) {
    		msg = expandDescription("[illegal-scan-detected-police]");
    		penalty = this.$rand(20) + 10;
    		p.setBounty(player.bounty + penalty, "seen by police");
    		this.$sendEmail(penalty);
    		police[0].commsMessage(msg, p);
    		se._phaseScannerDetected = true;
    	}
    	// are we too far away for a good scan?
    	if (dist > (p.scannerRange * 0.90242)) {
    		return;
    	}
    
    	if (p.speed < 100 && this._speedCount > 20) {
    		// too slow for too long
    		msg = expandDescription("[illegal-scan-detected-station]");
    		p.target.commsMessage(msg, p);
    		penalty = this.$rand(20) + 10;
    		p.setBounty(player.bounty + penalty, "seen by police");
    		this.$sendEmail(penalty);
    		se._phaseScannerDetected = true;
    	}
    
    	// ok, we're close enough, so increase the percentage
    	this._scanCount += 1;
    	player.consoleMessage("Scanning " + (this._scanCount * 10) + "% complete", 3);
    	if (this._scanCount >= 10) {
    		var phase = se.$getSystemPhase(system.ID);
    		player.consoleMessage("Scan complete. Best phase for station is " + phase + " mHz", 6);
    		// add it to our discovered list
    		se.$addPhaseScan(phase, system.ID, 1);
    		// stop the timer and reset the count
    		this._scanTimer.stop();
    		this._energy.stop();
    		this._scanCount = 0;
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$depleteEnergy = function $depleteEnergy() {
    	var p = player.ship;
    	p.energy -= this._energyReduction;
    	if (p.energy < 64) {
    		this._scanTimer.stop();
    		this._energy.stop();
    		player.consoleMessage("Insufficient energy to continue scanning.");
    	}
    	if (p.speed < 100) {
    		this._speedCount += 1;
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$sendEmail = function $sendEmail(penalty) {
    	var email = worldScripts.EmailSystem;
    	if (email) {
    		var ga = worldScripts.GalCopAdminServices;
    		if (ga._galCopBountyOfficer === "") ga._galCopBountyOfficer = expandDescription("%N [nom]");
    
    		email.$createEmail({
    			sender: expandDescription("[galcop-bounty-sender]"),
    			subject: "Bounty applied",
    			date: clock.seconds,
    			message: expandMissionText("galcop-bounty-applied", {
    				amount: penalty,
    				systemname: system.info.name,
    				sender: ga._galCopBountyOfficer
    			}),
    			expiryDays: ga._defaultExpiryDays
    		});
    	}
    }
    Scripts/smugglers_tradeembargo.js
    "use strict";
    this.name = "Smugglers_TradeEmbargo";
    this.author = "phkb";
    this.description = "Looks after the process of applying trade embargoes to systems.";
    this.licence = "CC BY-NC-SA 3.0";
    
    /* TODO:
    - work out how to actually implement a trade embargo
    	- idea: work out how to remove a planet from the galactic map
    	- then get special device that will override embargo witchspace blockage 
    	- witchpoint will be filled with police, arriving will instantly give player a bounty
    - Create routine to add trade embargos
    - Create more reasons to the list.
    */
    
    this._embargoReasons = [{
    		govTypes: "0",
    		name: "",
    		period: 60,
    		description: "GalCop is trying to get the system to crack down on pirates, but they have been unwilling. A trade embargo has been put in place to try to make them reconsider."
    	},
    	{
    		govTypes: "7",
    		name: "",
    		period: 60,
    		description: "The system has been attempting to subvert GalCop policies by working outside and around GalCop law. GalCop has imposed a trade embargo to bring them back into line."
    	}
    ];
    
    this._core = null; // link to core functions script
    
    //-------------------------------------------------------------------------------------------------------------
    this.startUpComplete = function () {
    	this._core = worldScripts.Smugglers_CoreFunctions;
    }