| Scripts/harderHermits.js | /*
========================================================================
harderHermits.js
This file is part of the Harder Hermits expansion pack.
Author: UK_Eliter
License: 2017 Creative Commons: attribution, non-commercial, sharealike.
========================================================================
*/
/*
===================
JSHINT: set options
===================
*/
/*jshint esversion: 6*/
/*jshint sub:true*/
// Needs oolite >=1.82 (or, er, higher?)
/*
 */
this.name = "harderHermits"; // should be 'harderHermits.js'?
this.author = "UK_Eliter";
this.copyright = "See the license field";
this.description = "harderHermits.js";
this.license = "2014 CC-by-nc-sa 3.0";
/*
==================
DEBUGGING SWITCHES
==================
*/
// this.logging = true;
//	Do NOT comment-out any of the following three assignments. Rather, to disable any one of them, set it to false.
var consoleDebugMessages = false;
this.debug = false;
this.debug_testResurrection = false;
//	Requires this.debug to be true, but does not *make* that variable true,
/*
========================
JSLINT: start of wrapper
========================
*/
(function() {
	"use strict";
	/*
	=======
	GLOBALS
	=======
	*/
	this.name = "harderHermits";
	this.abandoned_chanceAct = 0.15;
	//	0.15
	this.installed_spicyHermits = false;
	//	Gets changed to true if the OXP is installed.
	this.installed_hermitage = false;
	//	Gets changed to true if the OXP is installed.
	this.installed_pirateCove = false;
	//	Gets changed to true if the OXP is installed.
	/*
	=========
	DEBUGGING
	=========
	*/
	this.$msg_dbg = function(debugTxt) {
		if (!debugTxt) {
			player.consoleMessage("Problem with debug message text!", 30);
			return;
		}
		player.consoleMessage(this.name + " - " + debugTxt, 12);
		log(this.name + " - DEBUG", debugTxt);
	};
	this.$msg_info = function(msgTxt) {
		// if( !msgTxt ) { player.consoleMessage( "Problem with info message text!", 30 ); return; }
		log(this.name + " - INFO", msgTxt);
	};
	/*
	================
	SOUNDS
	================
	*/
	this.mySound_hullBang = new SoundSource();
	this.mySound_hullBang.sound = "hullbang.ogg";
	// this sound built-in to Oolite - and does not need to be declared in the 'customsounds' file.
	/*
	================
	ENTITY PICKER(S)
	================
	The following roles are from Oolite itself:
		rockhermit
		rockhermit-chaotic
		rock-hermit-pirate
	The following role is from SpicyHermits:
		spicy_hermits_abandoned
	*/
	this.$isCove = function(e) {
		return e.isShip && e.AI === "pirateCoveAI.plist";
		// Seemingly there is no more efficient way of doing the above.
	};
	this.$isAbandonedHermit = function(e) {
		return e.isShip && (e.primaryRole === "asteroid" && e.name === "Abandoned Rock Hermit");
	};
	this.$isHermit = function(e) {
		return e.isShip &&
			(e.primaryRole === "rockhermit" || e.primaryRole === "rockhermit-chaotic" || e.primaryRole === "rockhermit-pirate");
	};
	this.$isHermitButNotHermitage = function(e) {
		return e.isShip &&
			(e.primaryRole === "rockhermit" || e.primaryRole === "rockhermit-chaotic" || e.primaryRole === "rockhermit-pirate") &&
			!e.hasRole("hermitage");
	};
	/*
	======
	TIMERS
	======
	*/
	this.$timer_delete_hermitAction_warn = function() {
		if (!this.timer_hermitAction_warn) {
			return;
		}
		if (this.timer_hermitAction_warn.isRunning) {
			this.timer_hermitAction_warn.stop();
		}
		delete this.timer_hermitAction_warn;
	};
	this.$timer_delete_hermitAction_warnThenExplode = function() {
		if (!this.timer_hermitAction_warnThenExplode) {
			return;
		}
		if (this.timer_hermitAction_warnThenExplode.isRunning) {
			this.timer_hermitAction_warnThenExplode.stop();
		}
		delete this.timer_hermitAction_warnThenExplode;
	};
	this.$timer_delete_hermitAction_explode = function() {
		if (!this.timer_hermitAction_explode) {
			return;
		}
		if (this.timer_hermitAction_explode.isRunning) {
			this.timer_hermitAction_explode.stop();
		}
		delete this.timer_hermitAction_explode;
	};
	this.$timer_delete_hermitAction_resurrect = function() {
		if (!this.timer_hermitAction_resurrect) {
			return;
		}
		if (this.timer_hermitAction_resurrect.isRunning) {
			this.timer_hermitAction_resurrect.stop();
		}
		delete this.timer_hermitAction_resurrect;
	};
	this.$timer_delete_die = function() {
		if (!this.timer_die) {
			return;
		}
		if (this.timer_die.isRunning) {
			this.timer_die.stop();
		}
		delete this.timer_die;
	};
	this.$timer_delete_treatHermits_coves = function() {
		if (!this.timer_treatHermits_coves) {
			return;
		}
		if (this.timer_treatHermits_coves.isRunning) {
			this.timer_treatHermits_coves.stop();
		}
		delete this.timer_treatHermits_coves;
	};
	this.$timer_delete_all = function() {
		this.$timer_delete_die();
		this.$timer_delete_treatHermits_coves();
		this.$timer_delete_hermitAction_resurrect();
		this.$timer_delete_hermitAction_warn();
		this.$timer_delete_hermitAction_warnThenExplode();
		this.$timer_delete_hermitAction_explode();
		if (this.abandonedHermit) {
			delete this.abandonedHermit;
		}
	};
	/*
	===============
	TIMED FUNCTIONS
	===============
	*/
	this.$treatHermits_coves = function() {
		var s = system.filteredEntities(this, this.$isCove);
		if (this.debug) {
			// DEBUGGING version
			if (s.length === 0) {
				this.$msg_dbg("Seeking coves: none found.");
				return;
			}
			this.$msg_dbg("Seeking coves: at least one found ..");
			this.$treatHermits_core(s);
			return;
		}
		// NON-DEBUGGING VERSION
		if (s.length === 0) {
			return;
		}
		this.$treatHermits_core(s);
	};
	this.$die_timed = function $die_timed() {
		// The above format, with its repetition of the function name, is right. See http://aegidian.org/bb/viewtopic.php?p=257147#p257147.
		this.$timer_delete_die();
		// Do not automatically delete it, because it might not be running; other things can call this.
		if (!player.ship) {
			return;
		}
		// Next two lines are to avoid auto-eject shenanigans, etc.
		if (player.ship.equipmentStatus("EQ_AUTO_EJECT") === "EQUIPMENT_OK") {
			player.ship.removeEquipment("EQ_AUTO_EJECT");
		}
		if (player.ship.equipmentStatus("EQ_EEU") === "EQUIPMENT_OK") {
			player.ship.removeEquipment("EQ_EEU");
		}
		player.ship.explode();
	};
	this.$timed_hermitAction_message_warn = function(e) {
		if (!e || !e.isValid) {
			return;
		}
		e.commsMessage("WARNING: This station will explode. Clear the area.");
		e.broadcastCascadeImminent();
	};
	this.$timed_hermitAction_warn = function() {
		if (this.debug) {
			this.$msg_dbg("timed_hermitAction_warn.");
		}
		this.$timer_delete_hermitAction_warn();
		if (!this.abandonedHermit) {
			return;
		}
		if (!this.abandonedHermit.isValid) {
			delete this.abandonedHermit;
			return;
		}
		var e = this.abandonedHermit;
		this.$timed_hermitAction_message_warn(e);
	};
	this.$timed_hermitAction_warnThenExplode = function() {
		if (this.debug) {
			this.$msg_dbg("timed_hermitAction_warnThenExplode.");
		}
		this.$timer_delete_hermitAction_warnThenExplode();
		if (!this.abandonedHermit) {
			return;
		}
		if (!this.abandonedHermit.isValid) {
			delete this.abandonedHermit;
			return;
		}
		this.$timed_hermitAction_message_warn(this.abandonedHermit);
		var seconds = ~~((Math.random() * 60) + 0.5); // * 60
		this.timer_hermitAction_explode = new Timer(this, this.$timed_hermitAction_explode, seconds);
	};
	this.$timed_hermitAction_explode = function() {
		if (this.debug) {
			this.$msg_dbg("timed_hermitAction_explode.");
		}
		this.$timer_delete_hermitAction_explode();
		if (!this.abandonedHermit) {
			return;
		}
		if (!this.abandonedHermit.isValid) {
			delete this.abandonedHermit;
			return;
		}
		this.abandonedHermit.explode();
		// NB SpicyHermits occasionally adds a q-mine death action!
		delete this.abandonedHermit;
	};
	this.$timed_hermitAction_resurrect = function() {
		if (this.debug) {
			this.$msg_dbg("timed_hermitAction_resurrect.");
		}
		this.$timer_delete_hermitAction_resurrect();
		if (!this.abandonedHermit) {
			return;
		}
		if (!this.abandonedHermit.isValid) {
			delete this.abandonedHermit;
			return;
		}
		// We have to *replace* the hermit (otherwise it cannot be a station).
		// If the hermit is close enough to the player to notice the change, abort.
		if (!player.ship || !player.ship.position) {
			delete this.abandonedHermit;
			return;
		}
		var ps = player.ship; // for speed, but changes won't affect the original variable
		if (this.abandonedHermit.position.distanceTo(ps.position) < 50000) {
			if (this.debug) {
				this.$msg_dbg("Hermit too close to player for resurrection.");
			}
			delete this.abandonedHermit;
			return;
		}
		// Get data for the replacement.
		var orientation = this.abandonedHermit.orientation;
		var position = this.abandonedHermit.position;
		if (this.abandonedHermit) {
			this.abandonedHermit.remove(true);
			delete this.abandonedHermit;
		}
		// Do the replacement.
		// Note that Spicy Hermits overrides Oolite's rockhermit shipdata, so what we'll get here is 'spicy' stuff.
		var replacement;
		replacement = system.addShips("harderHermits_resurrected", 1, position);
		// this.replacement = this.abandonedHermit.spawnOne( "harderHermits_resurrected" );
		if (replacement[0] && replacement[0].isValid) {
			if (this.debug) {
				this.$msg_dbg("Created replacement.");
			}
			// this.replacement.orientation = this.abandonedHermit.orientation; 
			// this.replacement.position = this.abandonedHermit.position;
			replacement[0].orientation = orientation;
			replacement[0].lightsActive = false;
			replacement[0].desiredRange = 13000; // Tested?
			if (this.debug) {
				replacement[0].displayName = "Abandoned rock hermit (RESURRECTED)";
			}
			// Energy
			var r = Math.random();
			replacement[0].maxEnergy *= (r + 1.5);
		} else if (this.debug) {
			this.$msg_dbg("Replacement failed to exist.");
		}
	};
	/*
	==============
	EVENT HANDLERS
	==============
	*/
	this.startUp = function() {
		if (this.debug) {
			this.$msg_dbg("DEBUGGING is ON.");
			if (this.debug_testResurrection) {
				this.$msg_dbg("Testing resurrection.");
			}
		}
		if (worldScripts["Hermitage_Main"]) {
			// Hermitage_Main is the name the script itself declares.
			// Yet, that script's *filename* is hermitage_main.js.
			this.installed_spicyHermits = true;
			this.$msg_info("Detected hermitage OXP");
		}
		if (worldScripts["spicy_hermits_abandoned"]) {
			this.installed_spicyHermits = true;
			this.$msg_info("Detected SpicyHermits OXP");
		}
		if (worldScripts["Pirate_Coves"]) {
			this.installed_pirateCove = true;
			this.$msg_info("Detected pirateCove OXP.");
		}
	};
	this.shipDockedWithStation = function(station) {
		if (!player.ship.docked || player.ship.dockedStation.name !== "Abandoned Rock Hermit") {
			return;
		}
		// Unmodified abandoned hermits from SpicyHermits do not allow the player to dock (at all), so this routine won't fire for that.
		this.showScreen = "rejectPage";
		mission.runScreen({
				title: "Abandoned Rock Hermit (well, not really abandoned)",
				color: "redColor",
				message: "You have docked at a disguised pirate cove.\n\nNine out of ten for style, but minus several thousand for good thinking.",
				model: "rockhermit",
				choicesKey: "harderHermits_docked"
			},
			function(choice) {
				if (choice === "1_RETURN") {
					var r = Math.random();
					// r = 0.1; // TESTING
					if (r < 0.2) {
						player.commsMessage("The pirates attack your launching ship!");
						player.ship.launch();
						if (!this.timer_die) {
							this.timer_die = new Timer(this, this.$die_timed, 1.7);
						}
						this.mySound_hullBang.play();
					} else if (r < 0.8) {
						var d = 40 + ~~((Math.random() * 500) + 1);
						player.commsMessage("The pirates attack your launching ship!");
						player.ship.launch();
						player.ship.energy = player.ship.energy - d;
						if (player.ship.energy < 0) {
							if (!this.timer_die) {
								this.timer_die = new Timer(this, this.$die_timed, 1.7);
							}
						} else {
							this.mySound_hullBang.play();
						}
					}
					station.reactToAIMessage("ATTACKED");
				}
			}
		);
	};
	this.playerWillEnterWitchspace = function() {
		this.$timer_delete_all();
	};
	this.shipExitedWitchspace = function() {
		if (system.isInterstellarSpace) {
			return;
		}
		if (this.debug) {
			this.$msg_dbg("shipExitedWitchspace");
		}
		this.$treatHermits_normalAndSpicy();
		if (this.installed_spicyHermits) {
			this.$treatHermits_spicy_abandoned();
		}
		if (this.installed_pirateCove) {
			// PirateCoves adds hermits upon shipExitedWitchspace
			this.timer_$treatHermits_coves = new Timer(this, this.$treatHermits_coves, 1);
			if (this.debug) {
				this.$msg_dbg("Set timer to seek coves.");
			}
		}
	};
	/*
	=======================
	OTHER
	=======================
	*/
	this.$treatHermits_core = function(s) {
		var i;
		var debugStr;
		var energyToAdd;
		/*
		Adjust energy of all hermits detected. Those hermits comprise:
		- all hermits added by the core game;
		- hermits added by the SpicyHermits OXP (er, I think);
		- hermits added by the PirateCove OXP (which get added on Witchspace exit and so are detected within this function only when it is run on a timer.
		SpicyHermits gives pirate rock-hermits a maxEnergy of 2500.
		SpicyHermits leaves the maxEnergy of other types alone, i.e. at Oolite default, which is 1,000.
		(SpicyHermits saves the position of its hermits but anything else - including their maxEnergy - about them.
		We will not try to save that, either.)
		*/
		i = s.length;
		if (this.debug) {
			// * Debugging version *
			debugStr = "Hermits: iterating through <" + i + "> found ..";
			this.$msg_dbg(debugStr);
			while (i--) {
				if (!s[i] || !s[i].isValid) {
					continue;
				}
				debugStr = ".. Rock hermit <" + s[i].displayName + "> HAD energy " + s[i].maxEnergy;
				this.$msg_dbg(debugStr);
				if (s[i].maxEnergy === 1000) {
					energyToAdd = Math.ceil(Math.random() * 1499);
				} else if (s[i].maxEnergy === 2500) {
					energyToAdd = Math.ceil(Math.random() * 180);
				}
				s[i].maxEnergy += energyToAdd;
				debugStr = ".. Rock hermit <" + s[i].displayName + ">: added <" + energyToAdd + ">; NOW HAS energy " + s[i].maxEnergy;
				this.$msg_dbg(debugStr);
			}
		} else {
			// * NON-debugging version *
			while (i--) {
				if (!s[i] || !s[i].isValid) {
					continue;
				}
				if (s[i].maxEnergy === 1000) {
					energyToAdd = Math.ceil(Math.random() * 1499);
				} else if (s[i].maxEnergy === 2500) {
					energyToAdd = Math.ceil(Math.random() * 180);
				}
				s[i].maxEnergy += energyToAdd;
			}
		}
	};
	this.$treatHermits_spicy_abandoned = function() {
		if (!this.debug && !this.debug_testResurrection) {
			if (Math.random() > this.abandoned_chanceAct) {
				if (this.debug) {
					this.$msg_dbg("Abandoned hermits: decided to ignore.");
				}
				return;
			}
		}
		var action, debugStr, i, r, s, seconds;
		if (this.debug) {
			// DEBUGGING version
			this.$msg_dbg("Abandoned hermits: seeking ..");
			s = system.filteredEntities(this, this.$isAbandonedHermit);
			if (s.length < 1) {
				this.$msg_dbg(".. found no abandoned hermits.");
				return;
			}
			this.$msg_dbg(".. found at least one abandoned hermit.");
			// Pick one of them.
			i = ~~(Math.random() * s.length);
			// Check there is nothing fundamentally wrong with it.
			if (!(s[i] && s[i].isValid)) {
				this.$msg_dbg("Invalid abandoned hermit selected.");
				return;
			}
			this.abandonedHermit = s[i];
			if (this.debug_testResurrection) {
				action = "resurrect";
				seconds = 5;
				this.timer_hermitAction_resurrect = new Timer(this, this.$timed_hermitAction_resurrect, seconds);
			} else {
				seconds = ~~((Math.random() * 10800) + 1) + 5; // * 10800 + 5
				r = Math.random();
				if (r < 0.7) {
					action = "warn-then-explode";
					this.timer_hermitAction_warnThenExplode = new Timer(this, this.$timed_hermitAction_warnThenExplode, seconds);
				} else if (r < 0.75) {
					action = "warn";
					this.timer_warn = new Timer(this, this.$timed_hermitAction_warn, seconds);
				} else if (r < 0.8) {
					action = "explode";
					this.timer_hermitAction_explode = new Timer(this, this.$timed_hermitAction_explode, seconds);
				} else {
					action = "resurrect";
					this.timer_hermitAction_resurrect = new Timer(this, this.$timed_hermitAction_resurrect, seconds);
				}
			}
			s[i].displayName = "Abandoned Rock Hermit (TWEAKED)";
			debugStr = ".. Primed abandoned hermit <" + this.abandonedHermit.displayName + "> to <" + action + "> in " + seconds + " seconds time.";
			this.$msg_dbg(debugStr);
		} else {
			// NON-DEBUGGING version
			s = system.filteredEntities(this, this.$isAbandonedHermit);
			if (s.length < 1) {
				return;
			}
			i = ~~(Math.random() * s.length);
			if (!(s[i] && s[i].isValid)) {
				return;
			}
			this.abandonedHermit = s[i];
			seconds = ~~((Math.random() * 10800) + 1) + 5; // * 10800 + 5
			r = Math.random();
			if (r < 0.7) {
				this.timer_hermitAction_warnThenExplode = new Timer(this, this.$timed_hermitAction_warnThenExplode, seconds);
			} else if (r < 0.75) {
				this.timer_warn = new Timer(this, this.$timed_hermitAction_warn, seconds);
			} else if (r < 0.8) {
				this.timer_hermitAction_explode = new Timer(this, this.$timed_hermitAction_explode, seconds);
			} else {
				this.timer_hermitAction_resurrect = new Timer(this, this.$timed_hermitAction_resurrect, seconds);
			}
		}
	};
	this.$treatHermits_normalAndSpicy = function() {
		var s;
		if (this.installed_hermitage) {
			s = system.filteredEntities(this, this.$isHermitButNotHermitage);
		} else {
			s = system.filteredEntities(this, this.$isHermit);
		}
		if (this.debug) {
			// DEBUGGING version
			if (s.length === 0) {
				this.$msg_dbg("Seeking normal and spicy hermits: none found.");
				return;
			}
			this.$msg_dbg("Seeking normal and spicy hermits ..");
			this.$treatHermits_core(s);
			return;
		}
		// NON-DEBUGGING VERSION
		if (s.length > 0) {
			this.$treatHermits_core(s);
		}
	};
	/*
	=======================
	JSHINT: end of wrapper
	=======================
	*/
}).call(this);
// EOF |