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

Expansion GalCop Most Wanted

Content

Warnings

  1. http://wiki.alioth.net/index.php/GalCop%20Most%20Wanted -> 404 Not Found
  2. Information URL mismatch between OXP Manifest and Expansion Manager string length at character position 0
  3. Unresolved dependency reference to oolite.oxp.phkb.StationDockControl:1.2.2
  4. Unresolved dependency reference to oolite.oxp.phkb.StationDockControl:1.2.2

Manifest

from Expansion Manager's OXP list from Expansion Manifest
Description Maintains a list of GalCop's \"Most Wanted\" criminals, tracks their journeys and spawned ships at appropriate times. Maintains a list of GalCop's \"Most Wanted\" criminals, tracks their journeys and spawned ships at appropriate times.
Identifier oolite.oxp.phkb.GalCopMostWanted oolite.oxp.phkb.GalCopMostWanted
Title GalCop Most Wanted GalCop Most Wanted
Category Missions Missions
Author phkb phkb
Version 0.16 0.16
Tags
Required Oolite Version
Maximum Oolite Version
Required Expansions
  • oolite.oxp.phkb.BountySystem:0.4.4
  • oolite.oxp.Svengali.GNN:1.0
  • oolite.oxp.phkb.BountySystem:0.4.4
  • oolite.oxp.Svengali.GNN:1.0
  • Optional Expansions
  • oolite.oxp.phkb.Smugglers_TGU:1.2.2
  • oolite.oxp.phkb.StationDockControl:1.2.2
  • oolite.oxp.phkb.Smugglers_TGU:1.2.2
  • oolite.oxp.phkb.StationDockControl:1.2.2
  • Conflict Expansions
    Information URL n/a
    Download URL https://wiki.alioth.net/img_auth.php/9/92/GalCopMostWanted.oxz n/a
    License CC-BY-NC-SA 4.0 CC-BY-NC-SA 4.0
    File Size n/a
    Upload date 1613087865

    Documentation

    readme.txt

    Bounty System Addon - GalCop's Most Wanted
    By Nick Rogers
    
    Overview
    ========
    GalCop keeps a record of all offences committed by pilots throughout the eight charts. Sometimes pilots try to escape from their bounty by running. GalCop doesn't have the manpower to track down each and every pilot who tries to flee their legal problems, so instead GalCop regularly publishes a "Most Wanted" list. The "Most Wanted" list is available through the GalCop Security Office, accessible from the system's main station, or through the GalCop Security Kiosk, which is accessible from other GalCop-aligned stations.
    
    This list contains the most recent updates concerning the criminals in question: their last known location, their departure time and destination (when they had docked at a GalCop-aligned station), and the type and name of their ship. When data is available, the last three locations the criminal was seen will also be displayed(*), enabling pilots to track down where the criminal might be headed. When viewing the map to the last location, each of the last seen locations will be marked with a white or gray box. If GalCop doesn't have any location information concerning the pilot, this means the pilot is avoiding docking at main stations, so any tracking information is unavailable.
    
    Pilots can choose to show particular records on their manifest by selecting the "Add to manifest list" option(*) when viewing the details of the criminal. This can assist commanders who plan on tracking down a particular felon, as it allows details to be viewed during flight. You can remove the item from your manifest by selected the "Remove from manifest list" option.
    
    If you suspect the criminal is due to arrive within a small time frame, you can select the "Wait for time period" option(*), which allows you to wait for anywhere between 1 and 12 hours. Any updates to criminal tracking information will then be displayed.
    
    When a pilot tracks down a criminal from the most wanted list, they must use the Warrant Scanner to make the criminals offences visible in the current system. Destroying a criminal without performing this task will mean the whole bounty amount will not be paid to the pilot.
    
    (*) Premium features only available if you have purchased a "Bounty Hunters Licence" (see below).
    
    Bounty Bonus
    ------------
    GalCop appreciates the effort and danger involved in tracking down and destroying pilots on the most wanted list. To encourage pilots, GalCop will pay pilots a bonus amount for each pilot taken off the list. You can see the details for your claims by going to the "Bounty bonus claims" menu in GalCop Security Office screen. This bonus amount can only be claimed at the GalCop HQ system in your sector.
    
    If you travel to a new sector, the bonus amounts will remain - you can still claim them in other sectors.
    
    Bounty Hunters Licence
    -----------------------
    Prospective bounty hunters are advised to purchase a "Bounty Hunters Licence" from a GalCop-aligned station in any Communist TL11 system or higher. This licence can only be issued to "clean" pilots, but by having it you will get access to additional information relating to wanted pilots that will greatly assist in tracking them down.
    
    If your legal status becomes "Offender" or "Fugitive" while holding a Bounty Hunters Licence, and you dock at a GalCop-aligned station in a TL10 Confederate, Democratic or Corporate State system, your licence will be revoked.
    
    Your licence will expire each month, but a renewal can be purchased for half the licence cost when you are within 5 days of your licence expiry.
    
    Bounty Hunters Noticeboard
    --------------------------
    Once you have your licence, GalCop provides a noticeboard service within the Security Office menu, where tips and information can be shared by other bounty hunters. The reliability of some of these messages cannot be verified, however, so hunters should be careful in making decisions from this data.
    
    Required OXP's
    ==============
    - Bounty System
        Core changes to the bounty system are handled within this OXP.
    
    - GNN (v1.0)
    	Provides text randomisation system used for tip descriptions and ship names.
    
    Recommended OXP's
    =================
    It is highly recommended to install the "GalCop Galactic Registry" OXP, which can help with tracking criminals along spacelanes, or between safe/dangerous systems.
    
    The "Station Dock Control" OXP can help pilots by allowing them to see what ships are currently docked at stations. Additionally, the "Smugglers - The Galactic Underworld" OXP implements a "Dock master" F4 screen, which, when combined with "Station Dock Control", allows players to unlock the destination of any "most wanted" felon currently docked at the main station.
    
    License
    =======
    This work is licensed under the Creative Commons Attribution-Noncommercial-Share Alike 4.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/
    
    Clock image from http://simpleicon.com/clock_3.html
    Warning image from http://simpleicon.com/warning-3.html.
    Badge image based on http://simpleicon.com/star.html
    
    Discussion
    ==========
    This OXP is discussed at this forum link: http://aegidian.org/bb/viewtopic.php?f=4&t=17861
    
    Version History
    ===============
    0.16
    - Switched to using GNN for PhraseGen, making GNN a requirement.
    
    0.15
    - Fixed bug that would occur when showing manifest entries for targets who cannot be reached with normal jumps.
    
    0.14
    - Dealt with an edge case for courier bounties when the destination system is not found in any of the available, pre-planned courier routes.
    - Better data clean up when entering new galaxy.
    
    0.13
    - Made more compatible with OXP's that change the system.concealment property.
    - Improvements to ship group generation code.
    - Protection for ship name generation process, in case randomshipnames doesn't return a ship name when asked.
    
    0.12
    - Tweaks to role allocations.
    - Most Wanted data is now updated whenever the F5F5 Manifest page is displayed.
    
    0.11
    - Better protection for when Station Dock Control does not have any data for the current system yet.
    
    0.10
    - Added better logic for handling escort ship creation, particularly in the case when the creation process fails.
    - Added extra "bounty" ship versions.
    
    0.9
    - Moved first possible access of PhraseGen to startUpComplete to prevent startup sequencing issues.
    - Fixed incorrect text for defining route mode calculation (has "OPTIMIZED_FOR_" instead of "OPTIMIZED_BY_").
    - Added protection against condition where no route can be calculated between a character's current and destination systems.
    - Bug fixes.
    
    0.8
    - "autoWeapons" setting now honoured when creating ships.
    
    0.7
    - If a target leaves their ship via an escape pod, the large bounty will go to the escape pod with the pilot, and the abandoned ship will be left with a normal size bounty. Also, the bounty record will be removed at this time, and not depend on the destruction of the ship.
    - Bug fixes.
    
    0.6
    - Fixed route time calculations that were not taking changing destinations into account when doing multiple calculations.
    - Fixed issue where bounties flagged as pending were not coming out of pending if a large time-skip occurs (eg when the player ejects).
    - Fixed issue where bounties flagged as due to arrive at the witchpoint were not spawning if the witchpoint beacon has been destroyed or replaced via GRS Buoy repair.
    
    0.5
    - Small tweaks for ease of integration with other OXP's.
    
    0.4
    - Fixes for errors that occur if SDC is not installed.
    - Fix for error that could occur when a bounty that is moving between safe/dangerous systems is destroyed.
    
    0.3
    - Ships that have spawning restrictions are now excluded from being used as bounty ships.
    - Really fixed incorrect link to PhraseGen tool.
    
    0.2
    - Fixed incorrect link to PhraseGen tool.
    - Fixed typo in function call reference.
    
    0.1
    - Initial release.
    

    Equipment

    Name Visible Cost [deci-credits] Tech-Level
    Bounty Hunters Licence yes 8000 11+
    Bounty Hunters Licence Renewal no 4000 9+

    Ships

    Name
    bounty_PAG_monitor
    bounty_PAG_monitor2
    bounty_PAG_monitor2-player
    bounty_anaconda
    bounty_anaconda-lasercannon
    bounty_anaconda-lasercannon2
    bounty_anaconda-mms
    bounty_anaconda-pirate-lasercannon
    bounty_anaconda-pirate-lasercannon2
    bounty_anaconda_ptt
    bounty_aquatics_conger
    bounty_aquatics_fugu
    bounty_aquatics_manOWar
    bounty_arafura
    bounty_arafura_ind
    bounty_arafura_s1
    bounty_arafura_s1b
    bounty_arafura_s5a
    bounty_att1
    bounty_b_neoship_python-cruiser
    bounty_boa
    bounty_boa-clipper
    bounty_boa-cruiser-mms
    bounty_boa-dualcannon-mms
    bounty_boa-lasercannon
    bounty_boa-militcannon-mms
    bounty_boa-mk2
    bounty_boa-mk2-lasercannon
    bounty_boa-mk2-pirate-lasercannon
    bounty_boa-mk2-pirate-lasercannon2
    bounty_boa-mk2_ptt
    bounty_boa-mms
    bounty_boa-pirate
    bounty_boa-pirate-lasercannon
    bounty_boa-pirate-lasercannon2
    bounty_boa-sg
    bounty_boa3_ptt
    bounty_boa4_ptt
    bounty_boa_ptt
    bounty_classic_anaconda
    bounty_classic_anaconda-C
    bounty_classic_anaconda-pirate
    bounty_classic_boa
    bounty_classic_boa-C
    bounty_classic_boa-D
    bounty_classic_boa-mk2
    bounty_classic_boa-mk2-C
    bounty_classic_boa-mk2-pirate
    bounty_classic_boa-pirate
    bounty_classic_python
    bounty_classic_python-E
    bounty_classic_python-blackdog
    bounty_classic_python-cc
    bounty_classic_python-cc_pirate
    bounty_classic_python-et
    bounty_classic_python-et_pirate
    bounty_classic_python-trader
    bounty_classic_python_D
    bounty_convoys-adder
    bounty_convoys-anaconda
    bounty_convoys-anaconda-admiral
    bounty_convoys-anaconda-pirate
    bounty_convoys-asp
    bounty_convoys-asp-pirate
    bounty_convoys-boa
    bounty_convoys-boa-mk2
    bounty_convoys-boa-mk2-pirate
    bounty_convoys-boa-pirate
    bounty_convoys-cobra3
    bounty_convoys-cobra3-pirate
    bounty_convoys-ferdelance
    bounty_convoys-ferdelance-pirate
    bounty_convoys-gecko-pirate
    bounty_convoys-krait
    bounty_convoys-krait-pirate
    bounty_convoys-moray
    bounty_convoys-moray-morayMED
    bounty_convoys-moray-pirate
    bounty_convoys-python
    bounty_convoys-python-blackdog
    bounty_convoys-python-pirate
    bounty_deepspace_anaconda
    bounty_deepspace_anaconda-pirate
    bounty_deepspace_boa
    bounty_deepspace_boa-mk2
    bounty_deepspace_boa-mk2-pirate
    bounty_deepspace_boa-pirate
    bounty_deepspace_python
    bounty_deepspace_python-blackdog
    bounty_deepspace_python-trader
    bounty_dtt_atlas_cargo
    bounty_ferdelance3G_hardTrader3G+Escorted
    bounty_ferdelance3G_hardTrader3G+EscortedVariant
    bounty_ferdelance3G_hardTraderEscorted
    bounty_ferdelance3G_hardTraderEscortedVariant
    bounty_ferdelance3G_hardTraderEscortedVariant2
    bounty_ferdelance3G_hardestHunterEscorted
    bounty_ferdelance3G_pirateKing
    bounty_freighterconvoys-anaconda
    bounty_freighterconvoys-anaconda2
    bounty_freighterconvoys-boa
    bounty_freighterconvoys-boa-mk2
    bounty_freighterconvoys-cobra3
    bounty_freighterconvoys-miner
    bounty_freighterconvoys-moray-morayMED
    bounty_freighterconvoys-python
    bounty_freighterconvoys-shuttle
    bounty_freighterconvoys-shuttle2
    bounty_freighterconvoys-transporter
    bounty_freighterconvoys-transporter-miner
    bounty_freighterconvoys-worm
    bounty_freighterconvoys-worm-miner
    bounty_griff_anaconda-NPC
    bounty_griff_anaconda_alt-NPC
    bounty_griff_anaconda_alt2-NPC
    bounty_griff_anaconda_pirate-NPC
    bounty_griff_anaconda_pirate_alt-NPC
    bounty_griff_anaconda_pirate_alt2-NPC
    bounty_griff_boa-NPC
    bounty_griff_boa_MK2-NPC
    bounty_griff_boa_MK2_alt-NPC
    bounty_griff_boa_MK2_alt2-NPC
    bounty_griff_boa_MK2_alt3-NPC
    bounty_griff_boa_MK2_pirate-NPC
    bounty_griff_boa_MK2_pirate_alt-NPC
    bounty_griff_boa_MK2_pirate_alt2-NPC
    bounty_griff_boa_alt-NPC
    bounty_griff_boa_alt2-NPC
    bounty_griff_boa_alt3-NPC
    bounty_griff_boa_pirate-NPC
    bounty_griff_boa_pirate_alt-NPC
    bounty_griff_boa_pirate_alt2-NPC
    bounty_griff_cobra_Mk1_alt-NPC
    bounty_griff_moray-NPC
    bounty_griff_prototype_boa-NPC
    bounty_griff_prototype_boa-NPC_Pirate
    bounty_griff_python_alt-NPC
    bounty_griff_python_blackdog-NPC
    bounty_griff_python_blackdog_alt-NPC
    bounty_griff_python_blackdog_alt2-NPC
    bounty_griff_python_blackdog_alt3-NPC
    bounty_griff_python_trader-NPC
    bounty_griff_python_trader_alt-NPC
    bounty_griff_python_trader_alt2-NPC
    bounty_griff_python_trader_alt3-NPC
    bounty_griff_python_trader_alt4-NPC
    bounty_hard-anaconda
    bounty_hard-boa
    bounty_hardpython-pirate
    bounty_himsn_boa_mk2
    bounty_hornet_alt_npc
    bounty_ionics_redback-pirate
    bounty_ionics_redback-trader
    bounty_ionics_redback_mission
    bounty_ionics_rlf_leader
    bounty_kirin-cv
    bounty_kirin-hunter
    bounty_kirin-m
    bounty_kirin-xm
    bounty_neo_anaconda
    bounty_neo_boa
    bounty_neo_boa-mk2
    bounty_neo_python-blackdog
    bounty_neo_python-trader
    bounty_noshaders_lira-trader_normal_clean
    bounty_noshaders_lira-trader_normal_colonial
    bounty_noshaders_lira-trader_normal_green
    bounty_noshaders_lira-trader_normal_grey
    bounty_oolite_template_anaconda
    bounty_oolite_template_anaconda-pirate
    bounty_oolite_template_asp-cloaked
    bounty_oolite_template_boa
    bounty_oolite_template_boa-mk2
    bounty_oolite_template_boa-mk2-pirate
    bounty_oolite_template_boa-pirate
    bounty_oolite_template_python
    bounty_oolite_template_python-blackdog
    bounty_oolite_template_python-trader
    bounty_python
    bounty_python-battlecruiser-mms
    bounty_python-blackdog
    bounty_python-blackdog-lasercannon
    bounty_python-blackdog-lasercannon2
    bounty_python-clipper
    bounty_python-clipper-alternative
    bounty_python-lasercannon
    bounty_python-mms
    bounty_python-sg
    bounty_python-sg-mms
    bounty_python-staer9-mms
    bounty_python-trader
    bounty_python-trader-lasercannon
    bounty_python-trader-lasercannon2
    bounty_python2_ptt
    bounty_python3_ptt
    bounty_python_ptt
    bounty_python_red-mms
    bounty_python_x
    bounty_random_hits_big_boss_cobra4_spacelane
    bounty_random_hits_big_boss_iguana_spacelane
    bounty_random_hits_big_boss_impcourier_spacelane
    bounty_random_hits_big_boss_imptrader_spacelane
    bounty_random_hits_big_boss_pitviper_spacelane
    bounty_random_hits_big_boss_supercobra_spacelane
    bounty_random_hits_big_boss_vamppurg_spacelane
    bounty_random_hits_big_boss_wolfmk2SE_spacelane
    bounty_random_hits_mark_anaconda
    bounty_random_hits_mark_arachnid
    bounty_random_hits_mark_asp
    bounty_random_hits_mark_aspmk1
    bounty_random_hits_mark_aspspec
    bounty_random_hits_mark_boa
    bounty_random_hits_mark_boa2
    bounty_random_hits_mark_bushmaster
    bounty_random_hits_mark_cat
    bounty_random_hits_mark_chameleon
    bounty_random_hits_mark_cobra3
    bounty_random_hits_mark_cobra3courier
    bounty_random_hits_mark_cobra3rapier
    bounty_random_hits_mark_cobra4
    bounty_random_hits_mark_cobras9
    bounty_random_hits_mark_cutlass
    bounty_random_hits_mark_dttmk1
    bounty_random_hits_mark_ferdelance
    bounty_random_hits_mark_galtech_escort_fighter
    bounty_random_hits_mark_ghavial
    bounty_random_hits_mark_gnat
    bounty_random_hits_mark_igu
    bounty_random_hits_mark_imp
    bounty_random_hits_mark_imptrader
    bounty_random_hits_mark_monitor
    bounty_random_hits_mark_monitor2
    bounty_random_hits_mark_mussurana
    bounty_random_hits_mark_pcc
    bounty_random_hits_mark_phaze
    bounty_random_hits_mark_pitviper
    bounty_random_hits_mark_python
    bounty_random_hits_mark_pythonet
    bounty_random_hits_mark_revenge1
    bounty_random_hits_mark_revenge10
    bounty_random_hits_mark_revenge11
    bounty_random_hits_mark_revenge13
    bounty_random_hits_mark_revenge14
    bounty_random_hits_mark_revenge15
    bounty_random_hits_mark_revenge2
    bounty_random_hits_mark_revenge3
    bounty_random_hits_mark_revenge4
    bounty_random_hits_mark_revenge5
    bounty_random_hits_mark_revenge6
    bounty_random_hits_mark_revenge7
    bounty_random_hits_mark_revenge8
    bounty_random_hits_mark_revenge9
    bounty_random_hits_mark_salamander
    bounty_random_hits_mark_supercobra
    bounty_random_hits_mark_vamppurg
    bounty_random_hits_mark_wolf
    bounty_rhs_big_boss_cobra4_spacelane_shipset
    bounty_rhs_big_boss_iguana_spacelane_shipset
    bounty_rhs_big_boss_impcourier_spacelane_shipset
    bounty_rhs_big_boss_imptrader_spacelane_shipset
    bounty_rhs_big_boss_pitviper_spacelane_shipset
    bounty_rhs_big_boss_supercobra_spacelane_shipset
    bounty_rhs_big_boss_vamppurg_spacelane_shipset
    bounty_rhs_big_boss_wolfmk2SE_spacelane_shipset
    bounty_rhs_mark_arachnid_shipset
    bounty_rhs_mark_aspmk1_shipset
    bounty_rhs_mark_cobra3courier_shipset
    bounty_rhs_mark_cobra3rapier_shipset
    bounty_rhs_mark_cobra4_shipset
    bounty_rhs_mark_cobras9_shipset
    bounty_rhs_mark_drake2_shipset
    bounty_rhs_mark_dttmk1_shipset
    bounty_rhs_mark_galtech_escort_fighter_shipset
    bounty_rhs_mark_ghavial_shipset
    bounty_rhs_mark_gnat_shipset
    bounty_rhs_mark_imp_shipset
    bounty_rhs_mark_imptrader_shipset
    bounty_rhs_mark_phaze_shipset
    bounty_rhs_mark_pitviper_shipset
    bounty_rhs_mark_supercobra_shipset
    bounty_rhs_mark_vamppurg_shipset
    bounty_rhs_mark_wolf_shipset
    bounty_sb_neoship_monitor
    bounty_sb_neoship_python-cruiser
    bounty_staer9_monitor
    bounty_staer9_monitor-pirate
    bounty_staer9_monitor2
    bounty_staer9_monitor2-pirate
    bounty_staer9_python-cruiser
    bounty_staer9_python-cruiser-pirate
    bounty_staer9_python-x
    bounty_staer9_python-x-pirate
    bounty_taurockhopper
    bounty_ups-sun-anaconda
    bounty_ups-sun-boa-mk2
    bounty_ups-sun2-anaconda
    bounty_vector
    bounty_vector_benus
    bounty_vector_frood
    bounty_vector_geek
    bounty_vector_millionaire
    bounty_vortex-maelstrom-NPC
    bounty_wildShips_chatu
    bounty_wildShips_chatu_trader
    bounty_wildShips_tembo
    bounty_wildShips_tribalChatu

    Models

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

    Scripts

    Path
    Scripts/bountysystem_licence.js
    "use strict";
    this.name        = "BountySystem_Licence";
    this.author      = "phkb";
    this.copyright   = "2017 phkb";
    this.description = "Condition script for determing when the BH Licence is available";
    this.licence     = "CC BY-NC-SA 4.0";
    
    //-------------------------------------------------------------------------------------------------------------
    this.allowAwardEquipment = function(equipment, ship, context) {
    	if (context === "scripted") return true;
        if (context === "npc") return false;
        // not in dangerous systems
        if (system.government <= 3) return false;
        // not at non-galcop stations
        if (player.ship.dockedStation && player.ship.dockedStation.allegiance !== "galcop") return false;
        // hide if the OXP has been disabled
        var b = worldScripts.BountySystem_MostWanted;
        if (b._disabled === true) return false;
        if (equipment === "EQ_HUNTER_LICENCE_RENEWAL") {
            var days = Math.floor((clock.adjustedSeconds - b._licencePurchaseDate) / 86400);
            if (days > 40) return false; // too long since original purchase
            if (days < 25) return false; // too soon before renewal
        }
        if (equipment === "EQ_HUNTER_LICENCE") {
            var days = Math.floor((clock.adjustedSeconds - b._licencePurchaseDate) / 86400);
            if (days <= 40 && days > 0) return false;
        }
        return true;
    }
    
    Scripts/bountysystem_mostwanted.js
    "use strict";
    this.name        = "BountySystem_MostWanted";
    this.author      = "phkb";
    this.copyright   = "2017 phkb";
    this.description = "Routines for controlling the creation and movement of GalCop's most wanted.";
    this.licence     = "CC BY-NC-SA 4.0";
    
    /*
    	There will probably be a lot of edge cases (where timing would indicate ship should be present but it isn't, or it is present but it shouldn't be)
    	There's a lot of fuzzy logic in determining when to add a ship, to make it actually possible to lie in wait for a ship
    	More info/options may be required on bounty screen to assist in waiting
    
    	Idea: 
    		Snoopers news items about whereabouts of wanted pilots (News flash - Bill Smells was spotted in the Lave system on date...etc)
    		Have a random message be sent from ships arriving at WP along the lines of ("Did you see that ship in X? That's Y'd ship!")
    		Add additional methods of tracking bounties (bribing officials, planting tracking devices)
    		Have ships heading off on search for specialty items, then have notice that "X is searching for Y."
    
    	TODO: 
    		if ship has been created (jumped in), and time jump occurs (eg player ejects) - remove them
    
    		!! what happens if mark jumps in, then player docks and saves game? mark needs to be insta-docked on reload
    		!! add a script to reset escape pod occupants bounty to normal levels (otherwise player could get paid twice)
    		!! test process of waiting outside station for bounty if SDC is not installed
    		need to make sure all bounties will fight rather than just run away (paricularly for traders)
    		make tipsters move around chart, and only let them tip if a bounty is within 15LY
    
    		If no SDC, what happens to escorts that aren't able to dock before the mother ship relaunches?
    			need to check for undocked escorts when mother is relaunched. if found, cancel docking instructions and offer to escort mother
    		If a target is attacked by the player and manages to escape, plot a random path for escape, then return them to the closest 
    			system in a normal plot (ie spacelane, milkrun etc)
    			-- this should be handled by standard AI routines
    		How easy/hard is it to track a target?
    		Work out what happens if target goes through worm hole
    		investigate what would happen if target is docked in sdc, player jumps out and back again, before target has launched.
    			ship won't be pending, so need to update record to reflect it
    		add player to most wanted list if bounty is over 250
    		need better AI for target ships, to make them flee if they're scanned, or if there are a lot of police/hunters around
    			This might be OK, because AI is already getting tweaked by the accuracy setting
    			might be worth investigating how to add hiddenBountyFlee methods to the AI
    	
    	Wanted list item definition:
    			index				record index
    			system				their current system
    			dockTime			the time they docked at "system"
    			currentVisible		indicates their current "system" value is visible (ie they docked at the main station)
    			shipName			the name of their ship
    			shipType			the type of their ship
    			shipDataKey			the datakey of their ship
    			personality			the personality of their ship
    			primaryRole			their primary role
    			bounty				their interstellar bounty
    			escorts				the number of escorts following the pilot
    			escortData
    				escortShipKey		the ship keys for each of the escorts
    				escortShipName      the ship names for each of the escorts
    				escortPersonality	the entityPersonalities for each of the escorts
    				escortPilotName 	the names of each of the pilots
    				escortBounty		the amount of bounty for each escort
    				escortHiddenBounty
    			destinationSystem	their next destination system
    			pilotDescription	pilot info
    			pilotName			..
    			pilotHomeSystem		..
    			pilotLegalStatus	..
    			pilotInsurance		..
    			pilotSpecies		..
    			equipment
    			departureTime
    			lastChange
    			updateChance
    			movement			code to describe how the ship will move (eg, along spacelane, on milk run, etc)
    								1 = milk run
    								2 = spacelane
    								3 = safe/dangerous
    								4 = custom path - used to move ships from one location to another, and for couriers
    */
    
    this._disabled = false;
    this._debug = false;					// controls output of debug messages
    this._milkRuns = [];					// list of milk runs in the current sector
    this._spacelanes = [];					// list of spacelanes in the current sector
    this._safeDangerous = [];				// list of safe/dangerous systems (array of dictionarys, having "id"-int and "dangerous"-array of ints)
    this._courierRoutes = [];				// list of cross-chart systems couriers are likely to be on
    										// array has two elements (0, 1) 0 = top left to bottom right, 1 = bottom left to top right
    										// each element has a dictionary with two keys (point1 and point2)
    										// the value of these keys is an array of system ID's.
    this._customPath = [];					// list of custom paths currently in play
    this._wanted = [];						// full list of wanted pilots (accurate, updated every day)
    this._pending = [];						// array of ship records that may or may not be created/launched in the current system
    this._displayList = [];					// list of pilots for display (subset, only updated every 5 days)
    this._missionList = [];					// array of indexes of most wanted records to show on the manifest screen
    this._completed = [];					// list of criminals destroyed by player for whom they can claim a receipt for
    this._lastUpdate = 0;					// time of last update of the displaylist
    this._refreshDays = 3;					// how often to update the list (default 3)
    this._constantUpdates = true;
    this._randomShips = [];					// array of ship roles, types and data keys
    this._licencePurchaseDate = 0;			// date hunter licence was purchased
    this._menuColor = "orangeColor";
    this._exitColor = "yellowColor";
    this._holdConcealment = [];
    this._unconcealmentActive = false;
    this._rsnInstalled = false;				// indicates whether randomshipnames OXP is installed
    this._sdcInstalled = false;				// indicates whether Station Dock Control OXP is installed
    this._populateCounter = 30;				// counter used to control when the updateMovementData routine is called
    										// set to 30 so that the routine is called on the first repopulate event (about 20 seconds after startup)
    this._timeRange = 7200; 				// time differential (in seconds) for which ship will be considered for adding to a system (3600 = 1 hour)
    this._checkOnArrival = [];				// array of ships to check for on arrival in a new system 
    										// this is to cater for the times when a target jumps out, their wormhole closes, then the player
    										// jumps to the same system and the core game retains the original ship
    this._doLicenceRemoval = false;			// indicates when the player's bounty hunter licence will be removed
    this._renewalDockMessage = false;		// indicates when a licence renewal message has been added to dock reports
    this._hunterTips = [];					// array of current hunter tips
    this._hunterTipSources = [];			// array of tip sources (name, reliability)
    this._controlledRoles = [
    	"trader", "trader-courier", "trader-smuggler",
    	"pirate", "pirate-light-freighter", "pirate-medium-freighter", "pirate-heavy-freighter",
    	"pirate-light-fighter", "pirate-medium-fighter", "pirate-heavy-fighter",
    	"hunter", "hunter-medium", "hunter-heavy", "assassin-light", "assassin-medium", "assassin-heavy",
    	"escort", "escort-medium", "escort-heavy"
    ];
    // ships with these keys will not be used for any MW ship
    this._bountyShipKeyExclusions = ["convoys-adder","convoys-anaconda","convoys-anaconda2","convoys-anaconda3","convoys-anaconda-admiral",
    	"convoys-anaconda-pirate","convoys-asp","convoys-asp-pirate","convoys-boa","convoys-boa-pirate","convoys-boa-mk2","convoys-boa-mk2-pirate",
    	"convoys-cobra3","convoys-cobra3-pirate","convoys-ferdelance","convoys-ferdelance-pirate","convoys-gecko-pirate","convoys-krait",
    	"convoys-krait-pirate","convoys-mamba","convoys-moray","convoys-moray-pirate","convoys-moray-morayMED","convoys-python","convoys-python-team",
    	"convoys-python-pirate","convoys-python-blackdog","convoys-shuttle","convoys-transporter","convoys-transporter-miner","convoys-worm",
    	"convoys-worm-miner","freighterconvoys-anaconda","freighterconvoys-anaconda2","freighterconvoys-anaconda3","freighterconvoys-anaconda4",
    	"freighterconvoys-boa","freighterconvoys-boa-mk2","freighterconvoys-cobra3","freighterconvoys-moray","freighterconvoys-morayMED",
    	"freighterconvoys-python","freighterconvoys-python-team","freighterconvoys-python-trader","freighterconvoys-shuttle","freighterconvoys-shuttle2",
    	"freighterconvoys-transporter","freighterconvoys-transporter-miner","freighterconvoys-worm","freighterconvoys-worm-miner","freighterconvoys-miner"
    ];
    
    //-------------------------------------------------------------------------------------------------------------
    this.startUp = function() {
    	if (this._disabled === true) {
    		if (missionVariables.BountySystem_Wanted) delete missionVariables.BountySystem_Wanted;
    		if (missionVariables.BountySystem_LastUpdate) delete missionVariables.BountySystem_LastUpdate;
    		if (missionVariables.BountySystem_Pending) delete missionVariables.BountySystem_Pending;
    		if (missionVariables.BountySystem_DisplayList) delete missionVariables.BountySystem_DisplayList;
    		if (missionVariables.BountySystem_Completed) delete missionVariables.BountySystem_Completed;
    		if (missionVariables.BountySystem_Tips) delete missionVariables.BountySystem_Tips;
    		if (missionVariables.BountySystem_MissionList) delete missionVariables.BountySystem_MissionList;
    		delete this.startUpComplete;
    		delete this.systemWillPopulate;
    		delete this.systemWillRepopulate;
    		delete this.playerWillSaveGame;
    		delete this.playerEnteredNewGalaxy;
    		delete this.playerBoughtEquipment;
    		delete this.playerBoughtNewShip;
    		delete this.shipLaunchedFromStation;
    		delete this.shipWillDockWithStation;
    		delete this.shipDockedWithStation;
    		delete this.shipExitedWitchspace;
    		delete this.dayChanged;
    		delete this.missionScreenEnded;
    		delete this.missionScreenOpportunity;
    		delete this.guiScreenWillChange;
    		delete this.reportScreenEnded;
    		delete this.startUp;
    		return;
    	}
    
    	if (this._debug) this._refreshDays = 0;
    
    	// load up list of wanted people
    	if (missionVariables.BountySystem_Wanted) {
    		this._wanted = JSON.parse(missionVariables.BountySystem_Wanted);
    		delete missionVariables.BountySystem_Wanted;
    		for (var i = 0; i < this._wanted.length; i++) {
    			var wanted = this._wanted[i];
    			if (wanted.routeMode === "OPTIMIZED_FOR_JUMPS") wanted.routeMode = "OPTIMIZED_BY_JUMPS";
    			if (wanted.routeMode === "OPTIMIZED_FOR_TIME") wanted.routeMode = "OPTIMIZED_BY_TIME";
    			// add any missing shipname data
    			if (wanted.shipName === "") wanted.shipName = this.$getRandomShipName(wanted.primaryRole);
    			for (var j = 0; j < wanted.escortData.length; j++) {
    				if (wanted.escortData[j].shipName === "") wanted.escortData[j].shipName = this.$getRandomShipName(wanted.escortsRole);
    			}
    		}
    	}
    	if (missionVariables.BountySystem_DisplayList) {
    		this._displayList = JSON.parse(missionVariables.BountySystem_DisplayList);
    		delete missionVariables.BountySystem_DisplayList;
    	}
    	if (missionVariables.BountySystem_LastUpdate) this._lastUpdate = missionVariables.BountySystem_LastUpdate;
    	if (missionVariables.BountySystem_Pending) {
    		this._pending = JSON.parse(missionVariables.BountySystem_Pending);
    		delete missionVariables.BountySystem_Pending;
    		// add time property if missing
    		if (this._pending.length > 0) {
    			for (var i = 0; i < this._pending.length; i++) {
    				if (this._pending[i].hasOwnProperty("time") === false) {
    					var dta = JSON.parse(this._pending[i].data);
    					this._pending[i].time = dta.departureTime;
    				}
    			}
    		}
    	}
    	if (missionVariables.BountySystem_CustomPath) {
    		this._customPath = JSON.parse(missionVariables.BountySystem_CustomPath);
    		delete missionVariables.BountySystem_CustomPath;
    	}
    	if (missionVariables.BountySystem_MissionList) {
    		this._missionList = JSON.parse(missionVariables.BountySystem_MissionList);
    		delete missionVariables.BountySystem_MissionList;
    	}
    	if (missionVariables.BountySystem_Completed) {
    		this._completed = JSON.parse(missionVariables.BountySystem_Completed);
    		delete missionVariables.BountySystem_Completed;
    	}
    	if (missionVariables.BountySystem_LicencePurchased) {
    		this._licencePurchaseDate = missionVariables.BountySystem_LicencePurchased;
    	}
    	if (this._licencePurchaseDate === 0 && player.ship.equipmentStatus("EQ_HUNTER_LICENCE") === "EQUIPMENT_OK") {
    		this._licencePurchaseDate = clock.adjustedSeconds;
    	}
    	if (missionVariables.BountySystem_TipSources) {
    		this._hunterTipSources = JSON.parse(missionVariables.BountySystem_TipSources);
    		delete missionVariables.BountySystem_Wanted;
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.startUpComplete = function() {
    	if (worldScripts["randomshipnames"]) this._rsnInstalled = true;
    	if (worldScripts.StationDockControl) {
    		this._sdcInstalled = true;
    		worldScripts.StationDockControl._propertiesToKeep.push("_mostWanted");
    	}
    
    	this.$loadShipNames();
    	this.$getSpacelanes();
    	this.$getMilkRuns();
    	this.$calculateSafeDangerousSystems();
    	this.$calculateCourierRoutes();
    
    	this.$lookForPendingRecords();
    
    	if (this._hunterTipSources.length === 0) this.$createTipSources();
    	if (missionVariables.BountySystem_Tips) {
    		this._hunterTips = JSON.parse(missionVariables.BountySystem_Tips);
    		delete missionVariables.BountySystem_Tips;
    	}
    
    	// if we don't have any records yet, create some data
    	if (this._wanted.length === 0) this.$initialiseData();
    	if (this._hunterTips.length === 0) this.$createTips(false);
    	//if (this._debug) this.$testData();
    
    	// if we have data but no display list, update that as well.
    	if (this._displayList.length === 0) {
    		this.$updateDisplayList();
    	}
    	this.$customPathCleanup();
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.systemWillPopulate = function() {
    	this._loadShipData = true;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.systemWillRepopulate = function() {
    	if (this._loadShipData === true) {
    		this._loadShipData = false;
    		this.$loadShipNames();
    	}
    	// if we have pending ships in the queue, try processing them
    	if (this._pending.length > 0) this.$processPendingShips();
    	// count the number of times we hit the repopulate function (every 20 seconds or so)
    	// after 10 minutes update movement data
    	this._populateCounter += 1;
    	if (this._populateCounter >= 30) { // every ten minutes
    		this._populateCounter = 0;
    		this.$updateMovementData();
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.playerWillSaveGame = function() {
    	missionVariables.BountySystem_Wanted = JSON.stringify(this._wanted);
    	missionVariables.BountySystem_LastUpdate = this._lastUpdate;
    	missionVariables.BountySystem_LicencePurchased = this._licencePurchaseDate;
    	delete missionVariables.BountySystem_Pending;
    	delete missionVariables.BountySystem_DisplayList;
    	delete missionVariables.BountySystem_CustomPath;
    	delete missionVariables.BountySystem_MissionList;
    	delete missionVariables.BountySystem_Completed;
    	delete missionVariables.BountySystem_TipSources;
    	delete missionVariables.BountySystem_Tips;
    	if (this._pending.length > 0) missionVariables.BountySystem_Pending = JSON.stringify(this._pending);
    	if (this._displayList.length > 0) missionVariables.BountySystem_DisplayList = JSON.stringify(this._displayList);
    	if (this._customPath.length > 0) missionVariables.BountySystem_CustomPath = JSON.stringify(this._customPath);
    	if (this._missionList.length > 0) missionVariables.BountySystem_MissionList = JSON.stringify(this._missionList);
    	if (this._completed.length > 0) missionVariables.BountySystem_Completed = JSON.stringify(this._completed);
    	if (this._hunterTipSources.length > 0) missionVariables.BountySystem_TipSources = JSON.stringify(this._hunterTipSources);
    	if (this._hunterTips.length > 0) missionVariables.BountySystem_Tips = JSON.stringify(this._hunterTips);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // large time jumps can occur here...
    this.shipWillDockWithStation = function(station) {
    	this.$updateMovementData();
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // ...and here
    this.playerBoughtEquipment = function(equipment) {
    	this.$updateMovementData();
    	if (equipment === "EQ_HUNTER_LICENCE_RENEWAL") {
    		player.ship.removeEquipment(equipment);
    		player.ship.awardEquipment("EQ_HUNTER_LICENCE");
    		this._licencePurchaseDate = clock.adjustedSeconds;
    	}
    	if (equipment === "EQ_HUNTER_LICENCE") {
    		this._licencePurchaseDate = clock.adjustedSeconds;
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // ...and here
    this.playerBoughtNewShip = function(ship) {
    	this.$updateMovementData();
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // ...and here
    this.reportScreenEnded = this.missionScreenEnded = function() {
    	this.$updateMovementData();
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.playerEnteredNewGalaxy = function(galaxyNumber) {
    	this.$getSpacelanes();
    	this.$getMilkRuns();
    	this.$calculateSafeDangerousSystems();
    	// at the moment the only realistic thing to do is to reset our data array when entering a new galaxy
    	this._wanted.length = 0;
    	this._displayList.length = 0;
    	this._hunterTips.length = 0;
    	this._hunterTipSources.length = 0;
    	this._customPath.length = 0;
    	this.$createTipSources();
    	this._lastUpdate = 0;
    	this.$initialiseData();
    	this.$updateDisplayList();
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.shipLaunchedFromStation = function(station) {
    	if (missionVariables.BountySystem_Wanted) delete missionVariables.BountySystem_Wanted;
    	if (missionVariables.BountySystem_Pending) delete missionVariables.BountySystem_Pending;
    	if (missionVariables.BountySystem_DisplayList) delete missionVariables.BountySystem_DisplayList;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.shipDockedWithStation = function(station) {
    	// if the player has a bounty, and this is a galcop station, and the government/techlevel is reasonably high, and the player has a BH licence
    	if (player.bounty > 0 && station.allegiance === "galcop" && system.government > 4 && system.techLevel > 9 && player.ship.equipmentStatus("EQ_HUNTER_LICENCE") === "EQUIPMENT_OK") {
    		// remove it
    		this._doLicenceRemoval = true;
    	}
    	this._renewalDockMessage = false;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.shipExitedWitchspace = function() {
    	// reset the pending flag
    	for (var i = 0; i < this._wanted.length; i++) {
    		if (this._wanted[i].pending === true) this._wanted[i].pending = false;
    	}
    	this._pending.length = 0;
    	// check for any ships that might have been jumping into this system which we've created already
    	// any that are already here we'll set to "pending" so they will be ignored by the updateMovementData routine
    	this.$checkArrivalList();
    	// check for any updated ship movements
    	this.$updateMovementData();
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.dayChanged = function(newday) {
    	this.$updateMovementData();
    	// possibly add a new item
    	if (Math.random() > 0.9 && this._wanted.length < 30) this.$initialiseData(1);
    	// possibly remove one (simulating a criminal being caught by someone else in the galaxy)
    	if (Math.random() > 0.96 && this._wanted.length > 10) this.$removeItem();
    	// will we be doing an update of the display list?
    	if (this._constantUpdates === false && (clock.adjustedSeconds - this._lastUpdate) / 86400 >= this._refreshDays) this.$updateDisplayList();
    	var lic_exp = (clock.adjustedSeconds - this._licencePurchaseDate) / 86400;
    	if (lic_exp >= 25 && lic_exp < 30 && player.ship.equipmentStatus("EQ_HUNTER_LICENCE") === "EQUIPMENT_OK") {
    		if (player.ship.docked === false && this._renewalDockMessage === false) {
    			// add docking message (if in space)
    			player.addMessageToArrivalReport(expandDescription("[licence-renewal-docknotice]"));
    			this._renewalDockMessage = true;
    		} else {
    			// add console message (if docked)
    			player.consoleMessage(expandDescription("[licence-renewal-docknotice]"));
    		}
    	}
    	if (lic_exp >= 30 && player.ship.equipmentStatus("EQ_HUNTER_LICENCE") === "EQUIPMENT_OK") {
    		player.ship.removeEquipment("EQ_HUNTER_LICENCE");
    		// send email (if installed)
    		var w = worldScripts.EmailSystem;
    		if (w) {
    			var ga = worldScripts.GalCopAdminServices;
    			w.$createEmail({sender:"GalCop Security Office",
    				subject:"Bounty Hunter Licence Expiration Notice",
    				date:global.clock.adjustedSeconds,
    				message:expandDescription("[licence-expired]"),
    				expiryDays:ga._defaultExpiryDays
    			});
    		}
    		if (player.ship.docked === false) {
    			// add docking message (if in space)
    			player.addMessageToArrivalReport(expandDescription("[licence-expiry-docknotice]"));
    		} else {
    			// add console message (if docked)
    			player.consoleMessage(expandDescription("[licence-expiry-docknotice]"));
    		}
    	}
    	this.$createTips();
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.missionScreenOpportunity = function() {
    	if (this._doLicenceRemoval === true) {
    		this._doLicenceRemoval = false;
    		player.ship.removeEquipment("EQ_HUNTER_LICENCE");
    		this._licencePurchaseDate = 0;
    		mission.runScreen({
    			screenID:"oolite-bountysystem-licence-map",
    			title: "Licence Revoked",
    			overlay: {name:"mw-warning.png", height:546},
    			message: expandDescription("[licence-revoked]"),
    			exitScreen: "GUI_SCREEN_STATUS"
    		});
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.guiScreenWillChange = function(to, from) {
    	// force the manifest entries to update
    	// this keeps the "number of hours remaining" value up to date.
    	if (to === "GUI_SCREEN_MANIFEST") {
    		this.$updateManifest();
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$updateMovementData = function $updateMovementData() {
    	// note: do not execute this function from the startUpComplete or startUp events.
    	// some station naming occurs during these events, meaning that the displayName of stations could change,
    	// rendering any newly created pending records unusable.
    
    	this.$revealMap();
    
    	var type99found = false;
    	// run a check on everyone in the list
    	for (var i = 0; i < this._wanted.length; i++) {
    		var wanted = this._wanted[i];
    		// sometimes increase their bounty
    		if (Math.random() > 0.96) {
    			wanted.realBounty += (Math.floor(Math.random() * 30)) + (Math.random() > 0.7 ? (Math.floor(Math.random() * 30)) + (Math.random() > 0.95 ? (Math.floor(Math.random() * 30)) : 0): 0);
    		}
    		if (wanted.pending === false) {
    			if (this._debug) log(this.name, "checking " + wanted.pilotName + " for movement");
    			var active = false;
    			var nova = false;
    			var info = System.infoForSystem(galaxyNumber, wanted.system);
    			var info2 = System.infoForSystem(galaxyNumber, wanted.destinationSystem);
    			var rt = info.routeToSystem(info2, wanted.routeMode);
    			if (!rt) {
    				log(this.name, "!ERROR: No route calculated for wanted character movement: G" + (galaxyNumber + 1) + " - " + wanted.system + " to " + wanted.destinationSystem);
    			}
    			// make a note if either the source or destination are nova systems
    			// we'll prevent any display of ships going to or coming from these systems
    			if (info.sun_gone_nova || info2.sun_gone_nova) nova = true;
    
    			// check if any are noted as being in system
    			if (nova === false && wanted.system === system.ID) {
    				// if so, is their departure time still in the future?
    				if (this._debug) log(this.name, "dock checking: check 1 " + (clock.adjustedSeconds < wanted.departureTime) + ", check 2 " + (clock.adjustedSeconds >= wanted.dockTime));
    				if (clock.adjustedSeconds < wanted.departureTime && clock.adjustedSeconds >= wanted.dockTime) {
    					// if so, they are docked somewhere in the system - the player can lie in wait outside stations
    					active = true;
    					var stn = this.$getDockedStation(wanted);
    					// sdc - check for existing record
    
    					this._pending.push({type:"docked", chance:0, station:stn.displayName, index:wanted.index, shipKey:wanted.shipDataKey, time:wanted.departureTime, data:JSON.stringify(wanted)});
    					if (stn.isMainStation) {
    						if (wanted.lastVisibleSystem != -1) this.$cycleHistory(wanted);
    						wanted.updated = (wanted.departureTime > clock.adjustedSeconds ? clock.adjustedSeconds : wanted.departureTime);
    						wanted.lastVisibleSystem = wanted.system;
    						wanted.lastVisibleDestination = wanted.destinationSystem;
    					}
    					if (this._debug) {
    						log(this.name, "!!NOTE: Setting " + wanted.pilotName + " to be docked at a station " + stn.displayName);
    						log(this.name, "current time " + clock.clockStringForTime(clock.adjustedSeconds));
    						log(this.name, "departure    " + clock.clockStringForTime(wanted.departureTime));
    						log(this.name, "docked       " + clock.clockStringForTime(wanted.dockTime));
    					}
    					
    					wanted.pending = true;
    				} else {
    					// if their departure time is in the past, the player missed them.
    					// continue with normal update process
    				}
    			}
    
    			// check if any are noted as coming to this system
    			if (nova === false && wanted.destinationSystem === system.ID) {
    				var arrival = wanted.departureTime + (rt.time * 3600);
    				var diff = (arrival - clock.adjustedSeconds);
    				if (this._debug) log(this.name, "arrival checking: arrival " + arrival + ", diff " + diff + ", rt.time " + rt.time + ", rt.route.length " + rt.route.length);
    				// if so, is their arrival within -1.5/+1.5 hours of current time?
    				if (diff >= (-3600 * 1.5) && diff < (3600 * 1.5)) {
    					// if so, they are due to arrive soon - set up for their arrival
    					active = true;
    					this._pending.push({type:"witchpoint", chance:0, station:"", index:wanted.index, shipKey:wanted.shipDataKey, time:arrival + (this._timeRange * 4), data:JSON.stringify(wanted)});
    					if (this._debug) {
    						log(this.name, "!!NOTE: Setting " + wanted.pilotName + " to be created at the witchpoint");
    						log(this.name, "current time " + clock.clockStringForTime(clock.adjustedSeconds));
    						log(this.name, "departure    " + clock.clockStringForTime(wanted.departureTime));
    						log(this.name, "route time   " + rt.time + " hrs");
    						log(this.name, "arrival      " + clock.clockStringForTime(arrival));
    					}
    					wanted.pending = true;
    				} else if (diff < (-3600 * 4)) {
    					// if not, they got there ahead of the player, and they're ahead by at least 4 hours, then dock them somewhere in system
    					active = true;
    					var stn = this.$getDockedStation(wanted);
    					this._pending.push({type:"docked", chance:0, station:stn.displayName, index:wanted.index, shipKey:wanted.shipDataKey, time:arrival + (this._timeRange * 4), data:JSON.stringify(wanted)});
    					if (stn.isMainStation) {
    						if (wanted.lastVisibleSystem != -1) this.$cycleHistory(wanted);
    						wanted.updated = (wanted.departureTime > clock.adjustedSeconds ? clock.adjustedSeconds : wanted.departureTime);
    						wanted.lastVisibleSystem = wanted.system;
    						wanted.lastVisibleDestination = wanted.destinationSystem;
    					}
    					if (this._debug) {
    						log(this.name, "!!NOTE: Setting " + wanted.pilotName + " to be docked at a station " + stn.displayName);
    						log(this.name, "current time " + clock.adjustedSeconds);
    						log(this.name, "departure    " + wanted.departureTime);
    						log(this.name, "route time   " + rt.time + " hrs");
    						log(this.name, "arrival      " + clock.clockStringForTime(arrival));
    					}
    					wanted.dockTime = arrival;
    					wanted.departureTime = arrival + (this._timeRange * 4);
    					wanted.pending = true;
    				}
    			}
    			
    			// for all others, run a process of updating records where the departure time is now in the past
    			if (active === false && rt && (wanted.departureTime + (rt.time * 3600)) < clock.adjustedSeconds) {
    				// i've wrapped the movement code in a do/while loop, because there is a chance the movement type will change
    				// during the process, and we want to ensure all ships have been moved along their new courses correctly
    				do {
    					switch (wanted.movement) {
    						case 1: // milk run
    							// keep swapping the ship between the destinations until their departure time is in the future
    							do {
    								// TODO: chance that ship will either (a) switch to a new milk run, or (b) switch to a spacelane
    								if (this._debug) log(this.name, "Moving " + wanted.pilotName + " from " + wanted.system + " to " + wanted.destinationSystem);
    								wanted.dockTime = wanted.departureTime + (rt.time * 3600) + Math.floor(Math.random() * 1800) + 1800;
    								wanted.departureTime = wanted.dockTime + (Math.random() * 21600 + 21600);
    								var hold = wanted.destinationSystem;
    								wanted.destinationSystem = wanted.system;
    								if (nova === false && Math.random() > wanted.updateChance && wanted.dockTime < clock.adjustedSeconds) {
    									if (this._debug) log(this.name, "updating last visible system to " + wanted.system);
    									if (wanted.lastVisibleSystem != -1) this.$cycleHistory(wanted);
    									wanted.lastVisibleSystem = wanted.system;
    									wanted.lastVisibleDestination = wanted.destinationSystem;
    									wanted.updated = (wanted.departureTime < clock.adjustedSeconds ? wanted.departureTime : wanted.dockTime);
    								}
    							} while (wanted.departureTime < clock.adjustedSeconds);
    							break;
    						case 2: // spacelane
    							// TODO: chance that ship will either (a) switch to a new lane, or (b) switch to a milk run
    							var sl = this._spacelanes[wanted.arrayIndex];
    							do {
    								if (this._debug) log(this.name, "Moving " + wanted.pilotName + " from " + wanted.system + " to " + wanted.destinationSystem);
    								var idx1 = sl.indexOf(wanted.system);
    								var idx2 = sl.indexOf(wanted.destinationSystem);
    								// some planets are in lanes twice. 
    								if (Math.abs(idx1 - idx2) !== 1) {
    									// this must be a lane with multiple entries of the same system.
    									if (idx1 !== sl.lastIndexOf(wanted.system)) idx1 = sl.lastIndexOf(wanted.system);
    									if (idx2 !== sl.lastIndexOf(wanted.destinationSystem)) idx2 = sl.lastIndexOf(wanted.destinationSystem);
    									// note: this won't handle the scenario where a system is in the list 3 times, but there aren't any atm
    								}
    								wanted.system = wanted.destinationSystem;
    								if (idx1 > idx2) {
    									var idx3 = idx2 - 1;
    									if (idx2 === 0) idx3 = 1;
    								}
    								if (idx1 < idx2) {
    									var idx3 = idx2 + 1;
    									if (idx2 === (sl.length - 1)) idx3 = idx2 - 1;
    								}
    								// theoretically idx1 should never equal idx2
    								wanted.destinationSystem = sl[idx3];
    
    								wanted.dockTime = wanted.departureTime + (rt.time * 3600) + Math.floor(Math.random() * 1800) + 1800;
    								wanted.departureTime = wanted.dockTime + (Math.random() * 21600 + 21600);
    
    								// recalculate the route for the next interation
    								info = System.infoForSystem(galaxyNumber, wanted.system);
    								info2 = System.infoForSystem(galaxyNumber, wanted.destinationSystem);
    								rt = info.routeToSystem(info2, wanted.routeMode);
    					
    								if (nova === false && Math.random() > wanted.updateChance && wanted.dockTime < clock.adjustedSeconds) {
    									if (this._debug) log(this.name, "updating last visible system to " + wanted.system);
    									if (wanted.lastVisibleSystem != -1) this.$cycleHistory(wanted);
    									wanted.lastVisibleSystem = wanted.system;
    									wanted.lastVisibleDestination = wanted.destinationSystem;
    									wanted.updated = (wanted.departureTime < clock.adjustedSeconds ? wanted.departureTime : wanted.dockTime);
    								}
    							} while (wanted.departureTime < clock.adjustedSeconds);
    							break;
    						case 3: // safe/dangerous pair
    							var sd = this._safeDangerous[wanted.arrayIndex];
    							// only do something if we found a pair
    							if (sd) {
    								do {
    									if (this._debug) log(this.name, "Moving " + wanted.pilotName + " from " + wanted.system + " to " + wanted.destinationSystem);
    									wanted.system = wanted.destinationSystem;
    									if (wanted.destinationSystem === sd.id) {
    										//pick a dangerous system from list
    										wanted.destinationSystem = sd.dangerous[Math.floor(Math.random() * sd.dangerous.length)];
    									} else {
    										if (Math.random() > 0.98) {
    											// plot a path to another safe/dangerous combination
    											var check = 0;
    											var dest = null;
    											do {
    												dest = this._safeDangerous[Math.floor(Math.random() * this._safeDangerous.length)];
    												// make sure this is a new destination, not our current one
    												if (dest.id !== sd.id) {
    													// how far is this
    													var newrt = System.infoForSystem(galaxyNumber, wanted.destinationSystem).routeToSystem(System.infoForSystem(galaxyNumber, dest.id));
    													if (!newrt || newrt.distance > 30) {
    														dest = null;
    													}
    												} else {
    													dest = null;
    												}
    												check += 1;
    											} while (dest == null || check < 5);
    											if (dest) {
    												if (this._debug) log(this.name, "Moving to new destination at system " + dest.id);
    												// lock this in as the new custom path
    												var check = this.$createCustomPath(wanted.destinationSystem, dest.id, wanted.routeMode);
    												if (check !== -1) {
    													// select the first destination on the list
    													wanted.arrayIndex = check;
    													wanted.destinationSystem = this._customPath[wanted.arrayIndex][1];
    													wanted.movement = 4;
    												} else {
    													// invalid route, so revert back to original
    													wanted.destinationSystem = sd.id;
    												}
    											}
    										} else {
    											wanted.destinationSystem = sd.id;
    										}
    									}
    									wanted.dockTime = wanted.departureTime + (rt.time * 3600) + Math.floor(Math.random() * 1800) + 1800;
    									wanted.departureTime = wanted.dockTime + (Math.random() * 21600 + 21600);
    									if (nova === false && Math.random() > wanted.updateChance && wanted.dockTime < clock.adjustedSeconds) {
    										if (this._debug) log(this.name, "updating last visible system to " + wanted.system);
    										if (wanted.lastVisibleSystem != -1) this.$cycleHistory(wanted);
    										wanted.lastVisibleSystem = wanted.system;
    										wanted.lastVisibleDestination = wanted.destinationSystem;
    										wanted.updated = (wanted.departureTime < clock.adjustedSeconds ? wanted.departureTime : wanted.dockTime);
    									}
    
    									// recalculate the route for the next iteration
    									info = System.infoForSystem(galaxyNumber, wanted.system);
    									info2 = System.infoForSystem(galaxyNumber, wanted.destinationSystem);
    									rt = info.routeToSystem(info2, wanted.routeMode);
    
    								} while (wanted.departureTime < clock.adjustedSeconds && wanted.movement === 3);
    							} else {
    								// stop this ship from moving and flag it for deletion
    								wanted.movement = 99;
    							}
    							break;
    						case 4: // custom path, point to point, one way
    							var sl = this._customPath[wanted.arrayIndex];
    							do {
    								if (this._debug) log(this.name, "Moving " + wanted.pilotName + " from " + wanted.system + " to " + wanted.destinationSystem);
    								var idx = sl.indexOf(wanted.destinationSystem);
    								wanted.system = wanted.destinationSystem;
    								if (idx === sl.length - 1) {
    									// this is the end of the line!
    									if (wanted.primaryRole === "trader-courier") {
    										// pick another route from this point
    										// work out which segment we're on
    										var segment = -1;
    										var side = "";
    										var dest = 0;
    										var chk1 = this._courierRoutes[0]["point1"].indexOf(wanted.destinationSystem);
    										var chk2 = this._courierRoutes[0]["point2"].indexOf(wanted.destinationSystem);
    										var chk3 = this._courierRoutes[1]["point1"].indexOf(wanted.destinationSystem);
    										var chk4 = this._courierRoutes[1]["point2"].indexOf(wanted.destinationSystem);
    										if (chk1 >= 0) {segment = 0; side = "point2";}
    										if (chk2 >= 0) {segment = 0; side = "point1";}
    										if (chk3 >= 0) {segment = 1; side = "point2";}
    										if (chk4 >= 0) {segment = 1; side = "point1";}
    										var found = false;
    										var tries = 0;
    										// an edge case is resulting in the dest system not being found on any of the courier routes
    										// so check we have one before doing the loop
    										if (segment === -1) {
    											// create a custom path to the start of a new courier run
    											segment = (Math.random() > 0.5 ? 1 : 0);
    											side = (Math.random() > 0.5 ? "point1" : "point2");
    										}
    										do {
    											var dest = this._courierRoutes[segment][side][Math.floor(Math.random() * this._courierRoutes[segment][side].length)];
    											// create a new custom path
    											var check = -1;
    											if (dest != wanted.system)
    												check = this.$createCustomPath(wanted.system, dest, wanted.routeMode);
    											if (check !== -1) {
    												// select the first destination on the list
    												wanted.arrayIndex = check;
    												wanted.destinationSystem = this._customPath[wanted.arrayIndex][1];
    												wanted.movement = 4;
    												found = true;
    											} else {
    												tries += 1;
    											}
    										} while (found === false && tries < 5);
    										if (found === false) {
    											// shouldn't really happen
    											log(this.name, "!!ERROR: Courier route could not be found from system " + wanted.system + " (" + segment + ", " + side + ")");
    											wanted.movement = 99;
    										}
    									} else {
    										// switch to type 3 again
    										for (var j = 0; j < this._safeDangerous.length; j++) {
    											var sd = this._safeDangerous[j];
    											if (sd.id === sl[idx]) {
    												// clear out the custom path so it can be reused
    												this._customPath[wanted.arrayIndex] = null;
    												if (this._debug) log(this.name, ("Final destination reached - moving back to safe/dangerous around system " + sl[idx]));
    												wanted.arrayIndex = j;
    												wanted.movement = 3;
    												// set a new destination
    												wanted.destinationSystem = sd.dangerous[Math.floor(Math.random() * sd.dangerous.length)];
    												break;
    											}
    										}
    										// this shouldn't ever happen...
    										if (wanted.movement === 4) {
    											log(this.name, "!!ERROR: Safe/dangerous system combo not found for system " + wanted.system);
    											// stop this ship from moving and flag it for deletion
    											wanted.movement = 99;
    										}
    									}
    								} else {
    									// otherwise, just move the ship along the path
    									wanted.destinationSystem = sl[idx + 1];
    								}
    
    								wanted.dockTime = wanted.departureTime + (rt.time * 3600) + Math.floor(Math.random() * 1800) + 1800;
    								wanted.departureTime = wanted.dockTime + (Math.random() * 21600 + 21600);
    								if (nova === false && Math.random() > wanted.updateChance && wanted.dockTime < clock.adjustedSeconds) {
    									if (this._debug) log(this.name, "updating last visible system to " + wanted.system);
    									if (wanted.lastVisibleSystem != -1) this.$cycleHistory(wanted);
    									wanted.lastVisibleSystem = wanted.system;
    									wanted.lastVisibleDestination = wanted.destinationSystem;
    									wanted.updated = (wanted.departureTime < clock.adjustedSeconds ? wanted.departureTime : wanted.dockTime);
    								}
    
    								// recalculate the route for the next iteration
    								info = System.infoForSystem(galaxyNumber, wanted.system);
    								info2 = System.infoForSystem(galaxyNumber, wanted.destinationSystem);
    								rt = info.routeToSystem(info2, wanted.routeMode);
    								
    							} while (wanted.departureTime < clock.adjustedSeconds && wanted.movement === 4);
    							break;
    					}
    				} while (wanted.departureTime < clock.adjustedSeconds && wanted.movement < 99);
    				if (wanted.movement === 99) type99found = true;
    				if (this._debug) this.$writeDataToLog(wanted, "Updating record");
    			}
    		}
    	}
    
    	this.$restoreMap();
    
    	if (type99found === true) {
    		// clean up any type 99 movements (which shouldn't ever happen)
    		for (var i = this._wanted.length - 1; i >= 0; i--) {
    			if (this._wanted[i].movement === 99) {
    				this._wanted.splice(i, 1);
    			}
    		}
    	}
    	if (this._pending.length > 0) this.$createTips(true);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$checkArrivalList = function $checkArrivalList() {
    	for (var i = 0; i < this._checkOnArrival.length; i++) {
    		var check = this._checkOnArrival[i];
    		if (check.isValid) {
    			for (var j = 0; j < this._wanted.length; j++) {
    				var wanted = this._wanted[j];
    				if (check.script._mostWanted === wanted.index) wanted.pending = true;
    			}
    		}
    	}
    	// clear out the array - we won't need it again until the player next jumps
    	this._checkOnArrival.length = 0;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // removes a random entry from the list
    this.$removeItem = function $removeItem(itmIndx) {
    	var idx = -1;
    	var force = false;
    	if (itmIndx && isNaN(itmIndx) === false) {
    		force = true;
    		for (var i = 0; i < this._wanted.length; i++) {
    			if (this._wanted[i].index === itmIndx) {idx = i; break;}
    		}
    	} else {
    		idx = Math.floor(Math.random() * this._wanted.length);
    		itmIndx = this._wanted[idx].index;
    	}
    	if (idx === -1) return;
    	// only remove them if they're not pending (still just data)
    	if (force === true || this._wanted[idx].pending === false) {
    		if (this._debug) this.$writeDataToLog(this._wanted[idx], "Deleting record");
    		// remove ship from the display list
    		for (var i = 0; i < this._displayList.length; i++) {
    			if (this._displayList[i].index === itmIndx) {
    				this._displayList.splice(i, 1);
    				break;
    			}
    		}
    		// remove any tips
    		for (var i = this._hunterTips.length - 1; i >= 0; i--) {
    			if (this._hunterTips[i].index === itmIndx) {
    				this._hunterTips.splice(i, 1);
    			}
    		}
    		var ml = this._missionList.indexOf(itmIndx);
    		if (ml >= 0) {
    			this._missionList.splice(ml, 1);
    			this.$updateManifest();
    		}
    		this._wanted.splice(idx, 1);
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$removePendingItem = function $removePendingItem(idx) {
    	for (var i = 0; i < this._pending.length; i++) {
    		if (this._pending[i].index === idx) {
    			this._pending.splice(i, 1);
    			break;
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // creates new records of ship and pilot data
    this.$initialiseData = function $initialiseData(max) {
    	// work out how many criminals we will be creating
    	var total = parseInt(Math.random() * 30) + 10;
    	if (max && isNaN(max) === false) total = max;
    
    	var role = "";
    	var s_type = 0;
    	for (var i = 0; i < total; i++) {
    		s_type = 0;
    		var d_type = Math.random();
    
    		// work out the type and level of the criminal. higher levels should be rarer but stronger
    		// trader type
    		if (d_type <= 0.3) {
    			s_type = 1;
    			if (Math.random() > 0.5) {
    				s_type = 2;
    				if (Math.random() > 0.8) s_type = 3;
    			} else if(Math.random() > 0.7) {
    				// courier
    				s_type = 11;
    			}
    		}
    		// pirate type
    		if (d_type > 0.3 && d_type <= 0.7) {
    			s_type = 4;
    			if (Math.random() > 0.5) {
    				s_type = 5; 
    				if (Math.random() >= 0.7) {
    					s_type = 6;
    					if (Math.random() > 0.9) s_type = 7;
    				}
    			}
    		}
    		// assassin type
    		if (d_type > 0.7 && d_type <= 0.95) {
    			s_type = 8;
    			if (Math.random() > 0.7) s_type = 9;
    		}
    		// hunter type
    		if (d_type > 0.95) {
    			// this is pretty rare
    			if (Math.random() <= 0.05) s_type = 10;
    		}
    		if (s_type === 0) continue;
    
    		// start setting up our data
    		var dta = {};
    		dta.index = this.$newItemIndex();
    		dta.routeMode = "OPTIMIZED_BY_JUMPS";
    		dta.pending = false;
    		dta.bounty = 0;
    
    		// note: while weapons are being included in the definition, if "autoWeapons" is set on the shipdata,
    		// the weapon definitions in the original data will be used instead of anything set here.
    		switch (s_type) {
    			case 1: // trader
    			case 2: // trader
    			case 3: // trader
    			case 11: // courier
    				// build a ship
    				if (s_type <= 3) {
    					// bounty between 200 and 7100
    					dta.realBounty = parseInt(Math.random() * 150 + 200) + ((parseInt(Math.random() * 250) + 200) * Math.pow(3, s_type - 0.5));
    					// vary rarely add some really juicy numbers
    					if (Math.random() > 0.97) dta.realBounty += parseInt(Math.random() * 2000 + 2000);
    					role = "trader";
    					if (Math.random() > 0.5) dta.routeMode = "OPTIMIZED_BY_TIME";
    					if (Math.random() > (0.7 - (s_type / 10))) role = "trader-smuggler";
    
    					var found = false;
    					do {
    						dta.shipDataKey = this.$getRandomShipKey(role);
    						var shipPlist = Ship.shipDataForKey(dta.shipDataKey);
    						dta.shipType = shipPlist.name;
    						// make sure this is a serious trader (not a bug or hognose)
    						if (shipPlist.max_cargo >= 20 + (s_type - 1) * 20) found = true;
    					} while (found === false);
    				} else {
    					// bounty between 200 and 9350
    					dta.realBounty = parseInt(Math.random() * 150 + 200) + ((parseInt(Math.random() * 250) + 200) * 20);
    					// vary rarely add some really juicy numbers
    					if (Math.random() > 0.97) dta.realBounty += parseInt(Math.random() * 3000 + 3000);
    					role = "trader-courier";
    					dta.shipDataKey = this.$getRandomShipKey(role);
    					var shipPlist = Ship.shipDataForKey(dta.shipDataKey);
    					dta.shipType = shipPlist.name;
    					dta.routeMode = "OPTIMIZED_BY_TIME";
    				}
    
    				dta.shipAI = "oolite-traderAI.js";
    
    				dta.shipName = this.$getRandomShipName(role);
    				dta.personality = Math.floor(Math.random() * 32767);
    				dta.primaryRole = role;
    				dta.accuracy = (s_type <= 3 ? (5 + s_type) : 8);
    				dta.heatInsulation = 2;
    				switch (s_type) {
    					case 1:
    						dta.equipment = "FORE:EQ_WEAPON_BEAM_LASER,AFT:EQ_WEAPON_BEAM_LASER";
    						dta.escortsRole = "escort";
    						break;
    					case 2:
    						dta.equipment = "FORE:EQ_WEAPON_MILITARY_LASER,AFT:EQ_WEAPON_BEAM_LASER,EQ_CLOAKING_DEVICE";
    						dta.escortsRole = "escort-medium";
    						break;
    					case 3:
    						dta.equipment = "FORE:EQ_WEAPON_MILITARY_LASER,AFT:EQ_WEAPON_MILITARY_LASER,EQ_CLOAKING_DEVICE";
    						dta.escortsRole = "escort-heavy";
    						break;
    					case 11:
    						dta.equipment = "FORE:EQ_WEAPON_MILITARY_LASER,AFT:EQ_WEAPON_MILITARY_LASER,EQ_CLOAKING_DEVICE";
    						dta.escortsRole = "escort";
    						break;
    				}
    				dta.equipment += ",EQ_ESCAPE_POD";
    				if (s_type <= 3) {
    					dta.escorts = (s_type * 2); // 2, 4 or 6
    					this.$addEscortsToData(dta);
    				} else {
    					// no escorts for couriers
    					dta.escorts = 0;
    					dta.escortData = [];
    				}
    
    				// create a pilot, picking a random system as home
    				var pilot = this.$createPilot(Math.floor(Math.random() * 256));
    				dta.pilotDescription = pilot.description;
    				dta.pilotName = pilot.name;
    				dta.pilotHomeSystem = pilot.homeSystem;
    				dta.pilotSpecies = pilot.species;
    
    				if (s_type <= 3) {
    					// pick a starting point
    					// milk run or spacelane
    					if (Math.random() > 0.5) {
    						// milkrun it is
    						// pick a random milkrun
    						dta.arrayIndex = Math.floor(Math.random() * this._milkRuns.length);
    						dta.movement = 1; // milkrun
    						var mr = this._milkRuns[dta.arrayIndex];
    						dta.system = mr.id1;
    						dta.destinationSystem = mr.id2
    					} else {
    						// spacelane it is
    						// pick a random spacelane
    						dta.arrayIndex = Math.floor(Math.random() * this._spacelanes.length);
    						dta.movement = 2; // spacelane
    						var sl = this._spacelanes[dta.arrayIndex];
    						// pick a starting point
    						var idx = Math.floor(Math.random() * sl.length);
    						dta.system = sl[idx];
    						// which way are we going, l-to-r or r-to-l
    						if (idx < (sl.length - 1) && idx > 0) {
    							if (Math.random() > 0.5) {
    								// right to left
    								dta.destinationSystem = sl[idx - 1];
    							} else {
    								// left to right
    								dta.destinationSystem = sl[idx + 1];
    							}
    						} else {
    							// edge cases
    							if (idx === 0) {
    								dta.destinationSystem = sl[idx + 1];
    							} else {
    								dta.destinationSystem = sl[idx - 1];
    							}
    						}
    					}
    				} else {
    					// pick a start/end point from the available courier routes, and make sure it's connected
    					var side = (Math.random() > 0.5 ? 1 : 0);
    					var start = (Math.random() > 0.5 ? "point1" : "point2");
    					var end = (start === "point1" ? "point2" : "point1");
    
    					// try to find a connecting route
    					var found = false;
    					var tries = 0;
    					do {
    						var sys1 = this._courierRoutes[side][start][Math.floor(Math.random() * this._courierRoutes[side][start].length)];
    						var sys2 = this._courierRoutes[side][end][Math.floor(Math.random() * this._courierRoutes[side][end].length)];
    						var rt = System.infoForSystem(galaxyNumber, sys1).routeToSystem(System.infoForSystem(galaxyNumber, sys2));
    						if (!rt) {
    							tries += 1;
    						} else {
    							// create a custom route for this run
    							var check = this.$createCustomPath(sys1, sys2, dta.routeMode);
    							if (check !== -1) {
    								// select the first destination on the list
    								dta.arrayIndex = check;
    								dta.system = this._customPath[dta.arrayIndex][0];
    								dta.destinationSystem = this._customPath[dta.arrayIndex][1];
    								dta.movement = 4;
    								found = true;
    							} else {
    								tries += 1;
    							}
    						}
    					} while (found === false && tries < 5);
    					// we weren't able to create a patch, so give up on this entry
    					if (found === false) continue;
    				}
    
    				// when did they dock?
    				dta.dockTime = clock.adjustedSeconds - (Math.random() * 21600 + 3600);
    				// when will they leave dock?
    				dta.departureTime = clock.adjustedSeconds + (Math.random() * 21600 + 3600);
    				// arrival time in new system will be departureTime + travelTime(time between systems) + extraTime(random flight time)
    				// is their current position visible to GalCop?
    				dta.updateChance = (s_type <= 3 ? (s_type / 6) : 0.5);
    				break;
    			case 4: // pirate
    			case 5:
    			case 6:
    			case 7:
    				// bounty between 250 and 15200
    				dta.realBounty = parseInt(Math.random() * 100 + 250) + ((parseInt(Math.random() * 250) + 200) * Math.pow(3, s_type - 4));
    				// vary rarely add some really juicy numbers
    				if (Math.random() > 0.97) dta.realBounty += parseInt(Math.random() * 5000 + 5000);
    				dta.bounty = 60 + (Math.floor(Math.random() * 8)) + Math.floor(Math.random() * 8);
    				if (Math.random() > 0.5) dta.routeMode = "OPTIMIZED_BY_TIME";
    				// build a ship
    				var def_role = "pirate";
    				switch (s_type) {
    					case 4:
    						role = "pirate";
    						dta.escortsRole = "pirate-light-fighter";
    						break;
    					case 5:
    						role = "pirate-light-freighter";
    						dta.escortsRole = "pirate-light-fighter";
    						break;
    					case 6:
    						role = "pirate-medium-freighter";
    						dta.escortsRole = "pirate-medium-fighter";
    						break;
    					case 7:
    						role = "pirate-heavy-freighter";
    						dta.escortsRole = "pirate-heavy-fighter";
    						break;
    				}
    				if (this.$roleExists(role) === false) role = def_role;
    				dta.shipAI = "oolite-pirateFreighterAI.js";
    				var found = false;
    				do {
    					dta.shipDataKey = this.$getRandomShipKey(role);
    					var shipPlist = Ship.shipDataForKey(dta.shipDataKey);
    					dta.shipType = shipPlist.name;
    					// make sure this is a serious pirate (not a bug or hognose)
    					if (shipPlist.max_cargo >= 20 + (s_type - 4) * 20) found = true;
    				} while (found === false);
    
    				dta.shipName = this.$getRandomShipName(role);
    				dta.personality = Math.floor(Math.random() * 32767);
    				dta.primaryRole = role;
    				dta.accuracy = s_type;
    				dta.heatInsulation = 2;
    				switch (s_type) {
    					case 4:
    						dta.equipment = "FORE:EQ_WEAPON_MILITARY_LASER";
    						break;
    					case 5:
    						dta.equipment = "FORE:EQ_WEAPON_BEAM_LASER,AFT:EQ_WEAPON_BEAM_LASER,EQ_CLOAKING_DEVICE";
    						break;
    					case 6:
    						dta.equipment = "FORE:EQ_WEAPON_MILITARY_LASER,AFT:EQ_WEAPON_BEAM_LASER,EQ_CLOAKING_DEVICE";
    						break;
    					case 7:
    						dta.equipment = "FORE:EQ_WEAPON_MILITARY_LASER,AFT:EQ_WEAPON_MILITARY_LASER,EQ_CLOAKING_DEVICE";
    						break;
    				}
    				// assassins don't have escaspe pods
    				dta.equipment += ",EQ_ESCAPE_POD";
    				if (Math.random() < (s_type / 5)) {
    					dta.escorts = ((s_type - 3) * 2); // 2, 4, 6 or 8
    					this.$addEscortsToData(dta);
    				} else {
    					dta.escorts = 0;
    					dta.escortData = [];
    				}
    
    				// create a pilot, picking a random system as home
    				var pilot = this.$createPilot(Math.floor(Math.random() * 256));
    				dta.pilotDescription = pilot.description;
    				dta.pilotName = pilot.name;
    				dta.pilotHomeSystem = pilot.homeSystem;
    				dta.pilotSpecies = pilot.species;
    
    				// where are they now, and where are they headed?
    				// pick a safe/dangerous element, start them in the safe one
    				dta.arrayIndex = Math.floor(Math.random() * this._safeDangerous.length);
    				dta.movement = 3; // star-type movement (if there is more than 1 dangerous system against the safe one, otherwise same as a milkrun)
    				var sd = this._safeDangerous[dta.arrayIndex];
    				dta.system = sd.id;
    				dta.destinationSystem = sd.dangerous[0];
    
    				// when did they dock?
    				dta.dockTime = clock.adjustedSeconds - (Math.random() * 21600 + 3600);
    				// when will they leave dock?
    				dta.departureTime = clock.adjustedSeconds + (Math.random() * 21600 + 3600);
    				// arrival time in new system will be departureTime + travelTime(time between systems) + extraTime(random flight time)
    				// is their current position visible to GalCop?
    				dta.updateChance = (s_type - 3) / 6;
    				break;
    			case 8: // assassin
    			case 9:
    				// bounty between 250 and 11600
    				dta.realBounty = parseInt(Math.random() * 100 + 250) + ((parseInt(Math.random() * 250) + 200) * Math.pow(5, s_type - 7));
    				// vary rarely add some really juicy numbers
    				if (Math.random() > 0.97) dta.realBounty += parseInt(Math.random() * 5000 + 5000);
    				dta.routeMode = "OPTIMIZED_BY_TIME";
    
    				// build a ship
    				switch (s_type) {
    					case 8:
    						role = "assassin-medium";
    						dta.equipment = "FORE:EQ_WEAPON_MILITARY_LASER,AFT:EQ_WEAPON_BEAM_LASER";
    						dta.escortsRole = "assassin-light";
    						break;
    					case 9:
    						role = "assassin-heavy";
    						dta.equipment = "FORE:EQ_WEAPON_MILITARY_LASER,AFT:EQ_WEAPON_MILITARY_LASER";
    						dta.escortsRole = "assassin-medium";
    						break;
    				}
    				dta.equipment += ",X:EQ_ESCAPE_POD";
    				if (this.$roleExists(role) === false) continue;
    
    				dta.shipAI = "oolite-assassinAI.js";
    				dta.shipDataKey = this.$getRandomShipKey(role);
    				var shipPlist = Ship.shipDataForKey(dta.shipDataKey);
    				dta.shipType = shipPlist.name;
    
    				dta.shipName = this.$getRandomShipName(role);
    				dta.personality = Math.floor(Math.random() * 32767);
    				dta.primaryRole = role;
    				dta.accuracy = s_type - 1;
    				dta.heatInsulation = 2;
    				dta.equipment += ",EQ_ESCAPE_POD,EQ_CLOAKING_DEVICE";
    				dta.escorts = ((s_type - 6) * 2); // 4, or 6
    				this.$addEscortsToData(dta);
    
    				// create a pilot, picking a random system as home
    				var pilot = this.$createPilot(Math.floor(Math.random() * 256));
    				dta.pilotDescription = pilot.description;
    				dta.pilotName = pilot.name;
    				dta.pilotHomeSystem = pilot.homeSystem;
    				dta.pilotSpecies = pilot.species;
    
    				// where are they now, and where are they headed?
    				// pick a safe/dangerous element, start them in the safe one
    				dta.arrayIndex = Math.floor(Math.random() * this._safeDangerous.length);
    				dta.movement = 3; // star-type movement (if there is more than 1 dangerous system against the safe one, otherwise same as a milkrun)
    				var sd = this._safeDangerous[dta.arrayIndex];
    				dta.system = sd.dangerous[0];
    				dta.destinationSystem = sd.id;
    
    				// when did they dock?
    				dta.dockTime = clock.adjustedSeconds - (Math.random() * 21600 + 3600);
    				// when will they leave dock?
    				dta.departureTime = clock.adjustedSeconds + (Math.random() * 21600 + 3600);
    				// arrival time in new system will be departureTime + travelTime(time between systems) + extraTime(random flight time)
    				// is their current position visible to GalCop?
    				dta.updateChance = (s_type / 15);
    				break;
    			case 10: // hunter
    				// bounty between 500 and 7350
    				dta.realBounty = parseInt(Math.random() * 100 + 500) + ((parseInt(Math.random() * 250) + 200) * 15);
    				// vary rarely add some really juicy numbers
    				if (Math.random() > 0.97) dta.realBounty += parseInt(Math.random() * 5000 + 5000);
    				if (Math.random() > 0.3) dta.routeMode = "OPTIMIZED_BY_TIME";
    
    				// build a ship
    				role = "hunter-heavy";
    				if (this.$roleExists(role) === false) continue;
    
    				dta.shipAI = "oolite-bountyHunterLeaderAI.js";
    				dta.shipDataKey = this.$getRandomShipKey(role);
    				var shipPlist = Ship.shipDataForKey(dta.shipDataKey);
    				dta.shipType = shipPlist.name;
    
    				dta.shipName = this.$getRandomShipName(role);
    				dta.personality = Math.floor(Math.random() * 32767);
    				dta.primaryRole = role;
    				dta.accuracy = s_type - 1;
    				dta.heatInsulation = 2;
    				dta.equipment = "FORE:EQ_WEAPON_MILITARY_LASER,AFT:EQ_WEAPON_MILITARY_LASER";
    				dta.equipment += ",EQ_ESCAPE_POD,EQ_CLOAKING_DEVICE";
    				dta.escorts = Math.floor(Math.random() * 4) + 5;
    				dta.escortsRole = "hunter-medium";
    				this.$addEscortsToData(dta);
    
    				// create a pilot, picking a random system as home
    				var pilot = this.$createPilot(Math.floor(Math.random() * 256));
    				dta.pilotDescription = pilot.description;
    				dta.pilotName = pilot.name;
    				dta.pilotHomeSystem = pilot.homeSystem;
    				dta.pilotSpecies = pilot.species;
    
    				// where are they now, and where are they headed?
    				// pick a safe/dangerous element, start them in the safe one
    				dta.arrayIndex = Math.floor(Math.random() * this._safeDangerous.length);
    				dta.movement = 3; // star-type movement (if there is more than 1 dangerous system against the safe one, otherwise same as a milkrun)
    				var sd = this._safeDangerous[dta.arrayIndex];
    				dta.system = sd.dangerous[0];
    				dta.destinationSystem = sd.id;
    
    				// when did they dock?
    				dta.dockTime = clock.adjustedSeconds - (Math.random() * 21600 + 3600);
    				// when will they leave dock?
    				dta.departureTime = clock.adjustedSeconds + (Math.random() * 21600 + 3600);
    				// arrival time in new system will be departureTime + travelTime(time between systems) + extraTime(random flight time)
    				// is their current position visible to GalCop?
    				dta.updateChance = 0.6
    				break;
    		}
    
    		// finalise settings
    		if (Math.random() > dta.updateChance) {
    			dta.lastVisibleSystem = dta.system;
    			dta.lastVisibleDestination = dta.destinationSystem;
    			dta.updated = clock.adjustedSeconds - ((Math.random() * 14) * 86400) + (Math.random() * 86400);
    		} else {
    			dta.lastVisibleSystem = -1;
    		}
    		dta.lastVisibleHistory = [2];
    
    		// add this record to the array
    		if (this._debug) this.$writeDataToLog(dta, "Creating record");
    		this._wanted.push(dta);
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$addEscortsToData = function $addEscortsToData(dta) {
    	dta.escortData = [];
    	for (var j = 0; j < dta.escorts; j++) {
    		var esc = {};
    		esc["escortShipKey"] = this.$getRandomShipKey(dta.escortsRole);
    		esc["escortShipName"] = this.$getRandomShipName(dta.escortsRole);
    		esc["escortPersonality"] = Math.floor(Math.random() * 32768);
    		esc["escortPilotName"] = this.$generateName(); //randomName() + " " + randomName();
    		if (dta.primaryRole.indexOf("pirate") >= 0) {
    			esc["escortBounty"] = Math.floor(Math.random() * (dta.accuracy * 5) + 5);
    		} else {
    			esc["escortBounty"] = 0;
    		}
    		esc["escortHiddenBounty"] = Math.floor(Math.random() * 30 + 5);
    		dta.escortData.push(esc);
    	}	
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // looks for any ships that need to be added to the local system in some way, either witchpoint or station
    this.$processPendingShips = function $processPendingShips() {
    	var sdcInst = this._sdcInstalled;
    	var doUpdate = false;
    
    	for (var i = this._pending.length - 1; i >= 0; i--) {
    		var pend = this._pending[i];
    		var r_type = pend.type;
    		var chance = pend.chance;
    		var dta = JSON.parse(pend.data);
    
    		// check for a big time difference (eg caused by ejecting)
    		// if difference is too large, revert this record to be not pending
    		var diff = (pend.time - clock.adjustedSeconds);
    		// if the departure time less than the current time, switch this back to not-pending
    		if (diff < 0) {
    			doUpdate = true;
    			for (var j = 0; j < this._wanted.length; j++) {
    				if (this._wanted[j].index === pend.index) {
    					this._wanted[j].pending = false;
    					break;
    				}
    			}
    			this._pending.splice(i, 1);
    			continue;
    		}
    		
    		// work out if this ship is docked somewhere, or if it will be jumping in
    		// if docked, and SDC is installed, add it to a dock
    		// otherwise, we can choose a random time to add the ship
    		switch (r_type) {
    			case "witchpoint":
    				// how far is the player from the wp? If they're too far away, don't spawn anything yet
    				var wp = new Vector3D(0, 0, 0); //system.shipsWithRole("buoy-witchpoint")[0];
    				if (player.ship.position.distanceTo(wp) < (player.ship.scannerRange * 3)) {
    					// what's the actual time differential for this ship?
    					var info = System.infoForSystem(galaxyNumber, dta.system);
    					var rt = system.info.routeToSystem(info, dta.routeMode);
    					var diff = Math.abs((dta.departureTime + (rt.time * 3600)) - clock.adjustedSeconds);
    					if (Math.random() < (diff / 3600) && Math.random() < chance) {
    						// OK, here we go!
    						this.$createShips("wp", dta, this.$wormholePos(), rt);
    						this._pending.splice(i, 1);
    					} else {
    						// increase the chance for next time around.
    						pend.chance += 0.05;
    					}
    				}
    				break;
    			case "docked":
    				// we're only going to switch between rock hermits and the main station, so we know SDC will be handling the launch
    				var stn = this.$translateStationName(pend.station);
    				// this shouldn't happen, but still...
    				if (stn == null) continue;
    				// is SDC installed?
    				if (sdcInst === true) {
    					var sdc = worldScripts.StationDockControl; // link to the main SDC pack
    					var found = false;
    					// make sure the station is not in the process of being updated
    					if (sdc._populateStation.length > 0) {
    						for (var j = 0; j < sdc._populateStation.length; j++) {
    							if (sdc._populateStation.stn === stn) {
    								found = true;
    								break;
    							}
    						}
    						// station is about to be populated, so hold off on this one for the moment
    						// catch it next time round
    						if (found === true) continue;
    					}
    					var pilot = {name:dta.pilotName, insurance:0, description:dta.pilotDescription, homeSystem:dta.pilotHomeSystem, species:dta.pilotSpecies};
    
    					var dt = parseInt((dta.departureTime - clock.adjustedSeconds) / 60)
    					// we can now add the ship definition to the array
    					sdc.$addShipToArray({
    						station:stn,				// the station object the ship will be docked at
    						role:dta.primaryRole,
    						shipName:dta.shipName,
    						shipType:dta.shipType,
    						shipDataKey:dta.shipDataKey,
    						personality:dta.personality,
    						aiName:dta.shipAI,
    						accuracy:dta.accuracy,
    						equipment:dta.equipment,
    						heatInsulation:dta.heatInsulation,
    						homeSystem:pilot.homeSystem,
    						fuel:7,
    						bounty:dta.bounty,
    						escortName:"",
    						escortLeader:false,
    						groupName:(dta.escorts > 0 ? "bh_group_" + dta.index : ""),
    						groupLeader:(dta.escorts > 0 ? true : false),
    						destinationSystem:dta.destinationSystem,
    						destinationHidden:true,
    						goods:"PLENTIFUL_GOODS",
    						pilot:pilot,
    						dockTime:dta.dockTime,
    						departureTime:dt,
    						departureSeconds:0,
    						lastChance:clock.adjustedSeconds,
    						//lastChange:clock.adjustedSeconds + (3600 * 4), // so the ship doesn't get an updated departure slot too quickly
    						launchCallback:"$bounty_launchCallback",
    						worldScript:this.name,
    						properties:{
    							_mostWanted:dta.index, 
    							_hiddenBounty:dta.realBounty, 
    							_storedHiddenBounty:dta.realBounty
    						}
    					});
    
    					if (worldScripts.Smugglers_DockMaster) {
    						var sdci = worldScripts.StationDockControl_Interface;
    						if (sdci.$externalInterfaceExists({
    								pilotName:dta.pilotName,
    								menuText:"Flag ship for Dock Master notification",
    								worldScript:this.name,
    								callback:"$bounty_dockMaster_flag"}) === false) {
    							sdci.$externalInterface({
    								pilotName:dta.pilotName,
    								menuText:"Flag ship for Dock Master notification",
    								station:system.mainStation, // this menu will only be available at the main station (where there's a dock master)
    								worldScript:this.name,
    								callback:"$bounty_dockMaster_flag",
    								parameter:dta.index
    							});
    						}
    					}
    
    					if (this._debug) log(this.name, "adding BH ship to station - " + dta.shipName + ", " + stn.displayName);
    					if (this._debug) log(this.name, "dock time " + clock.clockStringForTime(dta.dockTime));
    
    					if (dta.escorts > 0 && dta.escortData.length > 0) {
    						// add all the escorts to SDC as well
    						for (var j = 0; j < dta.escortData.length; j++) {
    							var item = dta.escortData[j];
    							var st = this.$getShipType(item.escortShipKey);
    							if (st != "") {
    								var e_pilot = {name:item.escortPilotName, insurance:0, homeSystem:dta.pilotHomeSystem, species:dta.pilotSpecies};
    
    								sdc.$addShipToArray({
    									station:stn,				// the station object the ship will be docked at
    									role:dta.escortsRole,
    									shipName:item.escortShipName,
    									shipType:st,
    									shipDataKey:item.escortShipKey,
    									personality:item.escortPersonality,
    									aiName:"oolite-escortAI.js",
    									accuracy:dta.accuracy,
    									equipment:"",
    									heatInsulation:1,
    									homeSystem:e_pilot.homeSystem,
    									fuel:7,
    									bounty:item.escortBounty,
    									escortName:"",
    									escortLeader:false,
    									groupName:"bh_group_" + dta.index,
    									groupLeader:false,
    									destinationSystem:dta.destinationSystem,
    									destinationHidden:true,
    									goods:"PLENTIFUL_GOODS",
    									pilot:e_pilot,
    									dockTime:dta.dockTime + (j + 1),
    									departureTime:dt,
    									departureSeconds:5,
    									lastChange:clock.adjustedSeconds, // + (3600 * 4), // so the ship doesn't get an updated departure slot too quickly
    									launchCallback:"$bounty_escortLaunchCallback",
    									worldScript:this.name,
    									properties:{
    										_mostWanted:dta.index, 
    										_hiddenBounty:item.escortHiddenBounty, 
    										_storedHiddenBounty:item.escortHiddenBounty
    									}
    								});
    							}
    						}
    					}
    					// remove the item so it doesn't get created again.
    					this._pending.splice(i, 1);
    
    					continue; // just continue here, because with SDC there is no further work to do on this ship
    				} else {
    					//this._launching.push({station:pend.station, data:JSON.stringify(dta), })
    					var diff = dta.departureTime - clock.adjustedSeconds;
    					if (Math.random() < (diff / 3600) && Math.random() < chance) {
    						// without SDC we just launch the ships
    						// is there any space in the launch queue for this group of ships
    						// so, if there's only 1 slot, and there's 5 ships in this group, don't launch any of them
    						if (this.$launchAvailable(stn, dta.escorts + 1) === true) {
    							this.$createShips("stn", dta, stn);
    							this._pending.splice(i, 1);
    						}
    						continue;
    					}
    					pend.chance += 0.05;
    				}
    				break;
    		}
    	}
    
    	// if an item was switched back to non-pending, we should do a refresh of the movement data
    	// in case something needs to be switched from arriving to docked
    	if (doUpdate === true) this.$updateMovementData();
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$createShips = function $createShips(posType, dta, pos, rt) {
    	var wpop = worldScripts["oolite-populator"];
    	var shpKey = dta.shipDataKey;
    	// make sure a single ship is launched/added (ie without escorts), because we'll be doing the escorts manually
    	if (this.$bountyVersionExists(shpKey)) shpKey = "dock_" + shpKey;
    
    	var lead = null;
    	if (posType === "wp") {
    		var shps = system.addShips("[" + shpKey + "]", 1, pos, 0);
    		if (shps && shps.length > 0) lead = shps[0];
    	} 
    	if (posType === "stn") {
    		lead = pos.launchShipWithRole("[" + shpKey + "]");
    	}
    
    	if (lead) {
    		// attach scripts
    		this.$bounty_launchCallback("launched", lead);
    
    		lead.shipUniqueName = dta.shipName;
    		dta.fuel = 7 - (rt ? rt.distance : 0);
    		dta.goods = "";
    		if (dta.primaryRole.indexOf("trader") >= 0) dta.goods = "SCARCE_GOODS";
    		if (dta.primaryRole.indexOf("pirate") >= 0) dta.goods = "PIRATE_GOODS";
    
    		this.$updateShip(lead, dta);
    		if (Math.random() * 8 > system.info.government) {
    			// may have used some missiles already
    			wpop._setMissiles(lead, -1);
    		}
    		if (this._debug) log(this.name, "adding BH ship to " + posType + " - " + lead.shipUniqueName + ", " + lead);
    
    		if (this._sdcInstalled) {
    			var sdc = worldScripts.StationDockControl;
    			sdc.$awaitShipDocking({shipName:dta.shipName, pilotName:dta.pilotName, worldScript:this.name, callback:"$sdc_shipDockedWithStation", parameter:dta.index, launchCallback:"$bounty_launchCallback"});
    		}
    
    		if (dta.escorts > 0 && dta.escortData.length > 0) {
    			// set up a ship group
    			var grp = new ShipGroup("bh_group_" + dta.index, lead);
    			lead.group = grp;
    
    			for (var j = 0; j < dta.escortData.length; j++) {
    				var item = dta.escortData[j];
    				var esc = null;
    				if (posType === "wp") {
    					var shps = system.addShips("[" + item.escortShipKey + "]", 1, pos, 1E3);
    					if (shps && shps.length > 0) esc = shps[0];
    				}
    				if (posType === "stn") {
    					var escShpKey = item.escortShipKey;
    					// make sure a single ship is launched (ie without escorts)
    					if (this.$bountyVersionExists(shpKey)) escShpKey = "bounty_" + escShpKey;
    					esc = pos.launchShipWithRole("[" + escShpKey + "]");
    				}
    
    				if (esc) {
    					esc.shipUniqueName = item.escortShipName;
    					esc.entityPersonality = item.escortPersonality;
    					esc.fuel = 7;
    					esc.homeSystem = dta.pilotHomeSystem;
    					esc.destinationSystem = lead.destinationSystem;
    					// set the seed values so that the pilot's species can be defined by their home system
    					var seed = "0 0 0 0 " + dta.pilotHomeSystem.toString() + " 2";
    					esc.setCrew({name:item.escortPilotName, origin:dta.pilotHomeSystem,
    						bounty:item.escortBounty, random_seed:seed});
    
    					// attach a shipDied handler so we can update the master record when escorts are killed
    					// that way, if the player shoots 2 out of 5 escorts, and the rest manage to escape, if the player finds them again
    					// those killed escorts won't have magically regenerated
    					this.$bounty_escortLaunchCallback("launched", esc);
    
    					esc.script._mostWanted = dta.index;
    					esc.script._hiddenBounty = item.escortHiddenBounty;
    					wpop._setWeapons(esc, 2.5);
    
    					esc.group = grp;
    					grp.addShip(esc);
    
    					// try to do an escort offer
    					esc.performEscort();
    
    					if (this._debug) log(this.name, "add BH escort ship to " + posType + " - " + esc.shipUniqueName + ", " + esc);
    					//log(this.name, "escort offer to lead = " + ret);
    				} else {
    					log(this.name, "!!ERROR: Escort (shipkey " + item.escortShipKey + ") not created for ship " + lead.shipUniqueName + ", " + lead);
    				}
    			}
    		}
    		// make sure the leader has hardhead missiles
    		this.$switchMissiles(lead);
    		// remove the item so it doesn't get created again.
    		//this._pending.splice(i, 1);
    	} else {
    		log(this.name, "!!ERROR: Lead ship not created at " + posType + ": " + (posType === "stn" ? "(" + pos.shipUniqueName + ") " : "") + dta.shipUniqueName + ", " + dta.pilotName);
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // looks for any ships that have been created and are registered as docked in SDC 
    // when not found, reset pending flag back to false so ships will be regenerated
    this.$lookForPendingRecords = function $lookForPendingRecords() {
    	if (this._sdcInstalled === true) {
    		// get the data for this system
    		var sdcData = worldScripts.StationDockControl._systemDockingData;
    		if (sdcData[system.ID]) var sdc = sdcData[system.ID];
    		// get the list of object keys for this (basically a station index list)
    		if (sdc) var keys = Object.keys(sdc);
    		// we're doing all this here so we only do it once, rather than each time a pending flag is found
    	}
    	for (var i = 0; i < this._wanted.length; i++) {
    		var wanted = this._wanted[i];
    		if (wanted.pending === true) {
    			if (this._sdcInstalled === false) {
    				// without sdc, we can just reset the record - no saved data to mess with
    				this.$removePendingItem(wanted.index);
    				wanted.pending = false;
    			} else {
    				var found = false;
    				if (keys) {
    					// loop through the stations
    					for (var j = 0; j < keys.length; j++) {
    						// get the ships docked at the station
    						var stndata = sdc[keys[j]];
    						for (var k = 0; k < stndata.length; k++) {
    							// investigate each ship entry, looking for additionalProperties._mostWanted
    							var shipd = stndata[k];
    							if (shipd.hasOwnProperty("additionalProperties") && shipd.additionalProperties.hasOwnProperty("_mostWanted")) {
    								if (wanted.index == shipd.additionalProperties._mostWanted) {
    									// found! so we don't want to attempt to recreate this one
    									found = true;
    									break;
    								}
    							}
    						}
    						if (found === true) break;
    					}
    				}
    				// not found, so set the flag back to false so it can be regenerated
    				if (found === false) {
    					this.$removePendingItem(wanted.index);
    					wanted.pending = false;
    				}
    			}
    		}
    	}
    	// look for any remain pending records that are no longer valid
    	for (var i = this._pending.length - 1; i >= 0; i--) {
    		var pend = this._pending[i];
    		if (pend.system !== system.ID && pend.destinationSystem !== system.ID) {
    			this._pending.splice(i, 1);
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$getDockedStation = function $getDockedStation(data) {
    	// pick between main station and rock hermit
    	// default is main station
    	var main = true;
    	// work out if a rh should be used
    	switch (data.primaryRole) {
    		case "trader":
    		case "trader-courier":
    		case "trader-smuggler":
    			// more likely in dangerous systems
    			if (Math.random() > (0.5 - ((7 - system.info.government) / 7))) main = false;
    			break;
    		case "pirate":
    		case "pirate-light-freighter":
    		case "pirate-medium-freighter":
    		case "pirate-heavy-freighter":
    			// pirates will always prefer a non-main station
    			if (Math.random() > 0.2) main = false;
    			break;
    		case "hunter":
    		case "hunter-medium":
    		case "hunter-heavy":
    			if (Math.random() > (0.5 - ((7 - system.info.government) / 7))) main = false;
    			break;
    		case "assassin-light":
    		case "assassin-medium":
    		case "assassin-heavy":
    			break;
    	}
    	if (main) {
    		return system.mainStation;
    	} else {
    		var stns = system.stations;
    		var found = -1;
    		for (var i = 0; i < stns.length; i++) {
    			if (stns[i].hasRole("rockhermit")) {found = i; break;}
    		}
    		if (found >= 0) {
    			return stns[found];
    		} else {
    			// just in case there are no rock hermits here
    			return system.mainStation;
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$sdc_shipDockedWithStation = function $sdc_shipDockedWithStation(station, param) {
    	var mw = worldScripts.BountySystem_MostWanted;
    	for (var i = 0; i < mw._wanted.length; i++) {
    		var wanted = mw._wanted[i];
    		if (wanted.index === param) {
    			if (wanted.lastVisibleSystem != -1) mw.$cycleHistory(wanted);
    			wanted.dockTime = clock.adjustedSeconds;
    			wanted.lastVisibleSystem = system.ID;
    			wanted.lastVisibleDestination = wanted.destinationSystem;
    			wanted.updated = clock.adjustedSeconds;
    
    			if (worldScripts.Smugglers_DockMaster) {
    				var sdci = worldScripts.StationDockControl_Interface;
    				// make sure it's not already there
    				sdci.$removeExternalInterface({
    					pilotName:wanted.pilotName,
    					menuText:"Flag ship for Dock Master notification",
    					worldScript:"BountySystem_MostWanted",
    					callback:"$bounty_dockMaster_flag"
    				});
    				if (station.isMainStation) {
    					// add the external interface
    					sdci.$externalInterface({
    						pilotName:wanted.pilotName,
    						menuText:"Flag ship for Dock Master notification",
    						worldScript:"BountySystem_MostWanted",
    						callback:"$bounty_dockMaster_flag",
    						parameter:wanted.index
    					});
    				}
    			}
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$bounty_shipDied = function $bounty_shipWillEnterWormhole(whom, why) {
    	if (this.ship.script.$bh_shipDied) this.ship.script.$bh_shipDied(whom, why);
    	// remove ship from all wanted arrays
    	var mw = worldScripts.BountySystem_MostWanted;
    	mw.$removeItem(this.ship.script._mostWanted);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$bounty_shipWillEnterWormhole = function $bounty_shipWillEnterWormhole() {
    	if (this.ship.script.$bh_shipWillEnterWormhole) this.ship.script.$bh_shipWillEnterWormhole();
    	// logic: no shipDied/shipRemoved/entityDestroyed events on entering a wormhole
    	// status changes to "STATUS_ENTERING_WITCHSPACE" and stays valid until the player jumps out
    	// if wormhole collapses and player then jumps to same system...ship will be "STATUS_IN_FLIGHT" at destination
    	// entityDestroyed only fires if player jumps to a different system, or after 6 minutes
    	// not sure how long the game will wait before clearing out these ships - check
    	var mw = worldScripts.BountySystem_MostWanted;
    	for (var i = 0; i < mw._wanted.length; i++) {
    		var wanted = mw._wanted[i];
    		if (wanted.index === this.ship.script._mostWanted) {
    			// update the destination and departure time
    			wanted.destinationSystem = this.ship.destinationSystem;
    			wanted.departureTime = clock.adjustedSeconds;
    		}
    	}
    	mw._checkOnArrival.push(this.ship);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$bounty_shipDockedWithStation = function $bounty_shipDockedWithStation(station) {
    	if (this.ship.script.$bh_shipDockedWithStation) this.ship.script.$bh_shipDockedWithStation(station);
    	var mw = worldScripts.BountySystem_MostWanted;
    	// if SDC is installed don't do anything - SDC handles the everything
    	if (mw._sdcInstalled === true) return;
    	// put ship back onto the pending array, but with a negative chance value, so that it will be some minutes before they are launched again
    	// hopefully this will give their escorts time to dock as well.
    	var itm = null;
    	for (var i = 0; i < mw._wanted.length; i++) {
    		var wanted = mw._wanted[i];
    		if (wanted.index === this.ship.script._mostWanted) itm = wanted;
    	}
    	if (itm) {
    		var waitperiod = (0.05 * 3) * Math.floor(Math.random() * 10 + 7) * -1; // somewhere between 7 and 17 minutes
    		mw._pending.push({type:"docked", chance:waitperiod, station:station.displayName, index:itm.index, shipKey:itm.shipDataKey, time:clock.adjustedSeconds + Math.abs(chance * 60), data:JSON.stringify(itm)});	
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$bounty_shipLaunchedEscapePod = function $bounty_shipLaunchedEscapePod(escapepod, passengers) {
    	if (this.ship.script.$bh_shipLaunchedEscapePod) this.ship.script.$bh_shipLaunchedEscapePod(escapepod, passengers);
    
    	// remove ship from all wanted arrays
    	var mw = worldScripts.BountySystem_MostWanted;
    	mw.$removeItem(this.ship.script._mostWanted);
    	
    	// if the player hasn't done a warrant scan, leave everything alone
    	// if the player has done a warrant scan, make the remaining bounty in the range of (20-60) so the player doesn't get paid the big bounty twice
    	if (this.ship.script._hiddenBounty === 0) {
    		// the big bounty will move over to the escape pod with the pilot
    		// reset the bounty on the ship so the player doesn't get paid twice
    		var b = parseInt(Math.random() * 40 + 20);
    		this.ship.bounty = b;
    		//escapepod.crew[0].bounty = b;
    		//escapepod.bounty = b;
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$bounty_launchCallback = function $bounty_launchCallback(launchType, ship) {
    	if (this._debug) log(this.name, "launchCallback " + launchType + ", " + ship);
    	switch (launchType) {
    		case "launching":
    		case "launched":
    			// "ship" will be an actual ship
    			if (ship) {
    				var mw = worldScripts.BountySystem_MostWanted;
    				// attach our scripts to the new ship
    				if (ship.script.shipDied) ship.script.$bh_shipDied = ship.script.shipDied;
    				ship.script.shipDied = mw.$bounty_shipDied;
    				if (ship.script.shipWillEnterWormhole) ship.script.$bh_shipWillEnterWormhole = ship.script.shipWillEnterWormhole;
    				ship.script.shipWillEnterWormhole = mw.$bounty_shipWillEnterWormhole;
    				if (ship.script.shipDockedWithStation) ship.script.$bh_shipDockedWithStation = ship.script.shipDockedWithStation;
    				ship.script.shipDockedWithStation = mw.$bounty_shipDockedWithStation;
    				if (ship.script.shipLaunchedEscapePod) ship.script.$bh_shipLaunchedEscapePod = ship.script.shipLaunchedEscapePod;
    				ship.script.shipLaunchedEscapePod = mw.$bounty_shipLaunchedEscapePod;
    				// remove any external menu added to SDC
    				if (worldScripts.StationDockControl_Interface) {
    					var sdci = worldScripts.StationDockControl_Interface;
    					sdci.$removeExternalInterface({
    						pilotName:ship.crew[0].name,
    						menuText:"Flag ship for Dock Master notification",
    						worldScript:"BountySystem_MostWanted",
    						callback:"$bounty_dockMaster_flag"
    					});
    				}
    			}
    			break;
    		case "cleared":
    			// "ship"" will be the SDC ship data record
    			// reset the pending flag on any ships cleared by SDC so they go back into the movement process
    			var idx = ship.additionalProperties["_mostWanted"];
    			var mw = worldScripts.BountySystem_MostWanted;
    			for (var i = 0; i < mw._wanted.length; i++) {
    				var item = mw._wanted[i];
    				if (item.index === idx) item.pending = false;
    			}
    			break;
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$bounty_escortLaunchCallback = function $bounty_escortLaunchCallback(launchType, ship) {
    	switch (launchType) {
    		case "launching":
    		case "launched":
    			if (ship) {
    				var mw = worldScripts.BountySystem_MostWanted;
    				if (ship.script.shipDied) ship.script.$bh_shipDied = ship.script.shipDied;
    				ship.script.shipDied = mw.$bounty_escort_shipDied;
    			}
    			break;
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$bounty_escort_shipDied = function $bounty_escort_shipDied(whom, why) {
    	if (this.ship.script.$bh_shipDied) this.ship.script.$bh_shipDied(whom, why);
    	// find the record for this escort
    	var mw = worldScripts.BountySystem_MostWanted;
    	for (var i = 0; i < mw._wanted.length; i++) {
    		if (mw._wanted[i].index === this.ship.script._mostWanted) {
    			var dta = mw._wanted[i];
    			// look for the escort details for this wanted pilot
    			for (var j = 0; j < dta.escortData.length; j++) {
    				if (dta.escortData[j].escortShipKey === this.ship.dataKey && dta.escortData[j].escortPersonality === this.ship.entityPersonality) {
    					// bingo - time to remove it
    					dta.escorts -= 1;
    					dta.escortData.splice(j, 1);
    					break;
    				}
    			}
    			break;
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$bounty_dockMaster_flag = function $bounty_dockMaster_flag(shipIndex, param) {
    	var dm = worldScripts.Smugglers_DockMaster;
    	if (dm._flagged.length >= 3) {
    		player.consoleMessage("Unable to flag more than 3 ships");
    		return;
    	}
    	var mw = worldScripts.BountySystem_MostWanted;
    	for (var i = 0; i < mw._wanted.length; i++) {
    		if (mw._wanted[i].index == param) {
    			var fee = parseInt(mw._wanted[i].realBounty * 0.1);
    			player.consoleMessage("Ship has been flagged with the Dock Master");
    			dm._flagged.push({
    				text:this.$padTextRight(mw._wanted[i].pilotName + " (" + mw._wanted[i].shipType + (mw._rsnInstalled ? " \"" + mw._wanted[i].shipName + "\"" : "") + ")", 25) + this.$padTextLeft(formatCredits(fee, false, true), 6),
    				index:param,
    				shipIndex:shipIndex,
    				cost:fee
    			});
    			// remove the menu from SDC
    			var sdci = worldScripts.StationDockControl_Interface;
    			sdci.$removeExternalInterface({
    				pilotName:mw._wanted[i].name,
    				menuText:"Flag ship for Dock Master notification",
    				worldScript:"BountySystem_MostWanted",
    				callback:"$bounty_dockMaster_flag"
    			});
    			break;
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$cycleHistory = function $cycleHistory(dta) {
    	dta.lastVisibleHistory[2] = dta.lastVisibleHistory[1];
    	dta.lastVisibleHistory[1] = dta.lastVisibleHistory[0];
    	dta.lastVisibleHistory[0] = {updated:dta.updated, system:dta.lastVisibleSystem, destination:dta.lastVisibleDestination};
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$updateDisplayList = function $updateDisplayList() {
    	this._lastUpdate = clock.adjustedSeconds;
    	// update or add records
    	this._displayList.length = 0;
    	for (var i = 0; i < this._wanted.length; i++) {
    		var itm = this._wanted[i];
    		this._displayList.push({
    			lastVisibleSystem:itm.lastVisibleSystem, 
    			lastVisibleDestination:itm.lastVisibleDestination,
    			updated:itm.updated, 
    			departureTime:itm.departureTime,
    			lastVisibleHistory:itm.lastVisibleHistory,
    			pilotName:itm.pilotName, 
    			pilotDescription:itm.pilotDescription, 
    			shipName:itm.shipName, 
    			shipType:itm.shipType, 
    			bounty:itm.realBounty, 
    			index:itm.index});
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$translateStationName = function $translateStationName(name) {
    	var stns = system.stations;
    	for (var i = 0; i < stns.length; i++) {
    		if (stns[i].displayName === name) return stns[i];
    	}
    	return null;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns the amount of time it will take to travel between two systems
    this.$timeBetweenTwoPoints = function $timeBetweenTwoPoints(sys1, sys2, rtType) {
    	if (sys1 === sys2) return -1;
    	if (!rtType) {
    		var rtType = "OPTIMIZED_BY_JUMPS";
    	}
    	var info1 = System.infoForSystem(galaxyNumber, sys1);
    	var info2 = System.infoForSystem(galaxyNumber, sys2);
    	var rt = info1.routeToSystem(info2, rtType);
    	var result = rt.time * 3600
    	return result;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$customPathCleanup = function $customPathCleanup() {
    	for (var j = 0; j < this._customPath.length; j++) {
    		var found = false;
    		for (var i = 0; i < this._wanted.length; i++) {
    			var wanted = this._wanted[i];
    			if (wanted.movement == 4 && wanted.arrayIndex == j) {
    				found = true;
    			}
    		}
    		if (found === false && this._customPath[j] != null) {
    			if (this._debug) log(this.name, "cleaning up index " + j);
    			this._customPath[j] = null;
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // creates a custom path between two systems, returns custom path index if successful, otherwise -1
    this.$createCustomPath = function $createCustomPath(startID, endID, rtType) {
    	if (startID === endID) return -1;
    	if (!rtType) {
    		var rtType = "OPTIMIZED_BY_JUMPS";
    	}
    	var sys1 = System.infoForSystem(galaxyNumber, startID);
    	var sys2 = System.infoForSystem(galaxyNumber, endID);
    	var rt = sys1.routeToSystem(sys2, rtType);
    	if (rt && rt.route.length > 1) {
    		if (this._debug) log(this.name, "custom path route is valid " + startID + ", " + endID);
    		var list = [];
    		for (var i = 0; i < rt.route.length; i++) {
    			list.push(rt.route[i]);
    		}
    		var idx = -1;
    		for (var i = 0 ; i < this._customPath.length; i++) {
    			// look for any empty slots
    			if (this._customPath[i] == null) {
    				idx = i;
    				if (this._debug) log(this.name, "reusing custom path index " + idx + ", " + list);
    				this._customPath[i] = list;
    				break;
    			}
    		}
    		if (idx === -1) {
    			// no empty slots found, so add a new one
    			this._customPath.push(list);
    			idx = this._customPath.length - 1;
    			if (this._debug) log(this.name, "creating custom path index " + idx + ", " + list);
    		}
    		// return the index of the custom path
    		return idx;
    	} else {
    		if (this._debug) log(this.name, "custom path route is invalid " + startID + ", " + endID);
    		return -1;
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // loads the spacelane array with data for the current galaxy
    this.$getSpacelanes = function $getSpacelanes() {
    	this._spacelanes.length = 0;
    	switch (galaxyNumber) {
    		case 0:
    			this._spacelanes.push([141,250,111,186,50,73,35,188,250]);
    			this._spacelanes.push([7,129,227,73,89,222,29,93,42,131]);
    			this._spacelanes.push([131,62,150,198,36,28,16,221,99]);
    			this._spacelanes.push([99,241,86,126,18,246]);
    			break;
    		case 1:
    			this._spacelanes.push([82,23,188,23,54,74,122,57,115,178,45]);
    			this._spacelanes.push([45,144,189,140,65,48,204,193,221,236,66,58,88,127]);
    			this._spacelanes.push([127,109,42,114,202]);
    			this._spacelanes.push([127,207,136,6,24,53,78,48]);
    			this._spacelanes.push([205,59,100,9,156,92,61,41,179,189]);
    			this._spacelanes.push([113,115,57,94,29]);
    			break;
    		case 2:
    			this._spacelanes.push([38,182,246,91,22,140,5,56,177,150,26,75,144,209,17,218]);
    			this._spacelanes.push([50,65,79,154,38,139,189,135,42]);
    			this._spacelanes.push([205,241,101,132,6,180,164,70,10,85,81,225,81,85,10,70,202,53]);
    			this._spacelanes.push([140,58,152,76,77,97,168]);
    			this._spacelanes.push([106,250,245,160,245,22]);
    			this._spacelanes.push([118,161,54,165,129,186]);
    			break;
    		case 3:
    			this._spacelanes.push([116,230,167,48,210,36,150,84,240,193,223,118,197,135,85,154,213,162,166,213]);
    			this._spacelanes.push([16,42,172,40,80,65,144,105,119,208,189,1,174,146,100,233]);
    			this._spacelanes.push([50,239,103,108,103,177,17,177,103,45,105,137,95,54,77,76,21,2,249,113,141,113,97]);
    			this._spacelanes.push([209,33,62,25,134,58,138,191,78,57,242,234,245,181,82,117,53,74,38]);
    			this._spacelanes.push([71,79,68,110,220,158,215,63,44,66,146,174,1,4,121,145,142,6,142,11,7,161,140]);
    			this._spacelanes.push([7,32,232,216,60]);
    			break;
    		case 4:
    			this._spacelanes.push([41,216,112,230,28,102,181,35,180,24,19,156,1,45,79,172,169]);
    			this._spacelanes.push([140,137,173,70,7,253,243,135,193,225,184,150,115,201,189,227,226,221,57,202,135,202,57,221,226,227,189,115,11,54,158,175,85,178,204,12,215,212,158]);
    			this._spacelanes.push([194,8,210,41,222,183,254,149,130,108,82,92,29,121,34,183]);
    			this._spacelanes.push([114,118,2,116,244,248,65,146,153,10,99,91,200,244]);
    			this._spacelanes.push([120,9,233,104,136,129,139,163,61]);
    			this._spacelanes.push([249,16,20,39,93,207,131,74,182,252,188,40,146,153,146,40,188,252,182,166,53,221,220,225]);
    			this._spacelanes.push([30,89,87,89,152,170,88,194,77,246,106,190,208,167]);
    			break;
    		case 5:
    			this._spacelanes.push([84,3,8,88,206,136,144,13,92,99,85,133,40,215,115,83,246,39,35,114]);
    			this._spacelanes.push([169,51,80,103,243,98,102,16,109,101,67,142,60,209,233,112,225,131,210,12,150,91]);
    			this._spacelanes.push([104,54,162,148,211,6,181,17,108,28]);
    			this._spacelanes.push([156,113,14,216,82,177,116,154,120,50,232,124,215]);
    			this._spacelanes.push([10,77,159,226,31,203,30,234,240,234,140,159]);
    			break;
    		case 6:
    			this._spacelanes.push([103,58,253,65,39,44,236,150,247,193,179,133,215,95,109]);
    			this._spacelanes.push([194,73,146,239,86,74,199,23,138,18,234,244,166,152,162,242,184]);
    			this._spacelanes.push([218,130,125,205,79,191,17,178]);
    			this._spacelanes.push([211,7,163,67,209,250,19,239]);
    			this._spacelanes.push([46,36,212,187,53,107,174]);
    			break;
    		case 7:
    			this._spacelanes.push([114,12,241,39,23,38,123,130,167,63,136,8,44,91,56,62,186,93,32,61,244,243,116,46,157,230,151,213,198,148,49,76,203,255,161,98,53,1,145,33]);
    			this._spacelanes.push([215,2,211,9,13,147,216,225,21,218,71,206,236,190,18,26,152,51,209,24,174,82,179,173,30,251,233,125,45,41,104,110,73,135,117,55]);
    			this._spacelanes.push([126,6,81,65,169,232,252]);
    			this._spacelanes.push([75,219,248,117]);
    			this._spacelanes.push([10,72,219,248,117]);
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // loads the mulk run array with data for the current galaxy
    this.$getMilkRuns = function $getMilkRuns() {
    	this._milkRuns.length = 0;
    	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
    	}
    	// make sure we don't end up with nova systems!
    	for (var i = this._milkRuns.length - 1; i >= 0; i--) {
    		var sys1 = System.infoForSystem(galaxyNumber, this._milkRuns[i].id1);
    		var sys2 = System.infoForSystem(galaxyNumber, this._milkRuns[i].id2);
    		if (sys1.sun_gone_nova || sys2.sun_gone_nova) {
    			this._milkRuns.splice(i, 1);
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // calculates an array of safe systems with close by dangerous systems
    this.$calculateSafeDangerousSystems = function $calculateSafeDangerousSystems() {
    	this._safeDangerous.length = 0;
    	for (var i = 0; i < 256; i++) {
    		var data = {id:i, dangerous:[]};
    		var sys = System.infoForSystem(galaxyNumber, i);
    		// is this one of our safe systems (gov >= 5)
    		if (!sys.sun_gone_nova) {
    			if (sys.government >= 5) {
    				var locals = sys.systemsInRange(7);
    				// any dangerous systems in range?
    				for (var j = 0; j < locals.length; j++) {
    					var lcl = locals[j];
    					if (!lcl.sun_gone_nova && lcl.government <= 2) {
    						data.dangerous.push(lcl.systemID);
    					}
    				}
    			}
    			if (data.dangerous.length > 0) {
    				this._safeDangerous.push(data);
    				//if (this._debug) log(this.name, "SafeDangerous " + data.id + ", " + data.dangerous);
    			}
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // calculates a series of cross-chart possibilities for couriers
    // note: the chart 7 data will probably only have workable data in the [0] element (top left to lower right), 
    // because the large section of map in the lower left is cut off from the rest of the map
    this.$calculateCourierRoutes = function $calculateCourierRoutes() {
    	this._courierRoutes = [
    		{point1:[], point2:[]},
    		{point1:[], point2:[]}
    	];
    	for (var i = 0; i <= 255; i++) {
    		var sys = System.infoForSystem(galaxyNumber, i);
    		if (sys.coordinates.x < 31 && sys.coordinates.y < 16) this._courierRoutes[0].point1.push(i);
    		if (sys.coordinates.x > 71 && sys.coordinates.y > 36) this._courierRoutes[0].point2.push(i);
    		if (sys.coordinates.x < 31 && sys.coordinates.y > 36) this._courierRoutes[1].point1.push(i);
    		if (sys.coordinates.x > 71 && sys.coordinates.y < 16) this._courierRoutes[1].point2.push(i);
    	}
    	/*if (this._debug) {
    		log(this.name, "route 1 point1:" + this._courierRoutes[0].point1);
    		log(this.name, "route 1 point2:" + this._courierRoutes[0].point2);
    		log(this.name, "route 2 point1:" + this._courierRoutes[1].point1);
    		log(this.name, "route 2 point2:" + this._courierRoutes[1].point2);
    	}*/
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // populates the randomShips array with ship names (eg "Cobra Mark III", "Asp Mark II" etc) and their associated role (eg "trader")
    // because most of the time we will be working with a data array, and not an actual ship object, we need a way to link
    // roles to ship types and ship keys. this routine re-compiles the list of possibilties which we can then use throughout the code.
    // the result will be: each role and ship type will have one entry in the list, and the shipKeys element will have a list of
    // possible versions of that ship.
    this.$loadShipNames = function $loadShipNames() {
    	//-------------------------------------------------------------------------------------------------------------
    	// returns the role frequency out of a data string
    	function getRoleFrequency(roleName, roleData) {
    		var point1 = roleData.indexOf(roleName) + roleName.length;
    		var freq = "1"; // default of 1 (ie there is no bracket value)
    		if (roleData.charAt(point1) === "(") {
    			// extract the percentage
    			var point2 = roleData.indexOf(")", point1);
    			freq = roleData.substring(point1 + 1, point2);
    		}
    		return freq;
    	}
    
    	// compile a list of ships/roles
    	this._randomShips.length = 0;
    	var cr = this._controlledRoles;
    	var excl = this._bountyShipKeyExclusions;
    	for (var i = 0; i < cr.length; i++) {
    		var shipKeys = Ship.keysForRole(cr[i]);
    		var freq = "";
    		if (shipKeys) {
    			for (var j = 0; j < shipKeys.length; j++) {
    				var key = shipKeys[j];
    				// don't include any of these keys
    				if (excl.indexOf(key) >= 0) continue;
    
    				var include = false;
    				var shipdata = Ship.shipDataForKey(key);
    				// only alllow ships that don't have any spawning restrictions
    				// as we could be anywhere when we need to spawn them
    				if (shipdata.conditions) {
    					include = false;
    				} else if (shipdata.condition_script) {
    					include = false;
    				} else {
    					// otherwise we're free to play
    					include = true;
    				}
    
    				if (include) {
    					freq = getRoleFrequency(cr[i], shipdata.roles);
    					// make sure we don't load our "dock" versions of ships
    					if (key.indexOf("dock_") === -1 && key.indexOf("bounty_") === -1) {
    						if (this.$objIsInArray(cr[i], this._randomShips) === false) {
    							this._randomShips.push({role:cr[i], shipKeys:key, frequency:freq});
    						} else {
    							this.$addShipKeyToShipType(cr[i], key, freq);
    						}
    					}
    				}
    			}
    		} else {
    			log(this.name, "!!NOTE: No ship keys found for role " + cr[i]);
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // adds a key/frequency to a particular role. The key will be used for generating ships on launch
    this.$addShipKeyToShipType = function $addShipKeyToShipType(role, shipKey, freq) {
    	for (var i = 0; i < this._randomShips.length; i++) {
    		if (this._randomShips[i].role === role) {
    			this._randomShips[i].shipKeys += "," + shipKey;
    			this._randomShips[i].frequency += "," + freq;
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns a pilot object
    this.$createPilot = function $createPilot(home) {
    	var pilot = {};
    	// create the pilot
    	pilot["name"] = this.$generateName(); //expandDescription("%N [nom]");
    	pilot["description"] = "a " + System.infoForSystem(galaxyNumber, home).inhabitant.toLowerCase() + " from " + System.systemNameForID(home); // check
    	pilot["homeSystem"] = home;
    	pilot["species"] = System.infoForSystem(galaxyNumber, home).inhabitant.toLowerCase();
    
    	return pilot;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // select a random ship type key (eg "noshaders_z_groovy_cobra1_india_npc") for a particular the role (eg "trader")
    this.$getRandomShipKey = function $getRandomShipKey(role) {
    
    	var keylist = "";
    	var freqlist = "";
    	for (var i = 0; i < this._randomShips.length; i++) {
    		if (this._randomShips[i].role === role) {
    			keylist = this._randomShips[i].shipKeys;
    			freqlist = this._randomShips[i].frequency;
    			break;
    		}
    	}
    
    	if (keylist === "") {
    		// should never happen...
    		return "";
    	}
    
    	// the number of elements in these two arrays should match
    	var items = keylist.split(",");
    	var freq = freqlist.split(",");
    	var choice = -1;
    
    	do {
    		choice = Math.floor(Math.random() * items.length);
    		// will we keep this choice or reset it?
    		if (Math.random() > parseFloat(freq[choice])) choice = -1;
    	} while (choice === -1);
    
    	var bountyVer = false;
    	var prefix = "bounty_";
    	// use SDC's versions if installed
    	if (this._sdcInstalled) {
    		if (worldScripts.StationDockControl.$dockVersionExists(items[choice])) {
    			prefix = "dock_";
    			bountyVer = true;
    		}
    	} else {
    		if (this.$bountyVersionExists(items[choice])) bountyVer = true;
    	}
    
    	return (bountyVer === true ? prefix : "") + items[choice];
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // use randomshipnames OXP to generate ship names (if installed) - otherwise just return a blank string (no name)
    this.$getRandomShipName = function $getRandomShipName(role) {
    
    	var randomShipName = "";
    
    	if (this._rsnInstalled) {
    		if (player.ship) {
    			try {
    				if (Ship.roleIsInCategory(role, "oolite-bounty-hunter") || Ship.roleIsInCategory(role, "oolite-assassin") )
    					randomShipName = worldScripts["randomshipnames"].$randomHunterName(player.ship);
    				else if (Ship.roleIsInCategory(role, "oolite-pirate"))
    					randomShipName = worldScripts["randomshipnames"].$randomPirateName(player.ship);
    				// catch everything else as a trader
    				if (randomShipName === "")
    					randomShipName = worldScripts["randomshipnames"].$randomTraderName(player.ship);
    			}
    			catch (err) {
    				log(this.name, "!!ERROR: Unable to get ship name from RSN: " + err);
    			}
    		}
    		if (randomShipName === "" || randomShipName == null) randomShipName = this.$phraseGenShipName();
    	}
    
    	return randomShipName;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // if getRandomShipName fails to generate a name, create one using phrasegen
    this.$phraseGenShipName = function $phraseGenShipName() {
    	var mwpg = worldScripts.GNN_PhraseGen; //mw_Cabal_Common_PhraseGen;
        var text = mwpg._makePhrase(mwpg.$pool["GNN_NamesShips"]);
    	return text;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns the ship type (eg Cobra MkIII) for a given data key
    this.$getShipType = function $getShipType(dataKey) {
    	var info = Ship.shipDataForKey(dataKey);
    	if (info) {
    		return info.name;
    	} else {
    		return "";
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // checks if the role exists in our array of ships
    this.$roleExists = function $roleExists(role) {
    	var found = false;
    	for (var i = 0; i < this._randomShips.length; i++) {
    		if (this._randomShips[i].role === role) {
    			found = true;
    			break;
    		}
    	}
    	return found;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // checks if the role/ship combinations exists in the array
    this.$objIsInArray = function $objIsInArray(role, array) {
    	if (array != null && array.length > 0) {
    		for (var i = 0; i < array.length; i++) {
    			if (array[i].role === role) return true;
    		}
    	}
    	return false;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // writes the details of the selected record to the log
    this.$writeDataToLog = function $writeDataToLog(record, reason) {
    
    	log(this.name, "=========================================================");
    	log(this.name, reason + ":");
    	log(this.name, "---------------------------------------------------------");
    
    	log(this.name, "Pilot name:         " + record.pilotName);
    	log(this.name, "Pilot description:  " + record.pilotDescription);
    	log(this.name, "Pilot home system:  " + record.pilotHomeSystem);
    	log(this.name, "Pilot species:      " + record.pilotSpecies);
    	log(this.name, "Current system:     " + record.system);
    	log(this.name, "Destination:        " + record.destinationSystem);
    	log(this.name, "Dock time:          " + clock.clockStringForTime(record.dockTime));
    	log(this.name, "Departure time:     " + clock.clockStringForTime(record.departureTime));
    	log(this.name, "Ship name:          " + record.shipName);
    	log(this.name, "Ship type:          " + record.shipType);
    	log(this.name, "Ship dataKey:       " + record.shipDataKey);
    	log(this.name, "Ship personality:   " + record.personality);
    	log(this.name, "Primary role:       " + record.primaryRole);
    	log(this.name, "AI name:            " + record.shipAI);
    	log(this.name, "Accuracy:           " + record.accuracy);
    	//log(this.name, "Equipment:          " + record.equipment);
    	log(this.name, "Bounty:             " + record.realBounty);
    	log(this.name, "Escorts:            " + record.escorts);
    	log(this.name, "Escort role:        " + record.escortsRole);
    	log(this.name, "Array index:        " + record.arrayIndex);
    	log(this.name, "Movement:           " + record.movement);
    	log(this.name, "Last visible system:" + record.lastVisibleSystem);
    	log(this.name, "Last visible dest:  " + record.lastVisibleDestination);
    	log(this.name, "Updated:            " + clock.clockStringForTime(record.updated));
    	log(this.name, "Update chance:		" + record.updateChance);
    	log(this.name, "---------------------------------------------------------");
    
    	if (record.system === system.ID || record.destinationSystem === system.ID) {
    		if (this._debug) log(this.name, "!!NOTE: Ship is in or coming to current system!");
    	}
    
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$wormholePos = function $wormholePos() {
    	var v = Vector3D.randomDirection().multiply(2000 + Math.random() * 3000);
    	if (v.z < 0 && Math.abs(v.x) + Math.abs(v.y) < 1000) {
    		v.z = -v.z; // avoid collision risk with witchbuoy
    	}
    	return v;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // pushes details from the data array onto a launched ship
    this.$updateShip = function $updateShip(ship, shipData) {
    
    	var scn = worldScripts.BountySystem_WarrantScanner;
    	scn.$setBounty(ship, shipData.realBounty);
    
    	// push all the stored data onto the ship;
    	if (oolite.compareVersion("1.80") < 0) {
    		ship.entityPersonality = shipData.personality; // can only do this in Oolite 1.81 or greater
    	}
    	ship.primaryRole = shipData.primaryRole;
    	ship.accuracy = shipData.accuracy;
    	ship.heatInsulation = shipData.heatInsulation;
    	ship.setBounty(shipData.bounty, "setup actions");
    	ship.shipUniqueName = shipData.shipName;
    	ship.homeSystem = shipData.pilotHomeSystem;
    
    	ship.destinationSystem = shipData.destinationSystem;
    	ship.fuel = shipData.fuel;
    
    	// set the seed values so that the pilot's species can be defined by their home system
    	var seed = "0 0 0 0 " + shipData.pilotHomeSystem.toString() + " 2";
    
    	// todo: check what happens to "species"
    	if (shipData.pilotDescription != "") {
    		ship.setCrew({name:shipData.pilotName, origin:shipData.pilotHomeSystem,
    			bounty:0, short_description:shipData.pilotDescription, random_seed:seed});
    	} else {
    		ship.setCrew({name:shipData.pilotName, origin:shipData.pilotHomeSystem,
    			bounty:0, random_seed:seed});
    	}
    
    	// if we have goods defined (but not 'cargo')
    	if (shipData.goods != "" && (!shipData.cargo || shipData.cargo === "")) {
    		// put some cargo in the hold
    		ship.setCargoType(shipData.goods);
    	}
    
    	if (shipData.equipment != "") {
    		// process all the equipment additions
    		var items = shipData.equipment.split(",");
    		for (var j = 0; j < items.length; j++) {
    			var itm = items[j];
    			if (itm.indexOf(":") >= 0) {
    				var subitems = itm.split(":");
    				switch (subitems[0]) {
    					case "X":
    						ship.removeEquipment(subitems[1]);
    						break;
    					case "FORE":
    						if (ship.autoWeapons === true) ship.forwardWeapon = subitems[1];
    						break;
    					case "AFT":
    						if (ship.autoWeapons === true) ship.aftWeapon = subitems[1];
    						break;
    					case "PORT":
    						if (ship.autoWeapons === true) ship.portWeapon = subitems[1];
    						break;
    					case "STARBOARD":
    						if (ship.autoWeapons === true) ship.starboardWeapon = subitems[1];
    						break;
    				}
    			} else {
    				if (itm != "") {
    					ship.awardEquipment(itm);
    				}
    			}
    		}
    	}
    
    	// set the ai of the ship (if required)
    	if (shipData.shipAI != "") ship.switchAI(shipData.shipAI);
    
    	ship.script._mostWanted = shipData.index;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // return the display list entry for a particular index
    this.$getDisplayData = function $getDisplayData(idx) {
    	for (var i = 0; i < this._displayList.length; i++) {
    		if (this._displayList[i].index === idx) return this._displayList[i];
    	}
    	return null;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$isPending = function $isPending(idx) {
    	for (var i = 0; i < this._wanted.length; i++) {
    		if (this._wanted[i].index === idx) return this._wanted[i].pending;
    	}
    	return false;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$newItemIndex = function $newItemIndex() {
    	var idx = 1;
    	for (var i = 0; i < this._wanted.length; i++) {
    		if (this._wanted[i].index >= idx) idx = this._wanted[i].index + 1;
    	}
    	return idx;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // returns true if a "bounty_shipkey" entry exists in the shipdata.plist file. Otherwise false
    // used when launching/adding ships to the system to ensure that only 1 ship is created, without escorts
    // escorts are created manually
    this.$bountyVersionExists = function $bountyVersionExists(shipKey) {
    	var shipdata = Ship.shipDataForKey("bounty_" + shipKey);
    	if (shipdata) {
    		return true;
    	} else {
    		return false;
    	}
    }
    
    this.$testData = function $testData() {
    	// set up a dummy type 3 
    	var syslist = system.info.systemsInRange(4);
    	var list = [];
    	for (var i = 0; i < syslist.length; i++) {
    		list.push(syslist[i].systemID);
    	}
    	this._safeDangerous.push({id:system.ID, dangerous:list});
    
    	// create a record
    	var s_type = 4;
    	var dta = {};
    	dta.index = this.$newItemIndex();
    	// bounty between 250 and 15200
    	dta.realBounty = parseInt(Math.random() * 100 + 250) + ((parseInt(Math.random() * 250) + 200) * Math.pow(3, s_type - 4));
    	dta.bounty = 60 + (Math.floor(Math.random() * 8)) + Math.floor(Math.random() * 8);
    	dta.pending = false;
    	// build a ship
    	var def_role = "pirate";
    	var role = "";
    	switch (s_type) {
    		case 4:
    			role = "pirate";
    			dta.escortsRole = "pirate";
    			break;
    		case 5:
    			role = "pirate-light-freighter";
    			dta.escortsRole = "pirate-light-fighter";
    			break;
    		case 6:
    			role = "pirate-medium-freighter";
    			dta.escortsRole = "pirate-medium-fighter";
    			break;
    		case 7:
    			role = "pirate-heavy-freighter";
    			dta.escortsRole = "pirate-heavy-fighter";
    			break;
    	}
    	if (this.$roleExists(role) === false) role = def_role;
    	dta.shipAI = "oolite-pirateFreighterAI.js";
    	var found = false;
    	do {
    		dta.shipDataKey = this.$getRandomShipKey(role);
    		var shipPlist = Ship.shipDataForKey(dta.shipDataKey);
    		dta.shipType = shipPlist.name;
    		// make sure this is a serious pirate (not a bug or hognose)
    		if (shipPlist.max_cargo >= 20 + (s_type - 4) * 20) found = true;
    	} while (found === false);
    
    	dta.shipName = this.$getRandomShipName(role);
    	dta.personality = Math.floor(Math.random() * 32767);
    	dta.primaryRole = role;
    	dta.accuracy = s_type;
    	dta.heatInsulation = 2;
    	switch (s_type) {
    		case 4:
    			dta.equipment = "FORE:EQ_WEAPON_MILITARY_LASER";
    			break;
    		case 5:
    			dta.equipment = "FORE:EQ_WEAPON_BEAM_LASER,AFT:EQ_WEAPON_BEAM_LASER,EQ_CLOAKING_DEVICE";
    			break;
    		case 6:
    			dta.equipment = "FORE:EQ_WEAPON_MILITARY_LASER,AFT:EQ_WEAPON_BEAM_LASER,EQ_CLOAKING_DEVICE";
    			break;
    		case 7:
    			dta.equipment = "FORE:EQ_WEAPON_MILITARY_LASER,AFT:EQ_WEAPON_MILITARY_LASER,EQ_CLOAKING_DEVICE";
    			break;
    	}
    	dta.equipment += ",EQ_ESCAPE_POD";
    	dta.escorts = ((s_type - 3) * 2); // 2, 4, 6 or 8
    	this.$addEscortsToData(dta);
    
    	// create a pilot, picking a random system as home
    	var pilot = this.$createPilot(Math.floor(Math.random() * 256));
    	dta.pilotDescription = pilot.description;
    	dta.pilotName = pilot.name;
    	dta.pilotHomeSystem = pilot.homeSystem;
    	dta.pilotSpecies = pilot.species;
    
    	// where are they now, and where are they headed?
    	// pick a safe/dangerous element, start them in the safe one
    	dta.arrayIndex = this._safeDangerous.length - 1;
    	dta.movement = 3; // star-type movement (if there is more than 1 dangerous system against the safe one, otherwise same as a milkrun)
    	var sd = this._safeDangerous[dta.arrayIndex];
    	dta.system = sd.id; //sd.dangerous[0];
    	dta.destinationSystem = sd.dangerous[0]; //sd.id;
    
    	// when did they dock?
    	dta.dockTime = clock.adjustedSeconds - 3600; // (3600 * 10.5);
    	// when will they leave dock?
    	dta.departureTime = clock.adjustedSeconds + (3600 * 4.5); //(3600 * 8.5);
    	// arrival time in new system will be departureTime + travelTime(time between systems) + extraTime(random flight time)
    	// is their current position visible to GalCop?
    	dta.updateChance = (s_type - 3) / 5;
    	if (Math.random() > 0) {
    		dta.lastVisibleSystem = dta.system;
    		dta.lastVisibleDestination = dta.destinationSystem;
    		dta.updated = dta.dockTime;
    	} else {
    		dta.lastVisibleSystem = -1;
    	}
    	dta.lastVisibleHistory = [2];
    
    	if (this._debug) this.$writeDataToLog(dta, "Creating special record");
    	this._wanted.push(dta);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // determines whether there is room to launch a certain number of ships
    this.$launchAvailable = function $launchAvailable(station, ships) {
    	if (station) {
    		var subent = station.subEntities;
    		var space = 0;
    		var queued = 0;
    		if (subent) {
    			for (var i = 0; i < subent.length; i++) {
    				if (subent[i].isDock) {
    					space += 16 - subent[i].launchingQueueLength;
    					queued += subent[i].launchingQueueLength;
    				}
    			}
    		} else {
    			if (this._debug) log(this.name, "!NOTE: $launchAvailable -- subent is null (no subentities on station), could not find dock, returning false");
    		}
    		if (queued === 0 && space === 0 && this._debug) log(this.name, "!NOTE: $launchAvailable -- no queued ships but space is 0 - no docks found?");
    		return (space >= ships);
    	} else {
    		if (this._debug) log(this.name, "!ERROR: $launchAvailable -- station is null, defaulting to false");
    		return false;
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$updateManifest = function $updateManifest() {
    	var b = worldScripts.BountySystem_Core;
    	var markDeleted = [];
    	var textData = [];
    	if (this._constantUpdates === true) this.$updateDisplayList();
    	textData.push("Most Wanted:");
    	for (var i = 0; i < this._missionList.length; i++) {
    		var dta = this.$getDisplayData(this._missionList[i]);
    		if (dta) {
    			var sys = System.infoForSystem(galaxyNumber, dta.lastVisibleSystem);
    			var rt = system.info.routeToSystem(sys, player.ship.routeMode);
    			var rt2 = sys.routeToSystem(System.infoForSystem(galaxyNumber, dta.lastVisibleDestination), dta.routeMode);
    			var rt3 = system.info.routeToSystem(System.infoForSystem(galaxyNumber, dta.lastVisibleDestination), player.ship.routeMode);
    			var dist = 0;
    			var jmps = 0;
    			if (rt) {
    				if (player.ship.equipmentStatus("EQ_HUNTER_LICENCE") === "EQUIPMENT_OK" && dta.departureTime < clock.adjustedSeconds && rt3) {
    					dist = rt3.distance;
    					jmps = rt3.route.length - 1;
    				} else {
    					dist = rt.distance; 
    					jmps = rt.route.length - 1;
    				}
    			} else {
    				dist = system.info.distanceToSystem(sys);
    			}
    
    			//(this.$isPending(dta.index) ? "• " : "") + 
    			var text = dta.pilotName + ", bounty of " + formatCredits(dta.bounty, false, true) + ", flying a " + dta.shipType + 
    				(this._rsnInstalled === true ? " called \"" + dta.shipName + "\"" : "") + ", ";
    			
    			if (player.ship.equipmentStatus("EQ_HUNTER_LICENCE") === "EQUIPMENT_OK" && dta.departureTime < clock.adjustedSeconds && rt3) {
    				var arr = -1;
    				// if there is a route to the target, calc their arrival time
    				if (rt2) {
    					arr = dta.departureTime + (rt2.time * 3600);
    				}
    				text += "due in " + System.systemNameForID(dta.lastVisibleDestination) + " " + 
    					"(" + dist.toFixed(1) + "ly" + (jmps > 0 ? ", " + jmps + " jump" + (jmps > 1 ? "s" : "") : "") + ") ";
    
    				// if there is a route to the target, add some additional info to the manifest
    				if (arr >= 0) {
    					if (arr > clock.adjustedSeconds) {
    						text += "in " + b.$getTimeSince(clock.adjustedSeconds - (arr - clock.adjustedSeconds), false) + ".";
    					} else {
    						text += b.$getTimeSince(arr, false) + " ago.";
    					}
    				}
    			} else {
    				text += "last seen in " + System.systemNameForID(dta.lastVisibleSystem) + " " + 
    				"(" + dist.toFixed(1) + "ly" + (jmps > 0 ? ", " + jmps + " jump" + (jmps > 1 ? "s" : "") : "") + ") " +
    				b.$getTimeSince(dta.updated, false) + " ago.";
    			}
    
    			// make sure the mission text will fit on the display by breaking up the text into screen-width columns
    			var coltext = b.$columnText(text, 30);
    			for (var j = 0; j < coltext.length; j++) {
    				textData.push((j === 0 ? "" : " ") + coltext[j]);
    			}
    		} else {
    			// if we don't get any data back, mark this record for deletion
    			markDeleted.push(i);
    		}
    	}
    	// update the manifest
    	if (textData.length === 1) {
    		mission.setInstructions(null, this.name);
    	} else {
    		mission.setInstructions(textData, this.name);
    	}
    	// clean up any deleted records
    	for (var i = 0; i < markDeleted.length; i++) {
    		this._missionList.splice(markDeleted[i], 1);
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$generateName = function $generateName() {
    	var mwpg = worldScripts.GNN_PhraseGen; //mw_Cabal_Common_PhraseGen;
        var text = mwpg._makePhrase(mwpg.$pool["GNN_Names"]);
    	return text;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$askWaitTime = function $askWaitTime() {
    	var text = expandDescription("[mw_waittime_question]");
    	var curChoices = {};
    	for (var i = 1; i <= 12; i++) {
    		curChoices["02_WAIT_" + (i < 10 ? "0" : "") + i + "~" + i] = {text:"Wait " + i + " hour" + (i === 1 ? "" : "s"), color:this._menuColor}
    	}
    	var def = "99_CLOSE";
    	curChoices["99_CLOSE"] = {text:"[galcop-exit]", color:this._exitColor};
    	
    	var opts = {
    		screenID: "oolite-galcopsecoffice-wait-map",
    		title: "Most Wanted - Details",
    		overlay: {name:"mw-clock.png", height:546},
    		allowInterrupt: false,
    		exitScreen: "GUI_SCREEN_INTERFACES",
    		choices: curChoices,
    		initialChoicesKey: def,
    		message: text
    	};
    	mission.runScreen(opts, this.$askWaitHandler, this);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$askWaitHandler = function $askWaitHandler(choice) {
    	if (!choice) return;
    	
    	if (choice.indexOf("02_WAIT") >= 0) {
    		var wait = parseInt(choice.substring(choice.indexOf("~") + 1));
    		clock.addSeconds(wait * 3600);
    		// if the station dock control is installed, tell it to check for launched ships
    		if (worldScripts.StationDockControl) {
    			var w = worldScripts.StationDockControl;
    			if (w._disable === false) {
    				w.$checkForLaunchedShips();
    			}
    		}
    		this.dayChanged();
    		player.consoleMessage(wait + " hour" + (wait === 1 ? " has" : "s have") + " elapsed.");
    	}
    
    	worldScripts.BountySystem_Core.$showSecOffice();
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // by cag for speed improvements instead of using "indexOf"
    this.$indexInList = function $indexInList(item, list) { // for arrays only
        var k = list.length;
        while( k-- ) {
            if( list[k] === item ) return k;
        }
        return -1;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // 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);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    // creates/updates hunter tips array
    this.$createTips = function $createTips(pendingOnly) {
    	for (var i = 0; i < this._wanted.length; i++) {
    		var item = this._wanted[i];
    		if ((item.pending === true && (pendingOnly === true || Math.random() > (Math.random() * 0.3))) || (pendingOnly === false && Math.random() > (Math.random() * 0.5 + 0.5))) {
    			var tipster = this._hunterTipSources[Math.floor(Math.random() * this._hunterTipSources.length)];
    			// will this be an accurate tip?
    			var accurate = (Math.random() <= tipster.reliability ? true : false);
    			if (item.pending === true) {
    				// find the pending record
    				for (var j = 0; j < this._pending.length; j++) {
    					var pend = this._pending[j];
    					if (pend.index === item.index) {
    						var txt = "";
    						var tiptyp = 0;
    						var sysName = system.name;
    						if (accurate) {
    							switch (pend.type) {
    								case "docked":
    									tiptyp = 2;
    									txt = this.$generateTip(2, item.pilotName, item.shipType, sysName, (pend.station.indexOf("Rock") === -1 ? "the main station" : "a Rock Hermit"));
    									break;
    								case "witchpoint":
    									tiptyp = 1;
    									txt = this.$generateTip(1, item.pilotName, item.shipType, sysName, "");
    									break;
    							}
    						} else {
    							// create an inaccurate wp or docked message (it might still end up being accurate, but we won't know for sure)
    							var stn = "";
    							if (Math.random() > tipster.reliability) {
    								var sysList = System.infoForSystem(galaxyNumber, item.system).systemsInRange(7);
    								sysName = sysList[Math.floor(Math.random() * sysList.length)].name;
    							}
    							if (Math.random() > 0.5) {
    								// docked
    								stn = (Math.random() > 0.5 ? "the main station" : "a Rock Hermit");
    								tiptyp = 2;
    							} else {
    								// wp
    								tiptyp = 1;
    							}
    							txt = this.$generateTip(tiptyp, item.pilotName, item.shipType, sysName, stn);
    						}
    						if (this.$tipCountForBounty(item.index, tiptyp) > 2) continue;
    						if (this._debug) log(this.name, "checking pending " + tiptyp + " " + pend.type + " - " + txt);
    						// create message
    						this._hunterTips.push({
    							createdDate:clock.adjustedSeconds - (Math.floor(Math.random() * 21600)), 
    							text:txt,
    							bountyName:item.pilotName,
    							index:item.index,
    							source:tipster.name,
    							system:system.ID,
    							expireDate:clock.adjustedSeconds + (86400 * 5),
    							type:tiptyp
    						});
    						break;
    					}
    				}
    			} else {
    				var id = -1;
    				if (accurate) {
    					// create an accurate ship spotted message
    					id = item.system;
    				} else {
    					// create an inaccurate ship spotted message
    					var sysList = System.infoForSystem(galaxyNumber, item.system).systemsInRange(7);
    					id = sysList[Math.floor(Math.random() * sysList.length)].ID;
    				}
    				var txt = this.$generateTip(0, item.pilotName, item.shipType, System.systemNameForID(id), "")
    				if (this._debug) log(this.name, "checking non pending " + txt);
    				// create message
    				this._hunterTips.push({
    					createdDate:item.dockTime + (Math.floor(Math.random() * 21600)),
    					text:txt,
    					bountyName:item.pilotName,
    					index:item.index,
    					source:tipster.name,
    					system:id,
    					expireDate:item.dockTime + (86400 * 5),
    					type:0,
    				});
    			}
    		}
    	}
    	// Finally, delete any tips older than 5 days
    	for (var i = this._hunterTips.length - 1; i >= 0; i--) {
    		if ((clock.adjustedSeconds - this._hunterTips[i].createdDate) / 86400 > 5) {
    			this._hunterTips.splice(i, 1);
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$tipCountForBounty = function $tipCountForBounty(idx, type) {
    	var count = 0;
    	for (var i = 0; i < this._hunterTips.length; i++) {
    		if (this._hunterTips[i].index === idx && this._hunterTips[i].type === type) count += 1;
    	}
    	return count;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$removeTipForBounty = function $removeTipForBounty(idx, type) {
    	for (var i = this._hunterTips.length -1; i >= 0; i--) {
    		if (this._hunterTips[i].index === idx && this._hunterTips[i].type === type) {
    			this._hunterTips.splice(i, 1);
    			return;
    		}
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$createTipSources = function $createTipSources() {
    	var tot = Math.floor(Math.random() * 20 + 10);
    	for (var i = 0; i < tot; i++) {
    		this._hunterTipSources.push({name:this.$generateName(), reliability:Math.random() * 30 + 70});
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$generateTip = function $generateTip(tipType, bountyName, shipType, systemName, stationName) {
    	switch (tipType) {
    		case 0: // seen in location
    			var def = {
    				fieldA: ["I have it on good authority that","There are rumours that","Information has come to light indicating","My sources are telling me that","News from the underground says that","According to my best sources,","FYI,","News flash:","A word to the wise:","For those who are interested,","It's come to my attention that","Be aware that","It's probably worth noting that"],
    				fieldB: ["bandit","brigand","criminal","crook","desperado","fugitive","gangster","hijacker","hooligan","lawbreaker","marauder","mobster","murderer","psychopath","sinner","sociopath","outlaw","pirate","plunderer","racketeer","raider","ravager","renegade","robber","vagabond","villain"],
    				fieldC: ["seen","spotted","identified","noticed","recognised","observed","glimpsed","spied"],
    				fieldD: [" recently"],
    				fieldE: ["flying around in a","cruising around in a","flying a","flying in a"],
    				fieldF: [" tricked out"," shiny","n upgraded"," renovated","n above-spec","n over-clocked","n illegally modified"," fully kitted out","n iron-ass"," heavily modified"," seriously upgraded"," ferocious"],
    				fieldG: [", according to [8]",", based on [8]"],
    				fieldH: ["recent information","the latest information","data from reliable sources",],
    				fieldI: ["the \\[system\\] system","\\[system\\]"],
    				fieldJ: ["During a recent [7]","On a recent [7]","While I was on a recent [7]"],
    				fieldK: ["bandit","brigand","criminal","crook","desperado","fugitive","gangster","hijacker","hooligan","lawbreaker","marauder","mobster","murderer","psychopath","sinner","sociopath","outlaw","pirate","plunderer","racketeer","raider","ravager","renegade","robber","vagabond","villain"],
    				fieldL: ["through","in"],
    				fieldM: ["see","spot","notice","identify","recognise","observe","glimpse","spy"],
    				fieldN: ["happened to [4]","[6]"],
    				fieldO: ["saw","spotted","identified","noticed","recognised","observed","glimpsed","spied"],
    				fieldP: ["trip","journey","voyage"],
    				fieldQ: ["on my way","passing","going","travelling","heading"],
    				fieldR: [],
    				sentences: [
    					"1 (D50=the [2] |)\\[name\\] was4 3 in 9, 56 \\[shiptype\\].",
    					"1 (D50=the [2] |)\\[name\\], 56 \\[shiptype\\], was4 3 in 9.",
    					"1 (D50=the [2] |)\\[name\\] was4 3 in 9.",
    					"1 (D50=the [2] |)\\[name\\] was 34 in 9, 56 \\[shiptype\\].",
    					"1 (D50=the [2] |)\\[name\\], 56 \\[shiptype\\], was 34 in 9.",
    					"1 (D50=the [2] |)\\[name\\] was 34 in 9.",
    					"(D60=the [2] |)\\[name\\] has been4 3 in 9, 56 \\[shiptype\\].",
    					"(D60=the [2] |)\\[name\\], 56 \\[shiptype\\], has been4 3 in 9.",
    					"(D60=the [2] |)\\[name\\] has been4 3 in 9.",
    					"(D60=the [2] |)\\[name\\] has been 34 in 9, 56 \\[shiptype\\].",
    					"(D60=the [2] |)\\[name\\], 56 \\[shiptype\\], has been 34 in 9.",
    					"(D60=the [2] |)\\[name\\] has been 34 in 9.",
    					"(D60=the [2] |)\\[name\\] has been4 3 in 97, 56 \\[shiptype\\].",
    					"(D60=the [2] |)\\[name\\], 56 \\[shiptype\\], has been4 3 in 97.",
    					"(D60=the [2] |)\\[name\\] has been4 3 in 97.",
    					"(D60=the [2] |)\\[name\\] has been 34 in 97, 56 \\[shiptype\\].",
    					"(D60=the [2] |)\\[name\\], 56 \\[shiptype\\], has been 34 in 97.",
    					"(D60=the [2] |)\\[name\\] has been 34 in 97.",
    					"{1 3 }9, I {5 (D50=the [2] |)\\[name\\], }56 \\[shiptype\\].",
    					"I was {8 through }94, and {5 (D50=the [2] |)\\[name\\], }56 \\[shiptype\\]."
    				]
    			};
    			break;
    		case 1: // witchpoint
    			var def = {
    				fieldA: ["I have it on good authority that","There are rumours that","Information has come to light indicating","My sources are telling me that","News from the underground says that","According to my best sources","FYI,","News flash:","A word to the wise:","For those who are interested,","It's come to my attention that","Be aware that","It's probably worth noting that"],
    				fieldB: ["is due to [3]","is expected to [3]","will be [4]","is due in","is on their way to","is expected in","is heading for"],
    				fieldC: ["jump into","jet into","witchjump into","head into","arrive in"],
    				fieldD: ["jumping into","jetting into","witchjumping into","heading for","arriving in"],
    				fieldE: ["bandit","brigand","criminal","crook","desperado","fugitive","gangster","hijacker","hooligan","lawbreaker","marauder","mobster","murderer","psychopath","sinner","sociopath","outlaw","pirate","plunderer","racketeer","raider","ravager","renegade","robber","vagabond","villain"],
    				fieldF: [" tricked out"," shiny","n upgraded"," renovated","n above-spec","n over-clocked","n illegally modified"," fully kitted out","n iron-ass"," heavily modified"," seriously upgraded"," ferocious"],
    				fieldG: [", according to [8]"],
    				fieldH: ["recent information","the latest information","reliable sources"],
    				fieldI: ["the \\[system\\] system","\\[system\\]"],
    				fieldJ: ["real soon","any time now","really soon","at any moment","in next to no time","faster than you can say \"[2]\""],
    				fieldK: ["Braben lied","It's a trap","That's one doomed space marine","It's not my fault","Use the force, Luke","Punch it","We're going in full throttle","I thought they smelled bad on the outside","Geronimo","Phasers on stun","Kobayashi Maru"],
    				fieldL: ["in a","flying a","piloting a"],
    				fieldM: [],
    				fieldN: [],
    				fieldO: [],
    				fieldP: [],
    				fieldQ: [],
    				fieldR: [],
    				sentences: [
    					"1 (D50=the [5] |)\\[name\\] 2 9 {1, 3}6 \\[shiptype\\].",
    					"1 (D50=the [5] |)\\[name\\] 2 9 {1, 3 \\[shiptype\\].",
    					"1 (D50=the [5] |)\\[name\\], {3}6 \\[shiptype\\], 2 9 {1.",
    					"1 (D50=the [5] |)\\[name\\], {3 \\[shiptype\\], }2 9 {1.",
    					"1 (D50=the [5] |)\\[name\\] 2 9 {1.",
    					"(D60=the [5] |)\\[name\\] 2 97, {3}6 \\[shiptype\\].",
    					"(D60=the [5] |)\\[name\\] 2 97, {3 \\[shiptype\\].",
    					"(D60=the [5] |)\\[name\\], {3}6 \\[shiptype\\], 2 97.",
    					"(D60=the [5] |)\\[name\\], {3 \\[shiptype\\], }2 97.",
    					"(D60=the [5] |)\\[name\\] 2 97.",
    					"(D60=the [5] |)\\[name\\] 2 9 {1, 3}6 \\[shiptype\\].",
    					"(D60=the [5] |)\\[name\\] 2 9 {1, 3 \\[shiptype\\].",
    					"(D60=the [5] |)\\[name\\], {3}6 \\[shiptype\\], 2 9 {1.",
    					"(D60=the [5] |)\\[name\\], {3 \\[shiptype\\], }2 9 {1.",
    					"(D60=the [5] |)\\[name\\] 2 9 {1."
    				],
    			};
    			break;
    		case 2: // docked at station
    			var def = {
    				fieldA: ["I have it on good authority that","There are rumours that","Information has come to light indicating","My sources are telling me that","News from the underground says that","According to my best sources","FYI,","News flash:","A word to the wise:","For those who are interested,","It's come to my attention that","Be aware that","It's probably worth noting that"],
    				fieldB: ["bandit","brigand","criminal","crook","desperado","fugitive","gangster","hijacker","hooligan","lawbreaker","marauder","mobster","murderer","psychopath","sinner","sociopath","outlaw","pirate","plunderer","racketeer","raider","ravager","renegade","robber","vagabond","villain"],
    				fieldC: ["heading for","flying to","on their way to"],
    				fieldD: [],
    				fieldE: ["in a","flying a","piloting a"],
    				fieldF: [" tricked out"," shiny","n upgraded"," renovated","n above-spec","n over-clocked","n illegally modified"," fully kitted out","n iron-ass"," heavily modified"," seriously upgraded"," ferocious"],
    				fieldG: [", according to [8]"],
    				fieldH: ["recent information","the latest information","reliable sources"],
    				fieldI: ["the \\[system\\] system","\\[system\\]"],
    				fieldJ: [],
    				fieldK: [],
    				fieldL: [],
    				fieldM: [],
    				fieldN: [],
    				fieldO: [],
    				fieldP: [],
    				fieldQ: [],
    				fieldR: [],
    				sentences: [
    					"1 (D50=the [2] |)\\[name\\] is 3 \\[stationtype\\] in 9, 5 \\[shiptype\\].",
    					"1 (D50=the [2] |)\\[name\\] is 3 \\[stationtype\\] in 9, 56 \\[shiptype\\].",
    					"1 (D50=the [2] |)\\[name\\], 5 \\[shiptype\\], is 3 \\[stationtype\\] in 9.",
    					"1 (D50=the [2] |)\\[name\\], 56 \\[shiptype\\], is 3 \\[stationtype\\] in 9.",
    					"1 (D50=the [2] |)\\[name\\] is 3 \\[stationtype\\] in 9.",
    					"(D50=the [2] |)\\[name\\] is 3 \\[stationtype\\] 56 \\[shiptype\\]7.",
    					"(D50=the [2] |)\\[name\\] is 3 \\[stationtype\\] 5 \\[shiptype\\]7.",
    					"(D50=the [2] |)\\[name\\], 5 \\[shiptype\\], is 3 \\[stationtype\\]7.",
    					"(D50=the [2] |)\\[name\\], 56 \\[shiptype\\], is 3 \\[stationtype\\]7.",
    					"(D50=the [2] |)\\[name\\] is 3 \\[stationtype\\]7."
    				],
    			};
    			break;
    	}
    
        var text = worldScripts.GNN_PhraseGen._makePhrase(def);
    	return expandDescription(this.$cleanUp(text), {name:bountyName, shiptype:shipType, system:systemName, stationtype:stationName});
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$cleanUp = function $cleanUp(text) {
        // remove any of the \ chars that prefix any [ and ] chars
        text = text.replace(/\\\[/g, "[");
        text = text.replace(/\\\]/g, "]");
        // look for any \n operatives, and replace them with an actual new line
        text = text.replace(/\\n/g, String.fromCharCode(13) + String.fromCharCode(10));
        text = text.replace(/\[mission/g, "[mission_");
        return text;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$switchMissiles = function $switchMissiles(ship) {
    	var done = false;
    	do {
    		if (ship.equipmentStatus("EQ_MISSILE") === "EQUIPMENT_OK" && Math.random() > 0.5) {
    			ship.removeEquipment("EQ_MISSILE");
    			ship.awardEquipment("EQ_HARDENED_MISSILE");
    		} else {
    			done = true;
    		}
    	} while (done === false);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$revealMap = function $revealMap() {
    	this._holdConcealment = [];
    	this._unconcealmentActive = true;
    	if (this._debug) log(this.name, "system map uncovered");
    	for (var i = 0; i < 256; i++) {
    		var sys = System.infoForSystem(galaxyNumber, i);
    		var c = 0;
    		if (sys.concealment) c = sys.concealment;
    		this._holdConcealment.push(c);
    		if (c != 0) sys.setProperty(2, "concealment", 0);
    	}
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$restoreMap = function $restoreMap() {
    	if (this._holdConcealment.length === 0) return;
    	for (var i = 0; i < this._holdConcealment.length; i++) {
    		var sys = System.infoForSystem(galaxyNumber, i);
    		if (this._holdConcealment[i] != 0) sys.setProperty(2, "concealment", this._holdConcealment[i]);
    	}
    	if (this._debug) log(this.name, "system map restored");
    	this._unconcealmentActive = false;
    }