Page 5 of 11

Re: BroadcastComms MFD [Release]

Posted: Sat Jun 27, 2020 6:19 pm
by Milo
Somehow, I got the cops angry at me. Ahem. Anyway, I tried to surrender to them, and they said disable weapons, which I did. No change, they kept shooting. Then I tried a bribe:
Warning: Unknown expansion key [credittype] in string.
Active script: BroadcastCommsMFD_Equipment 1.2.8
broadcastcomms_mfd.js, line 1753:
var item = expandDescription(listname, dict);
Warning: Unbalanced ] in string.
Active script: BroadcastCommsMFD_Equipment 1.2.8
broadcastcomms_mfd.js, line 1753:
var item = expandDescription(listname, dict);
got message from Miner Cobra Mark III 12541 : I've got 1 [credittype] here if you let me go.

Re: BroadcastComms MFD [Release]

Posted: Sun Jun 28, 2020 1:32 am
by phkb
Fixed in v1.2.9.

Re: BroadcastComms MFD [Release]

Posted: Mon Jun 29, 2020 5:27 pm
by Milo
I just discovered pedantic mode in the debug console. I was wondering why strict mode wasn't producing more warnings...

While docking, two adjacent warnings:

Code: Select all

Warning (strict mode): reference to undefined property this._surrenderTimer_Pirate.isRunning
    Active script: BroadcastCommsMFD 1.2.9
    broadcastcomms_mfd.js, line 1713:
    	if (this._surrenderTimer_Pirate != null && this._surrenderTimer_Pirate.isRunning === true) this._surrenderTimer_Pirate.stop();
    	
   
Warning (strict mode): reference to undefined property this._surrenderTimer_Police.isRunning
    Active script: BroadcastCommsMFD 1.2.9
    broadcastcomms_mfd.js, line 1714:
    	if (this._surrenderTimer_Police != null && this._surrenderTimer_Police.isRunning === true) this._surrenderTimer_Police.stop();

Re: BroadcastComms MFD [Release]

Posted: Mon Jun 29, 2020 10:22 pm
by phkb
That's strange. There isn't a bug, because multiple checks in JS If statements are only processed if all the previous ones pass. So, the check on "isRunning" would only occur if this._surrenderTimer_Pirate is not null, meaning it would have to be a real timer, meaning there would be an "isRunning" property.

How did you turn on this "pedantic" mode?

Re: BroadcastComms MFD [Release]

Posted: Mon Jun 29, 2020 10:28 pm
by Milo
I'm using the new GUI for the debug console and at the top left there is a debug button which drops down a menu, in there go to Console Properties and then pedantic mode, and toggle it.

The issue in the code is that a property that doesn't exist is === undefined, which is not null. I wonder if !== null instead of != null would fix it.

Re: BroadcastComms MFD [Release]

Posted: Mon Jun 29, 2020 10:51 pm
by phkb
:shock: :shock: :shock:
Oh. Wow.

Re: BroadcastComms MFD [Release]

Posted: Mon Jun 29, 2020 11:21 pm
by Milo
Actually, I think !== wouldn't work, because you're looking for validity there. You could just "truthy" check it (remove the != null part, keep just the variable, in which case null and undefined would both be "false" and fail the check).

Re: BroadcastComms MFD [Release]

Posted: Tue Jun 30, 2020 5:17 am
by phkb
Fixed in 1.2.10

Re: BroadcastComms MFD [Release]

Posted: Fri Jul 03, 2020 10:00 pm
by dybal

Code: Select all

Warning: Unknown expansion key [transmit-greeting-] in string.
    Active script: BroadcastCommsMFD_Equipment 1.2.10
    broadcastcomms_mfd.js, line 1755:
    		var item = expandDescription(listname);

Re: BroadcastComms MFD [Release]

Posted: Tue Jul 07, 2020 5:17 am
by phkb
You don't remember what ship/entity you were targeting when you tried to greet them/it, do you?

Re: BroadcastComms MFD [Release]

Posted: Tue Jul 07, 2020 1:31 pm
by dybal
phkb wrote: Tue Jul 07, 2020 5:17 am
You don't remember what ship/entity you were targeting when you tried to greet them/it, do you?
No... I will start greeting everybody and see which ones generate this expansion problem

Re: BroadcastComms MFD [Release]

Posted: Sun Jul 12, 2020 11:50 pm
by Milo
Was wondering why surrendering never seemed to work. Turns out, it's a bug! The $surrenderCheckState_Police timer never gets started.

The variable is initialized like this: this._surrenderTimer_Police = {}; Then it is checked like this: if (!this._surrenderTimer_Police || this._surrenderTimer_Police.isRunning === false)

!this._surrenderTimer_Police evaluates as !{}, which is false because empty {} is truthy. this._surrenderTimer_Police.isRunning evaluates as {}.isRunning, which is undefined, which is !== false, making the OR condition also false.

The same problem applies to _surrenderTimer_Pirate. My sympathies to all the commanders who've tried to surrender. No, the pirates aren't that merciless, they just mixed up their lasers with their comms interface. Initializing the timer variable to null instead of {} would make the first condition true. Also, the $stopAllTimers function seems to expect the value to be null if the timer was never created. So I think the right fix is to initialize both timer variables to null instead of {}.

I tested setting the timer variable to null and sending another surrender message, and the timer fired and the police stopped attacking. Then their AI looked around for a serious offender, compared my bounty (15) to their threshold (50 - 6 * system.info.government), came up with 15 > 8 = (50 - 42), and went right back to attacking me!

I think if the police accept the player's surrender, Broadcast Comms should mostly hide the player's bounty (leave a 1 cr token bounty so they still show as offender), to be restored upon shipWillDockWithStation (so they get fined if they dock at the main station, and if they dock somewhere else first, the bounty comes back) or shipWillEnterWitchspace (for players not using Bounty System) or if the player deviates too far from the main station (run a timer to check distance from station periodically, require that it trend down but allow some slack in case player stops or tries to bypass a masslock, maybe 2 scanner ranges increased distance from station compared to the nearest they've been so far [compare current distance to saved distance only updated when player is closer to station than the previous saved distance]).

The possibility of the player accumulating additional bounty after surrender also needs to be handled, either because they committed an offence or because a Bounty System scan imported an out-of-system bounty. If it was not a local offence, and the player's bounty is currently hidden, the out-of-system bounty should be added to the hidden amount instead, which means Bounty System should cooperate with Broadcast Comms. Moreover, if Bounty System is installed, this OXP needs to set Bounty System's _changing flag to true before clearing or restoring the bounty (and to false after), because Bounty System otherwise would act on that change incorrectly (clear local offences, or consider the restoration as an additional offence).

See the Bounty System thread for how I suggest that OXP be modified to avoid re-applying an out-of-system bounty and integrate with this OXP.

Here is some revised code for Broadcast Comms to fix and enhance the surrender feature (as I was coding, I had quite a few other ideas [see my many comments in the code] beyond what I described above):

Code: Select all

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.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 (will be reset if player docks anywhere)
	this._surrenderedTo = [];				// keeps track of who we've surrendered to (will be reset if player docks anywhere)
	this._surrenderCheck_Pirate = false;	// indicates that player has surrendered and that cargo should be getting dumped
	this._surrenderTimer_Pirate = null;		// timer that will wait for cargo being dumped after surrender
	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;
}


...


//=============================================================================================================
// 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.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)
	}
}


...


//=============================================================================================================
// surrender
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");

	// 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";
			}
			this.$sendMessageToPlayer(this._source, this.$getRandomItemDescription("response-surrender-pirate", {pronouncap:pnCap, pronoun:pnLw}));

		} 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
				}

				this.$sendMessageToPlayer(this._source, this.$getRandomItemDescription("response-surrender-police"));
				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 {
				this.$sendMessageToPlayer(this._source, this.$getRandomItemDescription("response-deny-surrender-police")); // fugitive rejection
			}
		}
	} else {
		if (this._debug === true) log(this.name, "declined");
		if (this._source.isThargoid) {
			this.$sendMessageToPlayer(this._source, expandDescription("[thargoid_curses]"));
		}
	}
}


...


//-------------------------------------------------------------------------------------------------------------
// 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
					// 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
					}
					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");
					// don't break here because we want to record surrender for all nearby police and stop hostilities for all nearby police (and main station if it is nearby)
				}
				// once per player surrender request, make currently hostile nearby police or main station stop attacking
				if (law[i].target === p && law[i].hasHostileTarget) { // police/station is hostile and targeting player
					this.$addShipToArray(law[i], this._surrenderedTo); // make sure player can't surrender to them again
					law[i].target = null; // de-target the player so they stop attacking and their AI can find something else to do
					law[i].removeDefenseTarget(p); // reduce likelihood of police AI re-acquiring the player as a target if police are still in combat with someone else
					if (this._debug === true) log(this.name, law[i].displayName + " de-targeting player");
				}
				// so if the player gets a new bounty on the way to the station, they can surrender again to police they encounter, stopping hostilities
				// or 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
				// but as long as the player has not accumulated too much new bounty (still offender), they will be able to surrender again (especially with changes elsewhere to allow repeat surrenders to police that are attacking)
			}
		} 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)
	}
}

...


//-------------------------------------------------------------------------------------------------------------
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();
}


...


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 = [];

	var p = player.ship, t = p && p.target, tt = t && t.target;

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

		if (this._targetLastComms && this._disableInternal[10] === false) 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) 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) {
				this._lines.push({id:"core_3", text:"Send greeting to target"});
			}
			if (this._disableInternal[4] === false) 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) {
				// 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 && 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) this._lines.push({id:"core_8", text:"Surrender to nearest attacker"});
			if (this._disableInternal[6] === false) 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 && 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) {
					this._lines.push({id:"core_6", text:"Offer bribe of " + this._bribeAmount.toString() + "cr to target"});
				}
			}
			if (this._disableInternal[4] === false) this._lines.push({id:"core_4", text:"Send taunt to target"});
			if (this._disableInternal[5] === false) this._lines.push({id:"core_5", text:"Issue threat to target"});
			if (this._disableInternal[11] === false) 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) {
			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) {
				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) {
				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);
}


...



this.$transmit = function() {

	var p = player.ship;
	var msg = "";
	var id = "";

	// 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;
					this.$sendMessageToTarget(this.$getRandomItemDescription("transmit-greeting-" + this._targetType));
					if (this._delay && this._delay.isRunning) this._delay.stop();
					this._delay = new Timer(this, this.$respondToGreeting, this._defaultResponseWait, 0);
					break;
				case "core_4": //"Send taunt to target":
					if (this.$checkTarget() === false) return;
					this._source = p.target;
					this.$sendMessageToTarget(this.$getRandomItemDescription("transmit-taunt-" + this._targetType));
					if (this._delay && this._delay.isRunning) this._delay.stop();
					this._delay = new Timer(this, this.$respondToTaunt, 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;
					this.$sendMessageToTarget(this.$getRandomItemDescription("transmit-threat-" + this._targetType));
					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";

						this.$sendMessageToTarget(this.$getRandomItemDescription("transmit-bribe", {amount:this._bribeAmount.toString(), credittype:crType}));
						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) {
								this.$sendMessageToTarget(this.$getRandomItemDescription("transmit-bribe", {amount:this._bribeAmount.toString(), credittype:crType}));
								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");
					this.$sendMessageToTarget(this.$getRandomItemDescription("transmit-piracy", {tons:tons}));
					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;
						this.$sendMessageToTarget(this.$getRandomItemDescription("transmit-surrender"));
						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) {
							this.$sendMessageToTarget(this.$getRandomItemDescription("transmit-surrender"));
							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;
					this.$sendMessageToTarget(this.$getRandomItemDescription("transmit-escapepod"));
					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(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;
			}
		}
	}
}
I discovered that even if you have all of the nearby police de-target the player and you clear the bounty, you will still stay in red alert and be attacked by the police if you are near the main station because it also targets you and the police AI causes its allies (the other police) to re-acquire you. So I included the main station in the de-targeting code. I also added a removeDefenseTarget. It appears the police AI uses defense targets as potential targets when they are in combat, and they might go back into combat after the surrender de-target if you weren't the only hostile around.

The messages when police accept your surrender just say to go to the station, not that you are supposed to keep your weapons off until then, so I tweaked the non-compliance conditions to require weapons offline only for the period between sending a surrender message and the surrender being accepted - or rejected, but in that case you have other problems. We could change the messages, of course, but I think it's better to let players enable weapons again after surrendering: maybe they will need to fight off pirates on the way, which is a perfectly legal thing to do...

Of course, if you use your weapons inappropriately after surrendering, you will gain a new bounty, and may attract hostile police attention again. As it was originally, you could find some other police that had been off-scanner when you surrendered before and surrender to the new ones to pacify the ones currently fighting you, so I decided to simplify it and just make an exception to allow multiple surrenders to police that are attacking you.

Regardless, if you step too far over the line and gain fugitive status, you won't be able to surrender any more, and your hidden bounty will be restored (by the timer) as soon as any police see you. I also decided to only hide the bounty for the first surrender, not for subsequent ones, so further crimes will add up and may attract laser fire from police.

Re: BroadcastComms MFD [Release]

Posted: Wed Jul 22, 2020 6:25 pm
by phkb
Version 1.2.11 has been released, which includes the code improvements provided by Milo above to fix the "surrender to police" issue. Thanks Milo!

Re: BroadcastComms MFD [Release]

Posted: Wed Jul 22, 2020 6:33 pm
by Milo
I think perhaps my final decision should be re-considered — by not hiding (most of) the bounty on every surrender instead of only the first surrender, the police AI will re-aggro shortly after the surrender, which was the original scenario I was trying to cure.

Arguing in favor of hiding every time: Hiding the bounty just postpones the penalty; if not levied in the current system, it will be in the next (unless you avoid GalCop for a while).

Re: BroadcastComms MFD [Release]

Posted: Wed Jul 22, 2020 7:13 pm
by Milo
Suggested revision (based on my last version, not yours if you made any changes):

Code: Select all

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) { // things to do once only the first time the player surrenders; 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 - only need to do this the first time the player surrenders
					this._surrenderMainStationDist = dist; // begin tracking player distance to station (and make hasSurrendered true for the next timer update) ... don't reset if player re-surrenders!
					hasSurrendered = true; // make it true for the current timer update to trigger the next code block and take the correct code branch after the loop (clear this._surrenderCheck_Police)
				}
				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");
					if (player.bounty > 0) { // hide most of the player's current bounty because police attack offenders with bounty above a threshold, which is lower in higher-government systems
						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 were present when the player surrendered will send a message if they see the player breaking the terms
					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 ... or ignored by all nearby police (we use return to bypass this when needed)
	}
}
Re-surrender seems to be working as intended (bounty gets hidden again). In my testing, I tried shooting near neutral ships, and of course surrendering to police didn't pacify the non-police, so I was still in red alert afterwards. And sometimes, the police would re-aggro if the neutral ships decided to keep attacking me. So I changed the stop-hostilities loop to apply to all clean-legal-status ships and stations in scanner range. I also found that police may still re-aggro because a ship sent a distress call shortly before you surrendered. So now it also clears any recent distress call identifying the player as the aggressor.

No further edits planned for now...

A separate minor observation is that having to target a police ship specifically in order to surrender is a bit non-intuitive. Don't they hear you if you surrender to someone else? It also interacts awkwardly with Telescope because when you disable weapons, your target switches to most centered. I would consider eliminating the targeted surrender message and changing the "surrender to nearest aggressor" to be "broadcast surrender" ... and then check what types of ships are nearby and invoke the appropriate surrender methods.