Back to Index Page generated: May 8, 2024, 6:16:03 AM

Expansion BroadcastComms MFD

Content

Manifest

from Expansion Manager's OXP list from Expansion Manifest
Description Provides a way to send communications to other ships via a multi-function display and primable equipment. Provides a way to send communications to other ships via a multi-function display and primable equipment.
Identifier oolite.oxp.phkb.BroadcastCommsMFD oolite.oxp.phkb.BroadcastCommsMFD
Title BroadcastComms MFD BroadcastComms MFD
Category HUDs HUDs
Author phkb and Zireael phkb and Zireael
Version 1.3.7 1.3.7
Tags mfd, comms, equipment mfd, comms, equipment
Required Oolite Version
Maximum Oolite Version
Required Expansions
Optional Expansions
Conflict Expansions
Information URL https://wiki.alioth.net/index.php/BroadcastComms_MFD n/a
Download URL https://wiki.alioth.net/img_auth.php/6/62/BroadcastCommsMFD_1.3.7.oxz n/a
License CC-BY-NC-SA 4.0 CC-BY-NC-SA 4.0
File Size n/a
Upload date 1697519051

Documentation

Also read http://wiki.alioth.net/index.php/BroadcastComms%20MFD

Resources/BCC_Testbed.oxp/readme.txt

BCC Testbed
===========

This OXP is a testing and demonstrating the mechanism for script_info integrations in BCC. To use it, move the OXP into your AddOns folder. Then, after launching from the station, there will be a stationary Cobra MkIII between the station and the beacon. After targetting this ship, you will then be able to test the various functions BCC is providing.

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/

Discussion
==========
This OXP is discussed at this forum link: http://aegidian.org/bb/viewtopic.php?f=4&t=16826

readme.txt

Broadcast Comms MFD
===================

Author: phkb (Nick Rogers) and Zireael
With thanks to: cim, Norby, Wildeblood and Marte for their suggestions and fixes
Special thanks to Milo for fixes to the surrender to police method.

Only works with Oolite 1.79 or greater.

Overview
========
This OXP provides a means by which the player can communicate in a real way with other ships. That is, the types of communications the player transmits will have a bearing on the game in some way, depending on the transmission type

There are 11 types of transmissions:

1. Requests for wormholes (broadcast ie. to all ships in range)
   This transmission asks if anyone is going to your hyperspace destination. The message is only available if a destination has been set and the players status is not "red".
   Ships can respond in three ways: (a) Not at all. (b) By telling the player they are headed somewhere else, or (c) by telling the player they are heading to the players destination.
   This message can be sent repeatedly.

2. Send distress message (broadcast ie. to all ships in range)
   This transmission requests immediate assistance against attackers. NPC ships may or may not choose to help.
   Only available when the player ship is under attack.
   This message can be sent repeatedly.

3. Send greeting to target
   This transmission sends a basic "Hello" message to the ship the player is targeting. The other ship may choose to respond or not. Once a response has been received from the other ship, no further greetings can be sent to that ship.

4. Send taunt to target
   This transmission sends a taunt to the other ship, and the other ship may or may not respond. But regardless of a verbal response, the other ship may do one of the following:
      (a) Nothing at all
      (b) If they are currently attacking the player, they might get angry and their accuracy might decrease for a few seconds, or increase for a few seconds.
      (c) If they are not targeting the player at the moment, they might choose to start targeting the player instead.
   Multiple taunts can be sent to other ships.

5. Issue threat to target
   This transmission issues a threat to the other ship. Depending on who that ship is targeting, the response may be different:
      (a) If the other ship is targeting the player, there is a small chance they might choose to flee.
      (b) If the other ship is not targeting the player, they will possibly choose to attack the player.
   Only one threat can be issues to a ship.

6. Offer bribe to target/Offer bribe to nearest attacker
   This transmission offers money to a ship that is attacking the player. That ship can respond in a two ways.
      (a) They can reject the offer. The amount the player can offer will then increase by a factor of 2 or 2.5 and the player can then try the bribe again if they wish. If the amount of bribe increases beyond the players current credit balance, the offer to bribe will be removed.
      (b) They can accept the bribe. In this case, the bribe amount is deducted from the player bank account, and the ship targeting the player will break off their attack. If they are part of a pirate band, the pirate who accepted the bribe will share the spoils and they will all break off their attack.

   If the attacking ship is targeted by the player, they will see the next bribe amount in the message text when selecting the message. 
   If the player doesn't have a target (for instance, they are fleeing from a group of ships), the bribe amount will only be displayed when the transmission takes place. This is because it is not known who is the closest attacking ship until the transmission occurs, and the bid amounts are stored for each pirate group or ship. If a ship from one pirate group moves closer than another pirate group, the amount of the bribe will be adjusted accordingly.
   
7. Demand cargo from target
   This transmission sends a demand for cargo to the other ship. They can either accept or reject the demand. If they accept the demand, they will drop some cargo and flee. If this action is performed in sight of a police vessel or the main station, there is a chance the victim will report the crime and the player will receive an increase to their offender status.

8. Surrender to target/Surrender to nearest attacker
   This transmission sends a surrender message another ship. There is a small chance the attackers might accept the surrender. In that case they will stop attacking the player and wait for the player to dump some cargo. If the player doesn't dump any cargo, the pirates will start attacking the player again. If the player decides to use the lull in hostilities to start attacking the ships the pirates will get really angry and get a temporary boost in their accuracy.
   If the attacker is a police vessel, they might demand the player to disable their weapons. The player will have a few seconds to comply. If they do, the police will most likely accept your surrender and stop attacking.

9. Offer to rescue escape pod (only if fuel scoop is fitted)
   This sends a message to the targeted escape pod, asking the occupant if they would like to be scooped and returned to the main station.
   Commanders should check that they have available cargo space before scooping escape pods.
  
10. Target last comms message
    This will switch the players target to the ship that last sent a comms message.

11. Keep away from my target
    This will send a message to all ships in range, telling them to stay away from the players current target. Only ships that aren't fighting against the player will potentially respond. Enemy ships will ignore this message.
    
Usage:
The BroadcastComms MFD is available for purchase for 200CR at all stations, regardless of techlevel.
After displaying the BroadcastComms MFD and priming it, press the "b" (Mode) key to select a message from the available options. 
The selected message is marked with a ">" in the MFD. 
You don't need the BroadcastComms MFD to be visible, though. There might be situations where it is not be visible, so changes to the selected message will also be displayed as a console message. 
Once the desired message is selected, press the "n" (Activate) key to send the message.

External access
===============
Provision has been made for other OXP's to access the comms features in this OXP. There are seven methods that can be used:

	var w = worldScripts.BroadcastCommsMFD;
	w.$createMessage({messageName:string, callbackFunction:Function, displayText:string, messageText:string, ship:Ship, shipDisplayName:string, 
		transmissionType:string, deleteOnTransmit:Boolean, delayCallback:integer})

		Calling this function will add the message to the display that will be displayed when the conditions are met.

		messageName			The name of this message, internally used for identification purposes. Can be any text. It is recommended to prefix
							the message name with the name of your worldscript to prevent the possibility of duplicate message names.
		callbackFunction	The function to call when the player transmits the message.
		displayText			The message text to display
		messageText			The message text that will be transmitted, if different to the display text.
		ship				Reference to a ship object for a ship-to-ship transmission. Player must target the ship for the message to
							become available.
							Can be null, and if transmission type is "target" this will create a message that can be sent to any targeted ship.
							Will be ignored if transmission type is "broadcast".
		shipDisplayName		Display name of a ship for a ship-to-ship transmission. Player must target the ship for the message to 
							become available.
							Alternate method of targeting a specific ship, if the ship object is not available.
							Will be ignored if transmission type is "broadcast".
		transmissionType	Transmission type, either "broadcast" (ie. transmitted to all ships) or "target" (the ship targeted by the player)
							Including a ship property necessitates a "target" value here. Specifying "broadcast" will over override ship reference.
							If not set, will default to "broadcast".
		deleteOnTransmit	Boolean value indicating that the message will be removed from the list as soon as the player transmits it.
							If not set will default to true.
		delayCallback		Integer value indicating how long (in seconds) to take before calling the callback function. A value of 0 (zero)
							means immediately.
							If not set will default to 2.
		hideOnConditionRed	Boolean value indicating that the message will be hidden from the player if their ship is under attack.
							Default is false.
					  
	Special case: If the displayText starts with any type of bracket (ie. "(", "{", "<" or "[") there will be no external transmission. That is, the callback function will be called, but there will be no comms messages sent to any ships. An external OXP can use this option to, for example, switch comms modes between a specialised set of comms messages, and the standard set.
	
	var w = worldScripts.BroadcastCommsMFD;
	w.$updateMessage({messageName:string, displayText:string, messageText:string, callbackFunction:Function, deleteOnTransmit:boolean, delayCallback:integer});

		This function will update the settings of a particular message.
		
		messageName			The name of the message that will be updated
		displayText			The new message text to be displayed, otherwise null if not being changed.
		messageText			The new message text that will be transmitted, otherwise null if not being changed.
		callbackFunction	The new function to call when the player transmits the message, otherwise null if not being changed.
		deleteOnTransmit	Boolean value indicating that the message will be removed from the list as soon as the player transmits it. 
							Null if not being changed.
		delayCallback		Integer value indicating how long (in seconds) to take before calling the callback function. A value of 0 (zero)
							means immediately. Null if not being changed.

	var w = worldScripts.BroadcastCommsMFD;
	w.$checkMessageExists(messageName:string);

		This function returns true if messageName exists, otherwise false.

		messageName			The name of the message that will be searched for
	
	var w = worldScripts.BroadcastCommsMFD;
	w.$removeMessage(messageName:string);

		Calling this function will remove the message from the list. Only messages created via the "$createMessage" function can be removed.

		messageName			The name of the message that will be removed

	var w = worldScripts.BroadcastCommsMFD;
	w.$disableMessage(msgid:int);
	
		This function disables one or all of the internal messages, preventing it from being displayed
		
		msgid				The ID of the internal message that will be disabled, where
							0 - all
							1 - request for wormhole
							2 - send distress message
							3 - send greeting
							4 - send taunt
							5 - send threat
							6 - offer bribe
							7 - demand cargo
							8 - surrender to target
							9 - offer to rescue escape pod
							10 - target last comms message
							11 - keep away from my target

	var w = worldScripts.BroadcastCommsMFD;
	w.$enableMessage(msgid:int);
		
		This function enables one of all of the internal messages (if they have been disabled by the $disableMessage function), allow it to be displayed
		
		msgid				The ID of the internal message that will be enabled, where
							0 - all
							1 - request for wormhole
							2 - send distress message
							3 - send greeting
							4 - send taunt
							5 - send threat
							6 - offer bribe
							7 - demand cargo
							8 - surrender to target
							9 - offer to rescue escape pod
							10 - target last comms message
							11 - keep away from my target

	var w = worldScripts.BroadcastCommsMFD;
	w.$isMessageEnabled(msgid:int);
	
		This function is used to determine if an internal message is enabled or if it has been disabled.
		Returns true if message is enabled, otherwise false.
		Passing msgid value of 0 will check if all messages are enabled. Only returns true if all messages are enabled. Otherwise false.
		
		msgid				The ID of the internal message that will be evaluated.
		
Examples:
	var w = worldScripts.BroadcastCommsMFD;
	w.$createMessage({messageName:this.name + "myMessage1", 
		callbackFunction:this.$broadcastCallback.bind(this), 
		displayText:"Evacuation message", 
		messageText:"Attention all ships. Please evacuate the area and move to a safe distance.", 
		transmissionType:"broadcast", 
		deleteOnTransmit:true, 
		delayCallback:4,
		hideOnConditionRed:false});

		This example adds a broadcast message to the display. It will be removed when transmitted, and it will take 4 seconds for the
		callback function "this.$broadcastCallback.bind(this)" to be executed. It won't be hidden if the player is under attack.

	var w = worldScripts.BroadcastCommsMFD;
	w.$createMessage({messageName:this.name + "myMessage2", 
		callbackFunction:this.$targetCallback.bind(this), 
		displayText:"Ask native question", 
		messageText:"Are you a native of these parts?", 
		ship:myShip, 
		transmissionType:"target", 
		deleteOnTransmit:false, 
		delayCallback:0,
		hideOnConditionRed:true});

		This example adds a target-specific message to the display, to be shown when the ship object "myShip" is targeted by the player.
		It will be not removed when transmitted and the callback function this.$targetCallback.bind(this)" will be executed immediately.
		It will be hidden if the player is under attack.

	var w = worldScripts.BroadcastCommsMFD;
	w.$removeMessage(this.name + "myMessage2");

		This example will remove the message "myMessage2" from the list of external messages.

	var w = worldScripts.BroadcastCommsMFD;
	w.$disableMessage(0);
	
		This example will disable all internal messages, leaving only the external messages to be displayed.
		
	var w = worldScripts.BroadcastCommsMFD;
	w.$disableMessage(3);
	
		This example will disable the "Send greeting to target" message.
		
	var w = worldScripts.BroadcastCommsMFD;
	w.$enableMessage(0);
		
		This example will enable all internal messages.
		
	var w = worldScripts.BroadcastCommsMFD;
	w.$enableMessage(3);
		
		This example will enable the "Send greeting to target" message.
		
External messages, and the status of internal messages, are cleared each time the player loads from a saved game. If a persistent message needs to be shown, or if the status of internal messages needs to be changed in an ongoing manner, ensure the changes are made after the player loads from a saved game.

Using script_info to control messages
=====================================
It is also possible to control messages by using the "script_info" property of a ship definition in shipdata.plist. The following options are available:

	script_info = {
		bcc_disable_defaults = (); // an array of numeric identifiers that correspond to default message id's 
		bcc_custom_defaults = {}; // a dictionary object with keys corresponding to default message id's
		bcc_custom_messages = {}; // a dictionary object containing message definitions
	};

Looking at each of these in more detail:
	
	// this would disable message types 3 (greeting), 4 (taunt) and 7 (demand cargo)
	bcc_disable_defaults = (3,4,7); 

	// this definition includes all possible options for changing the standard transmissions and replies.
	bcc_custom_defaults = { // note: [xyz] refers to an entry in descriptions.plist. Each one should be unique
		"1" = { // wormhole request
			reply_confirm = "[xyz]";
			reply_decline = "[xyz]";
		};
		"3" = { // greeting
			transmit = "[xyz]";
			reply = "[xyz]";
		};
		"4" = { // taunt
			transmit = "[xyz]";
			reply = "[xyz]";
		};
		"5" = { // threat
			transmit = "[xyz]";
			reply_normal = "[xyz]";
			reply_fleeing = "[xyz]";
		};
		"6" = { // bribe
			transmit = "[xyz]"; // [amount] && [credittype] must be included in descriptions.plist entry
			reply_fine = "[xyz]"; // police vessel fines player
			reply_decline = "[xyz]"; // decline bribe
			reply_accept = "[xyz]"; // accept bribe
		};
		"7" = { // demand cargo
			reply_refuse = "[xyz]";
		};
		"8" = { // surrender
			transmit = "[xyz]";
			reply_impatient = "[xyz]";
			reply_police_accept = "[xyz]";
			reply_police_reject = "[xyz]";
		};
		"9" = { // escape pod offer to rescue
			transmit = "[xyz]";
			reply_friendly = "[xyz]";
			reply_unfriendly = "[xyz]";
		};
		"11" = { // keep away from target
			reply = "[xyz]";
		};
	};

	bcc_custom_messages = {
		"level1" = {
			// this will show "Level 1 message" in the Broadcast Comms MFD
			// when selected, it will display the "transmit" message
			// after 2 seconds, it will display the "reply" message, and run the post_reply_function, passing the ship and messagekey ("level1") to the function
			// then it will make the messages "level2a" and "level2b" visible

			state = "visible"; // whether the player will be offered this option immediately;
			display = "Level 1 message"; // what to display in the MFD
			delete_on_transmit = yes; // indicates whether message option should be removed from MFD after use
			transmit = "This is the transmitted message for level 1."; // what to transmit when the option is activated
			delay = 2; // how long to delay after transmission
			reply = "This is the response to level 1."; // the message that will be sent back to the player.
			post_reply_function = "$test_reply"; // function to call after sending the reply (optional)
			post_reply_worldscript = "MyWorldScriptName";
			post_reply_show_messages = ("level2a", "level2b"); // messages to make visible after the reply is received
		};
		"level2a" = {
			// this will show "Level 2 message A" in the Broadcast Comms MFD
			// when selected, it will display the "transmit" message
			// after 2 seconds, it will display the "reply" message, and run the post_reply_function, passing the ship and messagekey ("level2a") to the function
			// then it will hide "level2b" (it doesn't need to hide itself, as the "delete_on_transmit" is set to "yes")

			state = "hidden";
			display = "Level 2 message A"; // what to display in the MFD
			delete_on_transmit = yes; // indicates whether message option should be removed from MFD after use
			transmit = "This is the transmitted message for level 2A."; // what to transmit when the option is activated
			delay = 2; // how long to delay after transmission
			reply = "This is the response to level 2A."; // the message that will be sent back to the player.
			post_reply_function = "$test_reply"; // function to call after sending the reply (optional)
			post_reply_worldscript = "MyWorldScriptName";
			post_reply_hide_messages = ("level2b");
		};
		"level2b" = {
			// this will show "Level 2 message B" in the Broadcast Comms MFD
			// when selected, it will display the "transmit" message.
			// after 2 seconds it will display the "reply" message, and run the post_reply_function, passing the ship and messagekey ("level2b") to the function
			// then it will hide "level2a" (it doesn't need to hide itself, as the "delete_on_transmit" is set to "yes")

			state = "hidden";
			display = "Level 2 message B"; // what to display in the MFD
			delete_on_transmit = yes; // indicates whether message option should be removed from MFD after use
			transmit = "This is the transmitted message for level 2B."; // what to transmit when the option is activated
			delay = 2; // how long to delay after transmission
			reply = "This is the response to level 2B."; // the message that will be sent back to the player.
			post_reply_function = "$test_reply"; // function to call after sending the reply (optional)
			post_reply_worldscript = "MyWorldScriptName";
			post_reply_hide_messages = ("level2a");
		};
	};


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/

Discussion
==========
This OXP is discussed at this forum link: http://aegidian.org/bb/viewtopic.php?f=4&t=16826

Version History
===============
1.3.7
- Better handling of unpurchased equipment. MFD will not be shown at all in this case.

1.3.6
- Better handling of damaged equipment - MFD will now show a "damaged" message.

1.3.5
- Really fixed JS error when targeting wormholes (or any entity that doesn't have a script object).

1.3.4
- Fixed JS error when targeting wormholes (or any entity that doesn't have a scriptInfo object).
- Fixed JS error where a ship may not be in a group when attacking the player and a bribe is offered.

1.3.3
- Added check for existing message before trying to add custom messages via bcc_custom_messages.
- Removed debug message.

1.3.2
- Fixed issue with not being able to replace core messages with custom ones.

1.3.1
- Fixed issue with some transmissions defaulting to a blank message.
- Added checks for "bcc_disable_defaults" when validating new external messages, making it easier to replace core messages with custom ones.

1.3
- Added ability to control messages via script_info.
- Added TestBed OXP (in Resources folder) for demonstrating how to use the script_info integrations.
- Fixed issue where requesting docking clearance from a station with no docks would not be correctly interpreted.

1.2.12
- Improved integration with Bounty System OXP.
- Improvements to the process of surrendering to police, as suggested by Milo.
- Added method to allow message responses to be overridden by another OXP.

1.2.11
- Fix for issue where surrendering to police was not implemented correctly/completely.

1.2.10
- Tweaks to routine that stops timers.

1.2.9
- Fixed missing expansion in bribe message.

1.2.8
- Added missing ";" to role-categories.plist.

1.2.7
- Moved initialisations to startUp.

1.2.6
- Corrected errors in descriptions.plist.

1.2.5
- Moved "AccuracyAdj" out of the global namespace.
- Improved methodology for NPC type identification.
- Added some additional responses.
- Code refactoring.

1.2.4
- Fix for "Send distress message" not sending the correct message.
- Further updates for docking requests to handle further changes in Oolite 1.85/6.
- Code refactoring.

1.2.3
- Updated method for determining player's next target system, to use new property "nextSystem". (Oolite 1.85/6 only - previous version will use old method).
- Added "Request docking clearance" and "Withdraw docking clearance" messages, available when targeting a station. (Oolite 1.85/6 only - not available in previous versions).
- Removed hard-coded scanner range value.
- Code stability improvements.

1.2.2
- Sound effects added for mode/activate functions of MFD.
- Determination of player's hyperspace destination now considers the presence of the ANA.

1.2.1
- Added the ability to sell the MFD.
- Corrected determination of Elite rankings when used in chance calculations.
- Changed "==" comparisons to "===" for performance improvements.
- Adjustments to the $playerTargetSystem calculation.
- Changed the logic for determining if a message already exists or not, making it purely based on the message name.
- Restructured internal array to include messageName in list, making it easier to reference message.

1.2.0
- Added "hideOnConditionRed" option to custom messages, allowing messages to be hidden if the player is under attack.
- Fixed issue where, after having targeted something which is then scooped (ie escape capsule), messages were staying available to transmit.
- Internal code cleanup.

1.1.2
- Fixed issue with $checkMessageExists function, which was not checking for a null value in the message array.
- Added check for hostile targets when switching to red alert mode, so the MFD understands the difference between an environment red alert and a situational red alert.
- Fixed inconsistency in external function names. $checkMessageExists was missing the leading "$" symbol.
- Code cleanup.

1.1.1
- Further small tweaks to the wormhole request responses.

1.1.0
- Added some exclusions for Generation ships, just to be sure.
- Small tweaks to the wormhole request responses.
- Made the equipment less likely to be damaged.
- Turned off the "portable_between_ships", as this is equipment installed in a ship and logically would need to be installed in a new one.
- Fixed manifest file so the category matches the expansion manager setting.

1.0.19
- Fixed issue with JavaScript error when in interstellar space.

1.0.18
- Fixed issue with the MFD being too chatty and sending unnecessary updates to the console.
- Fixed issue with police responding to surrender like a pirate. If you're not a fugitive they will now wait for the player to disable their weapons systems, and if the player responds in time, they will (probably) accept the surrender.
- You can now also attempt to bribe a police ship. Police may accept a bribe, if they're not in the station aegis, and the chance increases with lower government types. They might also fine the player.
- Fixed issue with surrendering to thargoids. They will now curse you. And probably continue shooting.
- Bug fixes.

1.0.17
- Correctly resets the target system after a hyperspace jump or a launch from a station, so the "Is anyone heading to ..?" question will be available immediately.

1.0.16
- Code improvements as suggested by Wildeblood.

1.0.15
- Updates for 1.82 compatibility, particularly around the player.ship.targetSystem not being the next jump point any more. New process implemented.
- Made MFD equipment item portable between ships

1.0.14
- Changed specification of the createMessage and updateMessage functions to use an object rather than parameters.
- Added two new message types, "(Target last comms message)" and "Keep away from my target".
- Changed the default callback wait time for external messages to be 2 seconds.
- If there are more than 9 messages available to the player, the box will now scroll, and indicators in the title will show whether there are more items either on the top or bottom of the list.
- Lots of bug fixes

1.0.13
- Bug fixes and code refactoring
- Removed the "Answer distress call" message, as it never gets triggered
- Added the "Offer to rescue escape pod" message
- Added facility where messages starting with a bracket (ie. "(", "{", "<" or "[") don't get a message transmitted, although the callback function will still be called. This allows the MFD to used like a menu system.
  For instance, an OXP could add an item called "(Switch to special comms mode)". When the player executes that option, the OXP could disable all the default messages, and display a new, custom list, with an extra item called "(Switch to normal comms mode)" which then re-enables all then standard comms messages.
- Changed the initial bribe amount to 1 credit, based on the reasoning that 1 CR = 1/2 ton of food, therefore is a considerable amount
  Also, this means there are more chances the pirates will accept a lower amount of bribe, thus making it more useful.

1.0.12
- Added "Send bribe to closest target" and "Surrender to closest target" messages, for when the player doesn't have a target but is under attack. Thanks for Norby for the suggestions.
- Added the $disableMessage, $enableMessage, and $isMessageEnabled external functions, so that OXP developers can disable the standard internal messages, either individually or all of them.

Equipment

Name Visible Cost [deci-credits] Tech-Level
Broadcast Comms MFD yes 2000 1+
Remove Broadcast Comms MFD no 500 3+

Ships

This expansion declares no ships.

Models

This expansion declares no models.

Scripts

Path
Scripts/broadcastcomms_equip.js
"use strict";
this.name        = "BroadcastCommsMFD_Equipment";
this.author      = "phkb and Zireael";
this.copyright   = "2015 phkb and Zireael";
this.description = "Controls the selection and transmission of messages through the Broadcast Comms multi-function display.";
this.licence     = "CC BY-NC-SA 4.0";

this.activated = function() {
	var w = worldScripts.BroadcastCommsMFD;
	// transmit selected message
	if (w._selectedMsg >= 0) w.$transmit();
	this.$playSound("activate");
}

this.mode = function() {
	var w = worldScripts.BroadcastCommsMFD;
	// change the selected message
	if (w._selectedMsg < w.$maxMessages()) w._selectedMsg += 1;
	// if we've gone past the end, go back to the beginning
	if (w._selectedMsg == w.$maxMessages()) w._selectedMsg = 0;
	// refresh the MFD
	w.$updateView(true);
	this.$playSound("mode");
}

//-------------------------------------------------------------------------------------------------------------
// play the buy/sell sound effects
this.$playSound = function(soundtype) {
	var mySound = new SoundSource;

	switch (soundtype) {
		case "mode":
			mySound.sound = "[@click]";
			break;
		case "activate":
			mySound.sound = "[@switch-on]";
			break;
		case "stop":
			mySound.sound = "[@boop]";
			break;
	}
	mySound.loop = false;
	mySound.play();
}
Scripts/broadcastcomms_mfd.js
"use strict";
this.name = "BroadcastCommsMFD";
this.author = "phkb and Zireael";
this.copyright = "2015 phkb and Zireael";
this.description = "Displays available broadcast messages in a MFD.";
this.licence = "CC BY-NC-SA 4.0";

//TODO:
//Investigate the possibility of making all internal functions into a separate JS, and loading them using the external access functions

// specific settings for this oxp
this._debug = false; // change to false for less logging
this._MFDMaxLines = 9; // maximum number of lines visible in the MFD (10, minus 1 for the header line)
this._bribeInitial = 1; // initial bribe offering (credits)
this._demandCargo = 5; // amount of cargo to demand (tons)
this._tauntAccuracyLength = 10; // how long the accuracy adjustment will last (in seconds)
this._surrenderWait = 15; // time to wait (in seconds) after a surrender before checking player has complied
this._surrenderWait_Police = 5; // time to wait (in seconds) after surrendering to police before checking if the player has complied (repeats until player reaches main station, jumps out of system, or is declared non-compliant)
this._surrenderAccuracyLength = 20; // how long (in seconds) the extra accuracy lasts (given when player renegs surrender with lasers)
this._outputToConsole = true; // indicates the selected comms message will be displayed as a console message
// for cases where the MFD is not visible
this._defaultResponseWait = 2; // default amount of time to wait between sending comms and reply
this._internalMessages = 13; // number of internal messages
this._lastSource = -1; // used to establish the start point for destination system calculations
this._msgAlt = ""; // use to switch to a 3rd party OXP supplying new message responses
this._idSource = 0;

//=============================================================================================================
// set up initial variables
this.startUp = function () {
	this._killedNames = []; // array containing list of ship names killed by the player in this outing
	// used to determine how an escape pod will respond
	this._escapepodShipNames = []; // array of links between escape pods and ships
	this._scoopOffer = []; // keeps track of escape pods we've communicated with
	this._selectedMsg = -1; // currently selected message (defaults to no message)
	this._holdTarget = -1; // keeps track of the players hyperspace destination
	this._lines = []; // available messages as lines on the MFD
	this._greeted = []; // keeps track of ships we've greeted
	this._bribed = []; // keeps track of ships we're bribed
	this._demanded = []; // keeps track of ships we've demanded cargo from
	this._alertCond = 1; // keeps track of the current alert condition
	this._targetType = ""; // keeps track of the type of target (either npc, police, thargoid)
	this._closestAttackerType = ""; // keeps track of the type of closest attacker
	this._accuracyChanged = []; // keeps track of ships whose accuracy we've adjusted
	this._bribeCurrent = []; // keeps track of the current bribe offered to different targets
	this._bribeAmount = this._bribeInitial; // current bribe offering
	this._piracyWorked = false; // flag to indicate that the players demand for cargo (piracy) worked
	this._checkTimer = null; // timer to check for when the player target disappears (ie through a wormhole)
	this._piracyNPCTimer = null; // timer for an NPC telling police about players act of piracy
	this._piracyPoliceTimer = null; // timer for a police response to NPC telling them about players act of piracy
	this._waitingForPiracyResponse = false; // flag to indicate the player is waiting for a target to respond to a demand for cargo
	this._externalMessages = []; // any external messages
	this._surrenderCurrent_Pirate = []; // keeps track of the current list of ships surrendered to
	this._surrenderedTo = []; // keeps track of who we've surrendered to
	this._surrenderTimer_Pirate = null; // timer that will wait for cargo being dumped after surrender
	this._surrenderCheck_Pirate = false; // indicates that player has surrendered and that cargo should be getting dumped
	this._surrenderCheck_Police = false; // indicates that player has sent a surrender request to police and has been told to disable weapons
	this._surrenderTimer_Police = null; // timer to monitor player compliance after surrendering to police (will run until player docks or leaves system)
	this._surrenderPoliceChances = 0; // if player is not complying with police instructions, they have this many timer updates remaining to comply
	this._surrenderMainStationDist = 0; // keeps track of how close the player has come to the main station after surrender, in case they go somewhere else
	this._surrenderBounty = 0; // "hidden" bounty after surrendering to police, will be restored when leaving system or docking at any station

	this._dockingRequest = false;
	this._targetStation = null;
	this._targetLastComms = null; // holds reference to last transmission source
	this._disableInternal = []; // array of flags to keep track of which internal messages have been disabled
	for (var i = 0; i <= this._internalMessages; i++) {
		this._disableInternal.push(false);
	}
	this.$updateView(false);
	this._lastSource = system.ID;
}

// ideas: 	have a "sorry" message for when you accidentally fire on police or stations, which could then limit retaliation
//				need to work out when the player has fired on GalCop's or system stations.

//=============================================================================================================
// external access

//-------------------------------------------------------------------------------------------------------------
// function used to create a custom message
this.$createMessage = function (msgobj) {
	var truetypes = ["yes", "1", "true", true, 1, -1];
	var falsetypes = ["no", "0", "false", false, 0];
	var transtypes = ["broadcast", "target"];

	// check for some invalid conditions
	if (msgobj.hasOwnProperty("messageName") === false || msgobj.messageName === "") {
		throw "Invalid settings: messageName must not be null or blank.";
	}
	if (msgobj.hasOwnProperty("transmissionType") === true && transtypes.indexOf(msgobj.transmissionType) === -1) {
		throw "Invalid settings: transmissiontype must be either 'broadcast' or 'target'.";
	}
	if (msgobj.hasOwnProperty("displayText") === false || msgobj.displayText === "") {
		throw "Invalid settings: displayText must not be null or blank.";
	}
	if (msgobj.hasOwnProperty("callbackFunction") === false || msgobj.callbackFunction === "") {
		throw "Invalid settings: callbackFunction not set.";
	}
	// if these params are null, default them
	if (msgobj.hasOwnProperty("deleteOnTransmit") === false || falsetypes.indexOf(msgobj.deleteOnTransmit) === -1) msgobj.deleteOnTransmit = true;
	if (msgobj.hasOwnProperty("delayCallback") === false) msgobj.delayCallback = this._defaultResponseWait;
	if (msgobj.hasOwnProperty("messageText") === false || msgobj.messageText === "") msgobj.messageText = msgobj.displayText;
	if (msgobj.hasOwnProperty("shipDisplayName") === false) msgobj.shipDisplayName = "";
	if (msgobj.hasOwnProperty("hideOnConditionRed") === false || truetypes.indexOf(msgobj.hideOnConditionRed) === -1) msgobj.hideOnConditionRed = false;
	// check that this message isn't already in the list
	var found = false;
	// check the external messages
	if (!this._externalMessages) this._externalMessages = [];
	if (this._externalMessages) {
		for (var i = 0; i < this._externalMessages.length; i++) {
			if (this._externalMessages[i] && this._externalMessages[i].messageName === msgobj.messageName) {
				throw "Invalid settings: message already exists.";
			}
		}
	}
	var chkShip = null;
	var disable_defaults = [];
	if (msgobj.hasOwnProperty("ship")) chkShip = msgobj.ship;
	if (chkShip && chkShip.scriptInfo.hasOwnProperty("bcc_disable_defaults") && Array.isArray(chkShip.scriptInfo.bcc_disable_defaults)) disable_defaults = chkShip.scriptInfo.bcc_disable_defaults
	if (chkShip && chkShip.script.hasOwnProperty("bcc_disable_defaults") && Array.isArray(chkShip.script.bcc_disable_defaults)) disable_defaults = chkShip.script.bcc_disable_defaults

	// then check the built in messages
	if ((msgobj.displayText.indexOf("Is anyone heading") >= 0 && (!this._disableInternal[1] && disable_defaults.indexOf("1") == -1)) ||
		(msgobj.displayText === "Send distress message" && (!this._disableInternal[2] && disable_defaults.indexOf("2") == -1)) ||
		(msgobj.displayText === "Send greeting to target" && (!this._disableInternal[3] && disable_defaults.indexOf("3") == -1)) ||
		(msgobj.displayText === "Send taunt to target" && (!this._disableInternal[4] && disable_defaults.indexOf("4") == -1)) ||
		(msgobj.displayText === "Issue threat to target" && (!this._disableInternal[5] && disable_defaults.indexOf("5") == -1)) ||
		(msgobj.displayText === "Surrender to target" && (!this._disableInternal[8] && disable_defaults.indexOf("8") == -1)) ||
		(msgobj.displayText === "Offer to rescue escape pod" && (!this._disableInternal[9] && disable_defaults.indexOf("9") == -1)) ||
		(msgobj.displayText === "(Target last comms message)" && (!this._disableInternal[10] && disable_defaults.indexOf("10") == -1)) ||
		(msgobj.displayText === "Keep away from my target" && (!this._disableInternal[11] && disable_defaults.indexOf("11") == -1)) ||
		(msgobj.displayText === "Request docking clearance" && (!this._disableInternal[12] && disable_defaults.indexOf("12") == -1)) ||
		(msgobj.displayText === "Withdraw docking request" && (!this._disableInternal[13] && disable_defaults.indexOf("13") == -1)) ||
		(msgobj.displayText === "Demand " + this._demandCargo.toString() + " ton" + ((this._demandCargo === 1) ? "" : "s") + " of cargo from target" && (!this._disableInternal[7] && disable_defaults.indexOf("7") == -1)) ||
		(msgobj.displayText.indexOf("Offer bribe") >= 0 && (!this._disableInternal[6]) && disable_defaults.indexOf("6") == -1)) {
		throw "Invalid settings: Cannot use comm system message types.";
	}
	// if we didn't find it, add it now
	this._externalMessages.push({
		messageName: msgobj.messageName,
		callbackFunction: msgobj.callbackFunction,
		displayText: msgobj.displayText,
		messageText: msgobj.messageText,
		ship: msgobj.ship,
		shipDisplayName: msgobj.shipDisplayName,
		transmissionType: msgobj.transmissionType,
		deleteOnTransmit: msgobj.deleteOnTransmit,
		delayCallback: msgobj.delayCallback,
		hideOnConditionRed: msgobj.hideOnConditionRed
	});
	/*var keys = Object.keys(msgobj);
	for (var i = 0; i < keys.length; i++) {
		log(this.name, keys[i] + " - " + msgobj[keys[i]]);
	}*/
	this.$buildMessageList();
}

//-------------------------------------------------------------------------------------------------------------
// checks for the existence of a message in the current list. Returns true if found, otherwise false.
this.$checkMessageExists = function (messagename) {
	var found = false;
	if (this._externalMessages != null && this._externalMessages.length > 0) {
		for (var i = 0; i < this._externalMessages.length; i++) {
			if (this._externalMessages[i] && this._externalMessages[i].messageName === messagename) {
				found = true;
				break;
			}
		}
	}
	return found;
}

//-------------------------------------------------------------------------------------------------------------
// function used to update the message display text and transmission text of an existing message
this.$updateMessage = function (msgobj) {
	if (msgobj.hasOwnProperty("messageName") === false || msgobj.messageName === "") {
		throw "Invalid setting: messageName must not be null or blank.";
	}
	if (msgobj.hasOwnProperty("displayText") === true && msgobj.displayText === "") {
		throw "Invalid settings: displayText must not be blank.";
	}
	var found = false;
	if (this._externalMessages) {
		for (var i = 0; i < this._externalMessages.length; i++) {
			if (this._externalMessages[i] && this._externalMessages[i].messageName != msgobj.messageName && this._externalMessages[i].displayText === msgobj.displayText) {
				found = true;
			}
		}
	}
	if (found === true) {
		throw "Invalid settings: displayText is already used in another another message.";
	}
	var rebuild = false;
	// look through the list
	if (this._externalMessages) {
		for (var i = 0; i < this._externalMessages.length; i++) {
			if (this._externalMessages[i] && this._externalMessages[i].messageName === msgobj.messageName) {
				if (msgobj.hasOwnProperty("displayText") === true && this._externalMessages[i].displayText != msgobj.displayText) {
					this._externalMessages[i].displayText = msgobj.displayText;
					rebuild = true;
				}
				if (msgobj.hasOwnProperty("messageText") === true && msgobj.messageText != "") this._externalMessages[i].messageText = msgobj.messageText;
				if (msgobj.hasOwnProperty("callbackFunction") === true) this._externalMessages[i].callbackFunction = msgobj.callbackFunction;
				if (msgobj.hasOwnProperty("deleteOnTransmit") === true) this._externalMessages[i].deleteOnTransmit = msgobj.deleteOnTransmit;
				if (msgobj.hasOwnProperty("delayCallback") === true) this._externalMessages[i].delayCallback = msgobj.delayCallback;
				break;
			}
		}
	}
	// if we found a match, rebuild the message list
	if (rebuild === true) this.$buildMessageList();
}

//-------------------------------------------------------------------------------------------------------------
// function to disable internal messages
// disable options
// 0 - all
// 1 - request for wormhole
// 2 - send distress message
// 3 - send greeting
// 4 - send taunt
// 5 - send threat
// 6 - offer bribe
// 7 - demand cargo
// 8 - surrender to target
// 9 - escape pod offer to rescue
// 10 - target last comms message
// 11 - keep away from my target
// 12 - request docking clearance
// 13 - withdraw docking request
this.$disableMessage = function (msgid) {
	if (msgid > this._disableInternal.length - 1) {
		throw "Invalid settings: msgid must be less than " + (this._disableInternal.length - 1).toString() + ".";
	}
	if (msgid === 0) {
		for (var i = 0; i < this._disableInternal.length; i++) {
			this._disableInternal[i] = true;
		}
	} else {
		this._disableInternal[msgid] = true;
	}
	this.$buildMessageList();
}

//-------------------------------------------------------------------------------------------------------------
// function to enable internal messages
// enable options
// 0 - all
// 1 - request for wormhole
// 2 - send distress message
// 3 - send greeting
// 4 - send taunt
// 5 - send threat
// 6 - offer bribe
// 7 - demand cargo
// 8 - surrender to target
// 9 - escape pod offer to rescue
// 10 - target last comms message
// 11 - keep away from my target
this.$enableMessage = function (msgid) {
	if (msgid > this._disableInternal.length - 1) {
		throw "Invalid settings: msgid must be less than " + (this._disableInternal.length - 1).toString() + ".";
	}
	if (msgid === 0) {
		for (var i = 0; i < this._disableInternal.length; i++) {
			this._disableInternal[i] = false;
		}
	} else {
		this._disableInternal[msgid] = false;
	}
	this.$buildMessageList();
}

//-------------------------------------------------------------------------------------------------------------
// function to determine if an internal message is enabled or not
// msgid = id of internal message.
// returns true if message is enabled, otherwise false
// passing msgid value of 0 will check if all messages are enabled. Only returns true if all messages are enabled. Otherwise false.
this.$isMessageEnabled = function (msgid) {
	if (msgid > this._disableInternal.length - 1) {
		throw "Invalid settings: msgid must be less than " + (this._disableInternal.length - 1).toString() + ".";
	}
	if (msgid === 0) {
		var count = 0;
		for (var i = 0; i < this._disableInternal.length; i++) {
			if (this._disableInternal[i] === false) count += 1;
		}
		if (count === this._disableInternal.length) {
			return true;
		} else {
			return false;
		}
	} else {
		return !this._disableInternal[msgid]
	}
}

//-------------------------------------------------------------------------------------------------------------
// function used to remove a message from the external messages list
this.$removeMessage = function (messagename) {
	if (messagename == null || messagename === "") {
		throw "Invalid setting: messagename must not be null or blank.";
	}
	var rebuild = false;
	// look through the list
	if (this._externalMessages) {
		for (var i = 0; i < this._externalMessages.length; i++) {
			if (this._externalMessages[i] && this._externalMessages[i].messageName === messagename) {
				// found it, so set the array entry to null to remove it
				this._externalMessages[i] = null;
				rebuild = true;
			}
		}
	}
	// if we found a match, rebuild the message list
	if (rebuild === true) this.$buildMessageList();
}

//-------------------------------------------------------------------------------------------------------------
// the number of lines (messages) in our message box
this.$maxMessages = function () {
	return this._lines.length;
}

//=============================================================================================================
// ship interfaces
//-------------------------------------------------------------------------------------------------------------
this.shipWillDockWithStation = function _shipWillDockWithStation(station) {
	this.$stopAllTimers();
	this._dockingRequest = false;
	var that = _shipWillDockWithStation; // pointer to this function
	var bountysystem = (that.bountysystem = that.bountysystem || worldScripts.BountySystem_Core); // cache worldScript reference in a local property on this function
	if (this._surrenderBounty > 0) { // hidden bounty from surrendering to police will be restored when docking with any station (not only the main station!)
		if (bountysystem) bountysystem._changing = true; // tell Bounty System OXP to ignore the bounty change we are about to do (so it doesn't get recorded as a new offence)
		player.bounty += this._surrenderBounty; // make hidden bounty visible again (add it to current bounty in case the player gained additional bounty meanwhile)
		if (bountysystem) bountysystem._changing = false; // tell Bounty System OXP to pay attention to bounty changes again
		this._surrenderBounty = 0; // clear our hidden bounty variable (need to do this regardless of whether Bounty System is installed)
	}
}

//-------------------------------------------------------------------------------------------------------------
this.shipLaunchedFromStation = function (station) {
	this.$fullResetVariables();
	this._holdTarget = this.$playerTargetSystem();
	this.$buildMessageList();

	this._lastSource = system.ID;
}

//-------------------------------------------------------------------------------------------------------------
this.playerBoughtEquipment = function (equipment) {
	if (equipment === "EQ_BROADCASTCOMMSMFD_REMOVAL") {
		var p = player.ship;
		p.removeEquipment("EQ_BROADCASTCOMMSMFD_REMOVAL");
		p.removeEquipment("EQ_BROADCASTCOMMSMFD");
	}
}

//-------------------------------------------------------------------------------------------------------------
this.equipmentDamaged = function(equipment) {
	if (equipment == "EQ_BROADCASTCOMMSMFD") {
		this.$buildMessageList();
	}
}

//-------------------------------------------------------------------------------------------------------------
this.equipmentRepaired = function(equipment) {
	if (equipment == "EQ_BROADCASTCOMMSMFD") {
		this.$buildMessageList();
	}
}

//-------------------------------------------------------------------------------------------------------------
this.guiScreenChanged = function (to, from) {
	if (!player.ship.isInSpace) return;
	if (from === "GUI_SCREEN_SHORT_RANGE_CHART" || from === "GUI_SCREEN_LONG_RANGE_CHART") {
		if (this._holdTarget != this.$playerTargetSystem()) {
			this._holdTarget = this.$playerTargetSystem();
			this.$buildMessageList();
		}
	}
}

//-------------------------------------------------------------------------------------------------------------
this.alertConditionChanged = function (newCondition, oldCondition) {
	if (!player.ship.isInSpace) return;
	if (player.ship.isInSpace === true && this._alertCond != player.alertCondition) {
		if (player.alertCondition != 3 || (player.alertCondition === 3 && player.alertHostiles === true)) this._alertCond = player.alertCondition;
		this.$buildMessageList();
	}
}

//-------------------------------------------------------------------------------------------------------------
this.shipTargetLost = function (target) {
	this.$resetVariables();
	this.$startTargetTimer();
	if (target === this._targetLastComms) this._targetLastComms = null;
	this._bribeAmount = this._bribeInitial;
	this.$buildMessageList();
}

//-------------------------------------------------------------------------------------------------------------
this.shipWillEnterWitchspace = function _shipWillEnterWitchspace(station) {
	var that = _shipWillEnterWitchspace; // pointer to this function
	var bountysystem = (that.bountysystem = that.bountysystem || worldScripts.BountySystem_Core); // cache worldScript reference in a local property on this function
	if (this._surrenderBounty > 0) { // hidden bounty from surrendering to police will be restored when jumping out of the system
		if (!bountysystem) { // but do not do this if Bounty System is installed; it will take care of setting the player's bounty including the hidden amount
			player.bounty += this._surrenderBounty; // make hidden bounty visible again (add it to current bounty in case the player gained additional bounty meanwhile)
		}
		this._surrenderBounty = 0; // clear our hidden bounty variable (need to do this regardless of whether Bounty System is installed)
	}
}

//-------------------------------------------------------------------------------------------------------------
this.shipWillExitWitchspace = function () {
	this.$stopAllTimers();
	this.$fullResetVariables();
	this._holdTarget = this.$playerTargetSystem();
	this.$buildMessageList();
}

//-------------------------------------------------------------------------------------------------------------
this.shipExitedWitchspace = function () {
	// update the lastSource ID
	if (system.ID != -1) this._lastSource = system.ID;
}

//-------------------------------------------------------------------------------------------------------------
this.playerEnteredNewGalaxy = function (galaxyNumber) {
	this.$stopAllTimers();
	this.$fullResetVariables();
	this._holdTarget = -1;
	this.$buildMessageList();
	this._lastSource = system.ID;
}

//-------------------------------------------------------------------------------------------------------------
this.shipTargetAcquired = function (target) {
	this._targetType = "";
	this._closestAttackerType = "";
	this._bribeAmount = this._bribeInitial;
	if (target.isShip === true && target.isPiloted) {
		if (target.scanClass != "CLASS_CARGO") {
			this._targetType = "npc";
			// not sure about OXP ships, whether analysis of all roles (not just the primary role) will be required
			if (Ship.roleIsInCategory(target.primaryRole, "oolite-police") === true) this._targetType = "police";
			if (target.scanClass === "CLASS_THARGOID") this._targetType = "thargoid";
			if (Ship.roleIsInCategory(target.primaryRole, "oolite-bounty-hunter") === true) {
				this._targetType = "hunter";
				// work out what the bribe amount should be
				this.$workOutCurrentBribeAmount(target);
			}
			if (Ship.roleIsInCategory(target.primaryRole, "oolite-pirate") === true) {
				this._targetType = "pirate";
				// work out what the bribe amount should be
				this.$workOutCurrentBribeAmount(target);
			}
			if (target.isStation) this._targetType = "station";
		}
		if (target.scanClass === "CLASS_CARGO" && target.hasRole("escape-capsule") === true) this._targetType = "escapepod";
	}
	this.$buildMessageList();
	this.$startTargetTimer();
}

//-------------------------------------------------------------------------------------------------------------
this.shipDied = function (whom, why) {
	if (this._debug === true) log(this.name, "shipDied whom = " + whom.displayName + " why = " + why);
	this.$stopAllTimers();
}

//-------------------------------------------------------------------------------------------------------------
this.shipTargetDestroyed = function (target) {
	this.$resetVariables();
	this.$startTargetTimer();
	this._bribeAmount = this._bribeInitial;
	if (target === this._targetLastComms) this._targetLastComms = null;
	this.$buildMessageList();
}

//-------------------------------------------------------------------------------------------------------------
this.commsMessageReceived = function (message, sender) {
	if (sender && sender != player.ship && sender.isStation === false && sender != player.ship.target) {
		if (this._debug) log(this.name, "last comms: " + sender.displayName);
		this._targetLastComms = sender;
		this.$buildMessageList();
	}
}

//-------------------------------------------------------------------------------------------------------------
this.shipKilledOther = function (whom, damageType) {
	if (whom === this._targetLastComms) this._targetLastComms = null;
	// keep track of the names of ship's crew we've killed, so if they end up in an escape pod, we can adjust the message responses
	if (!whom.hasRole("escape-capsule") && (whom.isPiloted || whom.isDerelict || whom.hasRole("tharglet") || whom.hasRole("thargon"))) {
		this._killedNames.push(whom.displayName);

		// did the ship launch an escape pod? look for one within 1km of destroyed ship
		var podsinRange = system.shipsWithRole("escape-capsule", whom, 1000);

		if (podsinRange && podsinRange.length > 0) {
			// we only get here if we've found at least 1 escape pod
			podsinRange.forEach(function (pod) {
				var bAdd = true;
				// have we already added this pod to our array?
				if (this._escapepodShipNames.length != 0) {
					for (var i = 0; i < this._escapepodShipNames.length; i++) {
						if (this._escapepodShipNames[i].escapepod === pod) bAdd = false;
					}
				}
				if (bAdd === true) this._escapepodShipNames.push({
					escapepod: pod,
					shipname: whom.displayName
				});
			}, this);
		}
	}
}

//-------------------------------------------------------------------------------------------------------------
this.shipBeingAttacked = function (whom) {
	// add this ship to the greeting list - we don't want to send a greeting to someone who's fired at us
	this.$addShipToArray(whom, this._greeted);
}

//-------------------------------------------------------------------------------------------------------------
this.shipAttackedOther = function (other) {
	if (this._debug === true) log(this.name, "shipAttackedOther - " + other.displayName);

	// check if player is attacking a ship they surrendered to
	if (this._surrenderCheck_Pirate === true) {
		var scp = this._surrenderCurrent_Pirate;
		if (scp != null && scp.length > 0) {
			var found = false;
			var group = "";
			for (var i = 0; i < scp.length; i++) {
				if (scp[i] === other) {
					// we have a match - surrender is void
					found = true;
					group = scp[i].group;
					break;
				}
			}
			if (found === true) {
				// increase their accuracy
				for (var i = 0; i < scp.length; i++) {
					scp[i].target = player.ship;
					this.$addShipToAccuracyList(scp[i], 4, this._surrenderAccuracyLength);
					if (scp[i].isVisible === true && ((group == null || group === "") || this.$rand(20) > 10)) {
						// send a message from the group
						this.$sendMessageToPlayer(scp[0], this.$getRandomItemDescription("response-break-surrender-pirate"));
					}
				}
				// reset the surrender current values
				this._surrenderCheck_Pirate = false;
				this._surrenderCurrent_Pirate = [];
				this._surrenderTimer_Pirate.stop();
			}
		}
	}
}

//-------------------------------------------------------------------------------------------------------------
this.cargoDumpedNearby = function (cargo, ship) {
	// target dumped cargo after we asked
	if (this._source === ship && this._waitingForPiracyResponse) {
		if (this._debug === true) log(this.name, "cargo dumped from players piracy victim - piracy worked");

		// is ship in range of a police vessel or station
		if (this.$countLawVessels(ship) > 0 && this.$rand(50) > 40) {
			if (this._debug === true) log(this.name, "police vessel was notified of act of piracy - player bounty increased");

			this._piracyVictim = ship;
			// start a timer to send a notification from npc to police
			this._piracyNPCTimer = new Timer(this, this.$sendPiracyNPCResponse, 4, 0);
		}
		this._piracyWorked = true;
		this._waitingForPiracyResponse = false;
	}

	// player dumped cargo after surrender
	if (this._surrenderCheck_Pirate === true && ship === player.ship) {
		// player has complied with surrender demands - turn off retaliation
		this._surrenderTimer_Pirate.stop();
		this._surrenderCheck_Pirate = false;
		// set one of the pirates in the group to scoop cargo
		if (this._surrenderCurrent_Pirate.length > 0) {
			for (var i = 0; i < this._surrenderCurrent_Pirate.length; i++) {
				if (this._surrenderCurrent_Pirate[i].cargoSpaceCapacity > 0) {
					this._surrenderCurrent_Pirate[i] = cargo;
					break;
				}
			}
		}
		this._surrenderCurrent_Pirate = [];
	}
}

//-------------------------------------------------------------------------------------------------------------
this.playerDockingClearanceExpired = function () {
	this._dockingRequest = false;
	if (oolite.compareVersion("1.85") <= 0) this.$buildMessageList();
}

//-------------------------------------------------------------------------------------------------------------
this.playerDockingClearanceGranted = function () {
	this._dockingRequest = true;
	if (oolite.compareVersion("1.85") <= 0) this.$buildMessageList();
}

//-------------------------------------------------------------------------------------------------------------
this.playerDockingClearanceCancelled = function () {
	this._dockingRequest = false;
	if (oolite.compareVersion("1.85") <= 0) this.$buildMessageList();
}

//-------------------------------------------------------------------------------------------------------------
this.playerRequestedDockingClearance = function (message) {
	//log(this.name, "bcc " + message);
	switch (message) {
		case "DOCKING_CLEARANCE_EXTENDED":
		case "DOCKING_CLEARANCE_DENIED_TRAFFIC_OUTBOUND":
		case "DOCKING_CLEARANCE_DENIED_TRAFFIC_INBOUND":
		case "DOCKING_CLEARANCE_GRANTED":
			this._dockingRequest = true;
			break;
		case "DOCKING_CLEARANCE_DENIED_SHIP_FUGITIVE":
		case "DOCKING_CLEARANCE_DENIED_NO_DOCKS":
		case "DOCKING_CLEARANCE_NOT_REQUIRED":
		case "DOCKING_CLEARANCE_CANCELLED":
			this._dockingRequest = false;
			break;
	}
	if (oolite.compareVersion("1.85") <= 0) this.$buildMessageList();
}

//-------------------------------------------------------------------------------------------------------------
this.shipSpawned = function(ship) {
	var msgs = {};
	if (ship.scriptInfo.hasOwnProperty("bcc_custom_messages")) msgs = ship.scriptInfo.bcc_custom_messages;
	if (ship.script.hasOwnProperty("bcc_custom_messages")) msgs = ship.script.bcc_custom_messages;
	if (Object.keys(msgs).length > 0) {
		ship.script.bcc_id_source = this._idSource;
		this._idSource += 1;
		// put the dictionary on the ship script so we can track state (scriptInfo is readonly otherwise)
		ship.script.bcc_current_messages = JSON.parse(JSON.stringify(msgs));
		this.$setupCurrentCustomMessages(ship);
	}
}

//-------------------------------------------------------------------------------------------------------------
this.$setupCurrentCustomMessages = function(ship) {
	var msgs = ship.script.bcc_current_messages;
	if (Object.keys(msgs).length == 0) return;
	var keys = Object.keys(msgs);
	for (var i = 0; i < keys.length; i++) {
		var msg = msgs[keys[i]];
		if (msg.state == "visible") {
			if (this.$checkMessageExists(this.name + "_" + ship.script.bcc_id_source + "_" + keys[i]) == false) {
				this.$createMessage({
					messageName: this.name + "_" + ship.script.bcc_id_source + "_" + keys[i],
					transmissionType: "target",
					ship: ship,
					displayText: msg.display,
					messageText: expandDescription(msg.transmit),
					deleteOnTransmit: msg.delete_on_transmit,
					delayCallback: msg.delay,
					callbackFunction: this.$customMessageCallback.bind(this, ship, keys[i])
				});
			}
		} else {
			if (this.$checkMessageExists(this.name + "_" + ship.script.bcc_id_source + "_" + keys[i])) {
				this.$removeMessage(this.name + "_" + ship.script.bcc_id_source + "_" + keys[i]);
			}
		}
	}
}

//-------------------------------------------------------------------------------------------------------------
this.$customMessageCallback = function(ship, msg_key) {
	var msgs = ship.script.bcc_current_messages;
	var msg = msgs[msg_key];
	msg.state = "hidden";
	this.$sendMessageToPlayer(ship, expandDescription(msg.reply));
	if (msg.hasOwnProperty("post_reply_hide_messages") && Array.isArray(msg.post_reply_hide_messages)) {
		var replies = msg.post_reply_hide_messages;
		for (var i = 0; i < replies.length; i++) {
			msgs[replies[i]].state = "hidden";
		}
	}
	if (msg.hasOwnProperty("post_reply_show_messages") && Array.isArray(msg.post_reply_show_messages)) {
		var replies = msg.post_reply_show_messages;
		for (var i = 0; i < replies.length; i++) {
			msgs[replies[i]].state = "visible";
		}
	}
	if (msg.hasOwnProperty("post_reply_function") && msg.post_reply_function != "" && msg.hasOwnProperty("post_reply_worldscript") && msg.post_reply_worldscript != "") {
		worldScripts[msg.post_reply_worldscript][msg.post_reply_function](ship, msg_key);
	}
	// force the messages to be updated
	this.$setupCurrentCustomMessages(ship);
}

//=============================================================================================================
// starts the target watcher timer if it's appropriate to do so, otherwise stops the timer
this.$startTargetTimer = function () {
	// start/stop the check target timer
	if (this._targetType != "" && this._targetType != "station" && (this._checkTimer == null || this._checkTimer.isRunning === false)) {
		// if we have a non-station target, then start our check timer
		this._checkTimer = new Timer(this, this.$checkForTargetInvalid, 1, 1);
	} else if ((this._targetType === "" || this._targetType === "station") && this._checkTimer != null && this._checkTimer.isRunning === true) {
		// stop our timer - we don't have a target to watch anymore
		this._checkTimer.stop();
	}
}

//=============================================================================================================
// rebuilds the list of available messages, based on current state of play
this.$buildMessageList = function () {
	// store the currently selected message, so we can reselect it after the list is rebuilt
	var stored = "";
	if (this._selectedMsg >= 0 && this._selectedMsg < this._lines.length && this._lines[this._selectedMsg] != null) {
		stored = this._lines[this._selectedMsg].id;
	}

	// reset comms log settings
	this._lines = [];

	// p = player ship, t = player target, tt = player's targets target
	var p = player.ship,
		t = p && p.target,
		tt = t && t.target;

	var disable_defaults = [];
	if (t) {
		if (t.scriptInfo && t.scriptInfo.hasOwnProperty("bcc_disable_defaults") && Array.isArray(t.scriptInfo.bcc_disable_defaults)) disable_defaults = t.scriptInfo.bcc_disable_defaults;
		if (t.script && t.script.hasOwnProperty("bcc_disable_defaults") && Array.isArray(t.script.bcc_disable_defaults)) disable_defaults = t.script.bcc_disable_defaults;
	}

	if (p.equipmentStatus("EQ_BROADCASTCOMMSMFD") === "EQUIPMENT_OK") {

		if (this._targetLastComms && this._disableInternal[10] === false && disable_defaults.indexOf("10") == -1) this._lines.push({
			id: "core_10",
			text: "(Target last comms message)"
		});

		// add default messages (ie "broadcast" messages)
		// if there are hostiles present
		if (this._alertCond === 3 && this._disableInternal[2] === false && disable_defaults.indexOf("2") == -1) this._lines.push({
			id: "core_2",
			text: "Send distress message"
		});

		// load up any external messages
		if (this._externalMessages !== null && this._externalMessages.length > 0) {
			var cMsg = {};
			var bAdd = false;
			for (var i = 0; i < this._externalMessages.length; i++) {
				bAdd = false;
				cMsg = this._externalMessages[i];
				if (cMsg) {
					// are we going to display this message?
					if (cMsg.transmissionType === "broadcast" ||
						(cMsg.transmissionType === "target" && cMsg.ship === null && cMsg.shipDisplayName === "" && t !== null) ||
						(cMsg.transmissionType === "target" && cMsg.ship && t === cMsg.ship) ||
						(cMsg.transmissionType === "target" && cMsg.shipDisplayName && t.displayName === cMsg.shipDisplayName)) {
						bAdd = true;
					}
					if (this._alertCond === 3 && cMsg.hideOnConditionRed === true) bAdd = false;
					if (bAdd === true) this._lines.push({
						id: cMsg.messageName,
						text: cMsg.displayText
					});
				}
			}
		}

		// do we have a non-hostile target? add general target messages
		// greeting, taunt, drop your cargo,
		if (this._alertCond !== 3 && t && t.isPiloted && this._targetType !== "station" && this._targetType !== "escapepod") {
			// check who we've greeted before
			// only allow greeting to non-greeted ships
			if (this.$itemIsInArray(t, this._greeted) === false && this._disableInternal[3] === false && disable_defaults.indexOf("3") == -1) {
				this._lines.push({
					id: "core_3",
					text: "Send greeting to target"
				});
			}
			if (this._disableInternal[4] === false && disable_defaults.indexOf("4") == -1) this._lines.push({
				id: "core_4",
				text: "Send taunt to target"
			});

			// only add add a demand for cargo if the ship has cargo capacity
			if (t.cargoSpaceCapacity > 0 && this._disableInternal[7] === false && disable_defaults.indexOf("7") == -1) {
				// check who we've demanded cargo from before
				// only allow demands to ships once
				if (this.$itemIsInArray(t, this._demanded) === false) {
					this._lines.push({
						id: "core_7",
						text: "Demand " + this._demandCargo.toString() + " ton" + ((this._demandCargo === 1) ? "" : "s") + " of cargo from target"
					});
				}
			}
		}
		if (t && t.isPiloted && this._targetType === "escapepod") {
			if (this.$itemIsInArray(t, this._scoopOffer) === false && this._disableInternal[9] === false && disable_defaults.indexOf("9") == -1 && p.equipmentStatus("EQ_FUEL_SCOOPS") === "EQUIPMENT_OK") {
				this._lines.push({
					id: "core_9",
					text: "Offer to rescue escape pod"
				});
			}
		}

		// no target set (or target is not attacking player), but red alert condition
		if (this._alertCond === 3 && (!t || tt !== p)) {
			if (this._disableInternal[8] === false && disable_defaults.indexOf("8") == -1) this._lines.push({
				id: "core_8",
				text: "Surrender to nearest attacker"
			});
			if (this._disableInternal[6] === false && disable_defaults.indexOf("6") == -1) this._lines.push({
				id: "core_6",
				text: "Offer bribe to nearest attacker"
			});
		}

		// do we have a hostile target? add hostile target messages
		// i surrender, taunt, threat, bribe
		if (this._alertCond === 3 && t && t.isPiloted && this._targetType !== "station" && this._targetType !== "escapepod") {
			// only allow surrender to ships we haven't surrendered to before ... unless they are police attacking us!
			if ((t.isPolice && t.hasHostileTarget && tt == p) || (this._disableInternal[8] === false && disable_defaults.indexOf("8") == -1 && this.$itemIsInArray(t, this._surrenderedTo) === false)) {
				this._lines.push({
					id: "core_8",
					text: "Surrender to target"
				});
			}

			// only offer bribe option to ship targeting the player
			if (tt === p && player.credits >= this._bribeAmount && (this._targetType === "npc" || this._targetType === "pirate" || this._targetType === "hunter" || this._targetType === "police")) {
				// check who we've bribed before
				// only allow bribing to non-bribed ships
				if (this.$itemIsInArray(t, this._bribed) === false && this._disableInternal[6] === false && disable_defaults.indexOf("6") == -1) {
					this._lines.push({
						id: "core_6",
						text: "Offer bribe of " + this._bribeAmount.toString() + "cr to target"
					});
				}
			}
			if (this._disableInternal[4] === false && disable_defaults.indexOf("4") == -1) this._lines.push({
				id: "core_4",
				text: "Send taunt to target"
			});
			if (this._disableInternal[5] === false && disable_defaults.indexOf("5") == -1) this._lines.push({
				id: "core_5",
				text: "Issue threat to target"
			});
			if (this._disableInternal[11] === false && disable_defaults.indexOf("11") == -1) this._lines.push({
				id: "core_11",
				text: "Keep away from my target"
			});
		}

		// if a target system is set
		if (this._alertCond !== 3 && this._holdTarget !== global.system.ID && this._holdTarget >= 0 && this._disableInternal[1] === false && disable_defaults.indexOf("1") == -1) {
			this._lines.push({
				id: "core_1",
				text: "Is anyone heading to " + System.systemNameForID(this._holdTarget) + "?"
			});
		}
		// docking clearance
		if (typeof p.requestDockingClearance === "function") {
			if (t && t.isValid && t.isStation && t.canDockShip(p) && this._dockingRequest === false && this._disableInternal[12] === false && disable_defaults.indexOf("12") == -1) {
				this._lines.push({
					id: "core_12",
					text: "Request docking clearance"
				});
			}
			if (t && t.isValid && t.isStation && t.canDockShip(p) && this._dockingRequest === true && this._disableInternal[13] === false && disable_defaults.indexOf("13") == -1) {
				this._lines.push({
					id: "core_13",
					text: "Withdraw docking request"
				});
			}
		}
	}

	if (stored !== null && stored !== "") {
		// in our new array, check to see that our stored message still exists
		var found = false;
		for (var i = 0; i < this._lines.length; i++) {
			if (this._lines[i].id === stored) {
				// if it does, set the selected message pointer to it
				this._selectedMsg = i;
				found = true;
			}
		}
		if (found === false) {
			// if the selected message isn't there anymore, reset the selected message pointer
			this._selectedMsg = -1;
		}
	}

	this.$updateView(false);
}

//=============================================================================================================
// displays the available messages in the MFD
this.$updateView = function (sendConsole) {

	var p = player.ship;
	if (p.equipmentStatus("EQ_BROADCASTCOMMSMFD") === "EQUIPMENT_OK") {

		var output = "";
		var glyph = ">";

		// if we have lines, work out what to display, based on the current scroll position
		if (this._lines != null && (this._lines.length != 1 || this._lines[0].text != "")) {

			var start = 0;
			if (this._selectedMsg > this._MFDMaxLines - 1) start = this._selectedMsg - 8;

			// build the header
			output = "BROADCAST MESSAGE:";
			if (start > 0) output += " << ";
			if (this._lines.length > this._MFDMaxLines) {
				if (this._selectedMsg < this._lines.length - 1) {
					output += " >>";
				}
			}
			output += "\n";

			for (var i = start; i < this._lines.length; i++) {
				if (this._selectedMsg === i) {
					output += glyph + " ";
					// only send an update to the console if the option is on and the request came from user input
					if (this._outputToConsole === true && sendConsole === true) {
						player.consoleMessage("Selected: " + this._lines[i].text);
					}
				}
				output += this._lines[i].text + "\n";
			}
		} else {
			output = "BROADCAST MESSAGE:";
		}

		// send output to MFD
		p.setMultiFunctionText("BroadcastCommsMFD", output, false);

	} else {
		if (p.equipmentStatus("EQ_BROADCASTCOMMSMFD") == "EQUIPMENT_DAMAGED") {
			p.setMultiFunctionText("BroadcastCommsMFD", "BROADCAST MESSAGE:\n\nDamaged!\nTransmission system offline", false);
		}
	}
}

//=============================================================================================================
// transmit selected message
this.$transmit = function () {

	var p = player.ship;
	var msg = "";
	var id = "";
	var transmission = "";
	var params = {};

	// only allow a transmit if we're not currently waiting for a response to another message
	if (this._delay == null || this._delay.isRunning === false) {
		// do we have a selected message?
		if (this._selectedMsg >= 0 && this._selectedMsg < this._lines.length && this._lines[this._selectedMsg] != null) {
			id = this._lines[this._selectedMsg].id;
			msg = this._lines[this._selectedMsg].text;
			// perform tranmission
			switch (id) {
				case "core_1": //"Is anyone heading to somewhere?"
					this.$sendBroadcastMessage("Is anyone heading to " + System.systemNameForID(this._holdTarget) + "?");
					if (this._delay && this._delay.isRunning) this._delay.stop();
					this._delay = new Timer(this, this.$respondToRequestForWormhole, this._defaultResponseWait, 0);
					break;
				case "core_2": //"Send distress message":
					this.$sendBroadcastMessage(this.$getRandomItemDescription("transmit-help"));
					p.broadcastDistressMessage();
					break;
				case "core_3": //"Send greeting to target":
					if (this.$checkTarget() === false) return;
					this._source = p.target;
					transmission = this.$getCustomTransmission(this._source, 3);
					if (transmission == "") transmission = this.$getRandomItemDescription("transmit-greeting-" + this._targetType);
					this.$sendMessageToTarget(transmission);
					if (this._delay && this._delay.isRunning) this._delay.stop();
					this._delay = new Timer(this, this.$respondToGreeting.bind(this, this._source, this._targetType), this._defaultResponseWait, 0);
					break;
				case "core_4": //"Send taunt to target":
					if (this.$checkTarget() === false) return;
					this._source = p.target;
					transmission = this.$getCustomTransmission(this._source, 4);
					if (transmission == "") transmission = this.$getRandomItemDescription("transmit-taunt-" + this._targetType);
					this.$sendMessageToTarget(transmission);
					if (this._delay && this._delay.isRunning) this._delay.stop();
					this._delay = new Timer(this, this.$respondToTaunt.bind(this, this._source, this._targetType), this._defaultResponseWait, 0);
					// add this target to the greeted array - illogical to say Hi after a taunt
					this.$addShipToArray(this._source, this._greeted);
					// update the MFD straight away, because we might do this in yellow alert status
					this.$buildMessageList();
					break;
				case "core_5": //"Issue threat to target":
					if (this.$checkTarget() === false) return;
					this._source = p.target;
					transmission = this.$getCustomTransmission(this._source, 5);
					if (transmission == "") transmission = this.$getRandomItemDescription("transmit-threat-" + this._targetType);
					this.$sendMessageToTarget(transmission);
					if (this._delay && this._delay.isRunning) this._delay.stop();
					this._delay = new Timer(this, this.$respondToThreat, this._defaultResponseWait, 0);
					// add this target to the greeted array - illogical to say Hi after a threat
					this.$addShipToArray(this._source, this._greeted);
					break;
				case "core_6": //"Offer bribe to target":
					if (msg.indexOf("nearest") === -1) {
						if (this.$checkTarget() === false) return;
						this._source = p.target;
						var crType = "credits";
						if (this._bribeAmount === 1) crType = "credit";
						params = {amount: this._bribeAmount.toString(), credittype: crType};

						transmission = this.$getCustomTransmission(this._source, 6, params);
						if (transmission == "") transmission = this.$getRandomItemDescription("transmit-bribe", params);

						this.$sendMessageToTarget(transmission);
						if (this._delay && this._delay.isRunning) this._delay.stop();
						this._delay = new Timer(this, this.$respondToBribe, this._defaultResponseWait, 0);

						// add this target to the greeted array - illogical to say Hi after a bribe
						this.$addShipToArray(this._source, this._greeted);
					} else {
						this._source = this.$findNearestAttacker();
						if (this.$itemIsInArray(this._source, this._bribed) === false) {
							// add this target to the greeted array - illogical to say Hi after a bribe
							this.$addShipToArray(this._source, this._greeted);

							this.$workOutCurrentBribeAmount(this._source);
							if (this._bribeAmount < player.credits) {
								params = {amount: this._bribeAmount.toString(), credittype: crType};
								transmission = this.$getCustomTransmission(this._source, 6, params);
								if (transmission == "") transmission = this.$getRandomItemDescription("transmit-bribe", params);
		
								this.$sendMessageToTarget(transmission);
								if (this._delay && this._delay.isRunning) this._delay.stop();
								this._delay = new Timer(this, this.$respondToBribe, this._defaultResponseWait, 0);
							} else {
								// we shouldn't ever get here, because once you reach beyond your credit balance you are automatically
								// added to the bribed array, but still. Just in case.
								player.consoleMessage("Insufficient credits to offer");
							}
						} else {
							player.consoleMessage("Closest ship has already been bribed");
						}
					}
					break;
				case "core_7": //"Demand cargo":
					if (this.$checkTarget() === false) return;
					this._source = p.target;
					var tons = this._demandCargo.toString() + ((this._demandCargo === 1) ? " ton" : " tons");

					params = {"tons": tons};
					transmission = this.$getCustomTransmission(this._source, 7, params);
					if (transmission == "") transmission = this.$getRandomItemDescription("transmit-piracy", params);

					this.$sendMessageToTarget(transmission);
					this._waitingForPiracyResponse = true;

					if (this._targetType === "npc" || this._targetType === "pirate") {
						if (this._debug === true) log(this.name, "ship '" + this._source.displayName + "' might be responding");

						if (this._source.AIScript.oolite_priorityai) {
							if (this._debug === true) log(this.name, "ai found!");

							var hold = this._source.target;
							this._source.AIScript.oolite_intership.cargodemand = this._demandCargo;
							this._source.target = p;
							this._source.performAttack();
							this._source.AIScript.oolite_priorityai.reconsiderNow();
							// de-target the player so any red-alert is removed
							this._source.target = hold;
						} else {
							if (this._debug === true) log(this.name, "no priority AI found!");

						}
					}
					// we can only demand once - after that, if you want cargo, use your lasers!
					this.$addShipToArray(this._source, this._demanded);
					this.$addShipToArray(this._source, this._greeted);
					if (this._delay && this._delay.isRunning) this._delay.stop();
					this._delay = new Timer(this, this.$respondToDemandForCargo, this._defaultResponseWait, 0);
					this.$buildMessageList();
					break;
				case "core_8": //"Surrender to target":
					if (msg.indexOf("nearest") === -1) {
						if (this.$checkTarget() === false) return;
						this._source = p.target;
						transmission = this.$getCustomTransmission(this._source, 8);
						if (transmission == "") transmission = this.$getRandomItemDescription("transmit-surrender");
						this.$sendMessageToTarget(transmission);
						if (this._delay && this._delay.isRunning) this._delay.stop();
						this._delay = new Timer(this, this.$respondToSurrender, this._defaultResponseWait, 0);
						// add this target to the greeted array - illogical to say Hi after a surrender
						this.$addShipToArray(this._source, this._greeted);
					} else {
						this._source = this.$findNearestAttacker();
						if ((this._source && this._source.isPolice) || this.$itemIsInArray(this._source, this._surrenderedTo) === false) {
							transmission = this.$getCustomTransmission(this._source, 8);
							if (transmission == "") transmission = this.$getRandomItemDescription("transmit-surrender");
							this.$sendMessageToTarget(transmission);
							if (this._delay && this._delay.isRunning) this._delay.stop();
							this._delay = new Timer(this, this.$respondToSurrender, this._defaultResponseWait, 0);
							// add this target to the greeted array - illogical to say Hi after a surrender
							this.$addShipToArray(this._source, this._greeted);
						} else {
							player.consoleMessage("Closest ship has already been surrendered to");
						}
					}
					break;
				case "core_9": //"Offer to rescue escape pod":
					if (this.$checkTarget() === false) return;
					this._source = p.target;
					transmission = this.$getCustomTransmission(this._source, 9);
					if (transmission == "") transmission = this.$getRandomItemDescription("transmit-escapepod");
					this.$sendMessageToTarget(transmission);
					if (this._delay && this._delay.isRunning) this._delay.stop();
					this._delay = new Timer(this, this.$respondToEscapePodOffer, this._defaultResponseWait, 0);
					break;
				case "core_10": //"(Target last comms message)":
					if (this._targetLastComms) {
						// still in range?
						var found = false;
						var tlc = p.checkScanner(true);
						for (var i = 0; i < tlc.length; i++) {
							if (tlc[i] === this._targetLastComms) found = true;
						}
						if (found === false) {
							player.consoleMessage("Unable to locate target");
						} else {
							p.target = this._targetLastComms;
						}
					}
					this._targetLastComms = null;
					this.$buildMessageList();
					break;
				case "core_11": //"Keep away from my target":
					if (this.$checkTarget() === false) return;
					this._source = p.target;
					this.$sendBroadcastMessage(this.$getRandomItemDescription("transmit-keep-away-from-target"));
					if (this._delay && this._delay.isRunning) this._delay.stop();
					this._delay = new Timer(this, this.$respondToKeepAwayFromTarget, this._defaultResponseWait, 0);
					break;
				case "core_12": // request docking clearance
				case "core_13": // withdraw docking request
					if (this.$checkTarget() === false) return;
					if (p.target.isStation && p.target.canDockShip(p)) {
						if (this._dockingRequest === false) {
							this._dockingRequest = true;
							this.$sendBroadcastMessage(this.$getRandomItemDescription("request-docking-clearance"));
						} else {
							this.$sendBroadcastMessage(this.$getRandomItemDescription("withdraw-docking-request"));
							this._dockingRequest = false;
						}
						if (this._delay && this._delay.isRunning) this._delay.stop();
						this._delay = new Timer(this, this.$respondToDockingRequest, this._defaultResponseWait, 0);
						this._targetStation = p.target;
					} else {
						player.consoleMessage("Current target is not station or cannot dock ship");
					}
					break;
				default:
					// this should be any external messages
					var cMsg = {};
					// find the message to call
					for (var i = 0; i < this._externalMessages.length; i++) {
						cMsg = this._externalMessages[i];
						if (cMsg) {
							if (cMsg.messageName === id) {
								// we have a hit
								// but only transmit the message if it doesn't start with bracket characters
								if (msg.substring(0, 1) != "{" && msg.substring(0, 1) != "(" && msg.substring(0, 1) != "[" && msg.substring(0, 1) != "<") {  
									this.$sendMessageToTarget(expandDescription(cMsg.messageText));
								}
								if (this._delay && this._delay.isRunning) this._delay.stop();
								this._delay = new Timer(this, cMsg.callbackFunction, cMsg.delayCallback, 0);
								if (cMsg.deleteOnTransmit === true) {
									this._externalMessages[i] = null;
									this.$buildMessageList();
								}
								// break out of the loop once we've found a hit.
								break;
							}
						}
					}
					break;
			}
		}
	}
}

//-------------------------------------------------------------------------------------------------------------
this.$getCustomTransmission = function(ship, typeID, params) {
	var msg = "";
	
	var custom_defaults = {};
	if (ship.scriptInfo.hasOwnProperty("bcc_custom_defaults")) {
		custom_defaults = ship.scriptInfo.bcc_custom_defaults;
	}
	if (ship.script.hasOwnProperty("bcc_custom_defaults")) {
		custom_defaults = ship.script.bcc_custom_defaults;
	}

	if (Object.keys(custom_defaults).length > 0) {
		if (custom_defaults.hasOwnProperty(typeID) && custom_defaults[typeID].hasOwnProperty("transmit")) {
			if (params) {
				msg = expandDescription(custom_defaults[typeID].transmit, params);
			} else {
				msg = expandDescription(custom_defaults[typeID].transmit);
			}
		}
	}
	return msg;
}

//-------------------------------------------------------------------------------------------------------------
this.$getCustomReply = function(ship, typeID, customProperty, params) {
	var msg = "";
	var property = "reply";
	if (customProperty && customProperty != "") property = customProperty;
	
	var custom_defaults = {};
	if (ship.scriptInfo.hasOwnProperty("bcc_custom_defaults")) {
		custom_defaults = ship.scriptInfo.bcc_custom_defaults;
	}
	if (ship.script.hasOwnProperty("bcc_custom_defaults")) {
		custom_defaults = ship.script.bcc_custom_defaults;
	}

	if (Object.keys(custom_defaults).length > 0) {
		if (custom_defaults.hasOwnProperty(typeID) && custom_defaults[typeID].hasOwnProperty(property)) {
			if (params) {
				msg = expandDescription(custom_defaults[typeID][property], params);
			} else {
				msg = expandDescription(custom_defaults[typeID][property]);
			}
		}
	}
	return msg;
}

//-------------------------------------------------------------------------------------------------------------
// send message out to target
this.$sendMessageToTarget = function (msg) {
	// second parameter causes function to either fail or happen invisibly
	player.ship.commsMessage(msg); //player.ship.target
}

//-------------------------------------------------------------------------------------------------------------
// send message out to everyone
this.$sendBroadcastMessage = function (msg) {
	player.ship.commsMessage(msg);
}

//-------------------------------------------------------------------------------------------------------------
// send message to player
this.$sendMessageToPlayer = function (source, msg) {
	if (source.isValid === true) source.commsMessage(msg, player.ship);
}

//-------------------------------------------------------------------------------------------------------------
// checks that the player has a valid target
this.$checkTarget = function () {
	if (player.ship.target == null) {
		player.consoleMessage("Unable to locate target");
		this.$buildMessageList();
		return false;
	} else {
		return true;
	}
}

//=============================================================================================================
// response routines

//=============================================================================================================
// wormhole request - 1
this.$respondToRequestForWormhole = function $respondToRequestForWormhole() {
	// find all NPC ships in range who are about to go to destination
	var npc = this.$findNPCShips();
	var rsp = false;

	npc.forEach(function (ship) {
		// if the ships destination system is the same as the players, and a random chance passes, display a "come along" message
		if (ship.destinationSystem === this._holdTarget && this.$rand(10) > 1) {
			var respType = "std";
			// there is a 70% chance a pirate will give an unfriendly response, and a 30% chance a trader will do it as well
			if (((ship.hasRole("pirate") || ship.hasRole("assassin-light") || ship.hasRole("assassin-medium") || ship.hasRole("assassin-heavy")) && Math.random() > 0.3) || Math.random() > 0.7) {
				respType = "unfriendly";
			}
			var reply = "";
			reply = this.$getCustomReply(ship, 1, "reply_confirm");
			if (reply == "") reply = this.$getRandomItemDescription("response-wormhole-" + respType);
			this.$sendMessageToPlayer(ship, reply);
		}
		// if the ships destination system is not the players destination, but the ships destination is outbound, and a random chance passes, display a "I'm going somewhere else" message
		if (ship.destinationSystem != this._holdTarget && ship.destinationSystem != global.system.ID && ship.hasRole("generationship") === false && this.$rand(10) > 2) {
			if (ship.hasRole("pirate") || ship.hasRole("assassin-light") || ship.hasRole("assassin-medium") || ship.hasRole("assassin-heavy")) {
				// unfriendly ships won't respond
				return;
			}
			var reply = "";
			var params = {systemname: System.systemNameForID(ship.destinationSystem)};
			reply = this.$getCustomReply(ship, 1, "reply_decline", params);
			if (reply == "") reply = this.$getRandomItemDescription("response-wormhole-decline", params);
			this.$sendMessageToPlayer(ship, reply);
		}
	}, this);
}

//=============================================================================================================
// respond to comms message -- ???
this.$respondToCommsMessage = function $respondToCommsMessage () {
	// is target hostile?
	if (this._source.hasRole("generationship")) return;
	if (this._source.hasRole("thargoid")) {
		this.$sendMessageToPlayer(this._source, expandDescription("[thargoid_curses]"));
	} else {
		if (this._alertCond === 3) {
			// is target actually targeting the player, or is the target part of a group that is targeting the player?
			var friend = true;
			if (this._source.target === player.ship) {
				friend = false
			} else {
				if (this._source.group.ships && this._source.group.ships.length > 0) {
					var ships = this._source.group.ships;
					ships.forEach(function (ship) {
						if (ship.target === player.ship) friend = false;
					}, this);
				}
			}

			if (friend === false) {
				this.$sendMessageToPlayer(this._source, this.$getRandomItemDescription("response-comms-unfriendly"));
			} else {
				this.$sendMessageToPlayer(this._source, this.$getRandomItemDescription("response-comms-friendly"));
			}
		} else {
			// is this ship under attack?
			if (this.$countShipsTargetingNPC(this._source) > 0) {
				this.$sendMessageToPlayer(this._source, this.$getRandomItemDescription("response-comms-friendly"));
			} else {
				this.$sendMessageToPlayer(this._source, this.$getRandomItemDescription("response-comms-nobattle"));
			}
		}
	}
}

//=============================================================================================================
// escape pod - 9
this.$respondToEscapePodOffer = function $respondToEscapePodOffer() {
	// did we shoot his ship down?
	var podshipname = "";
	if (this._escapepodShipNames.length > 0) {
		for (var i = 0; i < this._escapepodShipNames.length; i++) {
			if (this._escapepodShipNames[i].escapepod === this._source) podshipname = this._escapepodShipNames[i].shipname;
		}
	}

	var reply = "";
	if (podshipname != "" && this.$itemIsInArray(podshipname, this._killedNames) === true) {
		reply = this.$getCustomReply(this._source, 9, "reply_unfriendly");
		if (reply == "") reply = this.$getRandomItemDescription("response-escapepod-unfriendly");
		this.$sendMessageToPlayer(this._source, reply);
	} else {
		reply = this.$getCustomReply(this._source, 9, "reply_friendly");
		if (reply == "") reply = this.$getRandomItemDescription("response-escapepod-friendly");
		this.$sendMessageToPlayer(this._source, reply);
	}

	this.$addShipToArray(this._source, this._scoopOffer);
	// rebuild the messagelist (so that offer to scoop is removed)
	this.$buildMessageList();
}

//=============================================================================================================
// keep away from target - 11
this.$respondToKeepAwayFromTarget = function $respondToKeepAwayFromTarget() {

	var ships = checkScanner(true);
	// look for any ships that are currently targeting the players target
	ships.forEach(function (ship) {
		if (ship.target === this._source) {
			// is this ship friendly towards player
			var friend = true;
			if (ship.group.ships && ship.group.ships.length > 0) {
				var shipgroup = ship.group.ships;
				shipgroup.forEach(function (g_ship) {
					if (g_ship.target === player.ship) friend = false;
				}, this);
			}
			// only respond if ship is friendly
			if (friend) {
				ship.target = null;
				ship.removeDefenseTarget(this._source);
				var reply = this.$getCustomReply(this._source, 11);
				if (reply == "") reply = this.$getRandomItemDescription("response-keep-away-from-target");
				this.$sendMessageToPlayer(this._source, reply);
			}
		}
	}, this);
}

//=============================================================================================================
// respond to "request docking clearance" - 12
this.$respondToDockingRequest = function $respondToDockingRequest() {
	if (this._targetStation.isValid && this._targetStation.isStation && this._targetStation.canDockShip(player.ship)) {
		if (this._dockingRequest === true) {
			player.ship.requestDockingClearance(this._targetStation);
		} else {
			player.ship.cancelDockingRequest(this._targetStation);
		}
		this.$buildMessageList();
	}
}

//=============================================================================================================
// greeting - 3
this.$respondToGreeting = function $respondToGreeting(ship, targetType) {

	if (ship != null && ship.hasRole("generationship")) return;

	// get one of predefined list of greetings and transmit
	var rsp = 0; // 0 = no response, 1 = pick a response
	if (this.$rand(10) > 2) rsp = 1;

	// always give a response for thargoids
	if (targetType === "thargoid") rsp = 1;

	// todo: could this make the target friendlier towards the player?
	// any changes to NPC behaviour will happen regardless of whether they have decided to send a response or not

	if (rsp === 1 && ship != null && targetType != "" && ship.isPiloted) {
		var reply = "";
		reply = this.$getCustomReply(ship, 3);
		if (reply == "") reply = this.$getRandomItemDescription("response-greeting-" + this._targetType);
		this.$sendMessageToPlayer(ship, reply);
		// add the target to the greeted list
		this.$addShipToArray(ship, this._greeted);
		// rebuild the messagelist (so that greeting is removed)
		this.$buildMessageList();
	}
}

//=============================================================================================================
// bribe - 6
this.$respondToBribe = function $respondToBribe() {
	// there will always be a response to this message
	// based on the amount of bribe, the chances of accepting grow steadily greater
	// i.e. low chance of accepting with low amounts, greater chance with higher amounts
	// initial chance of 2.5% of accepting (slightly higher chance of 12.5% when player is in the early game), increases as digits are added to bribe amount
	var shiptype = "";
	var chance = 0.025 + ((player.score < 100) ? 0.1 : 0.0) + (0.1 * (this._bribeAmount.toString().length - this._bribeInitial.toString().length))

	// set the shiptype - initially the targeted shiptype
	shiptype = this._targetType;
	// however, if the closestAttacker is set, use that
	if (this._closestAttackerType != "") shiptype = this._closestAttackerType;

	if (shiptype === "police") {
		// no chance of bribing a police vessel in the aegis
		if (this._source.position.distanceTo(system.mainStation) < 51200) chance = 0;

		// chance decreases based on system government level
		if (chance > 0) {
			chance -= system.info.government;
			if (chance < 0) chance = 0;
		}
	}

	if (this._debug === true) log(this.name, "chance of accepting bribe = " + chance.toString());

	if (this._source != null && shiptype != "") {
		var reply = "";
		// if the pirates are demanding cargo, they might be susceptible to a bribe.
		// if they're a bounty hunter, they probably want to kill the player, so they won't be
		if (((shiptype === "police" || shiptype === "pirate") && this.$rand(50) > (50 * chance)) || shiptype === "hunter") {
			// decline
			if (this._debug === true) log(this.name, shiptype + " '" + this._source.displayName + "' rejected bribe of " + this._bribeAmount.toString());

			// after a few bribes, the police will eventually fine the player
			if (shiptype === "police") {
				if (this._bribeAmount > 5 && Math.random() < 0.5) {
					player.ship.setBounty(player.ship.bounty + this.$rand(20), "attempted bribe");
					this.$addShipToBribedArray(this._source);
					this._bribeAmount = this._bribeInitial;
					reply = this.$getCustomReply(this._source, 6, "reply_fine");
					if (reply == "") reply = this.$getRandomItemDescription("response-bribe-police-fine");
					this.$sendMessageToPlayer(this._source, reply);
					this.$buildMessageList();
					return;
				}
			}

			reply = this.$getCustomReply(this._source, 6, "reply_decline");
			if (reply == "") reply = this.$getRandomItemDescription("response-bribe-decline-" + shiptype);
			this.$sendMessageToPlayer(this._source, reply);
			if (this._bribeAmount.toString().substring(0, 1) === "2") {
				// use 2.5 when then current amount starts with "2", so from 200 we end up on 500, from 2000 to 5000, 20000 to 50000 etc
				this._bribeAmount *= 2.5;
			} else {
				// otherwise use the default multiple of 2, so we go from 100 to 200, 500 to 1000 etc
				this._bribeAmount *= 2;
			}
			if (this._bribeAmount > player.credits) {
				// can't be bribed
				// add them to the bribed list so they can't be offered a bribe and reset bribe amount
				this.$addShipToBribedArray(this._source);
				this._bribeAmount = this._bribeInitial;
			} else {
				// update the current bribe array with the new bribe amount
				// if the player detargets the ship and retargets them, the same amount will then be shown
				this.$updateShipInCurrentBribeArray(this._source, true);
			}
			this.$buildMessageList();
		} else {
			// accept
			if (this._debug === true) log(this.name, shiptype + " '" + this._source.displayName + "' accepted bribe of " + this._bribeAmount.toString());

			reply = this.$getCustomReply(this._source, 6, "reply_accept");
			if (reply == "") reply = this.$getRandomItemDescription("response-bribe-accept-" + (shiptype === "police" ? "police" : "npc"));
			this.$sendMessageToPlayer(this._source, reply);

			player.credits -= this._bribeAmount;
			// detarget the player from the group
			if (this._source.group != "") {
				this.$removePlayerTargetFromPirateGroup(this._source);
			} else {
				if (this._debug === true) log(this.name, "player de-targeted from ship " + this._source.displayName);

				this._source.target = null;
			}
			// add the ship(s) to the bribed array so we don't offer that option again
			this.$addShipToBribedArray(this._source);
			this._bribeAmount = this._bribeInitial;
			this.$buildMessageList();
		}
	}
}

//-------------------------------------------------------------------------------------------------------------
// add a ship (or a group of ships) to the bribeCurrent array
this.$updateShipInCurrentBribeArray = function (ship, recursive) {

	// keep track of what we've offered different ships
	if (this._bribeCurrent != null && this._bribeCurrent.length > 0) {
		var found = false;
		for (var i = 0; i < this._bribeCurrent.length; i++) {
			if (this._bribeCurrent[i].ship === ship) {
				this._bribeCurrent[i].amount = this._bribeAmount;
				found = true;
			}
		}
		if (found === false) {
			this._bribeCurrent.push({
				ship: ship,
				amount: this._bribeAmount
			});
		}
	} else {
		this._bribeCurrent.push({
			ship: ship,
			amount: this._bribeAmount
		});
	}

	// add in all the pirates in the group, so we can't keep trying to bribe individual members
	if (recursive === true) {
		if (ship.group == null || ship.group === "") {
			// single
			// do nothing
		} else {
			// group
			var shipGroup = ship.group.ships;
			shipGroup.forEach(function (groupship) {
				this.$updateShipInCurrentBribeArray(groupship, false);
			}, this);
		}
	}
}

//-------------------------------------------------------------------------------------------------------------
// adds a ship (or a group of ships) to the bribed array
this.$addShipToBribedArray = function (ship) {
	if (ship.group == null || ship.group === "") {
		// single
		this.$addShipToArray(ship, this._bribed);
	} else {
		// group
		var shipgroup = ship.group.ships;
		shipgroup.forEach(function (groupship) {
			this.$addShipToArray(groupship, this._bribed);
		}, this);
	}
}

//=============================================================================================================
// taunt - 4
this.$respondToTaunt = function $respondToTaunt(ship, targetType) {
	if (ship != null && ship.hasRole("generationship")) return;

	var reply = "";

	// get one of predefined list of taunts and transmit
	var rsp = 0; // 0 = no response, 1 = pick a response
	// hight chance of a response
	if (this.$rand(10) > 2) rsp = 1;
	// always give a response for thargoids
	if (targetType === "thargoid") rsp = 1;

	var p = player;

	// any changes to NPC behaviour will happen regardless of whether they have decided to send a response or not
	// todo: could this make the target angrier towards the player?
	// possibilities: for enemy: briefly makes their accuracy drop
	// 				  			 briefly makes their accuracy increase
	if (ship != null && ship.target === p.ship && this._alertCond === 3 && targetType != "" && targetType != "thargoid") {
		if (this._debug === true) log(this.name, "we'll probably be changing the accuracy now...");

		// settings to configure randomness
		var chanceOfChange = 0.5; // likelihood of change as decimal, where 0.5 = 50%, .75 = 75%, etc
		var maxChange = 3; // maximum amount of change to accuracy

		// this npc is targeting the player
		// decide if this taunt is going to have an impact
		if (this.$rand(50) >= (50 * chanceOfChange)) {
			if (this._debug === true) log(this.name, "yep - changing");

			// decide how much better/worse their accuracy will be, either 1, 2 or 3 points
			var diff = this.$rand(maxChange);
			// decide if the npc will get become more or less accurate, although more likely it will be less accurate
			// 33% chance of more accurate, 66% chance of less accurate
			if (this.$rand(3) > 1) diff *= -1; // less accurate

			// add the ship to the accuracyChanged array
			this.$addShipToAccuracyList(ship, diff, this._tauntAccuracyLength);
		}
	}
	// if the players target is not targeting the player, then potentially switch their target to the player
	// this could be used to draw someone's fire away from another ship
	if (ship != null && ship.target != p.ship && targetType != "") {
		var chance = 45;
		// adjust the chance by player score - the higher the score the less likely they will attack
		// elite ranking
		if (p.score >= 32 && p.score < 64) chance -= 5; // average
		if (p.score >= 64 && p.score < 128) chance -= 10; // above average
		if (p.score >= 128 && p.score < 512) chance -= 15; // competant
		if (p.score >= 512 && p.score < 2560) chance -= 20; // dangerous
		if (p.score >= 2560 && p.score < 6400) chance -= 25; // deadly
		if (p.score >= 6400) chance -= 30; // elite
		this.$willPlayerTargetTurnOnPlayer(chance);
	}

	// for nonenemy: make them more likely to start firing at you

	// send out the response
	if (rsp === 1 && ship != null && ship.isPiloted) {
		reply = this.$getCustomReply(ship, 4);
		if (reply == "") reply = this.$getRandomItemDescription("response-taunt-" + targetType);
		this.$sendMessageToPlayer(ship, reply);
	}
}

//=============================================================================================================
// piracy (demand for cargo) - 7
this.$respondToDemandForCargo = function $respondToDemandForCargo() {

	if (this._source != null && this._source.hasRole("generationship")) return;

	// only need to send a response if the ships refuses to comply
	if (this._source != null && this._targetType != "" && this._piracyWorked === false) {
		var reply = this.$getCustomReply(this._source, 7, "reply_refuse");
		if (reply == "") reply = this.$getRandomItemDescription("response-piracy-" + this._targetType);
		this.$sendMessageToPlayer(this._source, reply);
		// rebuild the messagelist (so that greeting is removed)
		this._waitingForPiracyResponse = false;
		this.$buildMessageList();
	}

	this._piracyWorked === false;

}

//=============================================================================================================
// surrender - 8
this.$respondToSurrender = function $respondToSurrender() {
	// todo: has pirate asked for cargo?

	//note: real check will be 48
	if (this._debug === true) log(this.name, "responding to surrender");
	var reply = "";

	// pirate have a chance of accepting the surrender, police will always accept it
	if (this._source != null && (this._source.isPolice || this.$rand(50) > 45)) {
		// remove the player as the primary target from any NPC's targeting the player
		if (this._source.isPolice === false) {
			this._surrenderCurrent_Pirate = [];
			var isGroup = false;
			if (this._source.group && this._source.group != "") {
				isGroup = true;
				var group = this._source.group.ships;
				group.forEach(function (ship) {
					this.$addShipToArray(ship, this._surrenderedTo);
					this.$addShipToArray(ship, this._surrenderCurrent_Pirate);
				}, this);
			} else {
				this.$addShipToArray(this._source, this._surrenderedTo);
				this.$addShipToArray(this._source, this._surrenderCurrent_Pirate);
			}
			this.$buildMessageList();

			if (this._debug === true) log(this.name, "pirates accepted surrender");

			if (this._source.group != "") {
				this.$removePlayerTargetFromPirateGroup(this._source);
			} else {
				this._source.target = null;
			}
			if (!this._surrenderTimer_Pirate || this._surrenderTimer_Pirate.isRunning === false) {
				this._surrenderTimer_Pirate = new Timer(this, this.$surrenderCheckState_Pirate, this._surrenderWait, 0);
			}
			this._surrenderCheck_Pirate = true;
			// reply should be "Hurry up and dump some cargo, scum"
			var pnCap = "We";
			var pnLw = "we";
			if (isGroup === false) {
				pnCap = "I";
				pnLw = "I";
			}
			reply = this.$getCustomReply(this._source, 8, "reply_impatient");
			if (reply == "") reply = this.$getRandomItemDescription("response-surrender-pirate", {pronouncap: pnCap, pronoun: pnLw});
			this.$sendMessageToPlayer(this._source, reply);

		} else {
			// police surrender
			// only if bounty is less than 50 ie offender status only. Fugitive will not be allowed to surrender
			if (player.bounty < 50) {
				if (player.bounty > 30 && Math.random() < 0.5) {
					if (this._debug === true) log(this.name, "police ignored surrender request from player with high bounty (50% chance)");
					return; // no response - player can try sending another surrender
				}
				reply = this.$getCustomReply(this._source, 8, "reply_police_accept");
				if (reply == "") reply = this.$getRandomItemDescription("response-surrender-police");
				this.$sendMessageToPlayer(this._source, reply);
				this.$addShipToArray(this._source, this._surrenderedTo); // add the police ship that responded to list that player can't surrender to again (we also use this to identify who should send rejection if the player doesn't comply)
				this._surrenderCheck_Police = true; // a separate variable is needed because the timer keeps running and we want to know if the player surrenders again to new police after gaining additional bounty
				if (this._surrenderPoliceChances === 0) this._surrenderPoliceChances = 5; // number of timer updates to tolerate non-compliance (but no extra chances if they send multiple surrender messages)
				// start a timer, if it's not already started
				if (!this._surrenderTimer_Police || this._surrenderTimer_Police.isRunning === false) {
					this._surrenderTimer_Police = new Timer(this, this.$surrenderCheckState_Police, this._surrenderWait_Police, this._surrenderWait_Police); // repeats until stopped
				}
			} else {
				reply = this.$getCustomReply(this._source, 8, "reply_police_reject");
				if (reply == "") reply = this.$getRandomItemDescription("response-deny-surrender-police");
				this.$sendMessageToPlayer(this._source, reply); // fugitive rejection
			}
		}
	} else {
		if (this._debug === true) log(this.name, "declined");
		if (this._source.isThargoid) {
			this.$sendMessageToPlayer(this._source, expandDescription("[thargoid_curses]"));
		}
	}
}

//-------------------------------------------------------------------------------------------------------------
// check that the player has surrendered to the pirate
// if we get here, it means the player hasn't dumped cargo (which would stop the timer)
this.$surrenderCheckState_Pirate = function $surrenderCheckState_Pirate() {

	this._surrenderCheck_Pirate = false;
	var scp = this._surrenderCurrent_Pirate;
	if (scp != null && scp.length > 0) {
		for (var i = 0; i < scp.length; i++) {
			// retarget the player
			if (scp[i] && scp[i].isValid === true) {
				scp[i].target = player.ship;
				if (this._debug === true) log(this.name, "player re-targeted on ship " + scp[i].displayName + " in group " + scp[i].group);

				// send message, from random members of the group, or from the single pirate otherwise
				if (scp[i].isVisible === true && ((scp[i].group == null || scp[i].group === "") || this.$rand(20) > 10)) {
					// send a message from the group
					this.$sendMessageToPlayer(scp[0], this.$getRandomItemDescription("response-break-surrender-pirate"));
				}
				scp[i].performAttack();
			}
		}
	}
	// clear the array
	this._surrenderCurrent_Pirate = [];
}

//-------------------------------------------------------------------------------------------------------------
// check that the player has surrendered to the police
// player has to disable their weapons system ("_" key)
/*this.$surrenderCheckState_Police = function $surrenderCheckState_Police() {

	var law = this.$findLawVessels(player.ship);
	var sent = false;

	// if the player hasn't turned their weapons off transmit message
	if (player.ship.weaponsOnline) {
		if (this._debug === true) log(this.name, "Police rejected surrender");
		// send a message from the group
		if (law.length > 0) {
			for (var i = 0; i < law.length; i++) {
				if (law[i].isStation === false && law[i].target != player.ship) {
					if (sent === false) {
						this.$sendMessageToPlayer(law[i], this.$getRandomItemDescription("response-break-surrender-police"));
						sent = true;
					}
				}
			}
		}
	} else {
		if (this._debug === true) log(this.name, "Police accepted surrender");
		for (var i = 0; i < law.length; i++) {
			if (law[i].isStation === false && law[i].target === player.ship) {
				this.$addShipToArray(law[i], this._surrenderedTo);
				if (sent === false) {
					this.$sendMessageToPlayer(law[i], this.$getRandomItemDescription("response-accept-surrender-police"));
					sent = true;
					// mark the player for fines
					if (player.bounty > 0) {
						law[i].markTargetForFines();
					}
				}
				law[i].target = null;
				if (this._debug === true) log(this.name, law[i].displayName + " de-targeting player");
			}
		}
	}
}*/
//-------------------------------------------------------------------------------------------------------------
// check if the player is complying with instructions after surrendering to police, and make police stop hostilities if they do
// player has to disable their weapons system ("_" key) initially but can switch them on again after surrender is accepted
// player has to approach the main station without deviating more than 50km (player can take all day to travel the distance, but must not go somewhere else)
// this timer will continue running until the player is declared non-compliant, or docks and pays their fine, or jumps out of the system
this.$surrenderCheckState_Police = function $surrenderCheckState_Police() {
	var that = $surrenderCheckState_Police; // pointer to this function
	var bountysystem = (that.bountysystem = that.bountysystem || worldScripts.BountySystem_Core); // cache worldScript reference in a local property on this function
	var p = player.ship,
		ms = system.mainStation;
	var dist = (ms && ms.isValid) ? p.position.distanceTo(ms) : 0; // player current distance to main station
	var hasSurrendered = this._surrenderMainStationDist !== 0, // this._surrenderMainStationDist > 0 implies surrender was accepted and we are tracking their progress
		isThreat = p.weaponsOnline && this._surrenderCheck_Police, // we require weapons off when a surrender request is pending, after surrender is accepted they can be re-enabled
		isFar = dist > this._surrenderMainStationDist + 50000, // after surrendering, did distance from main station increase by more than 50km since the last time seen by police?
		isFugitive = player.bounty > 50; // gained after surrender, otherwise the surrender would be rejected immediately
	// begin nearby police/main-station loop
	var law = this.$findLawVessels(p);
	for (var i = 0; i < law.length; i++) {
		// is the player seen?
		if (law[i].hasHostileTarget && law[i].target !== p) {
			continue; // skip any police that are occupied with fights against someone other than the player
		} else if (dist < this._surrenderMainStationDist) { // for players that have surrendered, if they are near any police that aren't too busy to notice, update distance tracking
			this._surrenderMainStationDist = dist; // this is the closest the player has been to the main station since they surrendered (to be used in the next timer update)
		}
		// check compliance
		if (isFugitive) { // player became a fugitive after surrendering earlier and police nearby aren't too busy to notice
			hasSurrendered = false; // police AI will handle comms to fugitives...
			break; // fall through to restore hidden bounty and stop timer
		} else if (!isThreat && !isFar) { // player is compliant (handle this case first so later checks can assume non-compliance)
			// check if player has a pending surrender request	
			if (this._surrenderCheck_Police) {
				if (hasSurrendered === false && law[i].target == p) { // must be targeting the player because markTargetForFines uses the ship's target
					law[i].markTargetForFines(); // flag player to be fined when they dock at the main station
					this._surrenderMainStationDist = dist; // begin tracking player distance to station (and make hasSurrendered true for the next timer update)
					hasSurrendered = true; // make it true for the current timer update to skip bounty restoration code after the loop
				}
				if (hasSurrendered) { // things to do once each time the player surrenders (not just the first time)
					this.$sendMessageToPlayer(law[i], this.$getRandomItemDescription("response-accept-surrender-police")); // police ships or main station can send this message
					if (this._debug === true) log(this.name, "Police accepted surrender");
					// hide most of the player's current bounty (police attack offenders with bounty above a threshold, which is lower in higher-government systems)
					if (player.bounty > 0) {
						this._surrenderBounty += player.bounty - 1; // add current bounty to hidden bounty (player may gain additional bounty after surrender and surrender to a different group of police)
						if (bountysystem) bountysystem._changing = true; // tell Bounty System OXP to ignore the bounty change we are about to do (so it doesn't clear local offences)
						player.bounty = 1; // leave a token 1 cr visible bounty so player is offender but police won't attack (bounty hunters probably won't either, but who knows?)
						if (bountysystem) bountysystem._changing = false; // tell Bounty System OXP to pay attention to bounty changes again
					}
					// so if the player gets a new bounty on the way to the station, they can try to surrender again to police they encounter, stopping hostilities
					// but if they go off course and are seen by police, their hidden bounty will be restored (code below) and the police probably will attack them
					var nearbyShips = p.checkScanner(true); // poweredOnly : true (includes stations) - note this only returns up to 32 results even if more are in scanner range
					for (var k = 0; k < nearbyShips.length; k++) // inner loop to record surrender and stop hostilities with all nearby clean-legal-status ships/stations that are hostile to the player
					{
						if (nearbyShips[k].bounty === 0 && nearbyShips[k].target === p && nearbyShips[k].hasHostileTarget) { // nearby ship or station that is hostile and targeting player (stations also have bounty === 0 usually)
							this.$addShipToArray(nearbyShips[k], this._surrenderedTo); // make sure player can't surrender to them again
							nearbyShips[k].target = null; // de-target the player so they stop attacking and their AI can find something else to do
							nearbyShips[k].removeDefenseTarget(p); // reduce likelihood of re-acquiring the player as a target if ships are in combat with someone else
							if (nearbyShips[k].AIScript && nearbyShips[k].AIScript.oolite_priorityai.getParameter("oolite_distressAggressor") === p)
								nearbyShips[k].AIScript.oolite_priorityai.setParameter("oolite_distressAggressor",null); // clear a recent distress call made against the player
							if (this._debug === true) log(this.name, nearbyShips[k].displayName + " de-targeting player");
						}
					}
					break; // fall through to clear this._surrenderCheck_Police (using break as a do-once, but we have to do it inside the hasSurrendered check in case the first surrender needs to keep looking for police targeting the player)
				}
			} else { // this._surrenderCheck_Police === false
				return; // the player hasn't sent another surrender again, and is compliant, so we don't need to do anything else until the next timer update
			}
			continue; // if execution reaches this line, this._surrenderCheck_Police === true but hasSurrendered === false && law[i].target !== p so we are still looking for a law[i].target === p
		} else if (law[i].isStation === false) { // player is non-compliant (implied by previous check), and stations don't interact with non-compliant players, so we are a nearby police ship
			if (isThreat && this._surrenderPoliceChances--) { // player has not yet disabled weapons (pre-surrender) and we haven't exhausted our patience yet
				this.$sendMessageToPlayer(law[i], this.$getRandomItemDescription("response-surrender-police")); // one police sends another instructions message
				return; // take no further action until the next timer update (using return as a do-once and to skip the code that clears this._surrenderCheck_Police after the loop)
			} else { // either isThreat with no chances left, or isFar (going off course doesn't receive any tolerance (or warning), but the player may be able to surrender to a new group of police)
				hasSurrendered = false; // make sure this is set before the break in the block below so we restore their hidden bounty even if we don't send a message
				if (this.$itemIsInArray(law[i], this._surrenderedTo)) { // only police that witnessed the original surrender can send a message
					this.$sendMessageToPlayer(law[i], this.$getRandomItemDescription("response-break-surrender-police"));
					if (this._debug === true) log(this.name, "Police rejected surrender");
					break; // only send one message
				}
				// fall through to restore hidden bounty and stop timer
				// the police AI probably will attack them and send its own messages, since they were attacked by police before (why else did the player surrender?)
			}
		}
	}
	// end nearby police/main-station loop
	if (hasSurrendered === false) { // surrender was rejected because the player didn't disable weapons in time, or has been broken because player went off course or became a fugitive after surrender was accepted
		this._surrenderMainStationDist = 0; // reset distance tracking for future surrender attempts (would have to be addressed to other police than the ones to which the player originally surrendered)
		if (this._surrenderBounty > 0) { // player has a hidden bounty
			if (bountysystem) bountysystem._changing = true; // tell Bounty System OXP to ignore the bounty change we are about to do (so it doesn't get recorded as a new offence)
			player.bounty += this._surrenderBounty; // make hidden bounty visible again (add it to current bounty in case the player gained additional bounty meanwhile)
			if (bountysystem) bountysystem._changing = false; // tell Bounty System OXP to pay attention to bounty changes again
			this._surrenderBounty = 0; // clear hidden bounty
		}
		this._surrenderTimer_Police.stop();
	} else { // hasSurrendered is true
		this._surrenderCheck_Police = false; // any pending request has been handled (we use return to bypass this when needed)
	}
}

//=============================================================================================================
// threat - 5
this.$respondToThreat = function $respondToThreat() {

	if (this._source != null && this._source.hasRole("generationship")) return;

	// get one of a predefined list of threats and transmit
	var rsp = 0; // 0 = no response, 1 = pick a response
	if (this.$rand(10) > 2) rsp = 1;

	// always give a response for thargoids
	if (this._targetType === "thargoid") rsp = 1;

	var p = player;
	var altResponse = 0; // whether we're using the alternate responses because the npc is fleeing (0 = normal, 1 = fleeing)

	// for thargoid - no change in behaviour
	// any changes to NPC behaviour will happen regardless of whether they have decided to send a response or not

	// for police -- make it an offence
	if (this._source != null && this._targetType != "" && this._targetType === "police") {
		// threatening police results in a fine
		player.bounty += 1;
		this._source.target = player.ship;
		this._source.markTargetForFines();
	}

	// for npc/pirate/hunter - small chance of enemy giving up if player kill count is high and/or player status is offender or greater
	// if npc decides to flee, get different message
	// check: target is still there, they're not thargoids or police, and they are targeting the ship
	if (this._source != null && this._targetType != "" && this._targetType != "thargoid" && this._targetType != "police" && this._source.target === p.ship) {
		var chance = 0.0; // starting chance is 0% ie. no chance the target will flee
		// weight up the chances of this enemy fleeing
		// 1. how many ships are targeting the player? If lots, then less likely to flee
		// 		if >= 5, no change
		var shipcount = this.$countShipsTargetingPlayer();
		// if there are lots of ships targeting the player this function could make our value go negative, which we'll check for later
		if (shipcount < 5) chance += (0.3 - (shipcount * 0.05));

		// 2. how strong is the player
		// based on status (fugitive = higher chance, offender = not so high, clean = not high at all)
		if (p.bounty > 0 && p.bounty <= 50) chance += 0.1; // offender status
		if (p.bounty > 50) chance += 0.2; // fugitive status

		// elite ranking
		if (p.score >= 32 && p.score < 64) chance += 0.05; // average
		if (p.score >= 64 && p.score < 128) chance += 0.1; // above average
		if (p.score >= 128 && p.score < 512) chance += 0.15; // competant
		if (p.score >= 512 && p.score < 2560) chance += 0.2; // dangerous
		if (p.score >= 2560 && p.score < 6400) chance += 0.25; // deadly
		if (p.score >= 6400) chance += 0.3; // elite

		// threat assessment levels
		if (this._source.threatAssessment() < p.ship.threatAssessment()) chance += 0.1;

		// 3. how many other ships are targeting it
		var shipcount = this.$countShipsTargetingPlayerTarget();
		if (shipcount > 2) chance += (shipcount * 0.05);

		// make sure we're positive
		if (chance < 0) chance = 0.0;

		// not greater than a 50% chance
		if (chance > 0.5) chance = 0.5

		if (this._debug === true) log(this.name, "final chance percentage = " + chance.toString());

		// will the ship flee?
		if (this.$rand(70) < (50 * chance)) {
			if (this._debug === true) log(this.name, "ship '" + this._source.displayName + "' is fleeing");

			// force a response for a fleeing ship
			rsp = 1;
			// change response type to fleeing
			altResponse = 1;
			// remove the player as a target
			this._source.target = null;
			// make ship flee
			this._source.performFlee();
		}
	} else if (this._source != null && this._targetType != "" && this._targetType != "police" && this._source.target != p.ship) {
		// check: target is still there, they're not thargoids or police, and they are *not* targeting the ship
		// basically, if the other ship is not targeting the player, make them do so now
		var chance = 35;
		// adjust the chance by player score - the higher the score the less likely they will attack
		// elite ranking
		if (p.score >= 32 && p.score < 64) chance -= 5; // average
		if (p.score >= 64 && p.score < 128) chance -= 10; // above average
		if (p.score >= 128 && p.score < 512) chance -= 15; // competant
		if (p.score >= 512 && p.score < 2560) chance -= 20; // dangerous
		if (p.score >= 2560 && p.score < 6400) chance -= 25; // deadly
		if (p.score >= 6400) chance -= 30; // elite

		this.$willPlayerTargetTurnOnPlayer(chance);
		// force a response
		rsp = 1;
	}

	if (rsp === 1 && this._source != null && this._targetType != "" && this._source.isPiloted) {
		// get a standard "I'm not scared of you" threat response
		var reply = this.$getCustomReply(this._source, 5, (altResponse == 0 ? "reply_normal" : "reply_fleeing"));
		if (reply == "") reply = this.$getRandomItemDescription("response-threat-" + this._targetType + "-" + altResponse.toString());
		this.$sendMessageToPlayer(this._source, reply);
		// rebuild the messagelist (so that greeting is removed)
		this.$buildMessageList();
	}
}

//-------------------------------------------------------------------------------------------------------------
// handles the response of an NPC to piracy, where they have decided to dob the player in
this.$sendPiracyNPCResponse = function $sendPiracyNPCResponse() {
	// only send the response if the victim is still valid and is visible to player
	if (this._piracyVictim.isValid === true && this._piracyVictim.isVisible === true) {
		var listtype = "response-piracy-npc-police";
		if (this._piracyPolice.isPolice === false) listtype = "response-piracy-npc-station";

		this.$sendMessageToPlayer(this._piracyVictim, this.$getRandomItemDescription(listtype));
	}
	// if the victim has died before they get a chance to tell, don't send a response
	if (this._piracyVictim.isValid === true) this._piracyPoliceTimer = new Timer(this, this.$sendPiracyPoliceResponse, 4, 0);
}

//-------------------------------------------------------------------------------------------------------------
// handles the response of a police vessel to an NPC's notification of the players piracy
this.$sendPiracyPoliceResponse = function $sendPiracyPoliceResponse() {
	if (this._piracyPolice.isValid === true && this._piracyPolice.isVisible === true) {
		this.$sendMessageToPlayer(this._piracyPolice, this.$getRandomItemDescription("response-piracy-police-npc"));
	}
	// if the police vessel has died before they get a chance to set the players bounty, don't change it.
	if (this._piracyPolice.isValid === true) {
		player.ship.setBounty(player.ship.bounty + this.$rand(10), "act of piracy");
		if (this._debug === true) log(this.name, "player bounty now " + player.bounty.toString());

	}
}

//=============================================================================================================
// helper functions

//-------------------------------------------------------------------------------------------------------------
this.$resetVariables = function () {
	this._alertCond = player.alertCondition;
	this._targetType = "";
	this._closestAttackerType = "";
}

//-------------------------------------------------------------------------------------------------------------
this.$fullResetVariables = function () {
	this._escapepodShipNames = [];
	this._scoopOffer = [];
	this._greeted = [];
	this._bribed = [];
	this._demanded = [];
	this._bribeCurrent = [];
	this._surrenderCurrent_Pirate = [];
	this._surrenderedTo = [];
	this._surrenderCheck_Pirate = false;
	this._surrenderCheck_Police = false;
	this._surrenderPoliceChances = 0;
	this._surrenderMainStationDist = 0;
	this._surrenderBounty = 0;
	this._bribeAmount = this._bribeInitial;
	this._piracyWorked = false;
	this._waitingForPiracyResponse = false;
	this._killedNames = [];
	this._targetLastComms = null;
	this.$resetVariables();
}

//-------------------------------------------------------------------------------------------------------------
// stop any timers we have running
this.$stopAllTimers = function () {
	// stop the check timer
	if (this._checkTimer && this._checkTimer.isRunning === true) this._checkTimer.stop();
	if (this._piracyNPCTimer && this._piracyNPCTimer.isRunning === true) this._piracyNPCTimer.stop();
	if (this._piracyPoliceTimer && this._piracyPoliceTimer.isRunning === true) this._piracyPoliceTimer.stop();

	// stop the response message delay timer
	if (this._delay && this._delay.isRunning === true) this._delay.stop();

	// stop the surrender timer
	if (this._surrenderTimer_Pirate && this._surrenderTimer_Pirate.isRunning === true) this._surrenderTimer_Pirate.stop();
	if (this._surrenderTimer_Police && this._surrenderTimer_Police.isRunning === true) this._surrenderTimer_Police.stop();

	// stop any accuracy changes
	if (this._accuracyChanged && this._accuracyChanged.length > 0) {
		for (var i = 0; i < this._accuracyChanged.length; i++) {
			if (this._accuracyChanged[i].IsActive === true) this._accuracyChanged[i].StopChange();
		}
	}
	// reset the arrays
	this._accuracyChanged = [];
}

//-------------------------------------------------------------------------------------------------------------
// return a random number between 1 and max
this.$rand = function (max) {
	return Math.floor((Math.random() * max) + 1)
}

//-------------------------------------------------------------------------------------------------------------
// adds a ship to an array, but only once
this.$addShipToArray = function (ship, array) {
	var found = false;
	if (array != null && array.length > 0) {
		for (var i = 0; i < array.length; i++) {
			if (array[i] === ship) {
				found = true;
				break;
			}
		}
	}
	if (found === false) array.push(ship);
}

//-------------------------------------------------------------------------------------------------------------
// returns a description item
this.$getRandomItemDescription = function (listitem, dict) {
	// we need to distinguish hunters from pirates in the above code, but in the transmissions and responses they're the same thing
	var listname = "[" + listitem.replace("-hunter", "-pirate") + this._msgAlt + "]";
	// fail safe, incase there's a 
	if (listname.indexOf("-]") >= 0) {
		listname = listname.replace("-" + this._msgAlt + "]", "-npc" + this._msgAlt + "]");
	}
	if (dict) {
		var item = expandDescription(listname, dict);
	} else {
		var item = expandDescription(listname);
	}
	return item;
}

//-------------------------------------------------------------------------------------------------------------
// find all npc ships in range
this.$findNPCShips = function () {
	function _isNPC(entity) {
		return entity.isShip && !(entity.isPlayer) && !(entity.isMissile) && !(entity.isBeacon) && !(entity.isBoulder) && !(entity.isCargo) && !(entity.isRock) && !(entity.isWeapon) && !(entity.isTurret);
	}
	return system.filteredEntities(this, _isNPC, player.ship, player.ship.scannerRange);
}

//-------------------------------------------------------------------------------------------------------------
// find all police or main stations in range of ship
this.$findLawVessels = function (npc) {
	function _ships(entity) {
		return entity.isShip && !(entity.isPlayer) && (entity.isPolice || (entity.isStation && entity.isMainStation));
	}
	return system.filteredEntities(this, _ships, npc, npc.scannerRange);
}

//-------------------------------------------------------------------------------------------------------------
// return the count of law vesses in range of ship
this.$countLawVessels = function (npc) {
	var ships = this.$findLawVessels(npc);
	this._piracyPolice = null;
	ships.forEach(function (ship) {
		// find the first police vessel in the list and set our piracy police variable to that one
		if (ship.isPolice === true && this._piracyPolice == null) this._piracyPolice = ship;
	}, this);
	// if there was no police vessel in the list, it must be a main station, so set it instead
	if (this._piracyPolice == null && ships.length > 0) this._piracyPolice = ships[0];
	return ships.length;
}

//-------------------------------------------------------------------------------------------------------------
// find all npc ships in range that are targeting the player
this.$countShipsTargetingPlayer = function () {
	function _isShipTargetingPlayer(entity) {
		return entity.isShip && !(entity.isPlayer) && !(entity.isMissile) && !(entity.isBeacon) && !(entity.isBoulder) && !(entity.isCargo) && !(entity.isRock) && !(entity.isWeapon) && !(entity.isTurret) && entity.target === player.ship;
	}
	var ships = system.filteredEntities(this, _isShipTargetingPlayer, player.ship, player.ship.scannerRange);
	return ships.length;
}

//-------------------------------------------------------------------------------------------------------------
// find all npc ships in range that are targeting the player
this.$countShipsTargetingNPC = function (npc) {
	function _isShipTargetingNPC(entity) {
		return entity.isShip && !(entity.isPlayer) && !(entity.isMissile) && !(entity.isBeacon) && !(entity.isBoulder) && !(entity.isCargo) && !(entity.isRock) && !(entity.isWeapon) && !(entity.isTurret) && entity.target === npc;
	}
	var ships = system.filteredEntities(this, _isShipTargetingNPC, npc, npc.scannerRange);
	return ships.length;
}

//-------------------------------------------------------------------------------------------------------------
// find all npc ships in range that are targeting the player
this.$countShipsTargetingPlayerTarget = function () {
	function _isShipTargetingTarget(entity) {
		return entity.isShip && !(entity.isPlayer) && !(entity.isMissile) && !(entity.isBeacon) && !(entity.isBoulder) && !(entity.isCargo) && !(entity.isRock) && !(entity.isWeapon) && !(entity.isTurret) && entity.target === this._source;
	}
	var ships = system.filteredEntities(this, _isShipTargetingTarget, player.ship, player.ship.scannerRange);
	return ships.length;
}

//-------------------------------------------------------------------------------------------------------------
// remove the player as the target of a pirate group
this.$removePlayerTargetFromPirateGroup = function (pirateship) {
	if (pirateship.group) {
		var ships = pirateship.group.ships;
		ships.forEach(function (ship) {
			if (this._debug === true) log(this.name, "player de-targeted from ship " + ship.displayName + " in group " + pirateship.group);

			ship.target = null;
		}, this);
	} else {
		pirateship.target = null;
	}
}

//-------------------------------------------------------------------------------------------------------------
// adds a ship to the accuracy changes array and starts the process
this.$addShipToAccuracyList = function (ship, addvalue, seconds) {
	//-------------------------------------------------------------------------------------------------------------
	// internal class used for adjusting the accuracy of a ship for a given number of seconds
	function AccuracyAdj(ship, addvalue, seconds) {
		this.ship = ship;
		this.oldvalue = ship.accuracy;
		this.addvalue = CheckNewAccuracyLevel(this.oldvalue, addvalue);
		this.seconds = seconds;
		this.delay = null;

		this.DoChange = function () {
			if (this.addvalue != 0) {
				if (worldScripts.BroadcastCommsMFD._debug === true)
					log(this.name, "changing " + this.ship.displayName + " accuracy from " + this.oldvalue.toString() + " to " + (this.oldvalue + this.addvalue).toString());

				this.ship.accuracy += this.addvalue;
				this.delay = new Timer(this, this.RevertChange.bind(this), this.seconds, 0);
			}
		}

		this.IsActive = function () {
			if (this.delay && this.delay.isRunning) {
				return true;
			} else {
				return false;
			}
		}

		this.StopChange = function () {
			this.delay.stop();
			// reset the accuracy of the ship
			if (this.ship && this.ship.isValid) {
				this.ship.accuracy = this.oldvalue;
				if (worldScripts.BroadcastCommsMFD._debug === true)
					log(this.name, "reverting " + this.ship.displayName + " accuracy back to " + this.oldvalue.toString() + " from " + (this.oldvalue + this.addvalue).toString());
			}
		}

		this.RevertChange = function RevertChange() {
			if (this.ship && this.ship.isValid) {
				this.ship.accuracy = this.oldvalue;
				if (worldScripts.BroadcastCommsMFD._debug === true)
					log(this.name, "reverting " + this.ship.displayName + " accuracy back to " + this.oldvalue.toString() + " from " + (this.oldvalue + this.addvalue).toString());
			}
		}

		function CheckNewAccuracyLevel(curr, diff) {
			var newvalue = diff;
			if ((curr + diff) > 10 || (curr + diff) < -5) {
				if (diff < 0) {
					newvalue = -5 - curr;
				} else {
					newvalue = 10 - curr;
				}
			}
			return newvalue;
		}
	}

	var item = -1;
	// first, make sure we aren't already adjusting a ship's accuracy
	if (this._accuracyChanged != null && this._accuracyChanged.length > 0) {
		for (var i = 0; i < this._accuracyChanged.length; i++) {
			if (this._accuracyChanged[i] && this._accuracyChanged[i].ship === ship) {
				item = i;
				break;
			}
		}
	}
	// if we found an item, check to see if it's running.
	if (item != -1) {
		if (this._accuracyChanged[item].IsActive === false) {
			// not running, so remove it and add it as a new item
			this._accuracyChanged[item] = null;
			item = -1;
		}
		// if it is running, don't make any changes.
	}
	if (item === -1) {
		// not found, so add the ship
		var newItem = new AccuracyAdj(ship, addvalue, seconds);
		newItem.DoChange();
		this._accuracyChanged.push(newItem);
	}
}

//-------------------------------------------------------------------------------------------------------------
// check timer essentially looks to see if our target tas become invalid (by going through a wormhole)
// for some reason the "shipTargetLost" is not firing in this scenario
this.$checkForTargetInvalid = function $checkForTargetInvalid() {
	var p = player.ship;
	var found = false;
	if (this._targetType != "" && (!(p.target) || p.isValid === false)) {
		this._targetType = "";
		found = true;
		this.$startTargetTimer();
	}
	if (found === true) this.$buildMessageList();
}

//-------------------------------------------------------------------------------------------------------------
// determines if the players target will turn on the player
// used by the taunt and threat functions
this.$willPlayerTargetTurnOnPlayer = function (checkAmount) {
	if (this._debug === true) log(this.name, "Chance target will turn on player = " + checkAmount.toString());

	if (player.ship.target && player.ship.target.target != player.ship && this.$rand(50) > checkAmount) {
		player.ship.target.target = player.ship;
		if (this._debug === true) log(this.name, "target " + player.ship.target + " is now targeting player");

	}
}

//-------------------------------------------------------------------------------------------------------------
// sets the current bribe amount to the amount set for a particular ship
this.$workOutCurrentBribeAmount = function (ship) {
	if (this._bribeCurrent != null && this._bribeCurrent.length > 0) {
		for (var i = 0; i < this._bribeCurrent.length; i++) {
			if (this._bribeCurrent[i].ship === ship) this._bribeAmount = this._bribeCurrent[i].amount;
		}
	}
}

//-------------------------------------------------------------------------------------------------------------
// finds and returns the nearest ship targeting the player. If none found, returns null
this.$findNearestAttacker = function () {
	var ships = player.ship.checkScanner(true);
	var min = player.ship.scannerRange;
	var calc = 0;
	var selected = null;
	for (var i = 0; i < ships.length; i++) {
		if (ships[i].target === player.ship) {
			calc = player.ship.position.distanceTo(ships[i]);
			if (calc < min) {
				min = calc;
				selected = ships[i];
			}
		}
	}
	if (selected != null) {
		this._closestAttackerType = "npc";
		if (selected.hasRole("police") === true) this._closestAttackerType = "police";
		if (selected.hasRole("thargoid") === true) this._closestAttackerType = "thargoid";
		if (selected.hasRole("hunter") === true) this._closestAttackerType = "hunter";
		if (selected.hasRole("pirate") === true) this._closestAttackerType = "pirate";
	}
	return selected;
}

//-------------------------------------------------------------------------------------------------------------
// returns the player's target system (1.80) or the next jump to their target system (1.82)
this.$playerTargetSystem = function () {
	if (player.ship.hasOwnProperty("nextSystem")) return player.ship.nextSystem;

	var target = player.ship.targetSystem;
	if (oolite.compareVersion("1.81") < 0 && player.ship.hasEquipmentProviding("EQ_ADVANCED_NAVIGATIONAL_ARRAY") === true) {
		// in 1.81 or greater, the target system could be more than 7 ly away. It becomes, essentially, the final destination.
		// there could be multiple interim stop points between the current system and the target system.
		// the only way to get this info is to recreate a route using the same logic as entered on the ANA, and pick item 1
		// from the list. That should be the next destination in the list.
		if (system.ID === -1) {
			var myRoute = System.infoForSystem(galaxyNumber, this._lastSource).routeToSystem(System.infoForSystem(galaxyNumber, target), player.ship.routeMode);
		} else {
			var myRoute = System.infoForSystem(galaxyNumber, system.ID).routeToSystem(System.infoForSystem(galaxyNumber, target), player.ship.routeMode);
		}
		if (myRoute && myRoute.route.length >= 1) {
			target = myRoute.route[1];
		}
	} else {
		if (target >= 0 && system.info.distanceToSystem(System.infoForSystem(galaxyNumber, target)) > 7) target = -1;
	}
	return target;
}

//-------------------------------------------------------------------------------------------------------------
this.$itemIsInArray = function (element, array) {
	var found = false;
	if (array != null && array.length > 0) {
		for (var i = 0; i < array.length; i++) {
			if (array[i] === element) found = true;
		}
	}
	return found;
}